diff --git a/.circleci/config.yml b/.circleci/config.yml index ac2c2b0922..7f00bc8cd2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,8 +10,82 @@ workflows: setup-workflows: jobs: - path-filtering/filter: + pre-steps: + - checkout + - run: + name: Generate custom steps for test_web and test_integrations + command: | + cp .circleci/continue_config.yml ../continue_config.yml + + for testsuite in integration_snapshots integration_tests; do + START=$(grep -Pzom1 '\A(.*+\n)+?\s++'"$testsuite"':(.*+\n)+?(?=(\s++)- run.*\n(\3.*+\n)*?\s++name: Run.*tests)' ../continue_config.yml) + END=$(grep -Pzom1 "$testsuite"':[^\0]+?(\s+)- run:(\1.+)+?name: Run.*tests[^\0]+?\K(?=\1\S)(.*\n)++' ../continue_config.yml) + + command=$(grep -Pzo "$testsuite"':[^\0]+?(\s+)- run:(\1.+)+?name: Run.*tests\K[^\0]+?(?=\1\S)' ../continue_config.yml) + command=${command//$'\n'/$'\n '} + + { + echo "$START" + + for type in WEB INTEGRATIONS; do + eval "$(grep -Pzo 'TEST_'"${type}"'_[0-9]+[^\0]+?\n(?![\t#])' Makefile | grep -avE '^#' | sed -e 's/\s*:= */=(/g' -e 's/\x0/)\n/g' -e 's/\s*\\//g')" + PHP_VERSIONS=($(grep -Po "(?<=TEST_${type}_)[0-9]+" Makefile)) + ALL_TASKS=($(grep -Pzo 'TEST_'"${type}"'_[0-9]+[^\0]+?\n(?![\t#])' Makefile | grep -avE '^#' | grep -a '\t' | sed -e 's/\t//g' -e 's/\s*\\//g' | sort | uniq)) + + for task in "${ALL_TASKS[@]}"; do + targeted_command=${command//\<< parameters.make_target >>/$task} + targeted_command=${targeted_command/$'|\n'/$'|\n echo $$ >/tmp/background-'$task$'.pid; trap \'rv=$?; [ $rv -eq 0 ] || touch /tmp/background-error; exit $rv\' INT TERM EXIT\n'} + cat \<> ]" + fi + done) + - equal: [ "test_${type,,}", \<< parameters.make_target >> ] + steps: + - run: + background: true + name: Run $task$targeted_command + EOT + done + done + + cat \<> ] + - equal: [ "test_integrations", \<< parameters.make_target >> ] + steps: + - run: + name: Await all tests + command: | + sleep 10 # wait for all background tasks to have launched + for f in \$(echo /tmp/background-*); do + tail --pid=\$(cat \$f) -f /dev/null || true + done + ! [ -f /tmp/background-error ] + - when: + condition: + not: + or: + - equal: [ "test_web", \<< parameters.make_target >> ] + - equal: [ "test_integrations", \<< parameters.make_target >> ] + steps: + - run: + name: Run tests${command} + EOT + echo "$END" + } > ../continue_config.yml + done + base-revision: master - config-path: .circleci/continue_config.yml + config-path: ../continue_config.yml # mapping: | components/.* components-c true diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index e3ef804b68..3158aca2cb 100644 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -267,6 +267,14 @@ aliases: echo "export DD_TRACE_ASSUME_COMPILED=1" >> $BASH_ENV fi + - &STEP_SUBSTITUTE_SNAPSHOT_DIR + run: + name: Replace /home/circleci/app by /home/circleci/datadog in snapshot results + command: | + for f in $(find tests/snapshots -type f); do + sed -i 's/\/home\/circleci\/app\//\/home\/circleci\/datadog\//g' $f + done + - &STEP_STORE_TEST_RESULTS store_test_results: path: test-results @@ -376,7 +384,7 @@ commands: default: /bin/bash -eo pipefail steps: - run: - name: Append build id to version number + name: Append build id to version number and bump it shell: << parameters.shell >> command: | githash="${CIRCLE_SHA1?}" @@ -387,8 +395,20 @@ commands: if [[ "$CIRCLE_BRANCH" =~ "ddtrace-" ]] ; then echo "Release branch detected; not adding git sha1 to version number." else - echo -n "+$githash" >>VERSION - echo "Appended +$githash to version number." + version=$(cat VERSION) + # if we have e.g. a beta suffix, just strip it + if [[ $version == *-* ]]; then + version=${version%-*} + else + # otherwise increment minor version + parts=($(echo -n "$version" | tr '.' '\n')) + parts[1]=$((parts[1]+1)) + parts[2]=0 + version=$(export IFS=.; (echo "${parts[*]}")) + fi + version="$version+$githash" + echo -n "$version" > VERSION + echo "Set version number to $version." fi @@ -585,7 +605,7 @@ commands: -e DD_AGENT_HOST=127.0.0.1 \ -e DATADOG_HAVE_DEV_ENV=1 \ -e BASH_ENV=/home/circleci/bashenv/bash.sh \ - -e ASAN_OPTIONS=abort_on_error=1 \ + -e ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1" \ -e CIRCLE_SHA1 \ -e CIRCLE_BRANCH \ -e CODECOV_TOKEN \ @@ -630,7 +650,9 @@ commands: steps: - run: name: Build Profiler NTS + shell: /bin/bash -ieo pipefail command: | + source "$BASH_ENV" if [ -d '/opt/rh/devtoolset-7' ] ; then set +eo pipefail source scl_source enable devtoolset-7 @@ -639,7 +661,7 @@ commands: set -u prefix="<< parameters.prefix >>" mkdir -vp "${prefix}" - command -v switch-php && switch-php "${PHP_VERSION}" + switch-php "${PHP_VERSION}" cd profiling echo "${CARGO_TARGET_DIR}" cargo build --release @@ -648,7 +670,9 @@ commands: objcopy --compress-debug-sections "${prefix}/datadog-profiling.so" - run: name: Build Profiler ZTS + shell: /bin/bash -ieo pipefail command: | + source "$BASH_ENV" if [ -d '/opt/rh/devtoolset-7' ] ; then set +eo pipefail source scl_source enable devtoolset-7 @@ -657,7 +681,7 @@ commands: set -u prefix="<< parameters.prefix >>" mkdir -vp "${prefix}" - command -v switch-php && switch-php "${PHP_VERSION}-zts" + switch-php "${PHP_VERSION}-zts" cd profiling echo "${CARGO_TARGET_DIR}" touch build.rs #make sure `build.rs` gets executed after `switch-php` call @@ -800,6 +824,7 @@ jobs: executor: name: with_httpbin_and_request_replayer docker_image: << parameters.docker_image >> + resource_class: medium steps: - restore_cache: keys: @@ -815,7 +840,6 @@ jobs: name: Run xdebug tests command: | export REPORT_EXIT_STATUS=1 - export DD_TRACE_CLI_ENABLED=1 targetdir() { if [[ ${1:0:1} -eq 2 ]]; then echo $1 @@ -940,7 +964,7 @@ jobs: name: Run tests command: | set -euo pipefail - DD_TRACE_SIDECAR_TRACE_SENDER=1 make test_c RUST_DEBUG_BUILD=1 PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" TESTS=tests/ext/background-sender 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } + DD_TRACE_SIDECAR_TRACE_SENDER=1 _DD_DEBUG_SIDECAR_LOG_METHOD="file://$(pwd)/tmp/build_extension/tests/ext/sidecar.log" make test_c RUST_DEBUG_BUILD=1 PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" TESTS=tests/ext/background-sender 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } - <<: *STEP_STORE_TEST_RESULTS - run: command: | @@ -993,12 +1017,13 @@ jobs: name: Run tests command: | set -euo pipefail - make test_c_observer RUST_DEBUG_BUILD=1 PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } + _DD_DEBUG_SIDECAR_LOG_LEVEL=trace _DD_DEBUG_SIDECAR_LOG_METHOD="file://$(pwd)/tmp/build_extension/tests/ext/sidecar.log" make test_c_observer RUST_DEBUG_BUILD=1 PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } - <<: *STEP_STORE_TEST_RESULTS - run: command: | mkdir -p /tmp/artifacts/core_dumps - find tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps + find /tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps + cp -a tmp/build_extension/tests/ext /tmp/artifacts/tests when: on_fail - store_artifacts: path: /tmp/artifacts @@ -1068,6 +1093,10 @@ jobs: sudo useradd -u 3434 docker-circleci sudo chown -R docker-circleci . tests # - <<: *STEP_WAIT_REQUEST_REPLAYER + - run: + name: Set core pattern + command: | + sudo sh -c "echo '/tmp/core.%e.%p.%t' > /proc/sys/kernel/core_pattern" - run: name: Run tests command: | @@ -1075,6 +1104,7 @@ jobs: -e DDAGENT_HOSTNAME=127.0.0.1 \ -e DD_AGENT_HOST=127.0.0.1 \ -e DATADOG_HAVE_DEV_ENV=1 \ + -e ASAN_OPTIONS="abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1" \ -e PHP_VARIANT=<< parameters.switch_php_version >> \ -e PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" \ << parameters.php_major_minor >>-buster-arm64 \ @@ -1087,12 +1117,12 @@ jobs: # name: Run tests # command: | # set -euo pipefail - # make << parameters.make_target >> PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } + # _DD_DEBUG_SIDECAR_LOG_LEVEL=trace _DD_DEBUG_SIDECAR_LOG_METHOD="file://$(pwd)/tmp/build_extension/tests/ext/sidecar.log" make << parameters.make_target >> PHPUNIT_OPTS="--log-junit test-results/php-unit/results.xml" 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } - <<: *STEP_STORE_TEST_RESULTS - run: command: | mkdir -p /tmp/artifacts/core_dumps - find tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps + find /tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps cp -a tmp/build_extension/tests/ext /tmp/artifacts/tests when: on_fail - store_artifacts: @@ -1127,12 +1157,12 @@ jobs: name: Set test environment variables shell: powershell.exe command: | - docker exec php powershell.exe 'setx DD_AUTOLOAD_NO_COMPILE true; setx DD_TRACE_CLI_ENABLED 1; setx DATADOG_HAVE_DEV_ENV 1; setx DD_TRACE_GIT_METADATA_ENABLED 0' + docker exec php powershell.exe 'setx DD_AUTOLOAD_NO_COMPILE true; setx DATADOG_HAVE_DEV_ENV 1; setx DD_TRACE_GIT_METADATA_ENABLED 0' - run: name: Run extension tests shell: powershell.exe command: | - docker exec php powershell.exe 'cd app; $env:_DD_DEBUG_SIDECAR_LOG_METHOD="""file://${pwd}\sidecar.log"""; C:\php\php.exe -n -d memory_limit=-1 -d output_buffering=0 run-tests.php -g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP --show-diff -p C:\php\php.exe -d "extension=${pwd}\x64\Release\php_ddtrace.dll" "${pwd}\tests\ext"' + docker exec php powershell.exe 'cd app; $env:_DD_DEBUG_SIDECAR_LOG_LEVEL=trace; $env:_DD_DEBUG_SIDECAR_LOG_METHOD="""file://${pwd}\sidecar.log"""; C:\php\php.exe -n -d memory_limit=-1 -d output_buffering=0 run-tests.php -g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP --show-diff -p C:\php\php.exe -d "extension=${pwd}\x64\Release\php_ddtrace.dll" "${pwd}\tests\ext"' # - run: # name: Install the extension and setup composer # shell: powershell.exe @@ -1178,6 +1208,10 @@ jobs: - source-v1-{{ .Branch }}-{{ .Revision }} - git_checkout - <<: *STEP_ATTACH_WORKSPACE + - run: + name: Set core pattern + command: | + sudo sh -c "echo '/tmp/core.%e.%p.%t' > /proc/sys/kernel/core_pattern" - setup_docker: docker_image: datadog/dd-trace-ci:php-<< parameters.php_major_minor >>_buster extra: with_httpbin_and_request_replayer @@ -1194,7 +1228,7 @@ jobs: command: | set -euo pipefail if [[ << parameters.switch_php_version >> == *asan* ]]; then export TEST_PHP_JUNIT=$(pwd)/asan-extension-test.xml; fi - make << parameters.make_target >> RUST_DEBUG_BUILD=1 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } + _DD_DEBUG_SIDECAR_LOG_LEVEL=trace _DD_DEBUG_SIDECAR_LOG_METHOD="file://$(pwd)/tmp/build_extension/tests/ext/sidecar.log" make << parameters.make_target >> RUST_DEBUG_BUILD=1 2>&1 | tee /dev/stderr | { ! grep -qe "=== Total [0-9]+ memory leaks detected ==="; } - when: # codecov uploader only on amd64 condition: @@ -1214,7 +1248,7 @@ jobs: - run: command: | mkdir -p /tmp/artifacts/core_dumps - find tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps + find /tmp -name "core.*" | xargs -I % -n 1 cp % /tmp/artifacts/core_dumps cp -a tmp/build_extension/tests/ext /tmp/artifacts/tests when: on_fail - store_artifacts: @@ -1381,7 +1415,14 @@ jobs: command: | export DEBIAN_FRONTEND=noninteractive apt update - apt install -y wget sudo git g++ gcc gcovr cmake make curl libcurl4-gnutls-dev clang clang-tidy clang-format git php-dev php8.2-xml php-cgi cargo + apt install -y wget sudo git g++ gcc gcovr cmake make curl libcurl4-gnutls-dev clang clang-tidy clang-format git php-dev php8.2-xml php-cgi + - run: + name: Install rust + command: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh + chmod +x /tmp/rustup.sh + /tmp/rustup.sh -y --default-toolchain 1.76 + sudo ln -s $HOME/.cargo/bin/* /usr/bin/ - run: git config --global --add safe.directory /home/circleci/datadog/appsec/third_party/libddwaf - run: name: CMake @@ -1392,7 +1433,7 @@ jobs: - run: name: Test command: | - make -C appsec/build -j $(nproc) xtest ddappsec_helper_test + PATH=$PATH:$HOME/.cargo/bin make -C appsec/build -j $(nproc) xtest ddappsec_helper_test ./appsec/build/tests/helper/ddappsec_helper_test - run: name: Generate XML coverage @@ -1635,7 +1676,7 @@ jobs: - equal: [ "8.0", << parameters.php_major_minor >> ] - equal: [ "8.1", << parameters.php_major_minor >> ] - not: - equal: [ "test_composer", << parameters.make_target >> ] + equal: [ "test_composer", << parameters.make_target >> ] steps: - run: name: Updating composer to v2 @@ -1650,6 +1691,7 @@ jobs: equal: [ false, << parameters.coverage >> ] steps: - <<: *STEP_DISABLE_XDEBUG + - <<: *STEP_SUBSTITUTE_SNAPSHOT_DIR - <<: *STEP_AWAIT_LINK_EXTENSION - install_extension - <<: *STEP_WAIT_MYSQL @@ -1660,7 +1702,7 @@ jobs: command: | set -euo pipefail <<# parameters.coverage >>unset CI && unset CIRCLECI && export DD_AUTOLOAD_NO_COMPILE=true<> - DD_TRACE_AGENT_TIMEOUT=1000 <<# parameters.disable_runner_distributed_tracing >> DD_DISTRIBUTED_TRACING=false <> DD_TRACE_TEST_SAPI=<< parameters.sapi >> make << parameters.make_target >> PHPUNIT_OPTS="--log-junit test-results/php-composer/results.xml" + DD_TRACE_AGENT_TIMEOUT=1000 <<# parameters.disable_runner_distributed_tracing >> DD_DISTRIBUTED_TRACING=false <> DD_TRACE_TEST_SAPI=<< parameters.sapi >> make << parameters.make_target >> PHPUNIT_OPTS="--log-junit test-results/php-composer/results-<< parameters.make_target >>.xml" - <<: *STEP_CODE_COVERAGE - run: command: | @@ -1724,7 +1766,7 @@ jobs: - equal: [ "8.0", << parameters.php_major_minor >> ] - equal: [ "8.1", << parameters.php_major_minor >> ] - not: - equal: [ "test_composer", << parameters.make_target >> ] + equal: [ "test_composer", << parameters.make_target >> ] steps: - run: name: Updating composer to v2 @@ -1739,6 +1781,7 @@ jobs: equal: [ false, << parameters.coverage >> ] steps: - <<: *STEP_DISABLE_XDEBUG + - <<: *STEP_SUBSTITUTE_SNAPSHOT_DIR - <<: *STEP_AWAIT_LINK_EXTENSION - install_extension - when: @@ -1784,6 +1827,7 @@ jobs: executor: name: with_agent docker_image: << parameters.docker_image >> + resource_class: medium steps: - restore_cache: keys: @@ -1809,7 +1853,6 @@ jobs: ) fi cd /usr/local/src/php - export DD_TRACE_CLI_ENABLED=true export DD_TRACE_STARTUP_LOGS=0 export DD_TRACE_WARN_CALL_STACK_DEPTH=0 export DD_TRACE_WARN_LEGACY_DD_TRACE=0 @@ -1856,7 +1899,7 @@ jobs: type: string docker_image: type: string - integration_testsuite: + make_target: type: string lib_curl_command: type: string @@ -1905,6 +1948,7 @@ jobs: - <<: *STEP_COMPOSER_TESTS_UPDATE - <<: *STEP_EXPORT_CI_ENV - <<: *STEP_DISABLE_XDEBUG + - <<: *STEP_SUBSTITUTE_SNAPSHOT_DIR - <<: *STEP_AWAIT_LINK_EXTENSION - install_extension: lib_curl_command: << parameters.lib_curl_command >> @@ -1912,8 +1956,8 @@ jobs: - <<: *STEP_WAIT_REQUEST_REPLAYER - <<: *STEP_WAIT_TEST_AGENT - run: - name: Run << parameters.integration_testsuite >> integration test - command: DD_TRACE_AGENT_TIMEOUT=1000 <<# parameters.disable_runner_distributed_tracing >> DD_DISTRIBUTED_TRACING=false <> DD_TRACE_TEST_SAPI=<< parameters.sapi >> make << parameters.integration_testsuite >> RUST_DEBUG_BUILD=1 + name: Run << parameters.make_target >> integration tests + command: DD_TRACE_AGENT_TIMEOUT=1000 <<# parameters.disable_runner_distributed_tracing >> DD_DISTRIBUTED_TRACING=false <> DD_TRACE_TEST_SAPI=<< parameters.sapi >> make << parameters.make_target >> RUST_DEBUG_BUILD=1 - run: command: | mkdir -p /tmp/artifacts @@ -1967,6 +2011,8 @@ jobs: - run: name: Run << parameters.ext_name >> integration tests with ext/<< parameters.ext_name >> as shared lib + leak detection command: | + export _DD_DEBUG_SIDECAR_LOG_LEVEL=trace + export _DD_DEBUG_SIDECAR_LOG_METHOD="file://$(pwd)/tmp/build_extension/tests/ext/sidecar.log" make test_extension_ci \ RUST_DEBUG_BUILD=1 \ BUILD_DIR=$(pwd)/tmp/build_extension \ @@ -2032,11 +2078,11 @@ jobs: command: | switch-php debug export DDTRACE_PKG_SO="/opt/datadog-php/extensions/ddtrace-$(php -i | awk '/^PHP[ \t]+API[ \t]+=>/ { print $NF }')-debug.so" - make run_tests TESTS="-d 'extension=$DDTRACE_PKG_SO'" + make run_tests TESTS="-d 'extension=$DDTRACE_PKG_SO' tests/ext" MAX_TEST_PARALLELISM=8 - run: name: Run phpt tests against build from source command: | - make test_c + make test_c MAX_TEST_PARALLELISM=8 - <<: *STEP_STORE_TEST_RESULTS - run: command: | @@ -2201,55 +2247,6 @@ jobs: name: Validate centos package command: << parameters.configuration >> ./dockerfiles/verify_packages/verify.sh - verify_centos_6: - working_directory: ~/datadog - resource_class: small - parameters: - install_type: - type: string - # Possible values: php_installer, native_package - default: php_installer - machine: - image: ubuntu-2004:2023.04.2 - steps: - - restore_cache: - keys: - - source-v1-{{ .Branch }}-{{ .Revision }} - - run: - name: Install git - command: | - yum update -y - yum install -y git - - git_checkout - - run: mkdir -p build/packages - - when: - condition: - equal: [php_installer, << parameters.install_type >>] - steps: - - fetch_job_artifact: - job: "package extension" - artifact: 'dd-library-php-[^"]+x86_64-linux-gnu.tar.gz' - directory: build/packages - - fetch_job_artifact: - job: "package extension" - artifact: 'datadog-setup.php' - directory: build/packages - - when: - condition: - equal: [native_package, << parameters.install_type >>] - steps: - - fetch_job_artifact: - job: "package extension" - artifact: |- - datadog-php-tracer-[^"]+'"$(uname -m)"'.rpm - directory: build/packages - - run: mkdir -p test-results - - run: - name: Test installing packages on target systems - command: make -f dockerfiles/verify_packages/Makefile INSTALL_TYPE=<< parameters.install_type >> verify_centos_6 - - store_test_results: - path: test-results - verify_debian: working_directory: ~/datadog resource_class: small @@ -2596,7 +2593,6 @@ jobs: TERM=dumb \ HTTPBIN_HOSTNAME=${HTTPBIN_HOSTNAME} \ DATADOG_HAVE_DEV_ENV=1 \ - DD_TRACE_CLI_ENABLED=1 \ DD_TRACE_GIT_METADATA_ENABLED=0 \ pecl run-tests <<# parameters.showdiff >> --showdiff <> --ini=" -d datadog.trace.sources_path=" -p datadog_trace - run: @@ -3051,6 +3047,10 @@ jobs: - run: name: Compile sidecar command: | + # Workaround "error: failed to run custom build command for `aws-lc-sys v0.20.0`" + cargo install --force --locked bindgen-cli + export PATH="/root/.cargo/bin:$PATH" + SHARED=1 PROFILE=tracer-release host_os=linux-musl ./compile_rust.sh cp -v ${CARGO_TARGET_DIR:-target}/tracer-release/libddtrace_php.a libddtrace_php_$(uname -m)-alpine.a objcopy --compress-debug-sections ${CARGO_TARGET_DIR:-target}/tracer-release/libddtrace_php.so libddtrace_php_$(uname -m)-alpine.so @@ -3160,7 +3160,7 @@ jobs: - run: name: cargo fetch command: | - SUDO=$(! command -v sudo >/dev/null || echo "sudo") + SUDO=$(! command -v sudo >/dev/null || echo "sudo -E") # On occasion, we've observed the .package-cache being in the cache. # If it's there, it will cause commands to stall, waiting for the file to be released. if [ -e '/rust/cargo/.package-cache' ] ; then @@ -3251,7 +3251,7 @@ jobs: phpize ./configure make clean - make -j all ECHO_ARG="-e" CFLAGS="-std=gnu11 -O2 -g -Wall -Wextra -Werror" + make -j all ECHO_ARG="-e" CFLAGS="-std=gnu11 -O2 -g -Wall -Wextra -Werror -DPHP_DD_LIBRARY_LOADER_VERSION='\"$(cat ../VERSION)\"'" cp modules/dd_library_loader.so ../dd_library_loader-$(uname -m)-<< parameters.os >>.so - persist_to_workspace: root: '.' @@ -3270,6 +3270,9 @@ jobs: switch_php_version: type: string default: none + use_valgrind: + type: boolean + default: false resource_class: type: string default: medium @@ -3292,6 +3295,18 @@ jobs: command: | set -xeuo pipefail + if [[ "<< parameters.php_major_minor >>" == "8.3" ]]; then + export XDEBUG_SO_NAME=xdebug-3.3.0.so + elif [[ "<< parameters.php_major_minor >>" == "8.2" ]]; then + export XDEBUG_SO_NAME=xdebug-3.2.2.so + elif [[ "<< parameters.php_major_minor >>" == "8.1" ]]; then + export XDEBUG_SO_NAME=xdebug-3.1.0.so + elif [[ "<< parameters.php_major_minor >>" == "8.0" ]]; then + export XDEBUG_SO_NAME=xdebug-3.0.0.so + elif [[ "<< parameters.php_major_minor >>" == "7.4" ]]; then + export XDEBUG_SO_NAME=xdebug-2.9.5.so + fi + rm -rf dd-library-php-ssi tar -xzf dd-library-php-ssi-*-linux.tar.gz export DD_LOADER_PACKAGE_PATH=${PWD}/dd-library-php-ssi @@ -3300,6 +3315,10 @@ jobs: mkdir -p modules cp ../dd-library-php-ssi/linux-gnu/loader/dd_library_loader.so modules/ ./bin/test.sh + if [[ "<< parameters.php_major_minor >>" == "8.3" ]]; then + true + <<# parameters.use_valgrind >>echo "Run with Valgrind" ; TEST_USE_VALGRIND=1 ./bin/test.sh<> + fi ./bin/check_glibc_version.sh "test_loader_alpine": @@ -3332,6 +3351,7 @@ jobs: set -xeuo pipefail apk add --no-cache curl-dev << parameters.alpine_packages >> + export XDEBUG_SO_NAME=xdebug.so rm -rf dd-library-php-ssi tar -xzf dd-library-php-ssi-*-linux.tar.gz @@ -3456,13 +3476,17 @@ jobs: name: Link sidecar and extension command: | sed -i 's/-export-symbols .*\/ddtrace\.sym/-Wl,--retain-symbols-file=ddtrace.sym/g' ddtrace_$(uname -m)<< parameters.libddtrace_suffix >>.ldflags + pids=() for archive in extensions_$(uname -m)/*.a; do ( gcc -shared -Wl,-whole-archive $archive -Wl,-no-whole-archive $(cat ddtrace_$(uname -m)<< parameters.libddtrace_suffix >>.ldflags) libddtrace_php_$(uname -m)<< parameters.libddtrace_suffix >>.a -Wl,-soname -Wl,ddtrace.so -o ${archive%.a}.so objcopy --compress-debug-sections ${archive%.a}.so ) & + pids+=($!) + done + for pid in "${pids[@]}"; do + wait $pid done - wait - persist_to_workspace: root: '.' paths: [ './extensions_*' ] @@ -3624,23 +3648,19 @@ jobs: - git_checkout - append_build_id - setup_docker: - docker_image: "datadog/libddwaf:toolchain" + docker_image: "public.ecr.aws/b1o7r7e0/nginx_musl_toolchain" - run: mkdir -p appsec_$(uname -m) - - run: - name: Create clang symlinks - command: | - ln -s /usr/bin/clang++-16 /usr/bin/clang++ - ln -s /usr/bin/clang-16 /usr/bin/clang - ln -s /usr/bin/clang-cpp-16 /usr/bin/clang-cpp - run: name: Build command: | git config --global --add safe.directory $(pwd)/appsec/third_party/libddwaf mkdir -p appsec/build ; cd appsec/build - cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_EXTENSION=OFF -DCMAKE_TOOLCHAIN_FILE=$(pwd)/../cmake/Toolchain.$(uname -m).cmake + cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDD_APPSEC_BUILD_EXTENSION=OFF \ + -DDD_APPSEC_ENABLE_PATCHELF_LIBC=ON \ + -DCMAKE_TOOLCHAIN_FILE=/sysroot/$(arch)-none-linux-musl/Toolchain.cmake make -j $(nproc) - objcopy --compress-debug-sections ddappsec-helper - cp -v ddappsec-helper ../../appsec_$(uname -m)/ddappsec-helper + objcopy --compress-debug-sections libddappsec-helper.so + cp -v libddappsec-helper.so ../../appsec_$(uname -m)/libddappsec-helper.so - run: name: Test command: | @@ -3762,7 +3782,7 @@ jobs: php datadog-setup.php --file "${installable_bundle}" --php-bin php --enable-profiling # run phpize just to get run-tests.php phpize - php run-tests.php -p $(which php) --show-diff -g "FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP" tests/ext/profiling + php run-tests.php -p $(which php) -d datadog.remote_config_enabled=false --show-diff -g "FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP" tests/ext/profiling "cbindgen up-to-date": working_directory: ~/datadog @@ -3775,14 +3795,18 @@ jobs: - git_checkout - run: name: Install cbindgen - command: cargo install cbindgen + command: cargo install --version "^0.26" cbindgen - run: - name: Regenerate cbindgen headers and + name: Regenerate cbindgen headers and compare them command: | set -eu make cbindgen git diff --exit-code components-rs - + - run: + name: Ensure no non-bundled rust git dependencies are used + command: | + set -eu + ! grep -P 'source = "git+(?!.*libdatadog)' Cargo.lock placeholder: docker: @@ -4200,7 +4224,7 @@ workflows: - "Compile alpine x86_64 PHP 8.2" - "Compile alpine x86_64 PHP 8.3" libddtrace_suffix: "-alpine" - resource_class: "medium" + resource_class: "large" name: "Link x86_64 alpine" docker_image: "datadog/dd-trace-ci:php-compile-extension-alpine" - link_extension: @@ -4216,7 +4240,7 @@ workflows: - "Compile alpine aarch64 PHP 8.2" - "Compile alpine aarch64 PHP 8.3" libddtrace_suffix: "-alpine" - resource_class: "arm.medium" + resource_class: "arm.large" name: "Link aarch64 alpine" docker_image: "datadog/dd-trace-ci:php-compile-extension-alpine" - compile_extension_centos: @@ -4798,6 +4822,34 @@ workflows: cd system-tests DD_API_KEY=$SYSTEM_TESTS_DD_API_KEY ./run.sh + - system_tests: + requires: [ 'package extension' ] + name: "System tests Integration Tests" + build: | + cd system-tests + git checkout conti/fix-php-errors + ./build.sh php + run: | + export SYSTEM_TESTS_AWS_ACCESS_KEY_ID=$SYSTEM_TESTS_IDM_AWS_ACCESS_KEY_ID + export SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY=$SYSTEM_TESTS_IDM_AWS_SECRET_ACCESS_KEY + cd system-tests + git checkout conti/fix-php-errors + DD_API_KEY=$SYSTEM_TESTS_DD_API_KEY ./run.sh INTEGRATIONS + + - system_tests: + requires: [ 'package extension' ] + name: "System tests Crossed Tracer Propagation Tests for Messaging" + build: | + cd system-tests + git checkout conti/fix-php-errors + ./build.sh php + run: | + export SYSTEM_TESTS_AWS_ACCESS_KEY_ID=$SYSTEM_TESTS_IDM_AWS_ACCESS_KEY_ID + export SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY=$SYSTEM_TESTS_IDM_AWS_SECRET_ACCESS_KEY + cd system-tests + git checkout conti/fix-php-errors + DD_API_KEY=$SYSTEM_TESTS_DD_API_KEY ./run.sh CROSSED_TRACING_LIBRARIES + - system_tests: requires: [ 'package extension' ] name: "Parametric tests" @@ -4859,6 +4911,7 @@ workflows: name: "[Linux/x86_64] Loader PHP << matrix.php_major_minor >>-<< matrix.switch_php_version >>" docker_image: "datadog/dd-trace-ci:php-<< matrix.php_major_minor >>_buster" resource_class: "medium" + use_valgrind: true matrix: parameters: php_major_minor: @@ -4914,7 +4967,7 @@ workflows: name: "[Alpine/x86_64] Loader PHP Alpine << matrix.alpine_version >>" docker_image: "alpine:<< matrix.alpine_version >>" resource_class: "medium" - alpine_packages: "php83 php83-dev" + alpine_packages: "php83 php83-dev php83-pecl-xdebug" matrix: parameters: alpine_version: @@ -4928,7 +4981,7 @@ workflows: name: "[Alpine/aarch64] Loader PHP Alpine << matrix.alpine_version >>" docker_image: "alpine:<< matrix.alpine_version >>" resource_class: "arm.medium" - alpine_packages: "php83 php83-dev" + alpine_packages: "php83 php83-dev php83-pecl-xdebug" matrix: parameters: alpine_version: @@ -5140,7 +5193,7 @@ workflows: - integration: requires: [ 'Prepare Code', 'Compile << matrix.php_major_minor >> extension for testing', 'Compile rust code for testing' ] - resource_class: medium+ + resource_class: large matrix: parameters: php_major_minor: @@ -5229,33 +5282,12 @@ workflows: make_target: - test_distributed_tracing - - integration: - # NOTE: test_integrations_phpredis5 is not included in the PHP 8.0 integrations tests because of this bug that - # only shows up in debug builds of PHP (https://github.com/phpredis/phpredis/issues/1869). - # Since we run tests using php debug builds, we have to run test_integrations_phpredis5 in a separate runner - # and switch to a non-debug PHP build. - # Once the fix for https://github.com/phpredis/phpredis/issues/1869 is released, we can remove this additional - # runner and add back again test_integrations_phpredis5 to the PHP 8.0 test suite. - requires: [ 'Prepare Code' ] - with_executor: 'with_redis' - matrix: - parameters: - php_major_minor: - - '8.0' - - '8.1' - - '8.2' - - '8.3' - switch_php_version: - - nts - make_target: - - test_integrations_phpredis5 - - integration_tests: requires: [ 'Prepare Code', 'Compile 7.2 extension for testing', 'Compile rust code for testing' ] name: "PHP 72 web tests with apache (+ opcache)" resource_class: medium+ sapi: apache2handler - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-7.2_buster" php_major_minor: "7.2" - integration_tests: @@ -5263,7 +5295,7 @@ workflows: name: "PHP 72 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-7.2_buster" php_major_minor: "7.2" - integration_tests: @@ -5271,7 +5303,7 @@ workflows: name: "PHP 73 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-7.3_buster" php_major_minor: "7.3" - integration_tests: @@ -5279,7 +5311,7 @@ workflows: name: "PHP 74 web tests with apache (+ opcache)" resource_class: medium+ sapi: apache2handler - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-7.4_buster" php_major_minor: "7.4" - integration_tests: @@ -5287,7 +5319,7 @@ workflows: name: "PHP 74 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-7.4_buster" php_major_minor: "7.4" - integration_tests: @@ -5295,7 +5327,7 @@ workflows: name: "PHP 80 web tests with apache (+ opcache)" resource_class: medium+ sapi: apache2handler - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.0_buster" php_major_minor: "8.0" - integration_tests: @@ -5303,7 +5335,7 @@ workflows: name: "PHP 80 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.0_buster" php_major_minor: "8.0" - integration_tests: @@ -5311,7 +5343,7 @@ workflows: name: "PHP 81 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.1_buster" php_major_minor: "8.1" - integration_tests: @@ -5319,7 +5351,7 @@ workflows: name: "PHP 82 web tests with apache (+ opcache)" resource_class: medium+ sapi: apache2handler - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.2_buster" php_major_minor: "8.2" - integration_tests: @@ -5327,7 +5359,7 @@ workflows: name: "PHP 83 web tests with apache (+ opcache)" resource_class: medium+ sapi: apache2handler - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.3_buster" php_major_minor: "8.3" - integration_tests: @@ -5335,7 +5367,7 @@ workflows: name: "PHP 83 web tests with nginx + FastCGI" resource_class: medium+ sapi: cgi-fcgi - integration_testsuite: "test_web" + make_target: "test_web" docker_image: "datadog/dd-trace-ci:php-8.3_buster" php_major_minor: "8.3" - integration_tests: @@ -5343,7 +5375,7 @@ workflows: name: "PHP 74 custom autoloaded web tests with nginx + PHP-FPM" resource_class: medium+ sapi: fpm-fcgi - integration_testsuite: "test_web_custom" + make_target: "test_web_custom" docker_image: "datadog/dd-trace-ci:php-7.4_buster" php_major_minor: "7.4" diff --git a/.github/workflows/auto_add_pr_to_miletone.yml b/.github/workflows/auto_add_pr_to_miletone.yml index 17a27b08c6..357c9b1425 100644 --- a/.github/workflows/auto_add_pr_to_miletone.yml +++ b/.github/workflows/auto_add_pr_to_miletone.yml @@ -11,6 +11,11 @@ jobs: add_to_milestone: if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.title, '[Version Bump]') == false runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # need to modify existing PR + issues: write # need to potentially create a new milestone + steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/auto_check_snapshots.yml b/.github/workflows/auto_check_snapshots.yml index 18ee808434..e649dc7b47 100644 --- a/.github/workflows/auto_check_snapshots.yml +++ b/.github/workflows/auto_check_snapshots.yml @@ -6,6 +6,9 @@ on: jobs: check-snapshots: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write # need to add a comment to a PR steps: - name: Checkout diff --git a/.github/workflows/auto_label_prs.yml b/.github/workflows/auto_label_prs.yml index a45527256b..7b5a4e5d50 100644 --- a/.github/workflows/auto_label_prs.yml +++ b/.github/workflows/auto_label_prs.yml @@ -7,6 +7,10 @@ jobs: add-labels: runs-on: ubuntu-latest + permissions: + contents: read + issues: write # Update labels on PRs (might not be necessary, but we call the UpdateIssue API so...) + pull-requests: write # Update labels on PRs steps: - name: Checkout diff --git a/.github/workflows/prof_correctness.yml b/.github/workflows/prof_correctness.yml index aa3c8f1d1a..43c31e941b 100644 --- a/.github/workflows/prof_correctness.yml +++ b/.github/workflows/prof_correctness.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-version: [8.1, 8.2, 8.3] + php-version: [8.0, 8.1, 8.2, 8.3] phpts: [nts, zts] include: - phpts: zts diff --git a/.gitignore b/.gitignore index 74ae930873..1ee4690f12 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ modules/* php_test_results* results.bin run-tests.php -/target +/target* tests/ext/*.php tests/ext/*.out tests/ext/*.diff diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c833c2835a..fb8861f01a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,7 @@ variables: value: "master" description: "Run a specific datadog-reliability-env branch downstream" SYSTEM_TESTS_LIBRARY: php + include: - remote: https://gitlab-templates.ddbuild.io/libdatadog/include/ci_authenticated_job.yml - remote: https://gitlab-templates.ddbuild.io/libdatadog/include/one-pipeline.yml @@ -50,7 +51,7 @@ download_artifacts: tags: [ "runner:main", "size:large" ] script: - | - sleep 1m # Let time for the CircleCI pipeline to start + sleep 2m # Let time for the CircleCI pipeline to start source .gitlab/download-circleci_artifact.sh download_circleci_artifact "gh/DataDog/dd-trace-php" "build_packages" "package extension" "dd-library-php-ssi-.*-x86_64-linux.tar.gz" "dd-library-php-ssi-x86_64-linux.tar.gz" @@ -108,4 +109,20 @@ onboarding_tests_installer: parallel: matrix: - ONBOARDING_FILTER_WEBLOG: [test-app-php,test-app-php-container-83] - SCENARIO: SIMPLE_INSTALLER_AUTO_INJECTION \ No newline at end of file + SCENARIO: SIMPLE_INSTALLER_AUTO_INJECTION + +onboarding_tests_k8s_injection: + rules: + - when: never + parallel: + matrix: + - WEBLOG_VARIANT: + - dd-lib-php-init-test-83 + - dd-lib-php-init-test-alpine + +requirements_json_test: + rules: + - when: on_success + variables: + REQUIREMENTS_BLOCK_JSON_PATH: "loader/packaging/block_tests.json" + REQUIREMENTS_ALLOW_JSON_PATH: "loader/packaging/allow_tests.json" diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 98e3f7a76f..c39daa6d7a 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -63,9 +63,34 @@ benchmarks-tracer: compare_to: "master" when: on_success - when: manual + artifacts: + name: "logs" + paths: + - candidate.tar.gz + - baseline.tar.gz + expire_in: 2 days variables: SCENARIO: "tracer" +benchmarks-appsec: + extends: .microbenchmarks + rules: + - if: $CI_PIPELINE_SOURCE != "schedule" + changes: + paths: + - appsec/src/**/* + compare_to: "master" + when: on_success + - when: manual + artifacts: + name: "logs" + paths: + - candidate.tar.gz + - baseline.tar.gz + expire_in: 2 days + variables: + SCENARIO: "appsec" + download_circle_ci_release: stage: benchmarks diff --git a/.gitlab/download-circleci_artifact.sh b/.gitlab/download-circleci_artifact.sh index 1abe9ffe21..b473d8324e 100755 --- a/.gitlab/download-circleci_artifact.sh +++ b/.gitlab/download-circleci_artifact.sh @@ -24,8 +24,10 @@ download_circleci_artifact() { ARTIFACT_PATTERN=$4 # "loader/modules/dd_library_loader.so" ARTIFACT_NAME=$5 # "dd_library_loader-x86_64-linux-gnu.so" - BRANCH=${CI_COMMIT_BRANCH} # Set by Gilab CI - COMMIT_SHA=${CI_COMMIT_SHA} # Set by Gilab CI + # Circle CI workflow is not triggered by tags, + # So we fallback to the release branch (eg. "ddtrace-1.3.0") + BRANCH=${CI_COMMIT_BRANCH:-"ddtrace-${CI_COMMIT_TAG}"} # Set by Gilab CI + COMMIT_SHA=${CI_COMMIT_SHA} # Set by Gilab CI PIPELINES=$(call_api "https://circleci.com/api/v2/project/${SLUG}/pipeline?branch=${BRANCH}") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dd2687706..9200e45be3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to dd-trace-php -As an open-source project we welcome contributions of many forms, but due to the experimental pre-beta nature of this repository, you should [reach out to us](https://github.com/DataDog/dd-trace-php/issues) before starting work on any major code changes. This will ensure we avoid duplicating work, or that your code can't be merged due to a rapidly changing base. +As an open-source project we welcome contributions of many forms, but you should [reach out to us](https://github.com/DataDog/dd-trace-php/issues) before starting work on any major code changes. This will ensure we avoid duplicating work, or that your code can't be merged due to a rapidly changing base. ## Project initialization diff --git a/Cargo.lock b/Cargo.lock index e2fbfd2c71..432f48c53b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.1", ] [[package]] @@ -445,6 +445,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -548,6 +554,28 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" +[[package]] +name = "blazesym" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519a0f9df086d6c4f44576558523a777c984454daeb124bee79bde69227360c4" +dependencies = [ + "cpp_demangle", + "gimli 0.30.0", + "libc 0.2.154", + "miniz_oxide", + "rustc-demangle", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.6.1" @@ -561,6 +589,97 @@ dependencies = [ "piper", ] +[[package]] +name = "bolero" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212e8dca6d4001cc6cac941d6932ddaa8cd27f57e5e44a9da19c913eb6a43b33" +dependencies = [ + "bolero-afl", + "bolero-engine", + "bolero-generator", + "bolero-honggfuzz", + "bolero-kani", + "bolero-libfuzzer", + "cfg-if", + "rand 0.8.5", +] + +[[package]] +name = "bolero-afl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b34f05de1527425bb05287da09ff1ff1612538648824db49e16d9693b24065" +dependencies = [ + "bolero-engine", + "cc", +] + +[[package]] +name = "bolero-engine" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6206263ebdd42e093c1229dab3957f61c9fd68d73c00f238ae25a378778b6bd3" +dependencies = [ + "anyhow", + "backtrace", + "bolero-generator", + "lazy_static", + "pretty-hex", + "rand 0.8.5", +] + +[[package]] +name = "bolero-generator" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac749fb4f2e14734e835a9352c0d1eb2ab62a025d4c56a823fa3f391e015741a" +dependencies = [ + "bolero-generator-derive", + "either", + "rand_core 0.6.4", +] + +[[package]] +name = "bolero-generator-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53397bfda19ccb48527faa14025048fc4bb76f090ccdeef1e5a355bfe4a94467" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bolero-honggfuzz" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf78581db1a7263620a8767e645b93ad287c70122ae76f5bd67040c7f06ff8e3" +dependencies = [ + "bolero-engine", +] + +[[package]] +name = "bolero-kani" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e55cec272a617f5ae4ce670db035108eb97c10cd4f67de851a3c8d3f18f19cb" +dependencies = [ + "bolero-engine", +] + +[[package]] +name = "bolero-libfuzzer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb42f66ee3ec89b9c411994de59d4710ced19df96fea2059feea1c2d73904c5b" +dependencies = [ + "bolero-engine", + "cc", +] + [[package]] name = "bollard" version = "0.16.1" @@ -742,9 +861,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -918,6 +1037,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aca749d3d3f5b87a0d6100509879f9cf486ab510803a4a4e1001da1ff61c2bd6" +[[package]] +name = "constcat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5cd0c57ef83705837b1cb872c973eff82b070846d3e23668322b2c0f8246d0" + [[package]] name = "core-foundation" version = "0.9.4" @@ -934,6 +1059,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpu-time" version = "1.0.0" @@ -944,6 +1078,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc 0.2.154", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -1027,6 +1170,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -1039,6 +1191,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "csv" version = "1.3.0" @@ -1060,6 +1222,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct 0.6.1", +] + [[package]] name = "current_platform" version = "0.2.0" @@ -1111,6 +1282,45 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "datadog-crashtracker" +version = "0.0.1" +dependencies = [ + "anyhow", + "backtrace", + "blazesym", + "chrono", + "ddcommon 0.0.1", + "ddtelemetry", + "http 0.2.11", + "hyper 0.14.28", + "libc 0.2.154", + "nix 0.27.1", + "os_info", + "page_size", + "portable-atomic", + "rand 0.8.5", + "serde", + "serde_json", + "tempfile", + "tokio", + "uuid", +] + +[[package]] +name = "datadog-crashtracker-ffi" +version = "0.0.1" +dependencies = [ + "anyhow", + "build_common", + "datadog-crashtracker", + "ddcommon 0.0.1", + "ddcommon-ffi", + "hyper 0.14.28", + "symbolic-common", + "symbolic-demangle", +] + [[package]] name = "datadog-ddsketch" version = "0.0.1" @@ -1120,6 +1330,14 @@ dependencies = [ "protoc-bin-vendored", ] +[[package]] +name = "datadog-dynamic-configuration" +version = "0.0.1" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "datadog-ipc" version = "0.1.0" @@ -1128,6 +1346,7 @@ dependencies = [ "bytes", "criterion", "datadog-ipc-macros", + "ddcommon 0.0.1", "futures", "glibc_version", "io-lifetimes", @@ -1142,6 +1361,7 @@ dependencies = [ "spawn_worker", "tarpc", "tempfile", + "tinybytes", "tokio", "tokio-serde", "tokio-util 0.6.10", @@ -1159,6 +1379,43 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "datadog-live-debugger" +version = "0.0.1" +dependencies = [ + "anyhow", + "constcat", + "ddcommon 0.0.1", + "hyper 0.14.28", + "json", + "lazy_static", + "percent-encoding", + "regex", + "regex-automata 0.4.5", + "serde", + "serde_json", + "smallvec", + "sys-info", + "tokio", + "uuid", +] + +[[package]] +name = "datadog-live-debugger-ffi" +version = "0.0.1" +dependencies = [ + "build_common", + "datadog-live-debugger", + "ddcommon 0.0.1", + "ddcommon-ffi", + "log", + "percent-encoding", + "serde_json", + "tokio", + "tokio-util 0.7.10", + "uuid", +] + [[package]] name = "datadog-php-profiling" version = "0.0.0" @@ -1225,32 +1482,63 @@ dependencies = [ "tokio-util 0.7.10", ] +[[package]] +name = "datadog-remote-config" +version = "0.0.1" +dependencies = [ + "anyhow", + "base64 0.21.7", + "datadog-dynamic-configuration", + "datadog-live-debugger", + "datadog-trace-protobuf", + "ddcommon 0.0.1", + "futures", + "futures-util", + "http 0.2.11", + "hyper 0.14.28", + "lazy_static", + "manual_future", + "serde", + "serde_json", + "sha2", + "time", + "tokio", + "tokio-util 0.7.10", + "tracing", + "uuid", +] + [[package]] name = "datadog-sidecar" version = "0.0.1" dependencies = [ "anyhow", "arrayref", + "base64 0.21.7", "bincode", "bytes", "cadence", "chrono", "console-subscriber", + "datadog-crashtracker", + "datadog-dynamic-configuration", "datadog-ipc", "datadog-ipc-macros", + "datadog-live-debugger", + "datadog-remote-config", "datadog-sidecar-macros", "datadog-trace-normalization", "datadog-trace-protobuf", "datadog-trace-utils", "ddcommon 0.0.1", "ddtelemetry", + "dogstatsd-client", "futures", "hashbrown 0.12.3", "http 0.2.11", "httpmock", "hyper 0.14.28", "io-lifetimes", - "kernel32-sys", "lazy_static", "libc 0.2.154", "manual_future", @@ -1265,19 +1553,23 @@ dependencies = [ "rmp-serde", "sendfd", "serde", + "serde_json", "serde_with", + "sha2", "simd-json", "spawn_worker", "sys-info", "tempfile", + "tinybytes", "tokio", "tokio-util 0.7.10", "tracing", "tracing-log", "tracing-subscriber", "uuid", - "winapi 0.2.8", + "winapi 0.3.9", "windows 0.51.1", + "windows-sys 0.52.0", "zwohash", ] @@ -1286,12 +1578,15 @@ name = "datadog-sidecar-ffi" version = "0.0.1" dependencies = [ "datadog-ipc", + "datadog-live-debugger", + "datadog-remote-config", "datadog-sidecar", "datadog-trace-utils", "ddcommon 0.0.1", "ddcommon-ffi", "ddtelemetry", "ddtelemetry-ffi", + "dogstatsd-client", "hyper 0.14.28", "libc 0.2.154", "paste", @@ -1335,16 +1630,21 @@ name = "datadog-trace-utils" version = "0.0.1" dependencies = [ "anyhow", + "bolero", + "bolero-generator", "bytes", + "cargo-platform", "cargo_metadata", "criterion", "datadog-trace-normalization", "datadog-trace-protobuf", + "datadog-trace-utils", "ddcommon 0.0.1", "flate2", "futures", "httpmock", "hyper 0.14.28", + "hyper-proxy", "hyper-rustls 0.27.2", "log", "prost 0.11.9", @@ -1355,6 +1655,7 @@ dependencies = [ "serde", "serde_json", "testcontainers", + "tinybytes", "tokio", ] @@ -1373,6 +1674,7 @@ dependencies = [ "hyper-util", "indexmap 2.2.6", "lazy_static", + "libc 0.2.154", "log", "maplit", "pin-project", @@ -1383,6 +1685,7 @@ dependencies = [ "static_assertions", "tokio", "tokio-rustls 0.26.0", + "windows-sys 0.52.0", ] [[package]] @@ -1415,9 +1718,13 @@ name = "ddcommon-ffi" version = "0.0.1" dependencies = [ "anyhow", + "bolero", "build_common", + "chrono", + "crossbeam-queue", "ddcommon 0.0.1", "hyper 0.14.28", + "serde", ] [[package]] @@ -1466,6 +1773,12 @@ dependencies = [ "anyhow", "cbindgen", "const-str", + "datadog-crashtracker-ffi", + "datadog-dynamic-configuration", + "datadog-ipc", + "datadog-live-debugger", + "datadog-live-debugger-ffi", + "datadog-remote-config", "datadog-sidecar", "datadog-sidecar-ffi", "ddcommon 0.0.1", @@ -1473,10 +1786,14 @@ dependencies = [ "ddtelemetry", "ddtelemetry-ffi", "env_logger 0.10.2", + "itertools 0.11.0", "lazy_static", "log", "paste", + "regex", + "regex-automata 0.4.5", "serde", + "serde_json", "serde_with", "simd-json", "spawn_worker", @@ -1487,6 +1804,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1514,6 +1840,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1567,6 +1903,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "dogstatsd-client" +version = "0.0.1" +dependencies = [ + "anyhow", + "cadence", + "datadog-ddsketch", + "datadog-trace-normalization", + "datadog-trace-protobuf", + "ddcommon 0.0.1", + "http 0.2.11", + "hyper 0.14.28", + "serde", + "tracing", +] + [[package]] name = "dunce" version = "1.0.4" @@ -1730,6 +2082,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "1.9.0" @@ -1931,6 +2289,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check 0.9.4", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -1950,6 +2318,17 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gimli" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e1d97fbe9722ba9bbd0c97051c2956e726562b61f86a25a4360398a40edfc9" +dependencies = [ + "fallible-iterator", + "indexmap 2.2.6", + "stable_deref_trait", +] + [[package]] name = "glibc_version" version = "0.1.2" @@ -2040,6 +2419,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ + "ahash 0.8.11", "allocator-api2", ] @@ -2056,6 +2436,30 @@ dependencies = [ "num-traits", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.11", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.11", +] + [[package]] name = "heck" version = "0.4.1" @@ -2265,6 +2669,42 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "hyper-proxy" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +dependencies = [ + "bytes", + "futures", + "headers", + "http 0.2.11", + "hyper 0.14.28", + "hyper-rustls 0.22.1", + "rustls-native-certs 0.5.0", + "tokio", + "tokio-rustls 0.22.0", + "tower-service", + "webpki 0.21.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper 0.14.28", + "log", + "rustls 0.19.1", + "rustls-native-certs 0.5.0", + "tokio", + "tokio-rustls 0.22.0", + "webpki 0.21.4", +] + [[package]] name = "hyper-rustls" version = "0.23.2" @@ -2504,6 +2944,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -2759,6 +3205,15 @@ dependencies = [ "rustix 0.38.31", ] +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc 0.2.154", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -2827,6 +3282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -2856,6 +3312,15 @@ dependencies = [ "tempdir", ] +[[package]] +name = "msvc-demangler" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +dependencies = [ + "bitflags 2.4.2", +] + [[package]] name = "multimap" version = "0.8.3" @@ -3078,6 +3543,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -3352,6 +3828,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +dependencies = [ + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3380,6 +3865,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -3420,6 +3911,16 @@ dependencies = [ "indexmap 1.9.3", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3453,6 +3954,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bitflags 2.4.2", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.4", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -3657,6 +4174,15 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "raw-cpuid" version = "10.7.0" @@ -3897,6 +4423,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.1", + "log", + "ring 0.16.20", + "sct 0.6.1", + "webpki 0.21.4", +] + [[package]] name = "rustls" version = "0.20.9" @@ -3905,8 +4444,8 @@ checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring 0.16.20", - "sct", - "webpki", + "sct 0.7.1", + "webpki 0.22.4", ] [[package]] @@ -3938,6 +4477,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -4047,6 +4598,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + [[package]] name = "sct" version = "0.7.1" @@ -4101,9 +4662,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -4119,9 +4680,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -4130,11 +4691,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -4202,6 +4764,28 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4233,6 +4817,12 @@ dependencies = [ "libc 0.2.154", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simd-json" version = "0.13.8" @@ -4331,6 +4921,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -4385,6 +4981,30 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symbolic-common" +version = "12.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" +dependencies = [ + "cpp_demangle", + "msvc-demangler", + "rustc-demangle", + "symbolic-common", +] + [[package]] name = "syn" version = "1.0.109" @@ -4510,6 +5130,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d6cf5a7dffb3f9dceec8e6b8ca528d9bd71d36c9f074defb548ce161f598c0" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-macros" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" +dependencies = [ + "cfg-if", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "testcontainers" version = "0.17.0" @@ -4634,6 +5276,19 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinybytes" +version = "0.0.1" +dependencies = [ + "once_cell", + "pretty_assertions", + "proptest", + "serde", + "serde_json", + "test-case", + "tinybytes", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -4700,6 +5355,17 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -4708,7 +5374,7 @@ checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls 0.20.9", "tokio", - "webpki", + "webpki 0.22.4", ] [[package]] @@ -4783,6 +5449,8 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", + "hashbrown 0.14.3", "pin-project-lite", "slab", "tokio", @@ -4798,6 +5466,23 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.9.2" @@ -4964,6 +5649,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.7.0" @@ -5031,6 +5728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", + "serde", ] [[package]] @@ -5176,6 +5874,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + [[package]] name = "webpki" version = "0.22.4" @@ -5476,6 +6184,15 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "x86" version = "0.47.0" diff --git a/Makefile b/Makefile index c5dbcd7c20..2defe8b86f 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,11 @@ PROJECT_ROOT := ${PWD} TRACER_SOURCE_DIR := $(PROJECT_ROOT)/src/ ASAN ?= $(shell ldd $(shell which php) 2>/dev/null | grep -q libasan && echo 1) SHELL = /bin/bash +APPSEC_SOURCE_DIR = $(PROJECT_ROOT)/appsec/ BUILD_SUFFIX = extension -BUILD_DIR = $(PROJECT_ROOT)/tmp/build_$(BUILD_SUFFIX) +BUILD_DIR_NAME = tmp/build_$(BUILD_SUFFIX) +BUILD_DIR = $(PROJECT_ROOT)/$(BUILD_DIR_NAME) +BUILD_DIR_APPSEC = $(BUILD_DIR)/appsec/ ZAI_BUILD_DIR = $(PROJECT_ROOT)/tmp/build_zai$(if $(ASAN),_asan) TEA_BUILD_DIR = $(PROJECT_ROOT)/tmp/build_tea$(if $(ASAN),_asan) TEA_INSTALL_DIR = $(TEA_BUILD_DIR)/opt @@ -13,7 +16,8 @@ TEA_BUILD_BENCHMARKS ?= OFF TEA_BENCHMARK_REPETITIONS ?= 10 # Note: If the tea benchmark format or output is changed, make changes to ./benchmark/runall.sh TEA_BENCHMARK_FORMAT ?= json -TEA_BENCHMARK_OUTPUT ?= $(PROJECT_ROOT)/tea/benchmarks/reports/tea-bench-results.$(TEA_BENCHMARK_FORMAT) +TEA_BENCHMARK_OUTPUT ?= $(PROJECT_ROOT)/tea/benchmarks/reports/tracer-tea-bench-results.$(TEA_BENCHMARK_FORMAT) +BENCHMARK_EXTRA ?= COMPONENTS_BUILD_DIR = $(PROJECT_ROOT)/tmp/build_components SO_FILE = $(BUILD_DIR)/modules/ddtrace.so AR_FILE = $(BUILD_DIR)/modules/ddtrace.a @@ -27,6 +31,7 @@ QUIET_TESTS := ${CIRCLE_SHA1} RUST_DEBUG_BUILD ?= $(shell [ -n "${DD_TRACE_DOCKER_DEBUG}" ] && echo 1) EXTRA_CONFIGURE_OPTIONS ?= ASSUME_COMPILED := ${DD_TRACE_ASSUME_COMPILED} +MAX_TEST_PARALLELISM ?= $(shell nproc) VERSION := $(shell cat VERSION) @@ -34,11 +39,13 @@ INI_FILE := $(shell ASAN_OPTIONS=detect_leaks=0 php -i | awk -F"=>" '/Scan this RUN_TESTS_IS_PARALLEL ?= $(shell test $(PHP_MAJOR_MINOR) -ge 74 && echo 1) -RUN_TESTS_CMD := REPORT_EXIT_STATUS=1 TEST_PHP_SRCDIR=$(PROJECT_ROOT) USE_TRACKED_ALLOC=1 php -n -d 'memory_limit=-1' $(BUILD_DIR)/run-tests.php $(if $(QUIET_TESTS),,-g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP) $(if $(ASAN), --asan) --show-diff -n -p $(shell which php) -q $(if $(RUN_TESTS_IS_PARALLEL), -j$(shell nproc)) +# shuffle parallel tests to evenly distribute test load, avoiding a batch of 32 tests being request-replayer tests +RUN_TESTS_CMD := REPORT_EXIT_STATUS=1 TEST_PHP_SRCDIR=$(PROJECT_ROOT) USE_TRACKED_ALLOC=1 php -n -d 'memory_limit=-1' $(BUILD_DIR)/run-tests.php $(if $(QUIET_TESTS),,-g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP) $(if $(ASAN), --asan) --show-diff -n -p $(shell which php) -q $(if $(RUN_TESTS_IS_PARALLEL), --shuffle -j$(MAX_TEST_PARALLELISM)) C_FILES = $(shell find components components-rs ext src/dogstatsd zend_abstract_interface -name '*.c' -o -name '*.h' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) TEST_FILES = $(shell find tests/ext -name '*.php*' -o -name '*.inc' -o -name '*.json' -o -name 'CONFLICTS' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) -RUST_FILES = $(BUILD_DIR)/Cargo.toml $(shell find components-rs -name '*.c' -o -name '*.rs' -o -name 'Cargo.toml' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) $(shell find libdatadog/{alloc,build-common,ddcommon,ddcommon-ffi,ddsketch,ddtelemetry,ddtelemetry-ffi,ipc,sidecar,sidecar-ffi,spawn_worker,tools/{cc_utils,sidecar_mockgen},trace-*,Cargo.toml} -type f \( -path "*/src*" -o -path "*/examples*" -o -path "*Cargo.toml" -o -path "*/build.rs" -o -path "*/tests/dataservice.rs" -o -path "*/tests/service_functional.rs" \) -not -path "*/ipc/build.rs" -not -path "*/sidecar-ffi/build.rs") +RUST_FILES = $(BUILD_DIR)/Cargo.toml $(shell find components-rs -name '*.c' -o -name '*.rs' -o -name 'Cargo.toml' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) $(shell find libdatadog/{alloc,build-common,crashtracker,crashtracker-ffi,ddcommon,ddcommon-ffi,ddsketch,ddtelemetry,ddtelemetry-ffi,dogstatsd-client,dynamic-configuration,ipc,live-debugger,live-debugger-ffi,remote-config,sidecar,sidecar-ffi,spawn_worker,tinybytes,tools/{cc_utils,sidecar_mockgen},trace-*,Cargo.toml} -type f \( -path "*/src*" -o -path "*/examples*" -o -path "*Cargo.toml" -o -path "*/build.rs" -o -path "*/tests/dataservice.rs" -o -path "*/tests/service_functional.rs" \) -not -path "*/ipc/build.rs" -not -path "*/sidecar-ffi/build.rs") +ALL_OBJECT_FILES = $(C_FILES) $(RUST_FILES) $(BUILD_DIR)/Makefile TEST_OPCACHE_FILES = $(shell find tests/opcache -name '*.php*' -o -name '.gitkeep' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) TEST_STUB_FILES = $(shell find tests/ext -type d -name 'stubs' -exec find '{}' -type f \; | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) INIT_HOOK_TEST_FILES = $(shell find tests/C2PHP -name '*.phpt' -o -name '*.inc' | awk '{ printf "$(BUILD_DIR)/%s\n", $$1 }' ) @@ -46,10 +53,10 @@ M4_FILES = $(shell find m4 -name '*.m4*' | awk '{ printf "$(BUILD_DIR)/%s\n", $$ XDEBUG_SO_FILE = $(shell find $(shell php-config --extension-dir) -type f -name "xdebug*.so" -exec basename {} \; | tail -n 1) # Make 'sed -i' portable -ifeq ($(shell uname),Darwin) - SED_I = sed -i '' -else +ifeq ($(shell { sed --version 2>&1 || echo ''; } | grep GNU > /dev/null && echo GNU || true),GNU) SED_I = sed -i +else + SED_I = sed -i '' endif all: $(BUILD_DIR)/configure $(SO_FILE) @@ -104,12 +111,10 @@ $(BUILD_DIR)/run-tests.php: $(if $(ASSUME_COMPILED),, $(BUILD_DIR)/configure) $(BUILD_DIR)/Makefile: $(BUILD_DIR)/configure $(Q) (cd $(BUILD_DIR); ./configure --$(if $(RUST_DEBUG_BUILD),enable,disable)-ddtrace-rust-debug $(if $(ASAN), --enable-ddtrace-sanitize) $(EXTRA_CONFIGURE_OPTIONS)) -all_object_files: $(C_FILES) $(RUST_FILES) $(BUILD_DIR)/Makefile - -$(SO_FILE): $(if $(ASSUME_COMPILED),, all_object_files $(BUILD_DIR)/compile_rust.sh) +$(SO_FILE): $(if $(ASSUME_COMPILED),, $(ALL_OBJECT_FILES) $(BUILD_DIR)/compile_rust.sh) $(if $(ASSUME_COMPILED),,$(Q) $(MAKE) -C $(BUILD_DIR) -j) -$(AR_FILE): all_object_files +$(AR_FILE): $(ALL_OBJECT_FILES) $(Q) $(MAKE) -C $(BUILD_DIR) -j ./modules/ddtrace.a all $(PHP_EXTENSION_DIR)/ddtrace.so: $(SO_FILE) @@ -127,13 +132,31 @@ $(INI_FILE): install_ini: $(INI_FILE) +delete_ini: + $(SUDO) rm $(INI_FILE) + +install_appsec: + cmake -S $(APPSEC_SOURCE_DIR) -B $(BUILD_DIR_APPSEC) + cd $(BUILD_DIR_APPSEC);make extension ddappsec-helper + cp $(BUILD_DIR_APPSEC)/ddappsec.so $(PHP_EXTENSION_DIR)/ddappsec.so + cp $(BUILD_DIR_APPSEC)/libddappsec-helper.so $(PHP_EXTENSION_DIR)/libddappsec-helper.so + cp $(APPSEC_SOURCE_DIR)/recommended.json /tmp/recommended.json + $(Q) echo "extension=ddappsec.so" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.cli_start_on_rinit=true" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.helper_path=$(PHP_EXTENSION_DIR)/libddappsec-helper.so" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.rules=/tmp/recommended.json" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.helper_socket_path=/tmp/ddappsec.sock" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.helper_lock_path=/tmp/ddappsec.lock" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.log_file=/tmp/logs/appsec.log" | $(SUDO) tee -a $(INI_FILE) + $(Q) echo "datadog.appsec.helper_log_file=/tmp/logs/helper.log" | $(SUDO) tee -a $(INI_FILE) + install_all: install install_ini run_tests: $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-tests.php - $(RUN_TESTS_CMD) $(BUILD_DIR)/$(TESTS) + DD_TRACE_GIT_METADATA_ENABLED=0 $(RUN_TESTS_CMD) $(TESTS) test_c: $(SO_FILE) $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-tests.php - $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_CLI_ENABLED=1 DD_TRACE_GIT_METADATA_ENABLED=0 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) $(BUILD_DIR)/$(TESTS) + $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_GIT_METADATA_ENABLED=0 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) $(BUILD_DIR)/$(subst $(BUILD_DIR_NAME)/,,$(TESTS)) test_c_coverage: dist_clean DD_TRACE_DOCKER_DEBUG=1 EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" $(MAKE) test_c || exit 0 @@ -145,20 +168,19 @@ test_c_disabled: $(SO_FILE) $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-te ) test_c_observer: $(SO_FILE) $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-tests.php - $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_CLI_ENABLED=1 DD_TRACE_GIT_METADATA_ENABLED=0 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d extension=zend_test.so -d zend_test.observer.enabled=1 -d zend_test.observer.observe_all=1 -d zend_test.observer.show_output=0 $(BUILD_DIR)/$(TESTS) + $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_GIT_METADATA_ENABLED=0 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d extension=zend_test.so -d zend_test.observer.enabled=1 -d zend_test.observer.observe_all=1 -d zend_test.observer.show_output=0 $(BUILD_DIR)/$(TESTS) test_opcache: $(SO_FILE) $(TEST_OPCACHE_FILES) $(BUILD_DIR)/run-tests.php - $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_CLI_ENABLED=1 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d zend_extension=opcache.so $(BUILD_DIR)/tests/opcache + $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d zend_extension=opcache.so $(BUILD_DIR)/tests/opcache -test_c_mem: export DD_TRACE_CLI_ENABLED=1 test_c_mem: $(SO_FILE) $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-tests.php $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -m $(BUILD_DIR)/$(TESTS) test_c2php: $(SO_FILE) $(INIT_HOOK_TEST_FILES) $(BUILD_DIR)/run-tests.php ( \ set -xe; \ + export PATH="$(PROJECT_ROOT)/tests/ext/valgrind:$$PATH"; \ sed -i 's/stream_socket_accept($$listenSock, 5)/stream_socket_accept($$listenSock, 20)/' $(BUILD_DIR)/run-tests.php; \ - export DD_TRACE_CLI_ENABLED=1; \ export USE_ZEND_ALLOC=0; \ export ZEND_DONT_UNLOAD_MODULES=1; \ export USE_TRACKED_ALLOC=1; \ @@ -166,13 +188,12 @@ test_c2php: $(SO_FILE) $(INIT_HOOK_TEST_FILES) $(BUILD_DIR)/run-tests.php ) test_with_init_hook: $(SO_FILE) $(INIT_HOOK_TEST_FILES) $(BUILD_DIR)/run-tests.php - $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) DD_TRACE_CLI_ENABLED=1 $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d datadog.trace.sources_path=$(TRACER_SOURCE_DIR) $(INIT_HOOK_TEST_FILES); + $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) $(RUN_TESTS_CMD) -d extension=$(SO_FILE) -d datadog.trace.sources_path=$(TRACER_SOURCE_DIR) $(INIT_HOOK_TEST_FILES); test_extension_ci: $(SO_FILE) $(TEST_FILES) $(TEST_STUB_FILES) $(BUILD_DIR)/run-tests.php ( \ set -xe; \ export PATH="$(PROJECT_ROOT)/tests/ext/valgrind:$$PATH"; \ - export DD_TRACE_CLI_ENABLED=1; \ export TEST_PHP_JUNIT=$(JUNIT_RESULTS_DIR)/normal-extension-test.xml; \ export DD_TRACE_GIT_METADATA_ENABLED=0; \ $(RUN_TESTS_CMD) -d extension=$(SO_FILE) $(BUILD_DIR)/$(TESTS); \ @@ -336,7 +357,7 @@ clean: if [[ -f "$(BUILD_DIR)/Makefile" ]]; then $(MAKE) -C $(BUILD_DIR) clean; fi rm -f $(BUILD_DIR)/configure* rm -f $(SO_FILE) - rm -f composer.lock + rm -f composer.lock composer.lock-php$(PHP_MAJOR_MINOR) echo $(ZAI_BUILD_DIR) sudo: @@ -381,9 +402,9 @@ clang_format_fix: cbindgen: remove_cbindgen generate_cbindgen remove_cbindgen: - rm -f components-rs/ddtrace.h components-rs/telemetry.h components-rs/sidecar.h components-rs/common.h + rm -f components-rs/ddtrace.h components-rs/live-debugger.h components-rs/telemetry.h components-rs/sidecar.h components-rs/common.h components-rs/crashtracker.h -generate_cbindgen: cbindgen_binary # Regenerate components-rs/ddtrace.h components-rs/telemetry.h components-rs/sidecar.h components-rs/common.h +generate_cbindgen: cbindgen_binary # Regenerate components-rs/ddtrace.h components-rs/live-debugger.h components-rs/telemetry.h components-rs/sidecar.h components-rs/common.h components-rs/crashtracker.h ( \ $(command rustup && echo run nightly --) cbindgen --crate ddtrace-php \ --config cbindgen.toml \ @@ -392,17 +413,23 @@ generate_cbindgen: cbindgen_binary # Regenerate components-rs/ddtrace.h componen $(command rustup && echo run nightly --) cbindgen --crate ddcommon-ffi \ --config ddcommon-ffi/cbindgen.toml \ --output $(PROJECT_ROOT)/components-rs/common.h; \ + $(command rustup && echo run nightly --) cbindgen --crate datadog-live-debugger-ffi \ + --config live-debugger-ffi/cbindgen.toml \ + --output $(PROJECT_ROOT)/components-rs/live-debugger.h; \ $(command rustup && echo run nightly --) cbindgen --crate ddtelemetry-ffi \ --config ddtelemetry-ffi/cbindgen.toml \ --output $(PROJECT_ROOT)/components-rs/telemetry.h; \ $(command rustup && echo run nightly --) cbindgen --crate datadog-sidecar-ffi \ --config sidecar-ffi/cbindgen.toml \ --output $(PROJECT_ROOT)/components-rs/sidecar.h; \ + $(command rustup && echo run nightly --) cbindgen --crate datadog-crashtracker-ffi \ + --config crashtracker-ffi/cbindgen.toml \ + --output $(PROJECT_ROOT)/components-rs/crashtracker.h; \ if test -d $(PROJECT_ROOT)/tmp; then \ mkdir -pv "$(BUILD_DIR)"; \ export CARGO_TARGET_DIR="$(BUILD_DIR)/target"; \ fi; \ - cargo run -p tools -- $(PROJECT_ROOT)/components-rs/common.h $(PROJECT_ROOT)/components-rs/ddtrace.h $(PROJECT_ROOT)/components-rs/telemetry.h $(PROJECT_ROOT)/components-rs/sidecar.h \ + cargo run -p tools -- $(PROJECT_ROOT)/components-rs/common.h $(PROJECT_ROOT)/components-rs/ddtrace.h $(PROJECT_ROOT)/components-rs/live-debugger.h $(PROJECT_ROOT)/components-rs/telemetry.h $(PROJECT_ROOT)/components-rs/sidecar.h $(PROJECT_ROOT)/components-rs/crashtracker.h \ ) cbindgen_binary: @@ -489,14 +516,13 @@ cores: # TESTS ######################################################################################################################## TRACER_SOURCES_INI := -d datadog.trace.sources_path=$(TRACER_SOURCE_DIR) -ENV_OVERRIDE := $(shell [ -n "${DD_TRACE_DOCKER_DEBUG}" ] && echo DD_AUTOLOAD_NO_COMPILE=true DD_TRACE_SOURCES_PATH=$(TRACER_SOURCE_DIR)) DD_DOGSTATSD_URL=http://127.0.0.1:9876 DD_TRACE_CLI_ENABLED=true DD_TRACE_GIT_METADATA_ENABLED=false +ENV_OVERRIDE := $(shell [ -n "${DD_TRACE_DOCKER_DEBUG}" ] && echo DD_AUTOLOAD_NO_COMPILE=true DD_TRACE_SOURCES_PATH=$(TRACER_SOURCE_DIR)) DD_DOGSTATSD_URL=http://request-replayer:80 DD_TRACE_GIT_METADATA_ENABLED=false TEST_EXTRA_INI ?= TEST_EXTRA_ENV ?= ### DDTrace tests ### TESTS_ROOT = ./tests COMPOSER = $(if $(ASAN), ASAN_OPTIONS=detect_leaks=0) COMPOSER_MEMORY_LIMIT=-1 composer --no-interaction -COMPOSER_TESTS = $(COMPOSER) --working-dir=$(TESTS_ROOT) DDPROF_IDENTIFIER ?= PHPUNIT_OPTS ?= PHPUNIT = $(TESTS_ROOT)/vendor/bin/phpunit $(PHPUNIT_OPTS) --config=$(TESTS_ROOT)/phpunit.xml @@ -504,7 +530,7 @@ PHPUNIT_COVERAGE ?= PHPBENCH_OPTS ?= PHPBENCH_CONFIG ?= $(TESTS_ROOT)/phpbench.json PHPBENCH_OPCACHE_CONFIG ?= $(TESTS_ROOT)/phpbench-opcache.json -PHPBENCH = $(TESTS_ROOT)/vendor/bin/phpbench $(PHPBENCH_OPTS) run +PHPBENCH = $(TESTS_ROOT)/Benchmarks/vendor/bin/phpbench $(PHPBENCH_OPTS) run PHPCOV = $(TESTS_ROOT)/vendor/bin/phpcov TELEMETRY_ENABLED=0 @@ -524,8 +550,7 @@ TEST_INTEGRATIONS_70 := \ test_integrations_phpredis4 \ test_integrations_phpredis5 \ test_integrations_predis1 \ - test_integrations_sqlsrv \ - test_opentracing_beta5 + test_integrations_sqlsrv TEST_WEB_70 := \ test_metrics \ @@ -568,8 +593,6 @@ TEST_INTEGRATIONS_71 := \ test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_sqlsrv \ - test_opentracing_beta5 \ - test_opentracing_beta6 \ test_opentracing_10 TEST_WEB_71 := \ @@ -623,8 +646,6 @@ TEST_INTEGRATIONS_72 := \ test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_sqlsrv \ - test_opentracing_beta5 \ - test_opentracing_beta6 \ test_opentracing_10 TEST_WEB_72 := \ @@ -683,8 +704,6 @@ TEST_INTEGRATIONS_73 :=\ test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_sqlsrv \ - test_opentracing_beta5 \ - test_opentracing_beta6 \ test_opentracing_10 TEST_WEB_73 := \ @@ -745,8 +764,6 @@ TEST_INTEGRATIONS_74 := \ test_integrations_predis1 \ test_integrations_roadrunner \ test_integrations_sqlsrv \ - test_opentracing_beta5 \ - test_opentracing_beta6 \ test_opentracing_10 TEST_WEB_74 := \ @@ -787,8 +804,6 @@ TEST_WEB_74 := \ # NOTE: test_integrations_phpredis5 is not included in the PHP 8.0 integrations tests because of this bug that only # shows up in debug builds of PHP (https://github.com/phpredis/phpredis/issues/1869). # Since we run tests in CI using php debug builds, we run test_integrations_phpredis5 in a separate non-debug container. -# Once the fix for https://github.com/phpredis/phpredis/issues/1869 is released, we can remove that additional container -# and add back again test_integrations_phpredis5 to the PHP 8.0 test suite. TEST_INTEGRATIONS_80 := \ test_integrations_deferred_loading \ test_integrations_amqp2 \ @@ -808,6 +823,7 @@ TEST_INTEGRATIONS_80 := \ test_integrations_guzzle6 \ test_integrations_guzzle7 \ test_integrations_pcntl \ + test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_sqlsrv \ test_integrations_swoole_5 \ @@ -854,10 +870,12 @@ TEST_INTEGRATIONS_81 := \ test_integrations_mysqli \ test_integrations_openai \ test_opentelemetry_1 \ + test_opentelemetry_beta \ test_integrations_guzzle7 \ test_integrations_pcntl \ test_integrations_pdo \ test_integrations_elasticsearch7 \ + test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_sqlsrv \ test_integrations_swoole_5 \ @@ -905,11 +923,13 @@ TEST_INTEGRATIONS_82 := \ test_integrations_mysqli \ test_integrations_openai \ test_opentelemetry_1 \ + test_opentelemetry_beta \ test_integrations_guzzle7 \ test_integrations_pcntl \ test_integrations_pdo \ test_integrations_elasticsearch7 \ test_integrations_elasticsearch8 \ + test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_frankenphp \ test_integrations_roadrunner \ @@ -964,11 +984,13 @@ TEST_INTEGRATIONS_83 := \ test_integrations_mysqli \ test_integrations_openai \ test_opentelemetry_1 \ + test_opentelemetry_beta \ test_integrations_guzzle7 \ test_integrations_pcntl \ test_integrations_pdo \ test_integrations_elasticsearch7 \ test_integrations_elasticsearch8 \ + test_integrations_phpredis5 \ test_integrations_predis1 \ test_integrations_frankenphp \ test_integrations_roadrunner \ @@ -1018,7 +1040,7 @@ define run_composer_with_retry endef define run_tests_without_coverage - $(TEST_EXTRA_ENV) $(ENV_OVERRIDE) php $(TEST_EXTRA_INI) -d datadog.instrumentation_telemetry_enabled=$(shell (test $(TELEMETRY_ENABLED) && echo 1) || (test $(PHP_MAJOR_MINOR) -ge 84 && echo 1) || echo 0) -d datadog.trace.sidecar_trace_sender=$(shell test $(PHP_MAJOR_MINOR) -ge 84 && echo 1 || echo 0) $(TRACER_SOURCES_INI) $(PHPUNIT) $(1) --filter=$(FILTER) + $(TEST_EXTRA_ENV) $(ENV_OVERRIDE) php $(TEST_EXTRA_INI) -d datadog.instrumentation_telemetry_enabled=$(shell (test $(TELEMETRY_ENABLED) && echo 1) || (test $(PHP_MAJOR_MINOR) -ge 83 && echo 1) || echo 0) -d datadog.trace.sidecar_trace_sender=$(shell test $(PHP_MAJOR_MINOR) -ge 83 && echo 1 || echo 0) $(TRACER_SOURCES_INI) $(PHPUNIT) $(1) --filter=$(FILTER) endef define run_tests_with_coverage @@ -1042,13 +1064,19 @@ endef define run_benchmarks - $(ENV_OVERRIDE) php $(TEST_EXTRA_INI) $(TRACER_SOURCES_INI) $(PHPBENCH) --config=$(1) --filter=$(FILTER) --report=all --output=file --output=console + $(ENV_OVERRIDE) php -d extension=redis-5.3.7.so $(TEST_EXTRA_INI) $(TRACER_SOURCES_INI) $(PHPBENCH) --config=$(1) --filter=$(FILTER) --report=all --output=file --output=console $(BENCHMARK_EXTRA) endef define run_benchmarks_with_ddprof - $(ENV_OVERRIDE) ddprof -S $(DDPROF_IDENTIFIER) php $(TEST_EXTRA_INI) $(REQUEST_INIT_HOOK) $(PHPBENCH) --config=$(1) --filter=$(FILTER) --report=all --output=file --output=console + $(ENV_OVERRIDE) ddprof -S $(DDPROF_IDENTIFIER) php -d extension=redis-5.3.7.so $(TEST_EXTRA_INI) $(REQUEST_INIT_HOOK) $(PHPBENCH) --config=$(1) --filter=$(FILTER) --report=all --output=file --output=console $(BENCHMARK_EXTRA) endef +define run_composer_with_lock + rm $1/composer.lock-php* 2>/dev/null || true + $(call run_composer_with_retry,$1,) + find $1/vendor* \( -name Tests -prune -o -name tests -prune \) -exec rm -rf '{}' \; + touch $1/composer.lock-php$(PHP_MAJOR_MINOR) +endef # use this as the first target if you want to use uncompiled files instead of the _generated_*.php compiled file. dev: @@ -1059,25 +1087,14 @@ use_generated: $(Q) : $(Q) $(eval ENV_OVERRIDE:=$(ENV_OVERRIDE) DD_AUTOLOAD_NO_COMPILE=) -clean_test: clean_test_scenarios - rm -rf $(TESTS_ROOT)/composer.lock $(TESTS_ROOT)/.scenarios.lock $(TESTS_ROOT)/vendor +clean_test: + find $(TESTS_ROOT)/ -not \( -name "Frameworks" -prune \) -not \( -name "ext" -prune \) -not \( -name "randomized" -prune \) -name "composer.lock" -o -name "vendor" -print -exec rm -rf {} \; find $(TESTS_ROOT)/Frameworks/ -path "*/vendor/*" -prune -o -wholename "*/cache/*.php" -print -exec rm -rf {} \; -clean_test_scenarios: - $(TESTS_ROOT)/clean-composer-scenario-locks.sh - -COMPOSER_PHP_LOCK = $(TESTS_ROOT)/composer.lock.php$(PHP_MAJOR_MINOR) -$(COMPOSER_PHP_LOCK): - $(Q) touch $(COMPOSER_PHP_LOCK) - -$(TESTS_ROOT)/composer.lock: $(TESTS_ROOT)/composer.json $(COMPOSER_PHP_LOCK) - $(Q) find "$(TESTS_ROOT)" -maxdepth 1 -name 'composer.lock*' -not -wholename "$(COMPOSER_PHP_LOCK)" -delete - $(COMPOSER_TESTS) update - composer_tests_update: - $(COMPOSER_TESTS) update + $(call run_composer_with_lock,$(TESTS_ROOT)) -global_test_run_dependencies: install_all $(TESTS_ROOT)/composer.lock +global_test_run_dependencies: install_all $(TESTS_ROOT)/./composer.lock-php$(PHP_MAJOR_MINOR) test_all: \ test_unit \ @@ -1122,12 +1139,8 @@ test_distributed_tracing_coverage: test_metrics: global_test_run_dependencies $(call run_tests,--testsuite=metrics $(TESTS)) -benchmarks_run_dependencies: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_5_2,) +benchmarks_run_dependencies: global_test_run_dependencies tests/Frameworks/Symfony/Version_5_2/composer.lock-php$(PHP_MAJOR_MINOR) tests/Frameworks/Laravel/Version_10_x/composer.lock-php$(PHP_MAJOR_MINOR) tests/Benchmarks/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_5_2/bin/console cache:clear --no-warmup --env=prod - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_8_x,) - rm -f tests/.scenarios.lock/benchmarks/composer.lock - $(MAKE) test_scenario_benchmarks call_benchmarks: if [ -n "$(DDPROF_IDENTIFIER)" ]; then \ @@ -1147,25 +1160,29 @@ benchmarks: benchmarks_run_dependencies call_benchmarks benchmarks_opcache: benchmarks_run_dependencies call_benchmarks_opcache -test_opentelemetry_1: global_test_run_dependencies - rm -f tests/.scenarios.lock/opentelemetry1/composer.lock - $(MAKE) test_scenario_opentelemetry1 - $(call run_composer_with_retry,tests/Frameworks/Custom/OpenTelemetry,) +define setup_opentelemetry + cp $(1) $(dir $(1))/composer.json +endef + +define run_opentelemetry_tests $(eval TEST_EXTRA_ENV=$(shell [ $(PHP_MAJOR_MINOR) -ge 81 ] && echo "OTEL_PHP_FIBERS_ENABLED=1" || echo '') DD_TRACE_OTEL_ENABLED=1 DD_TRACE_GENERATE_ROOT_SPAN=0) $(call run_tests,--testsuite=opentelemetry1 $(TESTS)) $(eval TEST_EXTRA_ENV=) +endef + +_test_opentelemetry_beta_setup: global_test_run_dependencies + $(call setup_opentelemetry,tests/OpenTelemetry/composer-beta.json) + +test_opentelemetry_beta: _test_opentelemetry_beta_setup tests/Frameworks/Custom/OpenTelemetry/composer.lock-php$(PHP_MAJOR_MINOR) tests/OpenTelemetry/composer.lock-php$(PHP_MAJOR_MINOR) + $(call run_opentelemetry_tests) -test_opentracing_beta5: global_test_run_dependencies - $(MAKE) test_scenario_opentracing_beta5 - $(call run_tests,tests/OpenTracerUnit) +_test_opentelemetry_1_setup: global_test_run_dependencies + $(call setup_opentelemetry,tests/OpenTelemetry/composer-1.json) -test_opentracing_beta6: global_test_run_dependencies - $(MAKE) test_scenario_opentracing_beta6 - $(call run_tests,tests/OpenTracerUnit) +test_opentelemetry_1: _test_opentelemetry_1_setup tests/Frameworks/Custom/OpenTelemetry/composer.lock-php$(PHP_MAJOR_MINOR) tests/OpenTelemetry/composer.lock-php$(PHP_MAJOR_MINOR) + $(call run_opentelemetry_tests) -test_opentracing_10: global_test_run_dependencies - $(MAKE) test_scenario_opentracing10 - $(call run_composer_with_retry,tests/Frameworks/Custom/OpenTracing,) +test_opentracing_10: global_test_run_dependencies tests/OpenTracer1Unit/composer.lock-php$(PHP_MAJOR_MINOR) tests/Frameworks/Custom/OpenTracing/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests,tests/OpenTracer1Unit) $(call run_tests,tests/OpenTracing) @@ -1177,200 +1194,144 @@ test_web_coverage: test_integrations_coverage: PHPUNIT_COVERAGE=1 $(MAKE) test_integrations -test_integrations_amqp2: global_test_run_dependencies - $(MAKE) test_scenario_amqp2 - $(call run_tests_debug,tests/Integrations/AMQP) -test_integrations_amqp35: global_test_run_dependencies - $(MAKE) test_scenario_amqp35 - $(call run_tests_debug,tests/Integrations/AMQP) -test_integrations_deferred_loading: global_test_run_dependencies - $(MAKE) test_scenario_predis1 +test_integrations_amqp2: global_test_run_dependencies tests/Integrations/AMQP/V2/composer.lock-php$(PHP_MAJOR_MINOR) + $(call run_tests_debug,tests/Integrations/AMQP/V2) +test_integrations_amqp35: global_test_run_dependencies tests/Integrations/AMQP/V3_5/composer.lock-php$(PHP_MAJOR_MINOR) + $(call run_tests_debug,tests/Integrations/AMQP/V3_5) +test_integrations_deferred_loading: global_test_run_dependencies tests/Integrations/DeferredLoading/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/DeferredLoading) test_integrations_curl: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/Curl) -test_integrations_elasticsearch1: global_test_run_dependencies - $(MAKE) test_scenario_elasticsearch1 +test_integrations_elasticsearch1: global_test_run_dependencies tests/Integrations/Elasticsearch/V1/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Elasticsearch/V1) -test_integrations_elasticsearch7: global_test_run_dependencies - $(MAKE) test_scenario_elasticsearch7 - $(call run_tests_debug,tests/Integrations/Elasticsearch/V1) -test_integrations_elasticsearch8: global_test_run_dependencies - $(MAKE) test_scenario_elasticsearch8 +test_integrations_elasticsearch7: global_test_run_dependencies tests/Integrations/Elasticsearch/V7/composer.lock-php$(PHP_MAJOR_MINOR) + $(call run_tests_debug,tests/Integrations/Elasticsearch/V7) +test_integrations_elasticsearch8: global_test_run_dependencies tests/Integrations/Elasticsearch/V8/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Elasticsearch/V8) -test_integrations_guzzle5: global_test_run_dependencies - $(MAKE) test_scenario_guzzle5 +test_integrations_guzzle5: global_test_run_dependencies tests/Integrations/Guzzle/V5/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Guzzle/V5) -test_integrations_guzzle6: global_test_run_dependencies - $(MAKE) test_scenario_guzzle6 +test_integrations_guzzle6: global_test_run_dependencies tests/Integrations/Guzzle/V6/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Guzzle/V6) -test_integrations_guzzle7: global_test_run_dependencies - $(MAKE) test_scenario_guzzle7 +test_integrations_guzzle7: global_test_run_dependencies tests/Integrations/Guzzle/V7/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Guzzle/V7) -test_integrations_laminaslog2: global_test_run_dependencies - $(MAKE) test_scenario_laminaslog2 +test_integrations_laminaslog2: global_test_run_dependencies tests/Integrations/Logs/LaminasLogV2/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Logs/LaminasLogV2) test_integrations_memcached: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/Memcached) test_integrations_memcache: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/Memcache) -test_integrations_monolog1: global_test_run_dependencies - $(MAKE) test_scenario_monolog1 +test_integrations_monolog1: global_test_run_dependencies tests/Integrations/Logs/MonologV1/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Logs/MonologV1) -test_integrations_monolog2: global_test_run_dependencies - $(MAKE) test_scenario_monolog2 +test_integrations_monolog2: global_test_run_dependencies tests/Integrations/Logs/MonologV2/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Logs/MonologV2) -test_integrations_monolog3: global_test_run_dependencies - $(MAKE) test_scenario_monolog3 +test_integrations_monolog3: global_test_run_dependencies tests/Integrations/Logs/MonologV3/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Logs/MonologV3) test_integrations_mysqli: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/Mysqli) test_integrations_mongo: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/Mongo) -test_integrations_mongodb1: - $(MAKE) test_scenario_mongodb1 +test_integrations_mongodb1: global_test_run_dependencies tests/Integrations/MongoDB/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/MongoDB) -test_integrations_openai: - $(MAKE) test_scenario_openai +test_integrations_openai: global_test_run_dependencies tests/Integrations/OpenAI/composer.lock-php$(PHP_MAJOR_MINOR) $(eval TELEMETRY_ENABLED=1) $(call run_tests_debug,tests/Integrations/OpenAI) $(eval TELEMETRY_ENABLED=0) test_integrations_pcntl: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/PCNTL) test_integrations_pdo: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/PDO) test_integrations_phpredis3: global_test_run_dependencies - $(MAKE) test_scenario_phpredis3 + $(eval TEST_EXTRA_INI=-d extension=redis-3.1.6.so) $(call run_tests_debug,tests/Integrations/PHPRedis/V3) + $(eval TEST_EXTRA_INI=) test_integrations_phpredis4: global_test_run_dependencies - $(MAKE) test_scenario_phpredis4 + $(eval TEST_EXTRA_INI=-d extension=redis-4.3.0.so) $(call run_tests_debug,tests/Integrations/PHPRedis/V4) + $(eval TEST_EXTRA_INI=) test_integrations_phpredis5: global_test_run_dependencies - $(MAKE) test_scenario_phpredis5 + $(eval TEST_EXTRA_ENV=DD_IGNORE_ARGINFO_ZPP_CHECK=1) + $(eval TEST_EXTRA_INI=-d extension=redis-5.3.7.so) $(call run_tests_debug,tests/Integrations/PHPRedis/V5) -test_integrations_predis1: global_test_run_dependencies - $(MAKE) test_scenario_predis1 + $(eval TEST_EXTRA_INI=) + $(eval TEST_EXTRA_ENV=) +test_integrations_predis1: global_test_run_dependencies tests/Integrations/Predis/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Predis) test_integrations_frankenphp: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,--testsuite=frankenphp-test) -test_integrations_roadrunner: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Roadrunner/Version_2,) +test_integrations_roadrunner: global_test_run_dependencies tests/Frameworks/Roadrunner/Version_2/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Roadrunner/V2) test_integrations_sqlsrv: global_test_run_dependencies - $(MAKE) test_scenario_default $(call run_tests_debug,tests/Integrations/SQLSRV) test_integrations_swoole_5: global_test_run_dependencies - $(MAKE) test_scenario_swoole5 $(call run_tests_debug,--testsuite=swoole-test) -test_web_cakephp_28: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_2_8,) +test_web_cakephp_28: global_test_run_dependencies tests/Frameworks/CakePHP/Version_2_8/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=cakephp-28-test) -test_web_cakephp_310: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_3_10,) +test_web_cakephp_310: global_test_run_dependencies tests/Frameworks/CakePHP/Version_3_10/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=cakephp-310-test) -test_web_cakephp_45: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_4_5,) +test_web_cakephp_45: global_test_run_dependencies tests/Frameworks/CakePHP/Version_4_5/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=cakephp-45-test) -test_web_cakephp_50: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/CakePHP/Version_5_0,) +test_web_cakephp_50: global_test_run_dependencies tests/Frameworks/CakePHP/Version_5_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=cakephp-50-test) test_web_codeigniter_22: global_test_run_dependencies $(call run_tests_debug,--testsuite=codeigniter-22-test) -test_web_codeigniter_31: global_test_run_dependencies - $(COMPOSER) --working-dir=tests/Frameworks/CodeIgniter/Version_3_1 update +test_web_codeigniter_31: global_test_run_dependencies tests/Frameworks/CodeIgniter/Version_3_1/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=codeigniter-31-test) -test_web_drupal_89: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_8_9/core,--ignore-platform-reqs) - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_8_9,--ignore-platform-reqs) +test_web_drupal_89: global_test_run_dependencies tests/Frameworks/Drupal/Version_8_9/core/composer.lock-php tests/Frameworks/Drupal/Version_8_9/composer.lock-php $(call run_tests_debug,tests/Integrations/Drupal/V8_9) -test_web_drupal_95: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_9_5/core,--ignore-platform-reqs) - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_9_5,--ignore-platform-reqs) +test_web_drupal_95: global_test_run_dependencies tests/Frameworks/Drupal/Version_9_5/core/composer.lock-php tests/Frameworks/Drupal/Version_9_5/composer.lock-php $(call run_tests_debug,tests/Integrations/Drupal/V9_5) -test_web_drupal_101: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_10_1/core,--ignore-platform-reqs) - $(call run_composer_with_retry,tests/Frameworks/Drupal/Version_10_1,--ignore-platform-reqs) +test_web_drupal_101: global_test_run_dependencies tests/Frameworks/Drupal/Version_10_1/core/composer.lock-php tests/Frameworks/Drupal/Version_10_1/composer.lock-php $(call run_tests_debug,tests/Integrations/Drupal/V10_1) -test_web_laminas_rest_19: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laminas/ApiTools/Version_1_9,) +test_web_laminas_rest_19: global_test_run_dependencies tests/Frameworks/Laminas/ApiTools/Version_1_9/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Laminas/ApiTools/V1_9) -test_web_laminas_14: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laminas/Version_1_4,) +test_web_laminas_14: global_test_run_dependencies tests/Frameworks/Laminas/Version_1_4/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Laminas/V1_4) -test_web_laminas_20: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laminas/Version_2_0,) +test_web_laminas_20: global_test_run_dependencies tests/Frameworks/Laminas/Version_2_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Laminas/V2_0) -test_web_laravel_42: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_4_2,) +test_web_laravel_42: global_test_run_dependencies tests/Frameworks/Laravel/Version_4_2/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Laravel/Version_4_2/artisan optimize $(call run_tests_debug,tests/Integrations/Laravel/V4) -test_web_laravel_57: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_5_7,) +test_web_laravel_57: global_test_run_dependencies tests/Frameworks/Laravel/Version_5_7/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Laravel/V5_7) -test_web_laravel_58: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_5_8,) +test_web_laravel_58: global_test_run_dependencies tests/Frameworks/Laravel/Version_5_8/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-58-test) -test_web_laravel_8x: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_8_x,) +test_web_laravel_8x: global_test_run_dependencies tests/Frameworks/Laravel/Version_8_x/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-8x-test) -test_web_laravel_9x: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_9_x,) +test_web_laravel_9x: global_test_run_dependencies tests/Frameworks/Laravel/Version_9_x/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-9x-test) -test_web_laravel_10x: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_10_x,) +test_web_laravel_10x: global_test_run_dependencies tests/Frameworks/Laravel/Version_10_x/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-10x-test) -test_web_laravel_11x: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Laravel/Version_11_x,) +test_web_laravel_11x: global_test_run_dependencies tests/Frameworks/Laravel/Version_11_x/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-11x-test) -test_web_laravel_octane: global_test_run_dependencies - $(MAKE) test_scenario_swoole5 - $(call run_composer_with_retry,tests/Frameworks/Laravel/Octane,) +test_web_laravel_octane: global_test_run_dependencies tests/Frameworks/Laravel/Octane/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=laravel-octane-test) -test_web_lumen_52: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_5_2,) +test_web_lumen_52: global_test_run_dependencies tests/Frameworks/Lumen/Version_5_2/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V5_2) -test_web_lumen_56: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_5_6,) +test_web_lumen_56: global_test_run_dependencies tests/Frameworks/Lumen/Version_5_6/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V5_6) -test_web_lumen_58: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_5_8,) +test_web_lumen_58: global_test_run_dependencies tests/Frameworks/Lumen/Version_5_8/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V5_8) -test_web_lumen_81: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_8_1,) +test_web_lumen_81: global_test_run_dependencies tests/Frameworks/Lumen/Version_8_1/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V8_1) -test_web_lumen_90: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_9_0,) +test_web_lumen_90: global_test_run_dependencies tests/Frameworks/Lumen/Version_9_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V9_0) -test_web_lumen_100: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Lumen/Version_10_0,) +test_web_lumen_100: global_test_run_dependencies tests/Frameworks/Lumen/Version_10_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Lumen/V10_0) -test_web_slim_312: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Slim/Version_3_12,) +test_web_slim_312: global_test_run_dependencies tests/Frameworks/Slim/Version_3_12/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=slim-312-test) -test_web_slim_4: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Slim/Version_4,) +test_web_slim_4: global_test_run_dependencies tests/Frameworks/Slim/Version_4/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=slim-4-test) -test_web_symfony_23: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_2_3,) +test_web_symfony_23: global_test_run_dependencies tests/Frameworks/Symfony/Version_2_3/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Symfony/V2_3) -test_web_symfony_28: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_2_8,) +test_web_symfony_28: global_test_run_dependencies tests/Frameworks/Symfony/Version_2_8/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Symfony/V2_8) -test_web_symfony_30: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_3_0,) +test_web_symfony_30: global_test_run_dependencies tests/Frameworks/Symfony/Version_3_0/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_3_0/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V3_0) -test_web_symfony_33: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_3_3,) +test_web_symfony_33: global_test_run_dependencies tests/Frameworks/Symfony/Version_3_3/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_3_3/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V3_3) -test_web_symfony_34: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_3_4,) +test_web_symfony_34: global_test_run_dependencies tests/Frameworks/Symfony/Version_3_4/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_3_4/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V3_4) test_web_symfony_40: global_test_run_dependencies @@ -1379,32 +1340,26 @@ test_web_symfony_40: global_test_run_dependencies $(COMPOSER) --working-dir=tests/Frameworks/Symfony/Version_4_0 install --no-dev php tests/Frameworks/Symfony/Version_4_0/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V4_0) -test_web_symfony_42: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_4_2,) +test_web_symfony_42: global_test_run_dependencies tests/Frameworks/Symfony/Version_4_2/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_4_2/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V4_2) -test_web_symfony_44: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_4_4,) +test_web_symfony_44: global_test_run_dependencies tests/Frameworks/Symfony/Version_4_4/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_4_4/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,--testsuite=symfony-44-test) test_web_symfony_50: global_test_run_dependencies $(COMPOSER) --working-dir=tests/Frameworks/Symfony/Version_5_0 install # EOL; install from lock php tests/Frameworks/Symfony/Version_5_0/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V5_0) -test_web_symfony_51: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_5_1,) +test_web_symfony_51: global_test_run_dependencies tests/Frameworks/Symfony/Version_5_1/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_5_1/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,tests/Integrations/Symfony/V5_1) -test_web_symfony_52: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_5_2,) +test_web_symfony_52: global_test_run_dependencies tests/Frameworks/Symfony/Version_5_2/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_5_2/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,--testsuite=symfony-52-test) -test_web_symfony_62: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_6_2,) +test_web_symfony_62: global_test_run_dependencies tests/Frameworks/Symfony/Version_6_2/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_6_2/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,--testsuite=symfony-62-test) -test_web_symfony_70: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Symfony/Version_7_0,) +test_web_symfony_70: global_test_run_dependencies tests/Frameworks/Symfony/Version_7_0/composer.lock-php$(PHP_MAJOR_MINOR) php tests/Frameworks/Symfony/Version_7_0/bin/console cache:clear --no-warmup --env=prod $(call run_tests_debug,--testsuite=symfony-70-test) test_web_wordpress_48: global_test_run_dependencies @@ -1415,31 +1370,29 @@ test_web_wordpress_59: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/WordPress/V5_9) test_web_wordpress_61: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/WordPress/V6_1) -test_web_yii_2: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Yii/Version_2_0,) +test_web_yii_2: global_test_run_dependencies tests/Frameworks/Yii/Version_2_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Yii/V2_0) -test_web_magento_23: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Magento/Version_2_3,) +test_web_magento_23: global_test_run_dependencies tests/Frameworks/Magento/Version_2_3/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Magento/V2_3) -test_web_magento_24: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Magento/Version_2_4,) +test_web_magento_24: global_test_run_dependencies tests/Frameworks/Magento/Version_2_4/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Magento/V2_4) -test_web_nette_24: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Nette/Version_2_4,) +test_web_nette_24: global_test_run_dependencies tests/Frameworks/Nette/Version_2_4/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Nette/V2_4) -test_web_nette_30: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Nette/Version_3_0,) +test_web_nette_30: global_test_run_dependencies tests/Frameworks/Nette/Version_3_0/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,tests/Integrations/Nette/V3_0) test_web_zend_1: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/ZendFramework/V1) test_web_zend_1_21: global_test_run_dependencies $(call run_tests_debug,tests/Integrations/ZendFramework/V1_21) -test_web_custom: global_test_run_dependencies - $(call run_composer_with_retry,tests/Frameworks/Custom/Version_Autoloaded,) +test_web_custom: global_test_run_dependencies tests/Frameworks/Custom/Version_Autoloaded/composer.lock-php$(PHP_MAJOR_MINOR) $(call run_tests_debug,--testsuite=custom-framework-autoloading-test) -test_scenario_%: - $(Q) $(COMPOSER_TESTS) scenario $* +tests/Frameworks/Drupal/%/composer.lock-php: tests/Frameworks/Drupal/%/composer.json + $(call run_composer_with_retry,tests/Frameworks/Drupal/$*,--ignore-platform-reqs) + touch tests/Frameworks/Drupal/$(*)/composer.lock-php + +tests/%/composer.lock-php$(PHP_MAJOR_MINOR): tests/%/composer.json + $(call run_composer_with_lock,tests/$(*)) merge_coverage_reports: php -d memory_limit=-1 $(PHPCOV) merge --clover reports/coverage.xml reports/cov @@ -1455,7 +1408,7 @@ test_internal_api_randomized: $(SO_FILE) $(if $(ASAN), USE_ZEND_ALLOC=0 USE_TRACKED_ALLOC=1) php -n -ddatadog.trace.cli_enabled=1 -d extension=$(SO_FILE) tests/internal-api-stress-test.php 2> >(grep -A 1000 ==============) composer.lock: composer.json - $(Q) $(COMPOSER) update + $(call run_composer_with_retry,,) .PHONY: dev dist_clean clean cores all clang_format_check clang_format_fix install sudo_install test_c test_c_mem test_extension_ci test_zai test_zai_asan test install_ini install_all \ .apk .rpm .deb .tar.gz sudo debug prod strict run-tests.php verify_pecl_file_definitions verify_package_xml cbindgen cbindgen_binary diff --git a/VERSION b/VERSION index 867e52437a..e21e727f96 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.0 \ No newline at end of file +1.4.0 \ No newline at end of file diff --git a/appsec/README.md b/appsec/README.md index 9594f9857c..d9f9d88023 100644 --- a/appsec/README.md +++ b/appsec/README.md @@ -74,15 +74,15 @@ cd build cmake .. make -j ``` -This will produce the extension, `ddappsec.so` and the helper process `ddappsec-helper`. +This will produce the extension, `ddappsec.so` and the helper library `libddappsec-helper.so`. Alternatively, to build the extension but not the helper, you can disable the helper build on the cmake step: ``` -cmake .. -DDD_APPSEC_BUILD_HELPER=OFF +cmake .. -DDD_APPSEC_BUILD_HELPER=OFF ``` Similarly, to build the helper but not the extension: ``` -cmake .. DDD_APPSEC_BUILD_EXTENSION=OFF +cmake .. DDD_APPSEC_BUILD_EXTENSION=OFF ``` #### Testing the extension diff --git a/appsec/cmake/Toolchain.aarch64.cmake b/appsec/cmake/Toolchain.aarch64.cmake deleted file mode 100644 index dfe24329ca..0000000000 --- a/appsec/cmake/Toolchain.aarch64.cmake +++ /dev/null @@ -1,25 +0,0 @@ -set(sysroot /sysroot/aarch64-none-linux-musl) -set(arch aarch64) -set(interpreter ld-musl-aarch64.so.1) -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_PROCESSOR ${arch}) -set(CMAKE_SYSROOT ${sysroot}) -set(CMAKE_AR /usr/bin/llvm-ar-16) -set(triple ${arch}-none-linux-musl) -set(CMAKE_ASM_COMPILER_TARGET ${triple}) -set(CMAKE_C_COMPILER /usr/bin/clang-16) -set(CMAKE_C_COMPILER_TARGET ${triple}) - -set(c_cxx_flags "-Qunused-arguments -rtlib=compiler-rt -unwindlib=libunwind -static-libgcc -isystem/sysroot/aarch64-none-linux-musl/usr/include/c++/v1/") -set(CMAKE_C_FLAGS ${c_cxx_flags}) -set(CMAKE_CXX_COMPILER /usr/bin/clang++-16) -set(CMAKE_CXX_COMPILER_TARGET ${triple}) -set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${c_cxx_flags}") - -set(linker_flags "-v -fuse-ld=lld-16 -static -nodefaultlibs -lc++ -lc++abi ${sysroot}/usr/lib/libclang_rt.builtins.a -lunwind -lc ${sysroot}/usr/lib/libclang_rt.builtins.a -resource-dir ${sysroot}/usr/lib/resource_dir") -set(CMAKE_EXE_LINKER_FLAGS_INIT "${linker_flags}") -set(CMAKE_SHARED_LINKER_FLAGS_INIT ${linker_flags}) - -set(CMAKE_NM /usr/bin/llvm-nm-16) -set(CMAKE_RANLIB /usr/bin/llvm-ranlib-16) -set(CMAKE_STRIP /usr/bin/aarch64-linux-gnu-strip) # llvm-strip-11 doesn't seem to work correctly diff --git a/appsec/cmake/Toolchain.x86_64.cmake b/appsec/cmake/Toolchain.x86_64.cmake deleted file mode 100644 index 1ff15264d8..0000000000 --- a/appsec/cmake/Toolchain.x86_64.cmake +++ /dev/null @@ -1,25 +0,0 @@ -set(sysroot /sysroot/x86_64-none-linux-musl) -set(arch x86_64) -set(interpreter ld-musl-x86_64.so.1) -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_PROCESSOR ${arch}) -set(CMAKE_SYSROOT ${sysroot}) -set(CMAKE_AR /usr/bin/llvm-ar-16) -set(triple ${arch}-none-linux-musl) -set(CMAKE_ASM_COMPILER_TARGET ${triple}) -set(CMAKE_C_COMPILER /usr/bin/clang-16) -set(CMAKE_C_COMPILER_TARGET ${triple}) - -set(c_cxx_flags "-Qunused-arguments -rtlib=compiler-rt -unwindlib=libunwind -static-libgcc -isystem/sysroot/x86_64-none-linux-musl/usr/include/c++/v1/") -set(CMAKE_C_FLAGS ${c_cxx_flags}) -set(CMAKE_CXX_COMPILER /usr/bin/clang++-16) -set(CMAKE_CXX_COMPILER_TARGET ${triple}) -set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${c_cxx_flags}") - -set(linker_flags "-v -fuse-ld=lld-16 -static -nodefaultlibs -lc++ -lc++abi ${sysroot}/usr/lib/libclang_rt.builtins.a -lunwind -lc ${sysroot}/usr/lib/libclang_rt.builtins.a -resource-dir ${sysroot}/usr/lib/resource_dir") -set(CMAKE_EXE_LINKER_FLAGS_INIT "${linker_flags}") -set(CMAKE_SHARED_LINKER_FLAGS_INIT ${linker_flags}) - -set(CMAKE_NM /usr/bin/llvm-nm-16) -set(CMAKE_RANLIB /usr/bin/llvm-ranlib-16) -set(CMAKE_STRIP /usr/bin/x86_64-linux-gnu-strip) # llvm-strip-11 doesn't seem to work correctly diff --git a/appsec/cmake/cond_flag.cmake b/appsec/cmake/cond_flag.cmake new file mode 100644 index 0000000000..1dfc82e3e5 --- /dev/null +++ b/appsec/cmake/cond_flag.cmake @@ -0,0 +1,14 @@ +macro(target_linker_flag_conditional target) # flags as argv + try_compile(LINKER_HAS_FLAG "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/check.c" + LINK_OPTIONS ${ARGN} + OUTPUT_VARIABLE LINKER_HAS_FLAG_ERROR_LOG) + + if(LINKER_HAS_FLAG) + target_link_options(${target} PRIVATE ${ARGN}) + message(STATUS "Linker has flag ${ARGN}") + else() + #message(STATUS "Linker does not have flag: ${LINKER_HAS_FLAG_ERROR_LOG}") + endif() +endmacro() + + diff --git a/appsec/cmake/ddtrace.cmake b/appsec/cmake/ddtrace.cmake index 8b4b7f87a6..5658a5ec30 100644 --- a/appsec/cmake/ddtrace.cmake +++ b/appsec/cmake/ddtrace.cmake @@ -18,13 +18,39 @@ add_custom_target(libdatadog_stamp BYPRODUCT ${LIBDATADOG_STAMP_FILE} ) -set(EXPORTS_FILE "${CMAKE_BINARY_DIR}/exports.version") +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +set(EXPORTS_FILE "${CMAKE_BINARY_DIR}/ddtrace_exports.version") add_custom_target(ddtrace_exports COMMAND bash -c "{ echo -e '{\\nglobal:'; sed 's/$/;/' '${CMAKE_SOURCE_DIR}'/../ddtrace.sym; echo -e 'local:\\n*;\\n};'; } > '${EXPORTS_FILE}'" BYPRODUCT ${EXPORTS_FILE} DEPENDS ${CMAKE_SOURCE_DIR}/../ddtrace.sym VERBATIM ) +elseif(APPLE) +set(EXPORTS_FILE "${CMAKE_BINARY_DIR}/ddtrace_exports.sym") +add_custom_target(ddtrace_exports + COMMAND bash -c "sed 's/^/_/' '${CMAKE_SOURCE_DIR}'/../ddtrace.sym > '${EXPORTS_FILE}'" + BYPRODUCT ${EXPORTS_FILE} + DEPENDS ${CMAKE_SOURCE_DIR}/../ddtrace.sym + VERBATIM +) +endif() + +file(READ "${CMAKE_SOURCE_DIR}/../VERSION" VERSION_CONTENTS) +string(STRIP "${VERSION_CONTENTS}" PHP_DDTRACE_VERSION) +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/gen_ddtrace/ext") +set(VERSION_H_PATH "${CMAKE_BINARY_DIR}/gen_ddtrace/ext/version.h") + +add_custom_command( + OUTPUT "${VERSION_H_PATH}" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --switch= --green "Updating version.h" + COMMAND ${CMAKE_COMMAND} -E remove -f "${VERSION_H_PATH}" + COMMAND ${CMAKE_COMMAND} -E touch "${VERSION_H_PATH}" + COMMAND printf "\\#ifndef PHP_DDTRACE_VERSION\\\\n\\#define PHP_DDTRACE_VERSION \"%s\"\\\\n\\#endif" "'\"${PHP_DDTRACE_VERSION}\"'" >> "${VERSION_H_PATH}" + DEPENDS "${CMAKE_SOURCE_DIR}/../VERSION" + COMMENT "Generating version.h" +) +add_custom_target(update_version_h ALL DEPENDS "${VERSION_H_PATH}") ExternalProject_Add(components_rs_proj PREFIX ${CMAKE_BINARY_DIR}/components_rs @@ -99,8 +125,15 @@ set_target_properties(ddtrace PROPERTIES OUTPUT_NAME ddtrace DEBUG_POSTFIX "" PREFIX "") -target_compile_options(ddtrace PRIVATE -fms-extensions) -target_link_options(ddtrace PRIVATE "-Wl,--version-script=${EXPORTS_FILE}") +target_compile_options(ddtrace PRIVATE -fms-extensions -Wno-microsoft-anon-tag) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_compile_definitions(ddtrace PRIVATE _GNU_SOURCE) + target_link_options(ddtrace PRIVATE "-Wl,--version-script=${EXPORTS_FILE}") +elseif(APPLE) + target_link_options(ddtrace PRIVATE "-exported_symbols_list" "${EXPORTS_FILE}") +else() + message(FATAL_ERROR "Only Linux and Apple supported") +endif() target_link_libraries(ddtrace PRIVATE PhpConfig components_rs ${CURL_LIBRARIES}) if(CURL_DEFINITIONS) target_compile_definitions(ddtrace PRIVATE ${CURL_DEFINITIONS}) @@ -114,7 +147,8 @@ target_include_directories(ddtrace PRIVATE ${CMAKE_SOURCE_DIR}/../ext ${CMAKE_SOURCE_DIR}/../ext/vendor ${CMAKE_SOURCE_DIR}/../ext/vendor/mt19937 + ${CMAKE_BINARY_DIR}/gen_ddtrace ) -add_dependencies(ddtrace ddtrace_exports) +add_dependencies(ddtrace ddtrace_exports update_version_h) patch_away_libc(ddtrace) diff --git a/appsec/cmake/extension.cmake b/appsec/cmake/extension.cmake index 9f1c355c89..f42c835bee 100644 --- a/appsec/cmake/extension.cmake +++ b/appsec/cmake/extension.cmake @@ -24,19 +24,7 @@ set_target_properties(extension PROPERTIES target_compile_definitions(extension PRIVATE TESTING=1 ZEND_ENABLE_STATIC_TSRMLS_CACHE=1 -D_GNU_SOURCE) target_link_libraries(extension PRIVATE mpack PhpConfig zai) - -macro(target_linker_flag_conditional target) # flags as argv - try_compile(LINKER_HAS_FLAG "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/check.c" - LINK_OPTIONS ${ARGN} - OUTPUT_VARIABLE LINKER_HAS_FLAG_ERROR_LOG) - - if(LINKER_HAS_FLAG) - target_link_options(${target} PRIVATE ${ARGN}) - message(STATUS "Linker has flag ${ARGN}") - else() - #message(STATUS "Linker does not have flag: ${LINKER_HAS_FLAG_ERROR_LOG}") - endif() -endmacro() +target_include_directories(extension PRIVATE ..) # we don't have any C++ now, but just so we don't forget in the future... check_cxx_compiler_flag("-fno-gnu-unique" COMPILER_HAS_NO_GNU_UNIQUE) @@ -48,11 +36,13 @@ target_compile_options(extension PRIVATE -Wall -Wextra -Werror) # our thread local variables are only used by ourselves target_compile_options(extension PRIVATE -ftls-model=local-dynamic) +include(cmake/cond_flag.cmake) + target_linker_flag_conditional(extension -Wl,--as-needed) # ld doesn't necessarily respect the visibility of hidden symbols if # they're inside static libraries, so use a linker script only exporting # ddappsec.version as a safeguard -target_linker_flag_conditional(extension "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/ddappsec.version") +target_linker_flag_conditional(extension "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/extension/ddappsec.version") # Mac OS target_linker_flag_conditional(extension -flat_namespace "-undefined suppress") diff --git a/appsec/cmake/helper.cmake b/appsec/cmake/helper.cmake index 836285b3ae..eb265cdcba 100644 --- a/appsec/cmake/helper.cmake +++ b/appsec/cmake/helper.cmake @@ -10,33 +10,61 @@ configure_file(src/helper/version.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/helper/ set(HELPER_SOURCE_DIR src/helper) set(HELPER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/helper) -file(GLOB_RECURSE HELPER_SOURCE ${HELPER_SOURCE_DIR}/*.cpp) +file(GLOB_RECURSE HELPER_SOURCE CONFIGURE_DEPENDS + ${HELPER_SOURCE_DIR}/*.cpp ${HELPER_SOURCE_DIR}/*.c) list(FILTER HELPER_SOURCE EXCLUDE REGEX "^.*main\.cpp$") add_library(helper_objects OBJECT ${HELPER_SOURCE}) set_target_properties(helper_objects PROPERTIES + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES POSITION_INDEPENDENT_CODE 1) -target_include_directories(helper_objects PUBLIC ${HELPER_INCLUDE_DIR}) +target_include_directories(helper_objects INTERFACE ${HELPER_INCLUDE_DIR}) target_compile_definitions(helper_objects PUBLIC SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) +target_compile_options(helper_objects PRIVATE -ftls-model=global-dynamic) target_link_libraries(helper_objects PUBLIC libddwaf_objects pthread spdlog cpp-base64 msgpack_c RapidJSON::rapidjson Boost::system zlibstatic) -add_executable(ddappsec-helper src/helper/main.cpp - $ - $) +add_library(ddappsec-helper SHARED + src/helper/main.cpp + $ + $) target_link_libraries(ddappsec-helper helper_objects) # for its PUBLIC deps +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_compile_options(ddappsec-helper PRIVATE -ftls-model=global-dynamic) + # Bind symbols lookup of symbols defined in the library to the library itself + # also avoids relocation problems with libc++.a on linux/aarch64 + target_link_options(ddappsec-helper PRIVATE -Wl,-Bsymbolic) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_options(ddappsec-helper PRIVATE -undefined dynamic_lookup) +endif() +set_target_properties(ddappsec-helper PROPERTIES + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + POSITION_INDEPENDENT_CODE 1 + DEBUG_POSTFIX "" + SUFFIX .so +) + +include(cmake/cond_flag.cmake) +target_linker_flag_conditional(ddappsec-helper "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/helper/helper.version") + +patch_away_libc(ddappsec-helper) try_compile(STDLIBXX_FS_NO_LIB_NEEDED ${CMAKE_CURRENT_BINARY_DIR} SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED TRUE) try_compile(STDLIBXX_FS_NEEDS_STDCXXFS ${CMAKE_CURRENT_BINARY_DIR} + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED TRUE LINK_LIBRARIES stdc++fs) try_compile(STDLIBXX_FS_NEEDS_CXXFS ${CMAKE_CURRENT_BINARY_DIR} SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_fslib.cpp - CXX_STANDARD 17 + CXX_STANDARD 20 CXX_STANDARD_REQUIRED TRUE LINK_LIBRARIES c++fs) if(NOT STDLIBXX_FS_NO_LIB_NEEDED) diff --git a/appsec/cmake/patchelf.cmake b/appsec/cmake/patchelf.cmake index 892c90f2ee..4ae6873828 100644 --- a/appsec/cmake/patchelf.cmake +++ b/appsec/cmake/patchelf.cmake @@ -1,5 +1,5 @@ function(patch_away_libc target) - if (NOT ${DD_APPSEC_ENABLE_PATCHELF_LIBC}) + if(NOT ${DD_APPSEC_ENABLE_PATCHELF_LIBC}) return() endif() @@ -8,10 +8,15 @@ function(patch_away_libc target) endif() find_program(PATCHELF patchelf) - if (PATCHELF STREQUAL "PATCHELF-NOTFOUND") + find_program(READELF readelf) + if(PATCHELF STREQUAL "PATCHELF-NOTFOUND") message(WARNING "Patchelf not found. Can't build glibc + musl binaries") else() - add_custom_command(TARGET ${target} POST_BUILD - COMMAND patchelf --remove-needed libc.so $ ${SYMBOL_FILE}) + if(READELF STREQUAL "READELF-NOTFOUND") + message(WARNING "readelf not found. Can't build glibc + musl binaries") + else() + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_SOURCE_DIR}/cmake/strip_libc.sh "${PATCHELF}" "${READELF}" $) + endif() endif() endfunction() diff --git a/appsec/cmake/run-tests-wrapper.sh b/appsec/cmake/run-tests-wrapper.sh index 701cb35dd3..df84590d5f 100755 --- a/appsec/cmake/run-tests-wrapper.sh +++ b/appsec/cmake/run-tests-wrapper.sh @@ -7,7 +7,6 @@ export NO_INTERACTION=1 export DD_TRACE_ENABLED=true #export DD_TRACE_DEBUG=true export DD_TRACE_GENERATE_ROOT_SPAN=true -export DD_TRACE_CLI_ENABLED=true export DD_TRACE_AGENT_PORT=18126 export PHPRC= diff --git a/appsec/cmake/strip_libc.sh b/appsec/cmake/strip_libc.sh new file mode 100755 index 0000000000..128f566b28 --- /dev/null +++ b/appsec/cmake/strip_libc.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -e + +main() { + local patchelf=$1 + local readelf=$2 + local target=$3 + + "$patchelf" $( + "$readelf" -d "$target" 2>/dev/null | grep libc\\. | grep NEEDED | \ + awk -F'[][]' '{print "--remove-needed " $2;}' | xargs + ) \ + "$target" +} + +main "$@" diff --git a/appsec/src/extension/backtrace.c b/appsec/src/extension/backtrace.c new file mode 100644 index 0000000000..8d5e5c8e9f --- /dev/null +++ b/appsec/src/extension/backtrace.c @@ -0,0 +1,342 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +#include "backtrace.h" +#include "configuration.h" +#include "ddtrace.h" +#include "logging.h" +#include "php_compat.h" +#include "php_objects.h" +#include "string_helpers.h" + +static const int NO_LIMIT = 0; +static const double STACK_DEFAULT_TOP_RATE = 0.25; +static const char QUALIFIED_NAME_SEPARATOR[] = "::"; + +static zend_string *_frames_key; +static zend_string *_language_key; +static zend_string *_php_value; +static zend_string *_exploit_key; +static zend_string *_dd_stack_key; +static zend_string *_frame_line; +static zend_string *_frame_function; +static zend_string *_frame_file; +static zend_string *_id_key; +static zend_string *_line_field; +static zend_string *_function_field; +static zend_string *_file_field; +static zend_string *_class_field; + +static bool +php_backtrace_frame_to_datadog_backtrace_frame( // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + zval *nonnull php_backtrace_frame, zval *nonnull datadog_backtrace_frame, + zend_ulong index) +{ + if (Z_TYPE_P(php_backtrace_frame) != IS_ARRAY) { + return false; + } + HashTable *frame = Z_ARRVAL_P(php_backtrace_frame); + zval *line = zend_hash_find(frame, _line_field); + zval *function = zend_hash_find(frame, _function_field); + zval *file = zend_hash_find(frame, _file_field); + zval *class = zend_hash_find(frame, _class_field); + zval id; + ZVAL_LONG(&id, index); +#ifdef TESTING + if (file) { + // In order to be able to test full path encoded everywhere lets set + // only the file name without path + const char *file_name = + zend_memrchr(Z_STRVAL_P(file), '/', Z_STRLEN_P(file)); + if (file_name) { + zend_string *new_file = zend_string_init(file_name + 1, + Z_STRLEN_P(file) - (file_name + 1 - Z_STRVAL_P(file)), 0); + zval_ptr_dtor(file); + ZVAL_NEW_STR(file, new_file); + } + } +#endif + + if (!function) { + return false; + } + + // Remove tracer integration php code frames + if (strncmp(Z_STRVAL_P(function), "DDTrace", sizeof("DDTrace") - 1) == 0) { + return false; + } + + array_init(datadog_backtrace_frame); + HashTable *datadog_backtrace_frame_ht = Z_ARRVAL_P(datadog_backtrace_frame); + if (line) { + zend_hash_add(datadog_backtrace_frame_ht, _frame_line, line); + } + + zend_ulong qualified_name_size = Z_STRLEN_P(function); + qualified_name_size += + class ? Z_STRLEN_P(class) + sizeof(QUALIFIED_NAME_SEPARATOR) - 1 : 0; + zend_string *qualified_name_zstr = + zend_string_alloc(qualified_name_size, 0); + char *qualified_name = ZSTR_VAL(qualified_name_zstr); + int position = 0; + + if (class) { + memcpy(qualified_name, Z_STRVAL_P(class), Z_STRLEN_P(class)); + position = Z_STRLEN_P(class); + memcpy(&qualified_name[position], QUALIFIED_NAME_SEPARATOR, + sizeof(QUALIFIED_NAME_SEPARATOR) - 1); + position += 2; + } + + memcpy( + &qualified_name[position], Z_STRVAL_P(function), Z_STRLEN_P(function)); + + qualified_name[qualified_name_size] = '\0'; + + zval zv_qualified_name; + ZVAL_STR(&zv_qualified_name, qualified_name_zstr); + zend_hash_add( + datadog_backtrace_frame_ht, _frame_function, &zv_qualified_name); + + if (file) { + zend_hash_add(datadog_backtrace_frame_ht, _frame_file, file); + Z_TRY_ADDREF_P(file); + } + zend_hash_add(datadog_backtrace_frame_ht, _id_key, &id); + + return true; +} + +static void php_backtrace_to_datadog_backtrace( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + zval *nonnull php_backtrace, zval *nonnull datadog_backtrace) +{ + if (Z_TYPE_P(php_backtrace) != IS_ARRAY) { + return; + } + + HashTable *php_backtrace_ht = Z_ARRVAL_P(php_backtrace); + uint32_t frames_on_stack = zend_array_count(php_backtrace_ht); + + uint32_t top = frames_on_stack; + uint32_t bottom = 0; + if (get_global_DD_APPSEC_MAX_STACK_TRACE_DEPTH() != 0 && + frames_on_stack > get_global_DD_APPSEC_MAX_STACK_TRACE_DEPTH()) { + top = (uint32_t)round( + (double)get_global_DD_APPSEC_MAX_STACK_TRACE_DEPTH() * + STACK_DEFAULT_TOP_RATE); + bottom = get_global_DD_APPSEC_MAX_STACK_TRACE_DEPTH() - top; + } + + array_init(datadog_backtrace); + + HashTable *datadog_backtrace_ht = Z_ARRVAL_P(datadog_backtrace); + + zval *php_frame; + zend_ulong index; + if (top > 0) { + ZEND_HASH_FOREACH_NUM_KEY_VAL(php_backtrace_ht, index, php_frame) + { + zval new_frame; + + if (php_backtrace_frame_to_datadog_backtrace_frame( + php_frame, &new_frame, index)) { + zend_hash_next_index_insert_new( + datadog_backtrace_ht, &new_frame); + } + if (--top == 0) { + break; + } + } + ZEND_HASH_FOREACH_END(); + } + + if (bottom > 0) { + unsigned int position = frames_on_stack - bottom; + DD_FOREACH_FROM(php_backtrace_ht, 0, position, index) + { + php_frame = _z; + zval new_frame; + + if (!php_backtrace_frame_to_datadog_backtrace_frame( + php_frame, &new_frame, index)) { + continue; + } + + zend_hash_next_index_insert_new(datadog_backtrace_ht, &new_frame); + } + ZEND_HASH_FOREACH_END(); + } +} + +void dd_generate_backtrace(zend_string *nullable id, zval *nonnull dd_backtrace) +{ + array_init(dd_backtrace); + + if (!id) { + return; + } + + zval language; + ZVAL_STR_COPY(&language, _php_value); + zval id_zv; + ZVAL_STR_COPY(&id_zv, id); + zend_hash_add(Z_ARRVAL_P(dd_backtrace), _language_key, &language); + zend_hash_add(Z_ARRVAL_P(dd_backtrace), _id_key, &id_zv); + + zval frames; + zval php_backtrace; + zend_fetch_debug_backtrace( + &php_backtrace, 1, DEBUG_BACKTRACE_IGNORE_ARGS, NO_LIMIT); + php_backtrace_to_datadog_backtrace(&php_backtrace, &frames); + zend_hash_add(Z_ARRVAL_P(dd_backtrace), _frames_key, &frames); + + zval_dtor(&php_backtrace); +} + +static PHP_FUNCTION(datadog_appsec_testing_generate_backtrace) +{ + zend_string *id = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &id) != SUCCESS) { + RETURN_FALSE; + } + + dd_generate_backtrace(id, return_value); +} + +bool dd_report_exploit_backtrace(zend_string *nullable id) +{ + if (!get_global_DD_APPSEC_STACK_TRACE_ENABLED()) { + return false; + } + + if (!id) { + mlog(dd_log_warning, + "Backtrace can not be generated because id is missing"); + } + + zend_object *span = dd_trace_get_active_root_span(); + if (!span) { + if (!get_global_DD_APPSEC_TESTING()) { + mlog(dd_log_warning, "Failed to retrieve root span"); + } + return false; + } + + zval *meta_struct = dd_trace_span_get_meta_struct(span); + if (!meta_struct) { + if (!get_global_DD_APPSEC_TESTING()) { + mlog(dd_log_warning, "Failed to retrieve root span meta_struct"); + } + return false; + } + + if (Z_TYPE_P(meta_struct) == IS_NULL) { + array_init(meta_struct); + } else if (Z_TYPE_P(meta_struct) != IS_ARRAY) { + return false; + } + + zval *dd_stack = zend_hash_find(Z_ARR_P(meta_struct), _dd_stack_key); + zval *exploit = NULL; + if (!dd_stack || Z_TYPE_P(dd_stack) == IS_NULL) { + zval new_dd_stack; + zval new_exploit; + dd_stack = zend_hash_add_new( + Z_ARR_P(meta_struct), _dd_stack_key, &new_dd_stack); + array_init(dd_stack); + exploit = + zend_hash_add_new(Z_ARR_P(dd_stack), _exploit_key, &new_exploit); + array_init(exploit); + } else if (Z_TYPE_P(dd_stack) != IS_ARRAY) { + return false; + } else { + exploit = zend_hash_find(Z_ARR_P(dd_stack), _exploit_key); + } + + if (Z_TYPE_P(exploit) != IS_ARRAY) { + return false; + } + + unsigned int limit = get_global_DD_APPSEC_MAX_STACK_TRACES(); + if (limit != 0 && zend_array_count(Z_ARR_P(exploit)) == limit) { + mlog(dd_log_debug, + "Stacktrace not generated due to limit " + "DD_APPSEC_MAX_STACK_TRACES(%u) has been reached", + limit); + return false; + } + + zval backtrace; + dd_generate_backtrace(id, &backtrace); + + if (zend_hash_next_index_insert_new(Z_ARRVAL_P(exploit), &backtrace) == + NULL) { + return false; + } + + return true; +} + +static PHP_FUNCTION(datadog_appsec_testing_report_exploit_backtrace) +{ + zend_string *id = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &id) != SUCCESS) { + RETURN_FALSE; + } + + if (dd_report_exploit_backtrace(id)) { + RETURN_TRUE; + } + + RETURN_FALSE; +} + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( + void_ret_bool_arginfo, 0, 1, _IS_BOOL, 0) +ZEND_ARG_TYPE_INFO(0, id, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( + void_ret_array_arginfo, 0, 1, IS_ARRAY, 0) +ZEND_ARG_TYPE_INFO(0, id, IS_STRING, 0) +ZEND_END_ARG_INFO() + +// clang-format off +static const zend_function_entry testing_functions[] = { + ZEND_RAW_FENTRY(DD_TESTING_NS "generate_backtrace", PHP_FN(datadog_appsec_testing_generate_backtrace), void_ret_array_arginfo,0) + ZEND_RAW_FENTRY(DD_TESTING_NS "report_exploit_backtrace", PHP_FN(datadog_appsec_testing_report_exploit_backtrace), void_ret_bool_arginfo, 0) + PHP_FE_END +}; +// clang-format on + +static void _register_testing_objects() +{ + if (!get_global_DD_APPSEC_TESTING()) { + return; + } + + dd_phpobj_reg_funcs(testing_functions); +} + +void dd_backtrace_startup() +{ + _frames_key = zend_string_init_interned(LSTRARG("frames"), 1); + _language_key = zend_string_init_interned(LSTRARG("language"), 1); + _php_value = zend_string_init_interned(LSTRARG("php"), 1); + _exploit_key = zend_string_init_interned(LSTRARG("exploit"), 1); + _dd_stack_key = zend_string_init_interned(LSTRARG("_dd.stack"), 1); + _frame_line = zend_string_init_interned(LSTRARG("line"), 1); + _frame_function = zend_string_init_interned(LSTRARG("function"), 1); + _frame_file = zend_string_init_interned(LSTRARG("file"), 1); + _id_key = zend_string_init_interned(LSTRARG("id"), 1); + _line_field = zend_string_init_interned(LSTRARG("line"), 1); + _file_field = zend_string_init_interned(LSTRARG("file"), 1); + _function_field = zend_string_init_interned(LSTRARG("function"), 1); + _class_field = zend_string_init_interned(LSTRARG("class"), 1); +#ifdef TESTING + _register_testing_objects(); +#endif +} diff --git a/appsec/src/extension/backtrace.h b/appsec/src/extension/backtrace.h new file mode 100644 index 0000000000..c16ab1eef5 --- /dev/null +++ b/appsec/src/extension/backtrace.h @@ -0,0 +1,21 @@ +// Unless explicitly stated otherwise all files in this repository are +// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. +// +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +#ifndef BACKTRACE_H +#define BACKTRACE_H + +#include +#include +#include +#include + +#include "attributes.h" + +void dd_backtrace_startup(); +void dd_generate_backtrace( + zend_string *nullable id, zval *nonnull dd_backtrace); +bool dd_report_exploit_backtrace(zend_string *nullable id); + +#endif // BACKTRACE_H diff --git a/appsec/src/extension/commands/client_init.c b/appsec/src/extension/commands/client_init.c index 1329561a67..a20513129d 100644 --- a/appsec/src/extension/commands/client_init.c +++ b/appsec/src/extension/commands/client_init.c @@ -17,68 +17,20 @@ #include "../version.h" #include "client_init.h" -static const unsigned int DEFAULT_AGENT_PORT = 8126; -static const char *DEFAULT_AGENT_HOST = "127.0.0.1"; -static const unsigned int MAX_TCP_PORT_ALLOWED = UINT16_MAX; - static dd_result _pack_command(mpack_writer_t *nonnull w, void *nullable ctx); static dd_result _process_response(mpack_node_t root, void *nullable ctx); static void _process_meta_and_metrics( mpack_node_t root, struct req_info *nonnull ctx); -static void _pack_agent_details(mpack_writer_t *nonnull w); static const dd_command_spec _spec = { .name = "client_init", .name_len = sizeof("client_init") - 1, - .num_args = 7, + .num_args = 6, .outgoing_cb = _pack_command, .incoming_cb = _process_response, .config_features_cb = dd_command_process_config_features_unexpected, }; -static void _pack_agent_details(mpack_writer_t *nonnull w) -{ - zend_string *agent_host = get_global_DD_AGENT_HOST(); - zend_string *agent_url = get_global_DD_TRACE_AGENT_URL(); - unsigned int port = get_global_DD_TRACE_AGENT_PORT(); - char *host = NULL; - php_url *parsed_url = NULL; - - if (agent_host && ZSTR_LEN(agent_host) > 0) { - host = ZSTR_VAL(agent_host); - } else if (agent_url && ZSTR_LEN(agent_url) > 0) { - parsed_url = php_url_parse(ZSTR_VAL(agent_url)); - if (parsed_url) { -#if PHP_VERSION_ID < 70300 - if (parsed_url->host && strlen(parsed_url->host) > 0) { - host = parsed_url->host; - } -#else - if (parsed_url->host && ZSTR_LEN(parsed_url->host) > 0) { - host = ZSTR_VAL(parsed_url->host); - } -#endif - port = parsed_url->port; - } - } - - if (!host) { - host = (char *)DEFAULT_AGENT_HOST; - } - if (port <= 0 || port > MAX_TCP_PORT_ALLOWED) { - port = DEFAULT_AGENT_PORT; - } - - dd_mpack_write_lstr(w, "host"); - dd_mpack_write_nullable_cstr(w, host); - dd_mpack_write_lstr(w, "port"); - mpack_write_uint(w, port); - - if (parsed_url) { - php_url_free(parsed_url); - } -} - dd_result dd_client_init(dd_conn *nonnull conn, struct req_info *nonnull ctx) { return dd_command_exec_cred(conn, &_spec, ctx); @@ -97,39 +49,6 @@ static dd_result _pack_command( mpack_write_bool(w, DDAPPSEC_G(active)); } - // Service details - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - mpack_start_map(w, 6); - - dd_mpack_write_lstr(w, "service"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_SERVICE())); - - dd_mpack_write_lstr(w, "extra_services"); - zval extra_services; - ZVAL_ARR(&extra_services, get_global_DD_EXTRA_SERVICES()); - dd_mpack_write_zval(w, &extra_services); - - dd_mpack_write_lstr(w, "env"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_ENV())); - - dd_mpack_write_lstr(w, "tracer_version"); - dd_mpack_write_nullable_cstr(w, dd_trace_version()); - - dd_mpack_write_lstr(w, "app_version"); - dd_mpack_write_nullable_cstr(w, ZSTR_VAL(get_DD_VERSION())); - - // We send this empty for now. The helper will check for empty and if so it - // will generate it - dd_mpack_write_lstr(w, "runtime_id"); - zend_string *runtime_id = dd_trace_get_formatted_runtime_id(false); - if (runtime_id == NULL) { - dd_mpack_write_nullable_cstr(w, ""); - } else { - dd_mpack_write_nullable_zstr(w, runtime_id); - zend_string_free(runtime_id); - } - mpack_finish_map(w); - // Engine settings // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) mpack_start_map(w, 6); @@ -180,15 +99,13 @@ static dd_result _pack_command( // Remote config settings // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - mpack_start_map(w, 4); + mpack_start_map(w, 2); dd_mpack_write_lstr(w, "enabled"); mpack_write_bool(w, get_DD_REMOTE_CONFIG_ENABLED()); - _pack_agent_details(w); - - dd_mpack_write_lstr(w, "poll_interval"); - mpack_write_u32(w, get_DD_REMOTE_CONFIG_POLL_INTERVAL()); + dd_mpack_write_lstr(w, "shmem_path"); + dd_mpack_write_nullable_cstr(w, dd_trace_remote_config_get_path()); mpack_finish_map(w); diff --git a/appsec/src/extension/commands/config_sync.c b/appsec/src/extension/commands/config_sync.c index 1938561d5e..c7fcbddd58 100644 --- a/appsec/src/extension/commands/config_sync.c +++ b/appsec/src/extension/commands/config_sync.c @@ -8,32 +8,36 @@ #include #include "../commands_helpers.h" +#include "../ddtrace.h" +#include "../msgpack_helpers.h" +#include "config_sync.h" #include -static dd_result _request_pack( - mpack_writer_t *nonnull w, void *nullable ATTR_UNUSED ctx); +static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx); dd_result dd_command_process_config_sync( mpack_node_t root, ATTR_UNUSED void *unspecnull ctx); static const dd_command_spec _spec = { .name = "config_sync", .name_len = sizeof("config_sync") - 1, - .num_args = 0, // a single map + .num_args = 1, .outgoing_cb = _request_pack, .incoming_cb = dd_command_process_config_sync, .config_features_cb = dd_command_process_config_features, }; -dd_result dd_config_sync(dd_conn *nonnull conn) +dd_result dd_config_sync( + dd_conn *nonnull conn, const struct config_sync_data *nonnull data) { - return dd_command_exec(conn, &_spec, NULL); + return dd_command_exec(conn, &_spec, (void *)data); } -static dd_result _request_pack( - mpack_writer_t *nonnull w, void *nullable ATTR_UNUSED ctx) +static dd_result _request_pack(mpack_writer_t *nonnull w, void *nonnull ctx_) { - UNUSED(ctx); - UNUSED(w); + const struct config_sync_data *nonnull data = + (struct config_sync_data *)ctx_; + + dd_mpack_write_nullable_cstr(w, data->rem_cfg_path); return dd_success; } diff --git a/appsec/src/extension/commands/config_sync.h b/appsec/src/extension/commands/config_sync.h index 95a6ae39a5..eeb8976485 100644 --- a/appsec/src/extension/commands/config_sync.h +++ b/appsec/src/extension/commands/config_sync.h @@ -7,4 +7,9 @@ #include "../network.h" -dd_result dd_config_sync(dd_conn *nonnull conn); +struct config_sync_data { + char *nullable rem_cfg_path; +}; + +dd_result dd_config_sync( + dd_conn *nonnull conn, const struct config_sync_data *nonnull data); diff --git a/appsec/src/extension/commands_helpers.c b/appsec/src/extension/commands_helpers.c index 6efe3089c4..1bedf8323e 100644 --- a/appsec/src/extension/commands_helpers.c +++ b/appsec/src/extension/commands_helpers.c @@ -4,6 +4,7 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "commands_helpers.h" +#include "backtrace.h" #include "commands_ctx.h" #include "configuration.h" #include "ddappsec.h" @@ -175,7 +176,7 @@ static dd_result _dd_command_exec(dd_conn *nonnull conn, bool check_cred, return dd_error; } if (res != dd_success && res != dd_should_block && - res != dd_should_redirect) { + res != dd_should_redirect && res != dd_should_record) { mlog(dd_log_warning, "Processing for command %.*s failed: %s", NAME_L, dd_result_to_string(res)); return res; @@ -419,6 +420,22 @@ static void _command_process_redirect_parameters(mpack_node_t root) dd_set_redirect_code_and_location(status_code, location); } +static void _command_process_stack_trace_parameters(mpack_node_t root) +{ + size_t count = mpack_node_map_count(root); + for (size_t i = 0; i < count; i++) { + mpack_node_t key = mpack_node_map_key_at(root, i); + mpack_node_t value = mpack_node_map_value_at(root, i); + if (dd_mpack_node_lstr_eq(key, "stack_id")) { + zend_string *id = NULL; + size_t id_len = mpack_node_strlen(value); + id = zend_string_init(mpack_node_str(value), id_len, 0); + dd_report_exploit_backtrace(id); + zend_string_release(id); + break; + } + } +} dd_result _command_process_actions(mpack_node_t root, struct req_info *ctx) { @@ -456,6 +473,9 @@ dd_result _command_process_actions(mpack_node_t root, struct req_info *ctx) } else if (dd_mpack_node_lstr_eq(verdict, "record") && res == dd_success) { res = dd_should_record; + } else if (dd_mpack_node_lstr_eq(verdict, "stack_trace")) { + _command_process_stack_trace_parameters( + mpack_node_array_at(action, 1)); } } diff --git a/appsec/src/extension/configuration.c b/appsec/src/extension/configuration.c index cda3239ea7..b6e4259368 100644 --- a/appsec/src/extension/configuration.c +++ b/appsec/src/extension/configuration.c @@ -12,7 +12,7 @@ #include "ip_extraction.h" #include "logging.h" #include "php_objects.h" -#include "tags.h" +#include "user_tracking.h" #include "zai_string/string.h" #define DD_TO_DATADOG_INC 5 /* "DD" expanded to "datadog" */ diff --git a/appsec/src/extension/configuration.h b/appsec/src/extension/configuration.h index bae726683d..d4b4838419 100644 --- a/appsec/src/extension/configuration.h +++ b/appsec/src/extension/configuration.h @@ -45,11 +45,14 @@ extern bool runtime_config_first_init; CONFIG(CUSTOM(INT), DD_APPSEC_LOG_LEVEL, "warn", .parser = dd_parse_log_level) \ SYSCFG(STRING, DD_APPSEC_LOG_FILE, "php_error_reporting") \ SYSCFG(BOOL, DD_APPSEC_HELPER_LAUNCH, "true") \ - CONFIG(STRING, DD_APPSEC_HELPER_PATH, DD_BASE("bin/ddappsec-helper")) \ + CONFIG(STRING, DD_APPSEC_HELPER_PATH, DD_BASE("bin/libddappsec-helper.so")) \ + SYSCFG(BOOL, DD_APPSEC_STACK_TRACE_ENABLED, "true") \ + SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACE_DEPTH, "32") \ + SYSCFG(INT, DD_APPSEC_MAX_STACK_TRACES, "2") \ CONFIG(STRING, DD_APPSEC_HELPER_RUNTIME_PATH, "/tmp", .ini_change = dd_on_runtime_path_update) \ SYSCFG(STRING, DD_APPSEC_HELPER_LOG_FILE, "/dev/null") \ + SYSCFG(STRING, DD_APPSEC_HELPER_LOG_LEVEL, "info") \ CONFIG(CUSTOM(SET), DD_EXTRA_SERVICES, "", .parser = _parse_list) \ - CONFIG(STRING, DD_APPSEC_HELPER_EXTRA_ARGS, "") \ CONFIG(STRING, DD_SERVICE, "") \ CONFIG(STRING, DD_ENV, "") \ CONFIG(STRING, DD_VERSION, "") \ @@ -60,7 +63,9 @@ extern bool runtime_config_first_init; CONFIG(INT, DD_APPSEC_MAX_BODY_BUFF_SIZE, "524288") \ CONFIG(STRING, DD_TRACE_AGENT_URL, "") \ CONFIG(BOOL, DD_TRACE_ENABLED, "true") \ - CONFIG(CUSTOM(STRING), DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING, "safe", .parser = dd_parse_automated_user_events_tracking) \ + CALIAS(CUSTOM(STRING), DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE, "ident", \ + CALIASES("DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING"), .parser = dd_parse_user_collection_mode) \ + CONFIG(BOOL, DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED, "true") \ CONFIG(STRING, DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML, "") \ CONFIG(STRING, DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON, "") \ CONFIG(DOUBLE, DD_API_SECURITY_REQUEST_SAMPLE_RATE, "0.1", .ini_change = zai_config_system_ini_change) \ diff --git a/appsec/src/extension/ddappsec.c b/appsec/src/extension/ddappsec.c index e0f60b3bd3..eb840d940a 100644 --- a/appsec/src/extension/ddappsec.c +++ b/appsec/src/extension/ddappsec.c @@ -18,6 +18,7 @@ #include #include +#include "backtrace.h" #include "commands/client_init.h" #include "commands/config_sync.h" #include "commands/request_exec.h" @@ -32,6 +33,7 @@ #include "helper_process.h" #include "ip_extraction.h" #include "logging.h" +#include "msgpack_helpers.h" #include "network.h" #include "php_compat.h" #include "php_helpers.h" @@ -43,6 +45,7 @@ #include "user_tracking.h" #include +#include #if ZTS static atomic_int _thread_count; @@ -98,7 +101,7 @@ static zend_extension ddappsec_extension_entry = { PHP_DDAPPSEC_EXTNAME, PHP_DDAPPSEC_VERSION, "Datadog", - "https://github.com/DataDog/dd-appsec-php", + "https://github.com/DataDog/dd-trace-php", "Copyright Datadog", ddappsec_startup, NULL, @@ -218,6 +221,8 @@ static PHP_MINIT_FUNCTION(ddappsec) dd_tags_startup(); dd_ip_extraction_startup(); dd_entity_body_startup(); + dd_backtrace_startup(); + dd_msgpack_helpers_startup(); return SUCCESS; } @@ -241,10 +246,29 @@ static PHP_MSHUTDOWN_FUNCTION(ddappsec) return SUCCESS; } -static pthread_once_t _rinit_once_control = PTHREAD_ONCE_INIT; - static void _rinit_once() { dd_config_first_rinit(); } +void dd_appsec_rinit_once() +{ + static pthread_once_t _rinit_once_control = PTHREAD_ONCE_INIT; + pthread_once(&_rinit_once_control, _rinit_once); +} + +static void _warn_on_empty_service_or_env() +{ + if (!get_global_DD_APPSEC_TESTING() && get_DD_REMOTE_CONFIG_ENABLED() && + DDAPPSEC_G(enabled) != APPSEC_FULLY_DISABLED && + (zend_string_equals_literal(get_DD_ENV(), "") || + zend_string_equals_literal(get_DD_SERVICE(), ""))) { + mlog(dd_log_warning, + "AppSec is not disabled and Datadog service or env is empty. " + "Please set DD_SERVICE and DD_ENV rather than setting the " + "corresponding properties on the root span. Otherwise, remote " + "configuration for AppSec will use service=unnamed-php-service and " + "env=none"); + } +} + // NOLINTNEXTLINE static PHP_RINIT_FUNCTION(ddappsec) { @@ -254,9 +278,10 @@ static PHP_RINIT_FUNCTION(ddappsec) // Safety precaution DDAPPSEC_G(during_request_shutdown) = false; - pthread_once(&_rinit_once_control, _rinit_once); + dd_appsec_rinit_once(); zai_config_rinit(); _check_enabled(); + _warn_on_empty_service_or_env(); if (DDAPPSEC_G(enabled) == APPSEC_FULLY_DISABLED) { return SUCCESS; @@ -370,6 +395,23 @@ static void _check_enabled() }; } +__attribute__((visibility("default"))) void dd_appsec_rc_conf( + bool *nonnull appsec_features, bool *nonnull appsec_conf) // NOLINT +{ + bool prev_enabled = DDAPPSEC_G(enabled); + bool prev_active = DDAPPSEC_G(active); + bool prev_to_be_configured = DDAPPSEC_G(to_be_configured); + _check_enabled(); + + *appsec_features = DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG; + // only enable ASM / ASM_DD / ASM_DATA if no rules file is specified + *appsec_conf = get_global_DD_APPSEC_RULES()->len == 0; + + DDAPPSEC_G(enabled) = prev_enabled; + DDAPPSEC_G(active) = prev_active; + DDAPPSEC_G(to_be_configured) = prev_to_be_configured; +} + static PHP_FUNCTION(datadog_appsec_is_enabled) { if (zend_parse_parameters_none() == FAILURE) { diff --git a/appsec/src/extension/ddappsec.h b/appsec/src/extension/ddappsec.h index a45381ec46..7983ca2d4b 100644 --- a/appsec/src/extension/ddappsec.h +++ b/appsec/src/extension/ddappsec.h @@ -53,8 +53,12 @@ extern __thread void *unspecnull ATTR_TLS_LOCAL_DYNAMIC TSRMLS_CACHE; # define DDAPPSEC_G(v) (ddappsec_globals.v) #endif +void dd_appsec_rinit_once(void); int dd_appsec_rshutdown(bool ignore_verdict); +__attribute__((visibility("default"))) void dd_appsec_rc_conf( + bool *nonnull appsec_features, bool *nonnull appsec_conf); // NOLINT + // Add a NO_CACHE version. // Use tsrm_get_ls_cache() instead of thread-local _tsrmls_ls_cache #ifdef ZTS diff --git a/appsec/src/extension/ddappsec.version b/appsec/src/extension/ddappsec.version index f90d3623ed..7ab9abb2c3 100644 --- a/appsec/src/extension/ddappsec.version +++ b/appsec/src/extension/ddappsec.version @@ -1,4 +1,7 @@ { - global: get_module; + global: + get_module; + dd_appsec_maybe_enable_helper; + dd_appsec_rc_conf; local: *; }; diff --git a/appsec/src/extension/ddtrace.c b/appsec/src/extension/ddtrace.c index d3348765f5..36fb0da7fd 100644 --- a/appsec/src/extension/ddtrace.c +++ b/appsec/src/extension/ddtrace.c @@ -26,6 +26,7 @@ static bool _ddtrace_loaded; static zend_string *_ddtrace_root_span_fname; static zend_string *_meta_propname; static zend_string *_metrics_propname; +static zend_string *_meta_struct_propname; static THREAD_LOCAL_ON_ZTS bool _suppress_ddtrace_rshutdown; static uint8_t *_ddtrace_runtime_id = NULL; @@ -44,6 +45,8 @@ static bool (*nullable _ddtrace_user_req_add_listeners)( static zend_string *(*_ddtrace_ip_extraction_find)(zval *server); +static const char *nullable (*_ddtrace_remote_config_get_path)(void); + static void dd_trace_load_symbols(void) { bool testing = get_global_DD_APPSEC_TESTING(); @@ -97,6 +100,14 @@ static void dd_trace_load_symbols(void) dlerror()); // NOLINT(concurrency-mt-unsafe) } + _ddtrace_remote_config_get_path = + dlsym(handle, "ddtrace_remote_config_get_path"); + if (_ddtrace_remote_config_get_path == NULL && !testing) { + mlog(dd_log_error, + // NOLINTNEXTLINE(concurrency-mt-unsafe) + "Failed to load ddtrace_remote_config_get_path: %s", dlerror()); + } + dlclose(handle); } @@ -106,6 +117,8 @@ void dd_trace_startup() LSTRARG("ddtrace\\root_span"), 1 /* permanent */); _meta_propname = zend_string_init_interned(LSTRARG("meta"), 1); _metrics_propname = zend_string_init_interned(LSTRARG("metrics"), 1); + _meta_struct_propname = + zend_string_init_interned(LSTRARG("meta_struct"), 1); if (get_global_DD_APPSEC_TESTING()) { _register_testing_objects(); @@ -248,14 +261,14 @@ static zval *_get_span_modifiable_array_property( { #if PHP_VERSION_ID >= 80000 zval *res = - zobj->handlers->get_property_ptr_ptr(zobj, propname, BP_VAR_R, NULL); + zobj->handlers->get_property_ptr_ptr(zobj, propname, BP_VAR_IS, NULL); #else zval obj; ZVAL_OBJ(&obj, zobj); zval prop; ZVAL_STR(&prop, propname); zval *res = - zobj->handlers->get_property_ptr_ptr(&obj, &prop, BP_VAR_R, NULL); + zobj->handlers->get_property_ptr_ptr(&obj, &prop, BP_VAR_IS, NULL); #endif @@ -285,6 +298,11 @@ zval *nullable dd_trace_span_get_metrics(zend_object *nonnull zobj) return _get_span_modifiable_array_property(zobj, _metrics_propname); } +zval *nullable dd_trace_span_get_meta_struct(zend_object *nonnull zobj) +{ + return _get_span_modifiable_array_property(zobj, _meta_struct_propname); +} + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) zend_string *nullable dd_trace_get_formatted_runtime_id(bool persistent) { @@ -350,6 +368,16 @@ zend_string *nullable dd_ip_extraction_find(zval *nonnull server) return _ddtrace_ip_extraction_find(server); } +const char *nullable dd_trace_remote_config_get_path() +{ + if (!_ddtrace_remote_config_get_path) { + return NULL; + } + __auto_type path = _ddtrace_remote_config_get_path(); + mlog(dd_log_trace, "Remote config path: %s", path ? path : "(unset)"); + return path; +} + static PHP_FUNCTION(datadog_appsec_testing_ddtrace_rshutdown) { if (zend_parse_parameters_none() == FAILURE) { @@ -412,6 +440,23 @@ static PHP_FUNCTION(datadog_appsec_testing_root_span_get_meta) // NOLINT } } +static PHP_FUNCTION(datadog_appsec_testing_root_span_get_meta_struct) // NOLINT +{ + if (zend_parse_parameters_none() == FAILURE) { + RETURN_FALSE; + } + + __auto_type root_span = dd_trace_get_active_root_span(); + if (!root_span) { + RETURN_NULL(); + } + + zval *meta_struct_zv = dd_trace_span_get_meta_struct(root_span); + if (meta_struct_zv) { + RETURN_ZVAL(meta_struct_zv, 1 /* copy */, 0 /* no destroy original */); + } +} + static PHP_FUNCTION(datadog_appsec_testing_root_span_get_metrics) // NOLINT { if (zend_parse_parameters_none() == FAILURE) { @@ -462,6 +507,7 @@ static const zend_function_entry functions[] = { ZEND_RAW_FENTRY(DD_TESTING_NS "ddtrace_rshutdown", PHP_FN(datadog_appsec_testing_ddtrace_rshutdown), void_ret_bool_arginfo, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "root_span_add_tag", PHP_FN(datadog_appsec_testing_root_span_add_tag), arginfo_root_span_add_tag, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "root_span_get_meta", PHP_FN(datadog_appsec_testing_root_span_get_meta), void_ret_nullable_array, 0) + ZEND_RAW_FENTRY(DD_TESTING_NS "root_span_get_meta_struct", PHP_FN(datadog_appsec_testing_root_span_get_meta_struct), void_ret_nullable_array, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "root_span_get_metrics", PHP_FN(datadog_appsec_testing_root_span_get_metrics), void_ret_nullable_array, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "get_formatted_runtime_id", PHP_FN(datadog_appsec_testing_get_formatted_runtime_id), void_ret_nullable_string, 0) PHP_FE_END diff --git a/appsec/src/extension/ddtrace.h b/appsec/src/extension/ddtrace.h index 98e7d595e9..4a8a6ca624 100644 --- a/appsec/src/extension/ddtrace.h +++ b/appsec/src/extension/ddtrace.h @@ -52,6 +52,7 @@ void dd_trace_close_all_spans_and_flush(void); // It is ready for modification, with refcount == 1 zval *nullable dd_trace_span_get_meta(zend_object *nonnull); zval *nullable dd_trace_span_get_metrics(zend_object *nonnull); +zval *nullable dd_trace_span_get_meta_struct(zend_object *nonnull); zend_string *nullable dd_trace_get_formatted_runtime_id(bool persistent); // Set sampling priority on root span @@ -64,17 +65,19 @@ struct _ddtrace_user_req_listeners { zend_array *nullable (*nonnull start_user_req)( ddtrace_user_req_listeners *nonnull self, zend_object *nonnull span, zend_array *nonnull variables, zval *nullable rbe_zv); - zend_array *nullable(*nonnull response_committed)( + zend_array *nullable (*nonnull response_committed)( ddtrace_user_req_listeners *nonnull self, zend_object *nonnull span, int status, zend_array *nonnull headers, zval *nullable entity); void (*nonnull finish_user_req)( ddtrace_user_req_listeners *nonnull self, zend_object *nonnull span); void (*nonnull set_blocking_function)( - ddtrace_user_req_listeners *nonnull self, - zend_object *nonnull span, zval *nonnull blocking_function); + ddtrace_user_req_listeners *nonnull self, zend_object *nonnull span, + zval *nonnull blocking_function); void (*nullable delete)(ddtrace_user_req_listeners *nonnull self); }; bool dd_trace_user_req_add_listeners( ddtrace_user_req_listeners *nonnull listeners); zend_string *nullable dd_ip_extraction_find(zval *nonnull server); + +const char *nullable dd_trace_remote_config_get_path(void); diff --git a/appsec/src/extension/helper_process.c b/appsec/src/extension/helper_process.c index 04e914d259..8b15e64a2e 100644 --- a/appsec/src/extension/helper_process.c +++ b/appsec/src/extension/helper_process.c @@ -5,27 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. // NOLINTNEXTLINE(misc-header-include-cycle) +#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #define HELPER_PROCESS_C_INCLUDES #include "configuration.h" #include "ddappsec.h" @@ -34,10 +17,7 @@ #include "logging.h" #include "network.h" #include "php_compat.h" -#include "php_helpers.h" #include "php_objects.h" -#include "string_helpers.h" -#include "version.h" typedef struct _dd_helper_mgr { dd_conn conn; @@ -45,14 +25,12 @@ typedef struct _dd_helper_mgr { struct timespec next_retry; uint16_t failed_count; bool connected_this_req; - bool launched_this_req; + pid_t pid; char *nonnull socket_path; char *nonnull lock_path; } dd_helper_mgr; -static atomic_int _launch_failure_fd_lock; - static THREAD_LOCAL_ON_ZTS dd_helper_mgr _mgr; static const double _backoff_initial = 3.0; @@ -64,35 +42,27 @@ static const int timeout_send = 500; static const int timeout_recv_initial = 7500; static const int timeout_recv_subseq = 2000; -#define DD_PATH_FORMAT "%s%sddappsec_" PHP_DDAPPSEC_VERSION "_%u.%u" +#define DD_PATH_FORMAT "%s%sddappsec_" PHP_DDAPPSEC_VERSION "_%u" #define DD_SOCK_PATH_FORMAT DD_PATH_FORMAT ".sock" #define DD_LOCK_PATH_FORMAT DD_PATH_FORMAT ".lock" -#ifndef CLOCK_MONOTONIC_COARSE -# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC -#endif #ifdef TESTING static void _register_testing_objects(void); #endif +static void _read_settings(void); +static bool _wait_for_next_retry(void); +static void _inc_failed_counter(void); +static void _reset_retry_state(void); + void dd_helper_startup(void) { - atomic_store(&_launch_failure_fd_lock, -1); #ifdef TESTING _register_testing_objects(); #endif } -void dd_helper_shutdown(void) -{ - int failure_lock_fd = atomic_load(&_launch_failure_fd_lock); - if (failure_lock_fd != -1) { - // no need for compare and exchange, dd_helper_shutdown - // is called from MSHUTDOWN - atomic_store(&_launch_failure_fd_lock, -1); - close(failure_lock_fd); - } -} +void dd_helper_shutdown(void) {} void dd_helper_gshutdown() { @@ -100,17 +70,8 @@ void dd_helper_gshutdown() pefree(_mgr.lock_path, 1); } -void dd_helper_rshutdown() -{ - _mgr.connected_this_req = false; - _mgr.launched_this_req = false; -} +void dd_helper_rshutdown() { _mgr.connected_this_req = false; } -static bool _wait_for_next_retry(void); -static void _inc_failed_counter(void); -static void _prevent_launch_attempts(int lock_fd); -static bool /* retry */ _maybe_launch_helper(void); -static void _connection_succeeded(void); dd_conn *nullable dd_helper_mgr_acquire_conn( client_init_func nonnull init_func, void *unspecnull ctx) { @@ -121,72 +82,44 @@ dd_conn *nullable dd_helper_mgr_acquire_conn( if (_wait_for_next_retry()) { return NULL; } - zval runtime_path; - ZVAL_STR(&runtime_path, get_DD_APPSEC_HELPER_RUNTIME_PATH()); - dd_on_runtime_path_update(NULL, &runtime_path); - - bool retry = false; - for (int attempt = 0;; attempt++) { - int res = - dd_conn_init(conn, _mgr.socket_path, strlen(_mgr.socket_path)); - - if (res) { - // connection failure - if (attempt == 0) { - // on first attempt, try to launch the helper - retry = _maybe_launch_helper(); - if (retry) { - continue; - } - // no retry - mlog(dd_log_warning, - "Connection to helper failed and we are not going to " - "attempt to launch it: %s", - dd_result_to_string(res)); - goto error; - } else { // attempt == 1 - // 2nd connection attempt failed - // after apparently succeeding in launching the helper - mlog(dd_log_warning, - "Connection to helper failed; we tried to launch it " - "and connect again, only to fail again: %s", - dd_result_to_string(res)); - _prevent_launch_attempts(-1); - goto error; - } - } - - // else we have a connection. Set timeouts and test it - dd_conn_set_timeout(conn, comm_type_send, timeout_send); - dd_conn_set_timeout(conn, comm_type_recv, timeout_recv_initial); - - res = init_func(conn, ctx); - if (res) { - mlog_g(dd_log_warning, "Initial exchange with helper failed; " - "abandoning the connection"); - dd_conn_destroy(conn); - if (attempt == 1) { - _prevent_launch_attempts(-1); - } - goto error; - } else { - dd_conn_set_timeout( - &_mgr.conn, comm_type_recv, timeout_recv_subseq); - } - - // else success - break; + + _read_settings(); + + int res = dd_conn_init(conn, _mgr.socket_path, strlen(_mgr.socket_path)); + + if (res) { + // connection failure + mlog(dd_log_warning, "Connection to helper failed (socket: %s): %s", + _mgr.socket_path, dd_result_to_string(res)); + goto error; + } + + // else we have a connection. Set timeouts and test it + dd_conn_set_timeout(conn, comm_type_send, timeout_send); + dd_conn_set_timeout(conn, comm_type_recv, timeout_recv_initial); + + res = init_func(conn, ctx); + if (res) { + mlog_g(dd_log_warning, "Initial exchange with helper failed; " + "abandoning the connection"); + dd_conn_destroy(conn); + goto error; } - _connection_succeeded(); + dd_conn_set_timeout(&_mgr.conn, comm_type_recv, timeout_recv_subseq); mlog(dd_log_debug, "returning fresh connection"); + + _mgr.connected_this_req = true; + _reset_retry_state(); + return conn; error: _inc_failed_counter(); return NULL; } + dd_conn *nullable dd_helper_mgr_cur_conn(void) { dd_conn *conn = &_mgr.conn; @@ -197,35 +130,93 @@ dd_conn *nullable dd_helper_mgr_cur_conn(void) } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -bool dd_on_runtime_path_update(zval *nullable old_val, zval *nonnull new_val) +bool dd_on_runtime_path_update(zval *nullable old_val, zval *nonnull new_val, + zend_string *nullable new_str) { UNUSED(old_val); + UNUSED(new_str); uid_t uid = getuid(); - gid_t gid = getgid(); char *base = Z_STRVAL_P(new_val); size_t base_len = Z_STRLEN_P(new_val); char *separator = base[base_len - 1] != '/' ? "/" : ""; size_t sock_name_len = - snprintf(NULL, 0, DD_SOCK_PATH_FORMAT, base, separator, uid, gid); + snprintf(NULL, 0, DD_SOCK_PATH_FORMAT, base, separator, uid); char *sock_name = safe_pemalloc(sock_name_len, sizeof(char), 1, 1); snprintf(sock_name, sock_name_len + 1, DD_SOCK_PATH_FORMAT, base, separator, - uid, gid); + uid); pefree(_mgr.socket_path, 1); _mgr.socket_path = sock_name; size_t lock_name_len = - snprintf(NULL, 0, DD_LOCK_PATH_FORMAT, base, separator, uid, gid); + snprintf(NULL, 0, DD_LOCK_PATH_FORMAT, base, separator, uid); char *lock_name = safe_pemalloc(lock_name_len, sizeof(char), 1, 1); snprintf(lock_name, lock_name_len + 1, DD_LOCK_PATH_FORMAT, base, separator, - uid, gid); + uid); pefree(_mgr.lock_path, 1); _mgr.lock_path = lock_name; return true; } +static inline ddog_CharSlice to_char_slice(zend_string *zs) +{ + return (ddog_CharSlice){.len = ZSTR_LEN(zs), .ptr = ZSTR_VAL(zs)}; +} + +static void _read_settings() +{ + if (_mgr.socket_path) { + return; + } + + dd_appsec_rinit_once(); + + zval runtime_path; + ZVAL_STR(&runtime_path, get_DD_APPSEC_HELPER_RUNTIME_PATH()); + dd_on_runtime_path_update(NULL, &runtime_path, NULL); +} + +__attribute__((visibility("default"))) void dd_appsec_maybe_enable_helper( + sidecar_enable_appsec_t nonnull enable_appsec) +{ + _read_settings(); + + ddog_CharSlice helper_path = to_char_slice(get_DD_APPSEC_HELPER_PATH()); + mlog(dd_log_debug, "Helper path is %.*s", (int)helper_path.len, + helper_path.ptr); + ddog_CharSlice socket_path = {_mgr.socket_path, strlen(_mgr.socket_path)}; + ddog_CharSlice lock_path = {_mgr.lock_path, strlen(_mgr.lock_path)}; + ddog_CharSlice log_path = + to_char_slice(get_global_DD_APPSEC_HELPER_LOG_FILE()); + ddog_CharSlice log_level = + to_char_slice(get_global_DD_APPSEC_HELPER_LOG_LEVEL()); + + enable_appsec(helper_path, socket_path, lock_path, log_path, log_level); +} + +void dd_helper_close_conn() +{ + if (!dd_conn_connected(&_mgr.conn)) { + mlog(dd_log_debug, "Not connected; nothing to do"); + return; + } + + int res = dd_conn_destroy(&_mgr.conn); + if (res == -1) { + mlog_err(dd_log_warning, "Error closing connection to helper"); + } + + /* we treat closing the connection on the request it was opened a failure + * for the purposes of the connection backoff */ + if (_mgr.connected_this_req) { + mlog(dd_log_debug, "Connection was closed on the same request as it " + "opened. Incrementing backoff counter"); + _inc_failed_counter(); + } +} + // returns true if an attempt to connectt should not be made yet static bool _wait_for_next_retry() { @@ -234,7 +225,7 @@ static bool _wait_for_next_retry() } struct timespec cur_time; - if (clock_gettime(CLOCK_MONOTONIC_COARSE, &cur_time) == -1) { + if (clock_gettime(CLOCK_MONOTONIC, &cur_time) == -1) { mlog_err(dd_log_warning, "Call to clock_gettime() failed"); return false; } @@ -249,51 +240,6 @@ static bool _wait_for_next_retry() return false; } -static void _connection_succeeded() -{ - _mgr.connected_this_req = true; - _mgr.failed_count = 0; - _mgr.next_retry = (struct timespec){0}; -} - -static int /* fd */ _acquire_lock(void); -static dd_result _launch_helper_daemon(int lock_fd); -static bool _maybe_launch_helper() -{ - if (!get_global_DD_APPSEC_HELPER_LAUNCH()) { - mlog(dd_log_debug, "Will not try to launch daemon due to ini " - "datadog.appsec.launch_helper"); - return false; - } - - int lock_fd = _acquire_lock(); - if (lock_fd == -1) { - mlog(dd_log_info, - "Could not acquire exclusive lock for launching the daemon"); - return false; - } - - dd_result res = _launch_helper_daemon(lock_fd); - if (res) { - // if this fails, we don't want this worker or any other - // also trying to start the helper, so we hold on to the lock - _prevent_launch_attempts(lock_fd); - return false; - } - - // however, if we were successful, we let go of the lock and let - // the helper keep its copy of the file descriptor. If the helper dies, - // the lock will be released and we can try launching it again - UNUSED(close(lock_fd)); - - _mgr.launched_this_req = true; - - mlog(dd_log_info, - "Apparently successful launch of the helper; will try reconnecting"); - - return true; -} - static void _inc_failed_counter() { if (_mgr.failed_count != UINT16_MAX) { @@ -302,7 +248,7 @@ static void _inc_failed_counter() mlog(dd_log_debug, "Failed counter is now at %u", _mgr.failed_count); struct timespec cur_time; - int res = clock_gettime(CLOCK_MONOTONIC_COARSE, &cur_time); + int res = clock_gettime(CLOCK_MONOTONIC, &cur_time); if (res == -1) { mlog_err(dd_log_warning, "Call to clock_gettime() failed"); _mgr.next_retry = (struct timespec){0}; @@ -317,566 +263,10 @@ static void _inc_failed_counter() _mgr.next_retry.tv_sec += (time_t)wait; } -static void _prevent_launch_attempts(int lock_fd /* -1 to acquire it */) -{ - if (lock_fd == -1) { - lock_fd = _acquire_lock(); - if (lock_fd == -1) { - mlog(dd_log_info, - "Could not acquire exclusive lock to prevent helper " - "launch attempts"); - return; - } - } - - mlog(dd_log_warning, "holding the exclusive lock indefinitely to " - "prevent further attempts to start the helper"); - bool success = atomic_compare_exchange_strong( - &_launch_failure_fd_lock, &(int){-1}, lock_fd); - if (!success) { - mlog(dd_log_error, "failure to set _launch_failure_fd_lock. Bug"); - UNUSED(close(lock_fd)); - } -} - -static int /* fd */ _acquire_lock() -{ - // open file descriptor - const char *lock_file = _mgr.lock_path; - int fd = open(lock_file, O_CREAT | O_NOFOLLOW | O_RDONLY, 0600); // NOLINT - if (fd == -1) { - mlog_err(dd_log_warning, "Could not open lock file %s", lock_file); - return -1; - } - - // acquire lock - int res = flock(fd, LOCK_EX | LOCK_NB); - if (res == -1) { - if (errno == EWOULDBLOCK) { - mlog_g(dd_log_info, - "The helper lock on %s is already being held; " - "could not get exclusive lock", - lock_file); - } else { - mlog_err(dd_log_warning, "Failed getting a hold of a lock on %s", - lock_file); - } - res = close(fd); - if (res == -1) { - mlog_err(dd_log_warning, "Call to close() failed"); - } - return -1; - } - - mlog_g( - dd_log_debug, "Got exclusive lock on file %s, fd is %d", lock_file, fd); - - return fd; -} - -static int /* fd */ _open_socket_for_helper(void); -static char **nullable _split_params( - const char *exe, const char *orig_params_str); -static dd_result _wait_for_intermediate_process(pid_t pid); -static void _close_file_descriptors(int log_fd, int lock_fd, int sock_fd); -static bool _reset_signals_state(int log_fd); -static ATTR_NO_RETURN void _continue_in_intermediate_process( - int log_fd, int lock_fd, int sock_fd, const char *executable, char **argv); -static dd_result _launch_helper_daemon(int lock_fd) -{ - int log_mode = O_WRONLY | O_CREAT; -#ifdef TESTING - if (get_global_DD_APPSEC_TESTING()) { - log_mode |= O_TRUNC; - } else { - log_mode |= O_APPEND; - } -#else - log_mode |= O_APPEND; -#endif - char *helperlog = ZSTR_VAL(get_global_DD_APPSEC_HELPER_LOG_FILE()); - int log_fd = open(helperlog, log_mode, 0600); // NOLINT - if (log_fd == -1) { - mlog_err( - dd_log_warning, "Could not open log file for helper %s", helperlog); - return dd_error; - } - mlog_g(dd_log_debug, "Opened helper log at %s", helperlog); - - int sock_fd = _open_socket_for_helper(); - if (sock_fd == -1) { - close(log_fd); - return dd_error; - } - - char *binary = ZSTR_VAL(get_DD_APPSEC_HELPER_PATH()); - mlog_g(dd_log_debug, "The executable to launch is %s", binary); - - char **argv; - { - char *args; - char *extra_args = ZSTR_VAL(get_DD_APPSEC_HELPER_EXTRA_ARGS()); - spprintf(&args, 0, "%s%s--lock_path - --socket_path fd:%d", extra_args, - *extra_args ? " " : "", sock_fd); - argv = _split_params(binary, args); - efree(args); - } - if (!argv) { - mlog(dd_log_error, "Could not build argument array to launch helper"); - close(log_fd); - close(sock_fd); - return dd_error; - } - if (dd_log_level() >= dd_log_debug) { - for (char **arg = argv + 1; *arg; arg++) { - mlog(dd_log_debug, " argument: %s", *arg); - } - } - - /* fork */ - pid_t pid = fork(); - if (pid != 0) { // parent; the extension - UNUSED(close(log_fd)); - UNUSED(close(sock_fd)); - - if (argv[1]) { - efree(argv[1]); - } - efree(argv); - - if (pid == -1) { - mlog_err(dd_log_warning, "Failed to fork()"); - return dd_error; - } - - mlog_g( - dd_log_info, "Forked. Pid of intermediate process is %d", (int)pid); - - return _wait_for_intermediate_process(pid); - } - - /* fallback to the intermediary process */ - _continue_in_intermediate_process(log_fd, lock_fd, sock_fd, argv[0], argv); - return dd_error; // unreachable -} - -static int /* fd */ _open_socket_for_helper() -{ - struct sockaddr_un sockaddr = {0}; - sockaddr.sun_family = AF_UNIX; - if (strlen(_mgr.socket_path) >= sizeof(sockaddr.sun_path) - 1) { - mlog(dd_log_error, - "The value of datadog.appsec.socket_path (%s) is " - "longer than the max size (%zu)", - _mgr.socket_path, sizeof(sockaddr.sun_path) - 1); - return -1; - } - // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy) - strcpy(sockaddr.sun_path, _mgr.socket_path); - - int res = unlink(_mgr.socket_path); - if (res == -1 && errno != ENOENT) { - mlog_err(dd_log_warning, "Failed to unlink %s", _mgr.socket_path); - return -1; - } - - int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock_fd == -1) { - mlog_err(dd_log_warning, "Call to socket() failed"); - return -1; - } - - res = bind(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); - if (res == -1) { - mlog_err(dd_log_warning, "Call to bind() failed"); - UNUSED(close(sock_fd)); - return -1; - } - -#define BACKLOG 20 - res = listen(sock_fd, BACKLOG); - if (res == -1) { - mlog_err(dd_log_warning, "Call to listen() failed"); - UNUSED(close(sock_fd)); - return -1; - } - - mlog(dd_log_info, "Prepared socket for helper. fd %d", sock_fd); - - return sock_fd; -} - -/* caller should free returned array and also ret[1] if return is not null */ -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -static char **nullable _split_params( - const char *executable, const char *orig_params_str) // NOLINT -{ - unsigned count = 2; // at least one for executable and one for final null - char **ret = safe_emalloc(count, sizeof *ret, 0); - - ret[0] = (char *)(uintptr_t)executable; - ret[1] = NULL; - - const char *p; // read pointer - for (p = orig_params_str; *p == ' '; p++) {} - if (*p == '\0') { - // no arguments - return ret; - } - - // we never write more than the original size of the params - char *params_buffer = emalloc(strlen(orig_params_str) + 1); // NOLINT - char *wp = params_buffer; // write pointer - char *param_start; // position of write pointer where we started writing the - // current parameter - enum { - between, - double_quoted, - single_quoted, - bare_param, - } state = between; - - bool escaped = false; - param_start = wp; - for (; *p != '\0'; p++) { - switch (state) { // NOLINT - case between: - if (*p == ' ') { - // nothing to do - } else if (*p == '"') { - state = double_quoted; - param_start = wp; - } else if (*p == '\'') { - state = single_quoted; - param_start = wp; - } else if (*p == '\\') { - state = bare_param; - escaped = true; - param_start = wp; - } else { - state = bare_param; - param_start = wp; - *wp++ = *p; - } // next is \0: nothing to do - break; - case double_quoted: - case single_quoted: - if (escaped) { - *wp++ = *p; - escaped = false; - } else if (*p == (state == double_quoted ? '"' : '\'')) { - state = bare_param; - } else if (*p == '\\') { - escaped = true; - } else { - *wp++ = *p; - } // next is \0: we will trigger failure - break; - case bare_param: { - if (escaped) { - *wp++ = *p; - escaped = false; - } else if (*p == '\\') { - escaped = true; - } else if (*p == '"') { - state = double_quoted; - } else if (*p == '\'') { - state = single_quoted; - } else if (*p == ' ') { - *wp++ = '\0'; - count++; - ret = safe_erealloc(ret, count, sizeof *ret, 0); - ret[count - 2] = param_start; - ret[count - 1] = NULL; - state = between; - } else { - *wp++ = *p; - } - break; - } - } // end switch - } // end loop - - if (escaped) { - mlog(dd_log_warning, - "datadog.appsec.helper_extra_args has an unpaired \\ at the end: " - "%s", - orig_params_str); - efree(ret); - efree(params_buffer); - return NULL; - } - if (state == single_quoted || state == double_quoted) { - mlog(dd_log_warning, - "datadog.appsec.helper_extra_args has unmatched quotes: %s", - orig_params_str); - efree(ret); - efree(params_buffer); - return NULL; - } - - if (state != between) { - *wp = '\0'; - count++; - ret = safe_erealloc(ret, count, sizeof *ret, 0); - ret[count - 2] = param_start; - ret[count - 1] = NULL; - } - - return ret; -} - -static dd_result _wait_for_intermediate_process(pid_t pid) -{ - int stat_loc; - if (waitpid(pid, &stat_loc, 0) == -1) { - mlog_err(dd_log_info, "Call to waitpid() failed"); - return dd_error; - } - if (WIFEXITED(stat_loc) && WEXITSTATUS(stat_loc) == 0) { - mlog_g(dd_log_debug, "Intermediate process terminated normally"); - } else { - if (WIFEXITED(stat_loc)) { - mlog(dd_log_warning, - "Intermediate process %d exited with exit code %d", (int)pid, - WEXITSTATUS(stat_loc)); - } else if (WIFSIGNALED(stat_loc)) { - mlog(dd_log_warning, - "Intermediate process %d was signaled. Signal %d (dump: " - "%s)", - (int)pid, WTERMSIG(stat_loc), - WCOREDUMP(stat_loc) ? "yes" : "no"); - } else if (WIFSTOPPED(stat_loc)) { - mlog(dd_log_warning, - "Intermediate process %d was stopped. Signal %d", (int)pid, - WTERMSIG(stat_loc)); - } else { - mlog_g(dd_log_warning, - "Intermediate process %d did not end normally; " - "value of stat_loc is %d", - (int)pid, stat_loc); - } - return dd_error; - } - - return dd_success; -} - -#define PREEXEC_LOG(msgf) _preexec_log(log_fd, msgf, sizeof(msgf) - 1) -static void _preexec_log(int log_fd, const char *msg, size_t msg_len) -{ - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - - struct tm tinfo; - localtime_r(&ts.tv_sec, &tinfo); - char buffer[sizeof("[2010-01-01 00:00:00.000")]; - size_t ret = strftime(buffer, sizeof(buffer), "[%F %T", &tinfo); - if (!ret) { - buffer[0] = '\0'; - } - - size_t buffer_len = strlen(buffer); -#define TEN_E6 1000000 - snprintf(&buffer[buffer_len], sizeof(buffer) - buffer_len, ".%03ld", - (long)ts.tv_nsec / TEN_E6); - UNUSED(write(log_fd, buffer, strlen(buffer))); - UNUSED(write(log_fd, ZEND_STRL("] pre-exec: "))); - UNUSED(write(log_fd, msg, msg_len)); - UNUSED(write(log_fd, "\n", 1)); -} - -#define EXIT_SIGNALS_STATE 9 -#define EXIT_GETFD 2 -#define EXIT_FD_OPEN 3 -#define EXIT_UNEXPECTED_FD 4 -#define EXIT_SIGACTION 5 -#define EXIT_SIGHUP_IGNORE 6 -#define EXIT_SETSID 7 -#define EXIT_SECOND_FORK 8 -#define EXIT_CODE_MASK 0x7F - -static ATTR_NO_RETURN void _continue_in_intermediate_process( - int log_fd, int lock_fd, int sock_fd, const char *executable, char **argv) -{ - /* we can't log with mlog anymore! */ - - /* close all file descriptors except lock_fd and sock_fd*/ - _close_file_descriptors(log_fd, lock_fd, sock_fd); - - /* set default handlers and empty signal mask */ - if (!_reset_signals_state(log_fd)) { - exit(EXIT_SIGNALS_STATE); // NOLINT(concurrency-mt-unsafe) - } - - /* check that the remaining file descriptors are valid */ - int fildes[] = {log_fd, lock_fd, sock_fd}; - for (size_t i = 0; i < ARRAY_SIZE(fildes); i++) { - if (fcntl(fildes[i], F_GETFD) == -1) { - PREEXEC_LOG("call to fcntl F_GETFD failed"); - exit(EXIT_GETFD); // NOLINT(concurrency-mt-unsafe) - } - } - - /* open stdin, stdout /dev/null and stderr as dup of log_fd (typically - * /dev/null too, generally the value of datadog.appsec.helper_log_file */ - int fd0 = open("/dev/null", O_RDWR); // NOLINT - int fd1 = dup(0); // NOLINT - int fd2 = dup2(log_fd, 2); - close(log_fd); - log_fd = fd2; // for PREEXEC_LOG macro - - if (fd0 == -1 || fd1 == -1 || fd2 == -1) { - PREEXEC_LOG("failed opening one of the standard file descriptors"); - exit(EXIT_FD_OPEN); // NOLINT(concurrency-mt-unsafe) - } - - if (fd0 != 0 || fd1 != 1 || fd2 != 2) { - PREEXEC_LOG("one of the opened files did not have the expect file " - "descriptor number"); - exit(EXIT_UNEXPECTED_FD); // NOLINT(concurrency-mt-unsafe) - } - - if (chdir("/") == -1) { - PREEXEC_LOG("could not chdir /"); - // not fatal - } - - umask(0); // can't fail - - struct sigaction sa = {.sa_handler = SIG_IGN, .sa_flags = 0}; - if (sigemptyset(&sa.sa_mask) == -1) { - exit(EXIT_SIGACTION); // NOLINT(concurrency-mt-unsafe) - } - - if (sigaction(SIGHUP, &sa, NULL) == -1) { - PREEXEC_LOG("could not make SIGHUP ignored"); - exit(EXIT_SIGHUP_IGNORE); // NOLINT(concurrency-mt-unsafe) - } - - /* fork again */ - PREEXEC_LOG("Going for second fork"); - pid_t pid = fork(); - if (pid == -1) { - PREEXEC_LOG("Second fork failed"); - exit(EXIT_SECOND_FORK); // NOLINT(concurrency-mt-unsafe) - } - if (pid != 0) { // parent - PREEXEC_LOG("Intermediate process exiting"); - // skip atexit() hooks - _Exit(0); // NOLINT(concurrency-mt-unsafe) - } - - if (setsid() == -1) { - PREEXEC_LOG("Call to setsid() failed"); - exit(EXIT_SETSID); // NOLINT(concurrency-mt-unsafe) - } - - PREEXEC_LOG("About to call execv"); - if (execv(executable, argv) == -1) { - int exit_code = (errno & EXIT_CODE_MASK) ? (errno & EXIT_CODE_MASK) : 1; - PREEXEC_LOG("execv call failed"); - exit(exit_code); // NOLINT(concurrency-mt-unsafe) - } - - __builtin_unreachable(); -} - -#define DEFAULT_BASE 10 -#define MAX_RLIMIT 1024 - -static void _close_file_descriptors(int log_fd, int lock_fd, int sock_fd) -{ - DIR *self = opendir("/proc/self/fd/"); - if (self != NULL) { - struct dirent *entry = NULL; - // NOLINTNEXTLINE(concurrency-mt-unsafe) - while ((entry = readdir(self)) != NULL) { - if (*entry->d_name == '.') { - continue; - } - - char *endptr = NULL; - int fd = (int)strtol(entry->d_name, &endptr, DEFAULT_BASE); - if (endptr != entry->d_name && *endptr == '\0' && fd != log_fd && - fd != lock_fd && fd != sock_fd) { - close(fd); - } - } - - closedir(self); - } else { - int fd_max; - struct rlimit rl; - - // Determine max number of file descriptors, default and limit to 1024 - if (getrlimit(RLIMIT_NOFILE, &rl) == -1 || rl.rlim_max > MAX_RLIMIT) { - fd_max = MAX_RLIMIT; - } else { - fd_max = (int)rl.rlim_max; - } - - for (int i = 0; i < fd_max; i++) { - if (i != log_fd && i != lock_fd && i != sock_fd) { - close(i); - } - } - } -} - -static bool _reset_signals_state(int log_fd) -{ -#ifndef SIGRTMIN -# define SIGRTMIN 32 -#endif - // ignored signals are kept ignored across exec() - // NOLINTNEXTLINE(cert-err33-c) - for (int i = 1; i < SIGRTMIN; i++) { signal(i, SIG_DFL); } - - // the signal mask is also kept across exec() - sigset_t empty_mask; - sigemptyset(&empty_mask); - - // NOLINTNEXTLINE(concurrency-mt-unsafe) - if (sigprocmask(SIG_SETMASK, &empty_mask, NULL) == -1) { - PREEXEC_LOG("Call to sigprocmask failed"); - return false; - } - - return true; -} - -void dd_helper_close_conn() +static void _reset_retry_state() { - if (!dd_conn_connected(&_mgr.conn)) { - mlog(dd_log_debug, "Not connected; nothing to do"); - return; - } - - int res = dd_conn_destroy(&_mgr.conn); - if (res == -1) { - mlog_err(dd_log_warning, "Error closing connection to helper"); - } - - /* we treat closing the connection on the request it was opened a failure - * for the purposes of the connection backoff */ - if (_mgr.connected_this_req) { - mlog(dd_log_debug, "Connection was closed on the same request as it " - "opened. Incrementing backoff counter"); - _inc_failed_counter(); - } - - // also, if we launched, do not try again and prevent others from trying too - if (_mgr.launched_this_req) { - int lock_fd = _acquire_lock(); - if (lock_fd == -1) { - mlog(dd_log_info, "Could not acquire exclusive lock to prevent " - "further helper launching"); - return; - } - - _prevent_launch_attempts(lock_fd); - } + _mgr.failed_count = 0; + _mgr.next_retry = (struct timespec){0}; } #ifdef TESTING @@ -893,39 +283,7 @@ static PHP_FUNCTION(datadog_appsec_testing_set_helper_path) ->name, zstr, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); } -static PHP_FUNCTION(datadog_appsec_testing_set_helper_extra_args) -{ - zend_string *zstr; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) { - RETURN_FALSE; - } - zend_alter_ini_entry( - zai_config_memoized_entries[DDAPPSEC_CONFIG_DD_APPSEC_HELPER_EXTRA_ARGS] - .ini_entries[0] - ->name, - zstr, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); -} -static PHP_FUNCTION(datadog_appsec_testing_get_helper_argv) -{ - array_init(return_value); - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - char **argv = _split_params(ZSTR_VAL(get_DD_APPSEC_HELPER_PATH()), - ZSTR_VAL(get_DD_APPSEC_HELPER_EXTRA_ARGS())); - if (!argv) { - return; - } - - for (char **s = argv; *s; s++) { add_next_index_string(return_value, *s); } - - if (argv[1]) { - efree(argv[1]); - } - efree(argv); -} static PHP_FUNCTION(datadog_appsec_testing_is_connected_to_helper) { if (zend_parse_parameters_none() == FAILURE) { @@ -968,8 +326,6 @@ ZEND_END_ARG_INFO() static const zend_function_entry functions[] = { ZEND_RAW_FENTRY(DD_TESTING_NS "set_helper_path", PHP_FN(datadog_appsec_testing_set_helper_path), set_string_arginfo, 0) - ZEND_RAW_FENTRY(DD_TESTING_NS "set_helper_extra_args", PHP_FN(datadog_appsec_testing_set_helper_extra_args), set_string_arginfo, 0) - ZEND_RAW_FENTRY(DD_TESTING_NS "get_helper_argv", PHP_FN(datadog_appsec_testing_get_helper_argv), void_ret_array_arginfo, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "is_connected_to_helper", PHP_FN(datadog_appsec_testing_is_connected_to_helper), void_ret_bool_arginfo, 0) ZEND_RAW_FENTRY(DD_TESTING_NS "backoff_status", PHP_FN(datadog_appsec_testing_backoff_status), void_ret_array_arginfo, 0) PHP_FE_END diff --git a/appsec/src/extension/helper_process.h b/appsec/src/extension/helper_process.h index 481a86f12d..9534bfdecf 100644 --- a/appsec/src/extension/helper_process.h +++ b/appsec/src/extension/helper_process.h @@ -6,21 +6,29 @@ #ifndef DD_HELPER_MGR_H #define DD_HELPER_MGR_H +#include + #include "attributes.h" #include "dddefs.h" #include "network.h" +typedef typeof(&ddog_sidecar_enable_appsec) sidecar_enable_appsec_t; + +__attribute__((visibility("default"))) +void dd_appsec_maybe_enable_helper(sidecar_enable_appsec_t nonnull enable_appsec); + void dd_helper_startup(void); void dd_helper_shutdown(void); void dd_helper_gshutdown(void); void dd_helper_rshutdown(void); typedef dd_result (*client_init_func)(dd_conn *nonnull, void *unspecnull ctx); -dd_conn *nullable dd_helper_mgr_acquire_conn(client_init_func nonnull, void *unspecnull ctx); +dd_conn *nullable dd_helper_mgr_acquire_conn( + client_init_func nonnull, void *unspecnull ctx); dd_conn *nullable dd_helper_mgr_cur_conn(void); void dd_helper_close_conn(void); -bool dd_on_runtime_path_update( - zval *nullable old_value, zval *nonnull new_value); +bool dd_on_runtime_path_update(zval *nullable old_value, + zval *nonnull new_value, zend_string *nullable new_str); #endif // DD_HELPER_MGR_H diff --git a/appsec/src/extension/msgpack_helpers.c b/appsec/src/extension/msgpack_helpers.c index 809f5f4423..69d90c60f2 100644 --- a/appsec/src/extension/msgpack_helpers.c +++ b/appsec/src/extension/msgpack_helpers.c @@ -9,7 +9,11 @@ #include "logging.h" #include "msgpack_helpers.h" +#include "php_compat.h" #include "php_helpers.h" +#include "php_objects.h" + +static const int MAX_DEPTH = 32; static void _mpack_write_zval(mpack_writer_t *nonnull w, zval *nonnull zv); @@ -262,3 +266,129 @@ static void _iovec_writer_teardown(mpack_writer_t *w) w->buffer = NULL; w->context = NULL; } + +// NOLINTNEXTLINE(misc-no-recursion) +static bool parse_element( + mpack_reader_t *nonnull reader, int depth, zval *nonnull output) +{ + if (depth >= MAX_DEPTH) { // critical check! + mpack_reader_flag_error(reader, mpack_error_too_big); + mlog(dd_log_error, "decode_msgpack error: msgpack object too big"); + return false; + } + + mpack_tag_t tag = mpack_read_tag(reader); + if (mpack_reader_error(reader) != mpack_ok) { + return false; + } + + switch (mpack_tag_type(&tag)) { + case mpack_type_nil: + ZVAL_NULL(output); + break; + case mpack_type_bool: + ZVAL_BOOL(output, mpack_tag_bool_value(&tag)); + break; + case mpack_type_int: + ZVAL_LONG(output, mpack_tag_int_value(&tag)); + break; + case mpack_type_uint: { + ZVAL_LONG(output, (long)mpack_tag_uint_value(&tag)); + break; + } + + case mpack_type_str: { + uint32_t length = mpack_tag_str_length(&tag); + const char *data = mpack_read_bytes_inplace(reader, length); + ZVAL_STRINGL(output, data, length); + mpack_done_str(reader); + break; + } + case mpack_type_array: { + uint32_t count = mpack_tag_array_count(&tag); + array_init(output); + while (count-- > 0) { + zval new; + parse_element(reader, depth + 1, &new); + if (mpack_reader_error(reader) != mpack_ok) { // critical check! + // zval_dtor(&new); + mlog( + dd_log_error, "decode_msgpack error: error decoding array"); + return false; + } + zend_hash_next_index_insert(Z_ARRVAL_P(output), &new); + } + mpack_done_array(reader); + break; + } + case mpack_type_map: { + uint32_t count = mpack_tag_map_count(&tag); + array_init(output); + while (count-- > 0) { + zval key; + zval value; + if (!parse_element(reader, depth + 1, &key) || + Z_TYPE(key) != IS_STRING || + !parse_element(reader, depth + 1, &value) || + mpack_reader_error(reader) != mpack_ok) { // critical check! + mlog(dd_log_error, "decode_msgpack error: error decoding map"); + return false; + } + // Ignore clang because key is a string here + // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) + zend_hash_add_new(Z_ARRVAL_P(output), Z_STR(key), &value); + zval_dtor(&key); + } + mpack_done_map(reader); + break; + } + default: + mlog(dd_log_error, "decode_msgpack error: type %s not implemented.\n", + mpack_type_to_string(mpack_tag_type(&tag))); + return false; + } + + return true; +} + +static bool parse_messagepack( + const char *nonnull data, size_t length, zval *nonnull output) +{ + mpack_reader_t reader; + mpack_reader_init_data(&reader, data, length); + parse_element(&reader, 0, output); + return mpack_ok == mpack_reader_destroy(&reader); +} + +static PHP_FUNCTION(datadog_appsec_testing_decode_msgpack) +{ + zend_string *encoded = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &encoded) != SUCCESS) { + RETURN_FALSE; + } + + parse_messagepack(ZSTR_VAL(encoded), ZSTR_LEN(encoded), return_value); +} + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX( + void_ret_array_arginfo, 0, 1, IS_ARRAY, 0) +ZEND_ARG_TYPE_INFO(0, encoded, IS_STRING, 0) +ZEND_END_ARG_INFO() + +// clang-format off +static const zend_function_entry testing_functions[] = { + ZEND_RAW_FENTRY(DD_TESTING_NS "decode_msgpack", PHP_FN(datadog_appsec_testing_decode_msgpack), void_ret_array_arginfo, 0) + PHP_FE_END +}; +// clang-format on + +static void _register_testing_objects() +{ + if (!get_global_DD_APPSEC_TESTING()) { + return; + } + + dd_phpobj_reg_funcs(testing_functions); +} + +void dd_msgpack_helpers_startup() { _register_testing_objects(); } diff --git a/appsec/src/extension/msgpack_helpers.h b/appsec/src/extension/msgpack_helpers.h index 6f89c346f9..9edcd314db 100644 --- a/appsec/src/extension/msgpack_helpers.h +++ b/appsec/src/extension/msgpack_helpers.h @@ -40,4 +40,6 @@ void dd_mpack_write_zval(mpack_writer_t *nonnull w, zval *nullable zv); void dd_mpack_writer_init_iov( mpack_writer_t *nonnull writer, zend_llist *nonnull iovec_list); +void dd_msgpack_helpers_startup(); + #endif // DD_MSGPACK_HELPERS_H diff --git a/appsec/src/extension/network.c b/appsec/src/extension/network.c index 0803a0d53b..b698c8b375 100644 --- a/appsec/src/extension/network.c +++ b/appsec/src/extension/network.c @@ -19,6 +19,7 @@ #include #include #include +#include #define HELPER_PROCESS_C_INCLUDES #include "ddappsec.h" @@ -34,9 +35,13 @@ struct PACKED _dd_header { // NOLINT typedef struct PACKED _dd_header dd_header; -static const int CONNECT_TIMEOUT = 2500; // ms +static const int CONNECT_TIMEOUT = 2500; // ms +static const int CONNECT_RETRY_PAUSE = 100; // ms static const uint32_t MAX_RECV_MESSAGE_SIZE = 4 * 1024 * 1024; +static void _timespec_add_ms(struct timespec *ts, long num_ms); +static long _timespec_delta_ms(struct timespec *ts1, struct timespec *ts2); + int dd_conn_init( // NOLINT(readability-function-cognitive-complexity) dd_conn *nonnull conn, const char *nonnull path, size_t path_len) { @@ -45,6 +50,7 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity) return dd_error; } + // NOLINTNEXTLINE(android-cloexec-socket) int res = conn->socket = socket(AF_UNIX, SOCK_STREAM, 0); if (res == -1) { @@ -71,13 +77,28 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity) } mlog(dd_log_info, "Attempting to connect to UNIX socket %s", path); + struct timespec deadline; + clock_gettime(CLOCK_MONOTONIC, &deadline); + _timespec_add_ms(&deadline, CONNECT_TIMEOUT); + +try_again: res = connect( conn->socket, (struct sockaddr *)&conn->addr, sizeof(conn->addr)); if (res == -1) { - if (errno == EINPROGRESS) { + int errno_copy = errno; + if (errno_copy == EINPROGRESS) { struct pollfd pfds[] = { {.fd = conn->socket, .events = POLLIN | POLLOUT}}; - res = poll(pfds, 1, CONNECT_TIMEOUT); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + long time_left = _timespec_delta_ms(&deadline, &now); + if (time_left <= 0) { + dd_conn_destroy(conn); + mlog(dd_log_info, "Connection to helper timed out"); + return dd_error; + } + + res = poll(pfds, 1, (int)time_left); if (res == 0) { dd_conn_destroy(conn); mlog(dd_log_info, "Connection to helper timed out"); @@ -103,7 +124,32 @@ int dd_conn_init( // NOLINT(readability-function-cognitive-complexity) } // else good } else { + if (errno_copy == ENOENT || errno_copy == ECONNREFUSED) { + // the socket does not exist or is not being listened on. Retry + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + long time_left = _timespec_delta_ms(&deadline, &now); + if (time_left <= 0) { + dd_conn_destroy(conn); + mlog(dd_log_info, "Connection to helper timed out"); + return dd_error; + } + + mlog(dd_log_debug, "Socket %s. Waiting %d ms for next retry", + errno_copy == ENOENT ? "does not exist" + : "is not being listened on", + CONNECT_RETRY_PAUSE); + int ret = usleep(CONNECT_RETRY_PAUSE * 1000); // NOLINT + if (ret == 0) { + goto try_again; + } else { + mlog_err(dd_log_warning, + "Failed connecting to helper (usleep())"); + } + } + dd_conn_destroy(conn); + errno = errno_copy; // restore for mlog_err mlog_err( dd_log_info, "Failed connecting to helper (connect() call)"); return dd_error; @@ -308,7 +354,7 @@ dd_result dd_conn_recv_cred(dd_conn *nonnull conn, char *nullable *nonnull data, setsockopt(conn->socket, SOL_SOCKET, SO_PASSCRED, &(int){0}, sizeof(int)); - if (recv_bytes <= 0) { + if (recv_bytes == 0) { mlog(dd_log_info, "No data received"); return dd_network; } @@ -423,3 +469,29 @@ dd_result dd_conn_set_timeout( return dd_success; } + +#define ONE_E3 1000 +#define ONE_E6 1000000 +#define ONE_E9 1000000000 +static void _timespec_add_ms(struct timespec *ts, long num_ms) +{ + long seconds = num_ms / ONE_E3; + long nanoseconds = (num_ms % ONE_E3) * ONE_E6; + + ts->tv_sec += seconds; + ts->tv_nsec += nanoseconds; + + if (ts->tv_nsec >= ONE_E9) { + ts->tv_sec += ts->tv_nsec / ONE_E9; + ts->tv_nsec %= ONE_E9; + } +} + +static long _timespec_delta_ms(struct timespec *ts1, struct timespec *ts2) +{ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + long res = (ts1->tv_sec - ts2->tv_sec) * 1000; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + res += (ts1->tv_nsec - ts2->tv_nsec) / 1000000; + return res; +} diff --git a/appsec/src/extension/php_compat.c b/appsec/src/extension/php_compat.c index f4f8ddce87..608b16d097 100644 --- a/appsec/src/extension/php_compat.c +++ b/appsec/src/extension/php_compat.c @@ -6,6 +6,13 @@ #include "php_compat.h" #if PHP_VERSION_ID < 70300 +static zend_string _zend_empty_string_st = { + .gc.refcount = 1, + .gc.u.v.type = IS_STRING, + .gc.u.v.flags = IS_STR_PERSISTENT | IS_STR_INTERNED, +}; +zend_string *zend_empty_string = &_zend_empty_string_st; + zend_bool zend_ini_parse_bool(zend_string *str) { if ((ZSTR_LEN(str) == 4 && strcasecmp(ZSTR_VAL(str), "true") == 0) || diff --git a/appsec/src/extension/php_compat.h b/appsec/src/extension/php_compat.h index abd3205d52..3183a308ec 100644 --- a/appsec/src/extension/php_compat.h +++ b/appsec/src/extension/php_compat.h @@ -43,6 +43,7 @@ static zend_always_inline zend_string *zend_string_init_interned( return zend_new_interned_string(ret); # endif } +extern zend_string *zend_empty_string; #endif #if PHP_VERSION_ID < 70300 @@ -106,3 +107,27 @@ static zend_always_inline void _gc_try_delref(zend_refcounted_h *rc) } #define GC_TRY_DELREF(p) _gc_try_delref(&(p)->gc) #endif + +#if PHP_VERSION_ID < 80100 +# define ZEND_HASH_FOREACH_FROM(_ht, indirect, _from) \ + do { \ + Bucket *_p = (_ht)->arData + _from; \ + Bucket *_end = _p + (_ht)->nNumUsed; \ + for (; _p != _end; _p++) { \ + zval *_z = &_p->val; \ + if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \ + _z = Z_INDIRECT_P(_z); \ + } \ + if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) \ + continue; +#endif + +#if PHP_VERSION_ID < 80200 +# define DD_FOREACH_FROM(_ht, indirect, _from, index) \ + ZEND_HASH_FOREACH_FROM(_ht, indirect, _from) \ + index = _p->h; +#else +# define DD_FOREACH_FROM(_ht, indirect, _from, index) \ + ZEND_HASH_FOREACH_FROM(_ht, indirect, _from) \ + index = __h; +#endif diff --git a/appsec/src/extension/php_helpers.c b/appsec/src/extension/php_helpers.c index 18a62a91d2..779161c9c6 100644 --- a/appsec/src/extension/php_helpers.c +++ b/appsec/src/extension/php_helpers.c @@ -114,4 +114,4 @@ zend_string *nullable dd_php_get_string_elem( } return Z_STR_P(zresult); -} +} \ No newline at end of file diff --git a/appsec/src/extension/php_helpers.h b/appsec/src/extension/php_helpers.h index 552b1e4688..d8f8e61ca3 100644 --- a/appsec/src/extension/php_helpers.h +++ b/appsec/src/extension/php_helpers.h @@ -1,12 +1,12 @@ // Unless explicitly stated otherwise all files in this repository are // dual-licensed under the Apache-2.0 License or BSD-3-Clause License. // -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2021 Datadog, Inc. +// This product includes software developed at Datadog +// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include #include "attributes.h" +#include #ifdef ZTS # define THREAD_LOCAL_ON_ZTS __thread @@ -41,9 +41,8 @@ dd_php_array_type dd_php_determine_array_type(const zend_array *nonnull); zval *nullable dd_php_get_autoglobal( int track_var, const char *nonnull name, size_t len); -const zend_array *nonnull dd_get_superglob_or_equiv( - const char *nonnull name, size_t name_len, int track, - zend_array *nullable equiv); +const zend_array *nonnull dd_get_superglob_or_equiv(const char *nonnull name, + size_t name_len, int track, zend_array *nullable equiv); zend_string *nullable dd_php_get_string_elem( const zend_array *nullable arr, zend_string *nonnull zstr); zend_string *nullable dd_php_get_string_elem_cstr( diff --git a/appsec/src/extension/request_abort.c b/appsec/src/extension/request_abort.c index 08b8744666..0f0ed71373 100644 --- a/appsec/src/extension/request_abort.c +++ b/appsec/src/extension/request_abort.c @@ -67,7 +67,6 @@ static zend_string *_content_length_zstr; static zend_string *_location_zstr; static zend_string *_content_type_html_zstr; static zend_string *_content_type_json_zstr; -static zend_string *_empty_zstr; // older versions don't have zend_empty_string static THREAD_LOCAL_ON_ZTS int _response_code = DEFAULT_BLOCKING_RESPONSE_CODE; static THREAD_LOCAL_ON_ZTS dd_response_type _response_type = DEFAULT_RESPONSE_TYPE; @@ -107,7 +106,7 @@ static zend_string *nullable _read_file_contents(const char *nonnull path) php_stream_close(fs); if (!contents) { - return _empty_zstr; + return zend_empty_string; } return contents; } @@ -600,7 +599,6 @@ void dd_request_abort_startup() zend_string_init_interned(ZEND_STRL(HTML_CONTENT_TYPE), 1); _content_type_json_zstr = zend_string_init_interned(ZEND_STRL(JSON_CONTENT_TYPE), 1); - _empty_zstr = zend_string_init_interned(&(char){0}, 0, 1); if (!get_global_DD_APPSEC_TESTING()) { return; @@ -620,7 +618,7 @@ static zend_string *nonnull _get_json_blocking_template() // * if the template file is not found, return an empty template // * if the template file is empty, return the default if (!body_error_json) { - return _empty_zstr; + return zend_empty_string; } if (ZSTR_LEN(body_error_json) == 0) { zend_string_release(body_error_json); @@ -641,7 +639,7 @@ static zend_string *nonnull _get_html_blocking_template() zend_string *nullable body_error_html = _read_file_contents(ZSTR_VAL(html_template_file)); if (!body_error_html) { - return _empty_zstr; + return zend_empty_string; } if (ZSTR_LEN(body_error_html) == 0) { zend_string_release(body_error_html); diff --git a/appsec/src/extension/request_lifecycle.c b/appsec/src/extension/request_lifecycle.c index 0ed9d1d7d8..3aa826d903 100644 --- a/appsec/src/extension/request_lifecycle.c +++ b/appsec/src/extension/request_lifecycle.c @@ -47,6 +47,9 @@ static THREAD_LOCAL_ON_ZTS zend_array *nullable _superglob_equiv; static THREAD_LOCAL_ON_ZTS zend_string *nullable _client_ip; static THREAD_LOCAL_ON_ZTS zval _blocking_function; static THREAD_LOCAL_ON_ZTS bool _shutdown_done_on_commit; +#define MAX_LENGTH_OF_REM_CFG_PATH 31 +static THREAD_LOCAL_ON_ZTS char + _last_rem_cfg_path[MAX_LENGTH_OF_REM_CFG_PATH + 1]; #define CLIENT_IP_LOOKUP_FAILED ((zend_string *)-1) bool dd_req_is_user_req() { return _enabled_user_req; } @@ -125,6 +128,31 @@ static zend_array *nullable _do_request_begin_user_req(zval *nullable rbe_zv) return _do_request_begin(rbe_zv, true); } +static bool _rem_cfg_path_changed() +{ + const char *cur_path = dd_trace_remote_config_get_path(); + if (!cur_path) { + cur_path = ""; + } + if (strcmp(cur_path, _last_rem_cfg_path) == 0) { + return false; + } + + if (strlen(cur_path) > MAX_LENGTH_OF_REM_CFG_PATH) { + mlog(dd_log_warning, "Remote config path too long: %s", cur_path); + return false; + } + + mlog(dd_log_info, "Remote config path changed from %s to %s", + _last_rem_cfg_path[0] ? _last_rem_cfg_path : "(none)", + cur_path[0] ? cur_path : "(none)"); + + // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy) + strcpy(_last_rem_cfg_path, cur_path); + + return true; +} + static zend_array *nullable _do_request_begin( zval *nullable rbe_zv /* needs free */, bool user_req) { @@ -156,16 +184,17 @@ static zend_array *nullable _do_request_begin( } int res = dd_success; - if (DDAPPSEC_G(active)) { - // request_init - res = dd_request_init(conn, &req_info); - } else if (DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG) { - // config_sync - res = dd_config_sync(conn); - if (res == SUCCESS && DDAPPSEC_G(active)) { - // Since it came as enabled, lets proceed + if (_rem_cfg_path_changed() || + (!DDAPPSEC_G(active) && + DDAPPSEC_G(enabled) == APPSEC_ENABLED_VIA_REMCFG)) { + res = dd_config_sync(conn, + &(struct config_sync_data){.rem_cfg_path = _last_rem_cfg_path}); + if (res == dd_success && DDAPPSEC_G(active)) { res = dd_request_init(conn, &req_info); } + } else if (DDAPPSEC_G(active)) { + // request_init + res = dd_request_init(conn, &req_info); } if (rbe) { diff --git a/appsec/src/extension/tags.c b/appsec/src/extension/tags.c index 6b6ca7601d..626900526c 100644 --- a/appsec/src/extension/tags.c +++ b/appsec/src/extension/tags.c @@ -4,6 +4,7 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "tags.h" +#include "configuration.h" #include "ddappsec.h" #include "ddtrace.h" #include "ext/pcre/php_pcre.h" @@ -59,17 +60,6 @@ #define DD_EVENTS_USER_LOGIN_FAILURE_SDK \ "_dd.appsec.events.users.login.failure.sdk" -typedef enum _automated_user_events_tracking_mode { - NOT_ENABLED = 0, - SAFE, - EXTENDED -} automated_user_events_tracking_mode; -static THREAD_LOCAL_ON_ZTS automated_user_events_tracking_mode - automated_user_events_tracking = SAFE; -static zend_string *_mode_safe_cstr; -static zend_string *_mode_extended_cstr; -static THREAD_LOCAL_ON_ZTS zend_string *_mode_cstr; - static zend_string *_dd_tag_data_zstr; static zend_string *_dd_tag_event_zstr; static zend_string *_dd_tag_blocked_zstr; @@ -107,12 +97,10 @@ static zend_string *_true_zstr; static zend_string *_false_zstr; static zend_string *_track_zstr; static zend_string *_usr_exists_zstr; -static zend_string *_uuid_zstr; -static zend_string *_id_zstr; static zend_string *_server_zstr; static HashTable _relevant_headers; // headers for requests with attacks static HashTable _relevant_basic_headers; // headers for all requests -static THREAD_LOCAL_ON_ZTS bool _extended_user_event_triggered; +static THREAD_LOCAL_ON_ZTS bool _user_event_triggered; static THREAD_LOCAL_ON_ZTS bool _appsec_json_frags_inited; static THREAD_LOCAL_ON_ZTS zend_llist _appsec_json_frags; static THREAD_LOCAL_ON_ZTS zend_string *nullable _event_user_id; @@ -207,21 +195,8 @@ void dd_tags_startup() _usr_exists_zstr = zend_string_init_interned(LSTRARG("usr.exists"), 1 /* permanent */); - _uuid_zstr = zend_string_init_interned( - LSTRARG( - "/^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/"), - 1 /* permanent */); - _id_zstr = - zend_string_init_interned(LSTRARG("/^\\d+$/"), 1 /* permanent */); - _server_zstr = zend_string_init_interned(LSTRARG("_SERVER"), 1); - _mode_safe_cstr = - zend_string_init_interned(LSTRARG("safe"), 1 /* permanent */); - _mode_extended_cstr = - zend_string_init_interned(LSTRARG("extended"), 1 /* permanent */); - _mode_cstr = _mode_safe_cstr; // default - _init_relevant_headers(); _register_functions(); @@ -292,7 +267,7 @@ void dd_tags_shutdown() void dd_tags_rinit() { bool init_list = false; - _extended_user_event_triggered = false; + _user_event_triggered = false; if (UNEXPECTED(!_appsec_json_frags_inited)) { init_list = true; _appsec_json_frags_inited = true; @@ -363,8 +338,8 @@ void dd_tags_add_tags( } _add_basic_ancillary_tags(span, server, - _extended_user_event_triggered ? &_relevant_headers - : &_relevant_basic_headers); + _user_event_triggered ? &_relevant_headers + : &_relevant_basic_headers); return; } @@ -884,12 +859,6 @@ bool match_regex(zend_string *pattern, zend_string *subject) return Z_TYPE(ret) == IS_LONG && Z_LVAL(ret) > 0; } -static bool _is_user_id_sensitive(zend_string *user_id) -{ - return !( - match_regex(_uuid_zstr, user_id) || match_regex(_id_zstr, user_id)); -} - static zval *nullable _root_span_get_meta() { zend_object *nullable span = dd_req_lifecycle_get_cur_span(); @@ -905,14 +874,6 @@ static zval *nullable _root_span_get_meta() return meta; } -static void _set_extended_user_event_triggered(bool automated) -{ - if (automated && automated_user_events_tracking != EXTENDED) { - return; - } - _extended_user_event_triggered = true; -} - static PHP_FUNCTION(datadog_appsec_track_user_signup_event) { UNUSED(return_value); @@ -925,25 +886,30 @@ static PHP_FUNCTION(datadog_appsec_track_user_signup_event) zend_string *user_id = NULL; HashTable *metadata = NULL; zend_bool automated = false; // Don't document. Only internal usage + zend_bool copy_user_id = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|hb", &user_id, &metadata, &automated) == FAILURE) { mlog(dd_log_warning, "Unexpected parameter combination, expected " "(user_id, metadata)"); return; } + if (automated) { - if (automated_user_events_tracking == NOT_ENABLED) { + user_collection_mode mode = dd_get_user_collection_mode(); + if (mode == user_mode_disabled || + !get_DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED()) { return; } - if (automated_user_events_tracking == SAFE) { - if (user_id != NULL && _is_user_id_sensitive(user_id)) { - user_id = NULL; + if (mode == user_mode_anon) { + // Anonymize the user ID and ensure it isn't copied twice + user_id = dd_user_id_anonymize(user_id); + if (user_id == NULL) { + mlog(dd_log_debug, "Failed to anonymize user ID"); + return; } - if (metadata != NULL && zend_array_count(metadata) > 0) { - metadata = NULL; - } + copy_user_id = false; } } else { if (user_id == NULL || ZSTR_LEN(user_id) == 0) { @@ -954,10 +920,13 @@ static PHP_FUNCTION(datadog_appsec_track_user_signup_event) zval *nullable meta = _root_span_get_meta(); if (!meta) { + if (!copy_user_id) { + zend_string_release(user_id); + } return; } - _set_extended_user_event_triggered(automated); + _user_event_triggered = true; zend_array *meta_ht = Z_ARRVAL_P(meta); bool override = !automated; @@ -965,24 +934,27 @@ static PHP_FUNCTION(datadog_appsec_track_user_signup_event) if (user_id && ZSTR_LEN(user_id) > 0) { // usr.id = _add_new_zstr_to_meta( - meta_ht, _dd_tag_user_id, user_id, true, override); + meta_ht, _dd_tag_user_id, user_id, copy_user_id, override); } if (automated) { + // In automated mode, metadata must no longer be sent + // _dd.appsec.events.users.signup.auto.mode = // - if (_mode_cstr && automated_user_events_tracking != NOT_ENABLED) { + if (dd_get_user_collection_mode() != user_mode_disabled) { _add_new_zstr_to_meta(meta_ht, _dd_signup_event_auto_mode, - _mode_cstr, true, override); + dd_get_user_collection_mode_zstr(), true, override); } } else { // _dd.appsec.events.users.signup.sdk = true _add_new_zstr_to_meta( meta_ht, _dd_signup_event_sdk, _true_zstr, true, override); - } - // appsec.events.users.signup. = - _add_custom_event_metadata(meta_ht, _dd_signup_event, metadata, override); + // appsec.events.users.signup. = + _add_custom_event_metadata( + meta_ht, _dd_signup_event, metadata, override); + } // appsec.events.users.login.success.track = true _add_custom_event_keyval( @@ -1003,6 +975,7 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_success_event) zend_string *user_id = NULL; HashTable *metadata = NULL; zend_bool automated = false; // Don't document. Only internal usage + zend_bool copy_user_id = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|hb", &user_id, &metadata, &automated) == FAILURE) { mlog(dd_log_warning, "Unexpected parameter combination, expected " @@ -1010,18 +983,20 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_success_event) return; } if (automated) { - if (automated_user_events_tracking == NOT_ENABLED) { + user_collection_mode mode = dd_get_user_collection_mode(); + if (mode == user_mode_disabled || + !get_DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED()) { return; } - if (automated_user_events_tracking == SAFE) { - if (user_id != NULL && _is_user_id_sensitive(user_id)) { - user_id = NULL; + if (mode == user_mode_anon) { + user_id = dd_user_id_anonymize(user_id); + if (user_id == NULL) { + mlog(dd_log_debug, "Failed to anonymize user ID"); + return; } - if (metadata != NULL && zend_array_count(metadata) > 0) { - metadata = NULL; - } + copy_user_id = false; } } else { if (user_id == NULL || ZSTR_LEN(user_id) == 0) { @@ -1032,10 +1007,13 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_success_event) zval *nullable meta = _root_span_get_meta(); if (!meta) { + if (!copy_user_id) { + zend_string_release(user_id); + } return; } - _set_extended_user_event_triggered(automated); + _user_event_triggered = true; zend_array *meta_ht = Z_ARRVAL_P(meta); bool override = !automated; @@ -1044,30 +1022,32 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_success_event) dd_find_and_apply_verdict_for_user(user_id); // usr.id = _add_new_zstr_to_meta( - meta_ht, _dd_tag_user_id, user_id, true, override); + meta_ht, _dd_tag_user_id, user_id, copy_user_id, override); } if (automated) { + // In automated mode, metadata must no longer be sent + // _dd.appsec.events.users.login.success.auto.mode = // - if (_mode_cstr && automated_user_events_tracking != NOT_ENABLED) { + if (dd_get_user_collection_mode() != user_mode_disabled) { _add_new_zstr_to_meta(meta_ht, _dd_login_success_event_auto_mode, - _mode_cstr, true, override); + dd_get_user_collection_mode_zstr(), true, override); } } else { // _dd.appsec.events.users.login.success.sdk = true _add_new_zstr_to_meta( meta_ht, _dd_login_success_event_sdk, _true_zstr, true, override); + + // appsec.events.users.login.success. = + _add_custom_event_metadata( + meta_ht, _dd_login_success_event, metadata, override); } // appsec.events.users.login.success.track = true _add_custom_event_keyval(meta_ht, _dd_login_success_event, _track_zstr, _true_zstr, true, override); - // appsec.events.users.login.success. = - _add_custom_event_metadata( - meta_ht, _dd_login_success_event, metadata, override); - dd_tags_set_sampling_priority(); } @@ -1091,16 +1071,23 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_failure_event) return; } + zend_bool copy_user_id = true; if (automated) { - if (automated_user_events_tracking == NOT_ENABLED) { + user_collection_mode mode = dd_get_user_collection_mode(); + if (mode == user_mode_disabled || + !get_DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED()) { return; } - if (automated_user_events_tracking == SAFE) { - if (_is_user_id_sensitive(user_id)) { - user_id = NULL; + if (mode == user_mode_anon) { + user_id = dd_user_id_anonymize(user_id); + if (user_id == NULL) { + mlog(dd_log_debug, "Failed to anonymize user ID"); + return; } + copy_user_id = false; + if (metadata != NULL && zend_array_count(metadata) > 0) { metadata = NULL; } @@ -1109,10 +1096,13 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_failure_event) zval *nullable meta = _root_span_get_meta(); if (!meta) { + if (!copy_user_id) { + zend_string_release(user_id); + } return; } - _set_extended_user_event_triggered(automated); + _user_event_triggered = true; zend_array *meta_ht = Z_ARRVAL_P(meta); bool override = !automated; @@ -1120,7 +1110,7 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_failure_event) if (user_id != NULL && ZSTR_LEN(user_id) > 0) { // appsec.events.users.login.failure.usr.id = _add_custom_event_keyval(meta_ht, _dd_login_failure_event, - _dd_tag_user_id, user_id, true, override); + _dd_tag_user_id, user_id, copy_user_id, override); } // appsec.events.users.login.failure.track = true @@ -1128,26 +1118,28 @@ static PHP_FUNCTION(datadog_appsec_track_user_login_failure_event) _true_zstr, true, override); if (automated) { + // In automated mode, metadata must no longer be sent + // _dd.appsec.events.users.login.failure.auto.mode = // - if (_mode_cstr && automated_user_events_tracking != NOT_ENABLED) { + if (dd_get_user_collection_mode() != user_mode_disabled) { _add_new_zstr_to_meta(meta_ht, _dd_login_failure_event_auto_mode, - _mode_cstr, true, override); + dd_get_user_collection_mode_zstr(), true, override); } } else { // _dd.appsec.events.users.login.success.sdk = true _add_new_zstr_to_meta( meta_ht, _dd_login_failure_event_sdk, _true_zstr, true, override); + + // appsec.events.users.login.failure. = + _add_custom_event_metadata( + meta_ht, _dd_login_failure_event, metadata, override); } // appsec.events.users.login.failure.usr.exists = _add_custom_event_keyval(meta_ht, _dd_login_failure_event, _usr_exists_zstr, exists ? _true_zstr : _false_zstr, true, override); - // appsec.events.users.login.failure. = - _add_custom_event_metadata( - meta_ht, _dd_login_failure_event, metadata, override); - dd_tags_set_sampling_priority(); } @@ -1209,39 +1201,6 @@ static bool _set_appsec_enabled(zval *metrics_zv) NULL; } -bool dd_parse_automated_user_events_tracking( - zai_str value, zval *nonnull decoded_value, bool persistent) -{ - if (!value.len) { - return false; - } - - bool result = false; - size_t len = strlen((const char *)value.ptr); - if (dd_string_equals_lc(value.ptr, len, ZEND_STRL("safe"))) { - automated_user_events_tracking = SAFE; - result = true; - _mode_cstr = _mode_safe_cstr; - } else if (dd_string_equals_lc(value.ptr, len, ZEND_STRL("extended"))) { - automated_user_events_tracking = EXTENDED; - result = true; - _mode_cstr = _mode_extended_cstr; - } else if (dd_string_equals_lc(value.ptr, len, ZEND_STRL("disabled"))) { - automated_user_events_tracking = NOT_ENABLED; - result = true; - _mode_cstr = NULL; - } - - if (result) { - ZVAL_STR(decoded_value, zend_string_alloc(value.len, persistent)); - char *out = Z_STRVAL_P(decoded_value); - memcpy(out, value.ptr, value.len); - out[value.len] = '\0'; - } - - return result; -} - static PHP_FUNCTION(datadog_appsec_testing_add_all_ancillary_tags) { UNUSED(return_value); diff --git a/appsec/src/extension/tags.h b/appsec/src/extension/tags.h index 19beb1084b..172677e435 100644 --- a/appsec/src/extension/tags.h +++ b/appsec/src/extension/tags.h @@ -5,7 +5,6 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once #include "attributes.h" -#include "configuration.h" #include #include #include @@ -27,5 +26,4 @@ void dd_tags_set_event_user_id(zend_string *nonnull zstr); // does not increase the refcount on zstr void dd_tags_add_appsec_json_frag(zend_string *nonnull zstr); -bool dd_parse_automated_user_events_tracking( - zai_str value, zval *nonnull decoded_value, bool persistent); + diff --git a/appsec/src/extension/user_tracking.c b/appsec/src/extension/user_tracking.c index 43017fc0d0..8f301128f4 100644 --- a/appsec/src/extension/user_tracking.c +++ b/appsec/src/extension/user_tracking.c @@ -17,9 +17,24 @@ #include "string_helpers.h" #include "tags.h" #include +#include +#include + +static THREAD_LOCAL_ON_ZTS user_collection_mode _user_mode = user_mode_disabled; + +static zend_string *_user_mode_anon_zstr; +static zend_string *_user_mode_ident_zstr; +static zend_string *_user_mode_disabled_zstr; +static zend_string *_sha256_algo_zstr; static void (*_ddtrace_set_user)(INTERNAL_FUNCTION_PARAMETERS) = NULL; +#if PHP_VERSION_ID < 80000 +typedef const php_hash_ops *(*hash_fetch_ops_t)( + const char *algo, size_t algo_len); +static hash_fetch_ops_t _hash_fetch_ops; +#endif + static PHP_FUNCTION(set_user_wrapper) { if (DDAPPSEC_G(active) || UNEXPECTED(get_global_DD_APPSEC_TESTING())) { @@ -45,6 +60,24 @@ static PHP_FUNCTION(set_user_wrapper) void dd_user_tracking_startup(void) { + _user_mode_ident_zstr = zend_string_init_interned( + LSTRARG("identification"), 1 /* persistent */); + _user_mode_anon_zstr = + zend_string_init_interned(LSTRARG("anonymization"), 1 /* persistent */); + _user_mode_disabled_zstr = + zend_string_init_interned(LSTRARG("disabled"), 1 /* persistent */); + _sha256_algo_zstr = + zend_string_init_interned(LSTRARG("sha256"), 1 /* persistent */); + +#if PHP_VERSION_ID < 80000 + _hash_fetch_ops = + (hash_fetch_ops_t)dlsym(RTLD_DEFAULT, "php_hash_fetch_ops"); + if (!_hash_fetch_ops) { + mlog(dd_log_warning, "Failed to load php_hash_fetch_ops: %s", + dlerror()); // NOLINT(concurrency-mt-unsafe) + } +#endif + if (!dd_trace_loaded()) { return; } @@ -115,3 +148,109 @@ void dd_find_and_apply_verdict_for_user(zend_string *nonnull user_id) } } } + +bool dd_parse_user_collection_mode( + zai_str value, zval *nonnull decoded_value, bool persistent) +{ + if (!value.len) { + return false; + } + + if (dd_string_equals_lc(value.ptr, value.len, ZEND_STRL("ident")) || + dd_string_equals_lc( + value.ptr, value.len, ZEND_STRL("identification")) || + dd_string_equals_lc(value.ptr, value.len, ZEND_STRL("extended"))) { + _user_mode = user_mode_ident; + } else if (dd_string_equals_lc(value.ptr, value.len, ZEND_STRL("anon")) || + dd_string_equals_lc( + value.ptr, value.len, ZEND_STRL("anonymization")) || + dd_string_equals_lc(value.ptr, value.len, ZEND_STRL("safe"))) { + _user_mode = user_mode_anon; + } else { // If the value is disabled or an unknown value, we disable user ID + // collection + + if (!get_global_DD_APPSEC_TESTING()) { + mlog_g(dd_log_warning, "Unknown user collection mode: %.*s", + (int)value.len, value.ptr); + } + _user_mode = user_mode_disabled; + } + + ZVAL_STR(decoded_value, zend_string_init(value.ptr, value.len, persistent)); + + return true; +} + +zend_string *nullable dd_user_id_anonymize(zend_string *nonnull user_id) +{ + zend_string *digest; + const php_hash_ops *ops; + void *context; + +#if PHP_VERSION_ID < 80000 + if (!_hash_fetch_ops) { + return NULL; + } + + ops = _hash_fetch_ops( + ZSTR_VAL(_sha256_algo_zstr), ZSTR_LEN(_sha256_algo_zstr)); +#else + ops = php_hash_fetch_ops(_sha256_algo_zstr); +#endif + if (!ops) { + mlog(dd_log_debug, "Failed to load sha256 algorithm"); + return NULL; + } + +#if PHP_VERSION_ID < 80000 + context = emalloc(ops->context_size); +#else + context = php_hash_alloc_context(ops); +#endif + +#if PHP_VERSION_ID < 80100 + ops->hash_init(context); +#else + ops->hash_init(context, NULL); +#endif + + ops->hash_update( + context, (unsigned char *)ZSTR_VAL(user_id), ZSTR_LEN(user_id)); + + digest = zend_string_alloc(ops->digest_size, 0); + ops->hash_final((unsigned char *)ZSTR_VAL(digest), context); + efree(context); + +#define ANON_PREFIX "anon_" + // Anonymized IDs start with anon_ followed by the 128 most-significant bits + // of the sha256 + zend_string *anon_user_id = zend_string_safe_alloc( + LSTRLEN(ANON_PREFIX) + ops->digest_size, 1, 0, 0); + + // Copy prefix + memcpy(ZSTR_VAL(anon_user_id), LSTRARG(ANON_PREFIX)); + + char *digest_begin = ZSTR_VAL(anon_user_id) + LSTRLEN(ANON_PREFIX); + php_hash_bin2hex( + digest_begin, (unsigned char *)ZSTR_VAL(digest), ops->digest_size / 2); + digest_begin[ops->digest_size] = 0; + + zend_string_release(digest); + + return anon_user_id; +} + +user_collection_mode dd_get_user_collection_mode() { return _user_mode; } + +zend_string *nonnull dd_get_user_collection_mode_zstr() +{ + if (_user_mode == user_mode_ident) { + return _user_mode_ident_zstr; + } + + if (_user_mode == user_mode_anon) { + return _user_mode_anon_zstr; + } + + return _user_mode_disabled_zstr; +} diff --git a/appsec/src/extension/user_tracking.h b/appsec/src/extension/user_tracking.h index e55fc36578..2137b8c0c4 100644 --- a/appsec/src/extension/user_tracking.h +++ b/appsec/src/extension/user_tracking.h @@ -5,9 +5,25 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once +#include "configuration.h" #include "attributes.h" #include +typedef enum _user_collection_mode { + user_mode_disabled = 0, + user_mode_anon, + user_mode_ident, +} user_collection_mode; + void dd_user_tracking_startup(void); void dd_user_tracking_shutdown(void); + void dd_find_and_apply_verdict_for_user(zend_string *nonnull user_id); + +bool dd_parse_user_collection_mode( + zai_str value, zval *nonnull decoded_value, bool persistent); + +zend_string*nullable dd_user_id_anonymize(zend_string *nonnull user_id); + +user_collection_mode dd_get_user_collection_mode(void); +zend_string *nonnull dd_get_user_collection_mode_zstr(void); diff --git a/appsec/src/helper/client.cpp b/appsec/src/helper/client.cpp index bb6497c221..17b9470176 100644 --- a/appsec/src/helper/client.cpp +++ b/appsec/src/helper/client.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include +#include #include #include #include @@ -88,6 +89,7 @@ bool handle_message(client &client, const network::base_broker &broker, auto msg = broker.recv(initial_timeout); return maybe_exec_cmd_M(client, msg); } catch (const unexpected_command &e) { + SPDLOG_DEBUG("Unexpected command: {}", e.what()); send_error = true; if (!ignore_unexpected_messages) { result = false; @@ -145,12 +147,11 @@ bool handle_message(client &client, const network::base_broker &broker, bool client::handle_command(const network::client_init::request &command) { SPDLOG_DEBUG("Got client_id with pid={}, client_version={}, " - "runtime_version={}, service={}, engine_settings={}, " + "runtime_version={}, engine_settings={}, " "remote_config_settings={}", command.pid, command.client_version, command.runtime_version, - command.service, command.engine_settings, command.rc_settings); + command.engine_settings, command.rc_settings); - auto service_id = command.service; auto &&eng_settings = command.engine_settings; DD_STDLOG(DD_STDLOG_STARTUP); @@ -161,19 +162,15 @@ bool client::handle_command(const network::client_init::request &command) bool has_errors = false; client_enabled_conf = command.enabled_configuration; - if (service_id.runtime_id.empty()) { - service_id.runtime_id = generate_random_uuid(); - } - runtime_id_ = service_id.runtime_id; try { - service_ = service_manager_->create_service(std::move(service_id), - eng_settings, command.rc_settings, meta, metrics, - !client_enabled_conf.has_value()); - if (service_) { - // This null check is only needed due to some tests - service_->register_runtime_id(runtime_id_); - } + service_ = + service_manager_->create_service(eng_settings, command.rc_settings, + meta, metrics, !client_enabled_conf.has_value()); + + // save engine settings so we can recreate the service if rc path + // changes + engine_settings_ = eng_settings; } catch (std::system_error &e) { // TODO: logging should happen at WAF impl DD_STDLOG(DD_STDLOG_RULES_FILE_NOT_FOUND, @@ -339,13 +336,22 @@ bool client::compute_client_status() return request_enabled_; } -bool client::handle_command(network::config_sync::request & /* command */) +bool client::handle_command(network::config_sync::request &command) { if (!service_guard()) { return false; } - SPDLOG_DEBUG("received command config_sync"); + SPDLOG_DEBUG( + "received command config_sync with path {}", command.rem_cfg_path); + + std::map meta; + std::map metrics; + update_remote_config_path(command.rem_cfg_path, meta, metrics); + + // TODO: meta/metrics not transmitted fwd + // wait for new interface; see + // https://github.com/DataDog/dd-trace-php/pull/2735 if (compute_client_status()) { auto response_cf = @@ -362,7 +368,7 @@ bool client::handle_command(network::config_sync::request & /* command */) return true; } - SPDLOG_DEBUG("sending config_sync to config_sync"); + SPDLOG_DEBUG("sending response to config_sync"); try { return broker_->send( std::make_shared()); @@ -433,6 +439,24 @@ bool client::handle_command(network::request_shutdown::request &command) return send_message(response); } +void client::update_remote_config_path(std::string_view path, + std::map &meta, + std::map &metrics) +{ + if (service_->is_remote_config_shmem_path(path) || + !engine_settings_.has_value()) { + return; + } + + SPDLOG_INFO("Remote config path changed to {}, recreating service", path); + remote_config::settings rc_settings; + rc_settings.enabled = true; + rc_settings.shmem_path = path; + + service_ = service_manager_->create_service( + *engine_settings_, rc_settings, meta, metrics, true); +} + bool client::run_client_init() { static constexpr auto client_init_timeout{std::chrono::milliseconds{500}}; @@ -456,12 +480,6 @@ bool client::run_request() void client::run(worker::queue_consumer &q) { - const defer on_exit{[this]() { - if (this->service_) { - this->service_->unregister_runtime_id(this->runtime_id_); - } - }}; - if (q.running()) { if (!run_client_init()) { SPDLOG_DEBUG("Finished handling client (client_init failed)"); diff --git a/appsec/src/helper/client.hpp b/appsec/src/helper/client.hpp index 81722deb63..aa5f8a15b7 100644 --- a/appsec/src/helper/client.hpp +++ b/appsec/src/helper/client.hpp @@ -61,6 +61,10 @@ class client { void run(worker::queue_consumer &q); bool compute_client_status(); + void update_remote_config_path(std::string_view path, + std::map &meta, + std::map &metrics); + protected: template std::shared_ptr publish(typename T::request &command); @@ -71,11 +75,11 @@ class client { uint32_t version{}; network::base_broker::ptr broker_; std::shared_ptr service_manager_; + std::optional engine_settings_; std::shared_ptr service_ = {nullptr}; std::optional context_; std::optional client_enabled_conf; bool request_enabled_ = {false}; - std::string runtime_id_; }; } // namespace dds diff --git a/appsec/src/helper/config.cpp b/appsec/src/helper/config.cpp index 7150e016bd..921b15037a 100644 --- a/appsec/src/helper/config.cpp +++ b/appsec/src/helper/config.cpp @@ -8,63 +8,25 @@ namespace dds::config { // NOLINTNEXTLINE -config::config(int argc, char *argv[]) +config::config( + const std::function(std::string_view)> &fn) { - for (int i = 1; i < argc; ++i) { - std::string_view arg(argv[i]); - if (arg.size() < 2 || arg.substr(0, 2) != "--") { - // Not an option, weird - continue; - } - arg.remove_prefix(2); - - // Check if the option has an assignment - auto pos = arg.find('='); - if (pos != std::string::npos) { - kv_[arg.substr(0, pos)] = arg.substr(pos + 1); - continue; - } - - // Check the next argument - if ((i + 1) < argc) { - const std::string_view value(argv[i + 1]); - if (arg.size() < 2 || arg.substr(0, 2) != "--") { - // Not an option, so we assume it's a value - kv_[arg] = value; - // Skip on next iteration - ++i; - continue; - } - } - - // If the next argument is an option or this is the last argument, we - // assume it's just a modifier. - kv_[arg] = std::string_view(); - } -} - -template <> bool config::get(std::string_view key) const -{ - return kv_.find(key) != kv_.end(); -} - -template <> std::string config::get(std::string_view key) const -{ - return std::string(kv_.at(key)); -} - -template <> -std::string_view config::get(std::string_view key) const -{ - return kv_.at(key); + auto get_env = [&fn](std::string_view key) -> std::string_view { + return fn(key).value_or(defaults.at(key)); + }; + kv_[env_socket_file_path] = get_env(env_socket_file_path); + kv_[env_lock_file_path] = get_env(env_lock_file_path); + kv_[env_log_file_path] = get_env(env_log_file_path); + kv_[env_log_level] = get_env(env_log_level); } // NOLINTNEXTLINE const std::unordered_map config::defaults = { - {"lock_path", "/tmp/ddappsec.lock"}, - {"socket_path", "/tmp/ddappsec.sock"}, {"log_level", "warn"}, - {"runner_idle_timeout", "1440"} // minutes + {env_lock_file_path, "/tmp/ddappsec.lock"}, + {env_socket_file_path, "/tmp/ddappsec.sock"}, + {env_log_file_path, "/tmp/ddappsec_helper.log"}, + {env_log_level, "warn"}, }; } // namespace dds::config diff --git a/appsec/src/helper/config.hpp b/appsec/src/helper/config.hpp index a76dff5269..aa0e35edfe 100644 --- a/appsec/src/helper/config.hpp +++ b/appsec/src/helper/config.hpp @@ -6,6 +6,10 @@ #pragma once #include +#include +#include +#include +#include #include namespace dds::config { @@ -13,25 +17,43 @@ namespace dds::config { // Perhaps make this a "singleton" class config { public: - config() = default; - config(int argc, char *argv[]); // NOLINT + explicit config( + const std::function(std::string_view)> + &fn); - template T get(std::string_view key) const + [[nodiscard]] std::string_view socket_file_path() const { - return boost::lexical_cast(kv_.at(key)); + return kv_.at(env_socket_file_path); + } + + [[nodiscard]] std::string_view lock_file_path() const + { + return kv_.at(env_lock_file_path); + } + + [[nodiscard]] std::string_view log_file_path() const + { + return kv_.at(env_log_file_path); + } + + [[nodiscard]] spdlog::level::level_enum log_level() const + { + return spdlog::level::from_str(std::string{kv_.at(env_log_level)}); } protected: static const std::unordered_map defaults; std::unordered_map kv_{defaults}; -}; -template <> bool config::get(std::string_view key) const; - -template <> std::string config::get(std::string_view key) const; - -template <> -std::string_view config::get(std::string_view key) const; + static constexpr std::string_view env_socket_file_path = + "_DD_SIDECAR_APPSEC_SOCKET_FILE_PATH"; + static constexpr std::string_view env_lock_file_path = + "_DD_SIDECAR_APPSEC_LOCK_FILE_PATH"; + static constexpr std::string_view env_log_file_path = + "_DD_SIDECAR_APPSEC_LOG_FILE_PATH"; + static constexpr std::string_view env_log_level = + "_DD_SIDECAR_APPSEC_LOG_LEVEL"; +}; } // namespace dds::config diff --git a/appsec/src/helper/engine.cpp b/appsec/src/helper/engine.cpp index 52d5f7c1b7..e9b6fcabfb 100644 --- a/appsec/src/helper/engine.cpp +++ b/appsec/src/helper/engine.cpp @@ -4,9 +4,7 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include -#include -#include -#include +#include #include #include "engine.hpp" @@ -19,17 +17,16 @@ namespace dds { -void engine::subscribe(const subscriber::ptr &sub) +void engine::subscribe(std::unique_ptr sub) { - auto common = std::atomic_load(&common_); - common->subscribers.emplace_back(sub); + common_->subscribers.emplace_back(std::move(sub)); } void engine::update(engine_ruleset &ruleset, std::map &meta, std::map &metrics) { - std::vector new_subscribers; + std::vector> new_subscribers; new_subscribers.reserve(common_->subscribers.size()); dds::parameter param = json_to_parameter(ruleset.get_document()); for (auto &sub : common_->subscribers) { @@ -38,18 +35,17 @@ void engine::update(engine_ruleset &ruleset, } catch (const std::exception &e) { SPDLOG_WARN("Failed to update subscriber {}: {}", sub->get_name(), e.what()); - new_subscribers.emplace_back(sub); + return; // no partial updates } catch (...) { SPDLOG_WARN("Failed to update subscriber {}: unknown reason", sub->get_name()); - new_subscribers.emplace_back(sub); + return; } } - std::shared_ptr const new_common( - new shared_state{std::move(new_subscribers)}); - - std::atomic_store(&common_, new_common); + auto new_common = std::make_shared( + shared_state{std::move(new_subscribers)}); + std::atomic_store_explicit(&common_, new_common, std::memory_order_release); } std::optional engine::context::publish(parameter &¶m) @@ -70,12 +66,18 @@ std::optional engine::context::publish(parameter &¶m) event event_; for (auto &sub : common_->subscribers) { - auto it = listeners_.find(sub); + auto it = listeners_.find(sub.get()); if (it == listeners_.end()) { - it = listeners_.emplace(sub, sub->get_listener()).first; + auto listener = sub->get_listener(); + assert(listener.get() != nullptr); + auto &&[iterator, inserted] = + listeners_.emplace(sub.get(), std::move(listener)); + assert(inserted == true); + it = iterator; } try { - it->second->call(data, event_); + const auto &listener = it->second; + listener->call(data, event_); } catch (std::exception &e) { SPDLOG_ERROR("subscriber failed: {}", e.what()); } @@ -116,21 +118,22 @@ void engine::context::get_meta_and_metrics( } } -engine::ptr engine::from_settings(const dds::engine_settings &eng_settings, +std::unique_ptr engine::from_settings( + const dds::engine_settings &eng_settings, std::map &meta, std::map &metrics) - { auto &&rules_path = eng_settings.rules_file_or_default(); auto ruleset = engine_ruleset::from_path(rules_path); - std::shared_ptr engine_ptr{engine::create(eng_settings.trace_rate_limit)}; + std::unique_ptr engine_ptr{ + engine::create(eng_settings.trace_rate_limit)}; try { SPDLOG_DEBUG("Will load WAF rules from {}", rules_path); // may throw std::exception - const subscriber::ptr waf = + auto waf = waf::instance::from_settings(eng_settings, ruleset, meta, metrics); - engine_ptr->subscribe(waf); + engine_ptr->subscribe(std::move(waf)); } catch (...) { DD_STDLOG(DD_STDLOG_WAF_INIT_FAILED, rules_path); throw; diff --git a/appsec/src/helper/engine.hpp b/appsec/src/helper/engine.hpp index b42600a59b..a42797e8db 100644 --- a/appsec/src/helper/engine.hpp +++ b/appsec/src/helper/engine.hpp @@ -12,6 +12,7 @@ #include "parameter.hpp" #include "rate_limit.hpp" #include "subscriber/base.hpp" +#include #include #include #include @@ -34,10 +35,6 @@ namespace dds { **/ class engine { public: - using ptr = std::shared_ptr; - using subscription_map = - std::map>; - using action_map = std::unordered_map; struct result { @@ -48,7 +45,7 @@ class engine { protected: struct shared_state { - std::vector subscribers; + std::vector> subscribers; }; public: @@ -58,8 +55,9 @@ class engine { class context { public: explicit context(engine &engine) - : common_(std::atomic_load(&engine.common_)), - limiter_(engine.limiter_) + : common_{std::atomic_load_explicit( + &engine.common_, std::memory_order_acquire)}, + limiter_{engine.limiter_} {} context(const context &) = delete; context &operator=(const context &) = delete; @@ -73,10 +71,12 @@ class engine { std::map &metrics); protected: - std::vector prev_published_params_; - std::map listeners_; std::shared_ptr common_; - rate_limiter &limiter_; + std::map> + listeners_; + std::vector prev_published_params_; + rate_limiter & + limiter_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) }; engine(const engine &) = delete; @@ -85,30 +85,32 @@ class engine { engine &operator=(engine &&) = delete; virtual ~engine() = default; - static engine::ptr from_settings(const dds::engine_settings &eng_settings, + static std::unique_ptr from_settings( + const dds::engine_settings &eng_settings, std::map &meta, std::map &metrics); static auto create( uint32_t trace_rate_limit = engine_settings::default_trace_rate_limit) { - return std::shared_ptr(new engine(trace_rate_limit)); + return std::unique_ptr(new engine(trace_rate_limit)); } context get_context() { return context{*this}; } - void subscribe(const subscriber::ptr &sub); - // Update is not thread-safe, although only one remote config client should - // be able to update it so in practice it should not be a problem. + // Not thread-safe, should only be called after construction + void subscribe(std::unique_ptr sub); + virtual void update(engine_ruleset &ruleset, std::map &meta, std::map &metrics); protected: - explicit engine(uint32_t trace_rate_limit, action_map &&actions = {}) + explicit engine(uint32_t trace_rate_limit) : limiter_(trace_rate_limit), common_(new shared_state{{}}) {} + // in practice: the current ddwaf_handle, atomically swapped in update std::shared_ptr common_; rate_limiter limiter_; }; diff --git a/appsec/src/helper/engine_settings.cpp b/appsec/src/helper/engine_settings.cpp index 26b4e9f6dd..8f91eaee6a 100644 --- a/appsec/src/helper/engine_settings.cpp +++ b/appsec/src/helper/engine_settings.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "engine_settings.hpp" +#include #ifdef __has_include # if __has_include() @@ -21,6 +22,33 @@ namespace std { namespace filesystem = experimental::filesystem; } // namespace std #endif + +#include + +namespace { +std::filesystem::path get_helper_path() +{ + static constexpr std::string_view lib_name{"/libddappsec-helper.so"}; + + const std::filesystem::path maps_path{"/proc/self/maps"}; + std::ifstream maps_stream{maps_path}; + std::string line; + while (std::getline(maps_stream, line)) { + if (line.find(lib_name) == std::string::npos) { + continue; + } + + auto pos = line.find_first_of('/'); + assert(pos != std::string::npos); // NOLINT + maps_stream.close(); + return std::filesystem::path{line.substr(pos)}; + } + + maps_stream.close(); + throw std::runtime_error{"libappsec-helper.so not found in maps"}; +} +} // namespace + namespace dds { const std::string &engine_settings::default_rules_file() @@ -28,25 +56,40 @@ const std::string &engine_settings::default_rules_file() struct def_rules_file { def_rules_file() { - std::error_code ec; - auto self = std::filesystem::read_symlink({"/proc/self/exe"}, ec); - if (ec) { - // should not happen on Linux - file = ""; - } else { - auto self_dir = self.parent_path(); - file = self_dir / "../etc/recommended.json"; - if (!std::filesystem::exists(file)) { - // This fallback file is set by a custom/old installer on - // appsec repository - file = self_dir / "../etc/dd-appsec/recommended.json"; + std::filesystem::path base_path; + + try { + base_path = get_helper_path().parent_path(); + } catch (const std::exception &e) { + file = ""; + + // try relative to the executable instead + try { + auto psp = std::filesystem::path{"/proc/self/exe"}; + if (std::filesystem::is_symlink(psp)) { + psp = std::filesystem::read_symlink(psp); + } + base_path = psp.parent_path(); + } catch (const std::exception &e) { + file = + ""; + return; } } + + file = base_path / "../etc/recommended.json"; + if (!std::filesystem::exists(file)) { + // This fallback file is set by a custom/old installer on + // appsec repository + file = base_path / "../etc/dd-appsec/recommended.json"; + } } std::string file; }; - static def_rules_file const drf; + static def_rules_file const drf; // NOLINT return drf.file; } } // namespace dds diff --git a/appsec/src/helper/engine_settings.hpp b/appsec/src/helper/engine_settings.hpp index e9bdf91012..b61fc52b41 100644 --- a/appsec/src/helper/engine_settings.hpp +++ b/appsec/src/helper/engine_settings.hpp @@ -39,6 +39,11 @@ struct engine_settings { std::string obfuscator_value_regex; schema_extraction_settings schema_extraction; + engine_settings() = default; + engine_settings(const engine_settings &) = default; + engine_settings(engine_settings &&) = default; + engine_settings &operator=(const engine_settings &) = default; + engine_settings &operator=(engine_settings &&) = default; virtual ~engine_settings() = default; static const std::string &default_rules_file(); @@ -79,14 +84,16 @@ struct engine_settings { << ", schema_extraction.sample_rate=" << std::fixed << c.schema_extraction.sample_rate << "}"; } - - struct settings_hash { - std::size_t operator()(const engine_settings &s) const noexcept - { - return hash(s.rules_file, s.waf_timeout_us, s.trace_rate_limit, - s.obfuscator_key_regex, s.obfuscator_value_regex, - s.schema_extraction.enabled, s.schema_extraction.sample_rate); - } - }; }; } // namespace dds + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::engine_settings &s) const noexcept + { + return dds::hash(s.rules_file, s.waf_timeout_us, s.trace_rate_limit, + s.obfuscator_key_regex, s.obfuscator_value_regex, + s.schema_extraction.enabled, s.schema_extraction.sample_rate); + } +}; +} // namespace std diff --git a/appsec/src/helper/helper.version b/appsec/src/helper/helper.version new file mode 100644 index 0000000000..3c9f9a284d --- /dev/null +++ b/appsec/src/helper/helper.version @@ -0,0 +1,7 @@ +{ + global: + appsec_helper_main; + appsec_helper_shutdown; + local: *; +}; + diff --git a/appsec/src/helper/json_helper.cpp b/appsec/src/helper/json_helper.cpp index 45d0c54f8c..9a785ddc7e 100644 --- a/appsec/src/helper/json_helper.cpp +++ b/appsec/src/helper/json_helper.cpp @@ -200,19 +200,10 @@ json_helper::get_field_of_type( return get_field_of_type(*parent_field, key, type); } -bool json_helper::get_json_base64_encoded_content( - const std::string &content, rapidjson::Document &output) +bool json_helper::parse_json( + std::string_view content, rapidjson::Document &output) { - std::string base64_decoded; - try { - base64_decoded = base64_decode(content, true); - } catch (const std::runtime_error &error) { - SPDLOG_DEBUG( - "Invalid base64 encoded content: " + std::string(error.what())); - return false; - } - - if (output.Parse(base64_decoded).HasParseError()) { + if (output.Parse(content.data(), content.size()).HasParseError()) { SPDLOG_DEBUG("Invalid json: " + std::string(rapidjson::GetParseError_En( output.GetParseError()))); return false; diff --git a/appsec/src/helper/json_helper.hpp b/appsec/src/helper/json_helper.hpp index 8124628cbf..42e07c046e 100644 --- a/appsec/src/helper/json_helper.hpp +++ b/appsec/src/helper/json_helper.hpp @@ -63,8 +63,7 @@ std::optional get_field_of_type( std::optional get_field_of_type( rapidjson::Value::ConstValueIterator parent_field, std::string_view key, rapidjson::Type type); -bool get_json_base64_encoded_content( - const std::string &content, rapidjson::Document &output); +bool parse_json(std::string_view content, rapidjson::Document &output); // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void merge_arrays(rapidjson::Value &destination, rapidjson::Value &source, rapidjson::Value::AllocatorType &allocator); diff --git a/appsec/src/helper/main.cpp b/appsec/src/helper/main.cpp index 425f240f4d..68a8f69166 100644 --- a/appsec/src/helper/main.cpp +++ b/appsec/src/helper/main.cpp @@ -3,33 +3,37 @@ // // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include "config.hpp" #include "runner.hpp" -#include "subscriber/waf.hpp" #include -#include -#include +#include +#include +#include +#include #include +#include + +extern "C" { +#include +#include #include #include #include +} -std::atomic exit_signal = false; -std::atomic global_runner = nullptr; - -void signal_handler(int signum) -{ - SPDLOG_INFO("Got signal {}", signum); +namespace { +constexpr std::chrono::seconds log_flush_interval{5}; - dds::runner *runner = global_runner.load(); - runner->exit(); - exit_signal = true; -} +std::atomic interrupted; // NOLINT +std::atomic finished; // NOLINT +pthread_t thread_id; -bool ensure_unique(const dds::config::config &config) +bool ensure_unique(const std::string &lock_path) { - auto lock_path = config.get("lock_path"); - // do not acquire the lock / assume we inherited it if (lock_path == "-") { return true; @@ -37,47 +41,138 @@ bool ensure_unique(const dds::config::config &config) /** The first helper process will create and exclusively lock the file */ // NOLINTNEXTLINE - int fd = ::open(lock_path.data(), O_WRONLY | O_CREAT, 0744); + const int fd = ::open(lock_path.c_str(), O_WRONLY | O_CREAT, 0744); if (fd == -1) { + SPDLOG_INFO("Failed to open lock file: {}", lock_path); return false; } + SPDLOG_DEBUG("Opened lock file {}: fd {}", lock_path, fd); int const res = ::flock(fd, LOCK_EX | LOCK_NB); // If we fail to obtain the lock, for whichever reason, assume we can't // run for now. - return res != -1; + if (res == -1) { + SPDLOG_INFO("Failed to get exclusive lock on file {}: errno {}", + lock_path, errno); + return false; + } + return true; } -int main(int argc, char *argv[]) +int appsec_helper_main_impl() { - std::signal(SIGTERM, signal_handler); // NOLINT(cert-err33-c) - std::signal(SIGPIPE, SIG_IGN); // NOLINT - dds::config::config const config(argc, argv); + dds::config::config const config{ + [](std::string_view key) -> std::optional { + // NOLINTNEXTLINE + char const *value = std::getenv(key.data()); + if (value == nullptr) { + return std::nullopt; + } + return std::string_view{value}; + }}; + + std::shared_ptr logger; + bool stderr_fallback = false; + try { + logger = spdlog::basic_logger_mt( + "ddappsec", std::string{config.log_file_path()}, false); + } catch (std::exception &e) { + // NOLINTNEXTLINE + logger = spdlog::stderr_logger_mt("ddappsec"); + stderr_fallback = true; + } - auto logger = spdlog::stderr_color_mt("ddappsec"); spdlog::set_default_logger(logger); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l][%t] %v"); - - auto level = spdlog::level::from_str(config.get("log_level")); + auto level = config.log_level(); spdlog::set_level(level); + spdlog::flush_on(level); + + if (stderr_fallback) { + logger->warn("Failed to open log file {}, falling back to stderr", + config.log_file_path()); + } + dds::waf::initialise_logging(level); - if (!ensure_unique(config)) { + if (!ensure_unique(std::string{config.lock_file_path()})) { logger->warn("helper launched, but not unique, exiting"); // There's another helper running - return 0; + return 1; } + // block SIGUSR1 (only used to interrupt the runner) + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (auto err = pthread_sigmask(SIG_BLOCK, &mask, nullptr)) { + SPDLOG_ERROR("Failed to block SIGUSR1: error number {}", err); + return 1; + } + + dds::remote_config::resolve_symbols(); + dds::runner::resolve_symbols(); + + auto runner = std::make_shared(config, interrupted); + SPDLOG_INFO("starting runner on new thread"); + std::thread thr{[runner = std::move(runner)]() { +#ifdef __linux__ + pthread_setname_np(pthread_self(), "appsec_helper runner"); +#elif defined(__APPLE__) + pthread_setname_np("appsec_helper runner"); +#endif + runner->register_for_rc_notifications(); + + runner->run(); + + finished.store(true, std::memory_order_release); + }}; + thread_id = thr.native_handle(); + thr.detach(); + + return 0; +} +} // namespace + +extern "C" __attribute__((visibility("default"))) int +appsec_helper_main() noexcept +{ try { - SPDLOG_INFO("starting runner"); - dds::runner runner(config); - global_runner = &runner; - if (!exit_signal) { - runner.run(); - } - } catch (const std::exception &e) { - SPDLOG_ERROR("exception: {}", e.what()); + return appsec_helper_main_impl(); + } catch (std::exception &e) { + SPDLOG_ERROR("Unhandled exception: {}", e.what()); + return 2; + } catch (...) { + SPDLOG_ERROR("Unhandled exception"); + return 2; } + return 0; +} +extern "C" __attribute__((visibility("default"))) int +appsec_helper_shutdown() noexcept +{ + interrupted.store(true, std::memory_order_release); + pthread_kill(thread_id, SIGUSR1); + + // wait up to 1 second for the runner to finish + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1}; + while (true) { + if (finished.load(std::memory_order_acquire)) { + SPDLOG_INFO("AppSec helper finished"); + return 0; + } + if (std::chrono::steady_clock::now() >= deadline) { + // we need to call exit() to avoid a segfault in the still running + // helper threads after the helper shared library is unloaded by + // trampoline.c + SPDLOG_WARN("Could not finish AppSec helper before deadline. " + "Calling exit()."); + std::exit(EXIT_FAILURE); // NOLINT + __builtin_unreachable(); + } + std::this_thread::sleep_for(std::chrono::milliseconds{10}); // NOLINT + } + spdlog::shutdown(); return 0; } diff --git a/appsec/src/helper/network/acceptor.cpp b/appsec/src/helper/network/acceptor.cpp index 7ae10b99e1..2c2a567d57 100644 --- a/appsec/src/helper/network/acceptor.cpp +++ b/appsec/src/helper/network/acceptor.cpp @@ -21,6 +21,7 @@ using namespace std::chrono_literals; namespace dds::network::local { acceptor::acceptor(const std::string_view &sv) + // NOLINTNEXTLINE(android-cloexec-socket) : sock_(::socket(AF_UNIX, SOCK_STREAM, 0)) { if (sock_ == -1) { @@ -35,20 +36,34 @@ acceptor::acceptor(const std::string_view &sv) strcpy(static_cast(addr.sun_path), sv.data()); // NOLINT // Remove the existing socket - ::unlink(static_cast(addr.sun_path)); + int res = ::unlink(static_cast(addr.sun_path)); + if (res == -1 && errno != ENOENT) { + SPDLOG_ERROR("Failed to unlink {}: errno {}", addr.sun_path, errno); + throw std::system_error(errno, std::generic_category()); + } + SPDLOG_DEBUG("Unlinked {}", addr.sun_path); - socklen_t const len = sv.size() + sizeof(addr.sun_family); - // NOLINTNEXTLINE - auto res = ::bind(sock_, reinterpret_cast(&addr), len); + res = + // NOLINTNEXTLINE + ::bind(sock_, reinterpret_cast(&addr), sizeof(addr)); + if (res == -1) { + SPDLOG_ERROR( + "Failed to bind socket to {}: errno {}", addr.sun_path, errno); + throw std::system_error(errno, std::generic_category()); + } + + res = ::chmod(sv.data(), 0777); // NOLINT if (res == -1) { + SPDLOG_ERROR( + "Failed to chmod socket {}: errno {}", addr.sun_path, errno); throw std::system_error(errno, std::generic_category()); } - ::chmod(sv.data(), 0777); // NOLINT static constexpr int backlog = 50; if (::listen(sock_, backlog) == -1) { throw std::system_error(errno, std::generic_category()); } + SPDLOG_INFO("Started listening on {}", sv); } void acceptor::set_accept_timeout(std::chrono::seconds timeout) @@ -65,20 +80,17 @@ socket::ptr acceptor::accept() struct sockaddr_un addr {}; socklen_t len = sizeof(addr); - int s; - do { - // NOLINTNEXTLINE - s = ::accept(sock_, reinterpret_cast(&addr), &len); - } while (s == -1 && errno == EINTR); + // NOLINTNEXTLINE + int s = ::accept(sock_, reinterpret_cast(&addr), &len); if (s == -1) { - if (errno == EAGAIN) { - throw dds::timeout_error(); + if (errno == EINTR || errno == EAGAIN) { + return {}; } throw std::system_error(errno, std::generic_category()); } - SPDLOG_DEBUG("New socket: {}", s); + SPDLOG_DEBUG("accept() returned a new socket: {}", s); return std::make_unique(s); } diff --git a/appsec/src/helper/network/acceptor.hpp b/appsec/src/helper/network/acceptor.hpp index ec57df5398..7ed5e36fa3 100644 --- a/appsec/src/helper/network/acceptor.hpp +++ b/appsec/src/helper/network/acceptor.hpp @@ -47,7 +47,12 @@ class acceptor : public base_acceptor { return *this; } - ~acceptor() override { close(sock_); } + ~acceptor() override + { + if (sock_ != -1) { + close(sock_); + } + } void set_accept_timeout(std::chrono::seconds timeout) override; [[nodiscard]] base_socket::ptr accept() override; diff --git a/appsec/src/helper/network/msgpack_helpers.cpp b/appsec/src/helper/network/msgpack_helpers.cpp index 7778c25c22..6effe74db8 100644 --- a/appsec/src/helper/network/msgpack_helpers.cpp +++ b/appsec/src/helper/network/msgpack_helpers.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "msgpack_helpers.hpp" +// NOLINTNEXTLINE(modernize-concat-nested-namespaces) namespace msgpack { MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { namespace adaptor { diff --git a/appsec/src/helper/network/proto.cpp b/appsec/src/helper/network/proto.cpp index 9fa03d76e2..9af84f10aa 100644 --- a/appsec/src/helper/network/proto.cpp +++ b/appsec/src/helper/network/proto.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "proto.hpp" +// NOLINTNEXTLINE(modernize-concat-nested-namespaces) namespace msgpack { MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { namespace adaptor { diff --git a/appsec/src/helper/network/proto.hpp b/appsec/src/helper/network/proto.hpp index 819d321cd2..3c629b4363 100644 --- a/appsec/src/helper/network/proto.hpp +++ b/appsec/src/helper/network/proto.hpp @@ -5,15 +5,14 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "engine_settings.hpp" +#include "../engine_settings.hpp" +#include "../remote_config/settings.hpp" +#include "../version.hpp" #include "msgpack_helpers.hpp" -#include "remote_config/settings.hpp" -#include "service_identifier.hpp" #include #include #include #include -#include using stream_packer = msgpack::packer; @@ -101,7 +100,6 @@ struct client_init { std::string runtime_version; std::optional enabled_configuration; - dds::service_identifier service; dds::engine_settings engine_settings; dds::remote_config::settings rc_settings; @@ -113,7 +111,7 @@ struct client_init { ~request() override = default; MSGPACK_DEFINE(pid, client_version, runtime_version, - enabled_configuration, service, engine_settings, rc_settings); + enabled_configuration, engine_settings, rc_settings); }; struct response : base_response_generic { @@ -223,7 +221,10 @@ struct config_sync { request(request &&) = default; request &operator=(request &&) = default; ~request() override = default; - MSGPACK_DEFINE() + + std::string rem_cfg_path; + + MSGPACK_DEFINE(rem_cfg_path) }; struct response : base_response_generic { diff --git a/appsec/src/helper/rate_limit.hpp b/appsec/src/helper/rate_limit.hpp index c9ac7671b6..7797582852 100644 --- a/appsec/src/helper/rate_limit.hpp +++ b/appsec/src/helper/rate_limit.hpp @@ -12,10 +12,6 @@ #include #include "timer.hpp" -using std::chrono::duration_cast; -using std::chrono::microseconds; -using std::chrono::milliseconds; -using std::chrono::seconds; namespace dds { @@ -25,6 +21,11 @@ template class rate_limiter { : max_per_second_(max_per_second){}; bool allow() { + using std::chrono::duration_cast; + using std::chrono::microseconds; + using std::chrono::milliseconds; + using std::chrono::seconds; + if (max_per_second_ == 0) { return true; } diff --git a/appsec/src/helper/remote_config/client.cpp b/appsec/src/helper/remote_config/client.cpp index 26ab48c948..5b851b4bb3 100644 --- a/appsec/src/helper/remote_config/client.cpp +++ b/appsec/src/helper/remote_config/client.cpp @@ -6,240 +6,216 @@ #include "client.hpp" #include "exception.hpp" #include "product.hpp" -#include "protocol/tuf/parser.hpp" -#include "protocol/tuf/serializer.hpp" #include #include +#include #include -#include +#include +#include + +extern "C" { +#include +} + +namespace { +struct ddog_CharSlice { + const char *ptr; + uintptr_t len; +}; +ddog_RemoteConfigReader *(*ddog_remote_config_reader_for_path)( + const char *path); +bool (*ddog_remote_config_read)( + ddog_RemoteConfigReader *reader, ddog_CharSlice *data); +void (*ddog_remote_config_reader_drop)(struct ddog_RemoteConfigReader *); + +bool sets_are_indentical_for_subbed_products( + const std::unordered_set &products, + const std::set &before, + const std::set &after) +{ + auto set_is_subset_of = [&products](auto &set1, auto &set2) { + for (auto &&elem_set_1 : set1) { // NOLINT(readability-use-anyofallof) + if (!products.contains(elem_set_1.get_product())) { + continue; + } + if (!set2.contains(elem_set_1)) { + return false; + } + } + return true; + }; + + return set_is_subset_of(before, after) && set_is_subset_of(after, before); +} +} // namespace namespace dds::remote_config { -config_path config_path::from_path(const std::string &path) +void resolve_symbols() { - static const std::regex regex( - "^(datadog/\\d+|employee)/([^/]+)/([^/]+)/[^/]+$"); + ddog_remote_config_reader_for_path = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_reader_for_path")); + if (ddog_remote_config_reader_for_path == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_reader_for_path"}; + } - std::smatch base_match; - if (!std::regex_match(path, base_match, regex) || base_match.size() < 4) { - throw invalid_path(); + ddog_remote_config_read = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_read")); + if (ddog_remote_config_read == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_remote_config_read"}; } - return config_path{base_match[3].str(), base_match[2].str()}; + ddog_remote_config_reader_drop = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_reader_drop")); + if (ddog_remote_config_reader_drop == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_reader_drop"}; + } } -client::client(std::unique_ptr &&arg_api, service_identifier &&sid, - remote_config::settings settings, - std::vector listeners) - : api_(std::move(arg_api)), id_(dds::generate_random_uuid()), - sid_(std::move(sid)), settings_(std::move(settings)), - listeners_(std::move(listeners)) +client::client(remote_config::settings settings, + std::vector> listeners) + : reader_{ddog_remote_config_reader_for_path(settings.shmem_path.c_str()), + ddog_remote_config_reader_drop}, + settings_{std::move(settings)}, listeners_{std::move(listeners)} { + assert(settings_.enabled == true); // NOLINT + for (auto const &listener : listeners_) { - const auto &supported_products = listener->get_supported_products(); - for (const auto &[name, capabilities] : supported_products) { - products_.insert(std::pair( - name, product(name, listener))); - capabilities_ |= capabilities; + for (const product p : listener->get_supported_products()) { + std::vector &vec_listeners = + listeners_per_product_[p]; + vec_listeners.push_back(listener.get()); + all_products_.insert(p); } } } -client::ptr client::from_settings(service_identifier &&sid, +std::unique_ptr client::from_settings( const remote_config::settings &settings, - std::vector listeners) + std::vector> listeners) { - return std::make_unique(std::make_unique(settings.host, - std::to_string(settings.port)), - std::move(sid), settings, std::move(listeners)); + return std::unique_ptr{new client(settings, std::move(listeners))}; } -[[nodiscard]] protocol::get_configs_request client::generate_request() const +bool client::poll() { - std::vector config_states; - std::vector files; - - for (const auto &[product_name, product] : products_) { - // State - const auto configs_on_product = product.get_configs(); - for (const auto &[id, config] : configs_on_product) { - config_states.push_back({config.id, config.version, config.product, - config.apply_state, config.apply_error}); - - std::vector hashes; - hashes.reserve(config.hashes.size()); - for (auto const &[algo, hash_sting] : config.hashes) { - hashes.push_back({algo, hash_sting}); - } - files.push_back({config.path, config.length, std::move(hashes)}); - } - } + const std::lock_guard lock{mutex_}; - const protocol::client_tracer ct{std::move(ids_.get()), sid_.tracer_version, - sid_.service, sid_.extra_services, sid_.env, sid_.app_version}; + SPDLOG_DEBUG("Polling remote config"); - const protocol::client_state cs{targets_version_, config_states, - !last_poll_error_.empty(), last_poll_error_, opaque_backend_state_}; - std::vector products_str; - products_str.reserve(products_.size()); - for (const auto &[product_name, product] : products_) { - products_str.push_back(product_name); + ddog_CharSlice slice{}; + const bool has_update = ddog_remote_config_read(reader_.get(), &slice); + if (!has_update) { + SPDLOG_DEBUG("No update available for {}", settings_.shmem_path); + return false; } - protocol::client protocol_client = { - id_, products_str, ct, cs, capabilities_}; - - return {std::move(protocol_client), std::move(files)}; -}; -bool client::process_response(const protocol::get_configs_response &response) -{ - if (!response.targets.has_value()) { - return true; + std::set new_configs; + // NOLINTNEXTLINE + std::string_view resp{reinterpret_cast(slice.ptr), slice.len}; + auto pos_lf = resp.find('\n'); + if (pos_lf == std::string_view::npos) { + throw std::runtime_error{ + "Invalid response from remote config (no newline)"}; + return false; } + SPDLOG_DEBUG("Runtime id is {}", resp.substr(0, pos_lf)); - const std::unordered_map paths_on_targets = - response.targets->paths; - const std::unordered_map target_files = - response.target_files; - std::unordered_map> - configs; - for (const std::string &path : response.client_configs) { - try { - auto cp = config_path::from_path(path); - - // Is path on targets? - auto path_itr = paths_on_targets.find(path); - if (path_itr == paths_on_targets.end()) { - // Not found - last_poll_error_ = "missing config " + path + " in targets"; - return false; - } - auto length = path_itr->second.length; - std::unordered_map hashes = - path_itr->second.hashes; - int custom_v = path_itr->second.custom_v; - - // Is product on the requested ones? - auto product = products_.find(cp.product); - if (product == products_.end()) { - // Not found - last_poll_error_ = "received config " + path + - " for a product that was not requested"; - return false; - } - - // Is path on target_files? - auto path_in_target_files = target_files.find(path); - std::string raw; - if (path_in_target_files == target_files.end()) { - // Check if file in cache - auto configs_on_product = product->second.get_configs(); - auto config_itr = std::find_if(configs_on_product.begin(), - configs_on_product.end(), [&path, &hashes](auto &pair) { - return pair.second.path == path && - pair.second.hashes == hashes; - }); - - if (config_itr == configs_on_product.end()) { - // Not found - last_poll_error_ = "missing config " + path + - " in target files and in cache files"; - return false; - } - - raw = config_itr->second.contents; - length = config_itr->second.length; - custom_v = config_itr->second.version; - } else { - raw = path_in_target_files->second.raw; - } - - const std::string path_c = path; - config const config_ = { - cp.product, cp.id, raw, path_c, hashes, custom_v, length}; - auto configs_itr = configs.find(cp.product); - if (configs_itr == - configs.end()) { // Product not in configs yet. Create entry - std::unordered_map configs_on_product; - configs_on_product.emplace(cp.id, config_); - configs.insert(std::pair>( - cp.product, configs_on_product)); - } else { // Product already exists in configs. Add new config - configs_itr->second.emplace(cp.id, config_); - } - } catch (invalid_path &e) { - last_poll_error_ = "error parsing path " + path; - return false; + std::string_view configs = resp.substr(pos_lf + 1); + while (!configs.empty()) { + auto pos_lf = configs.find('\n'); + if (pos_lf == std::string_view::npos) { + break; } + new_configs.emplace(config::from_line(configs.substr(0, pos_lf))); + configs = configs.substr(pos_lf + 1); } - // Since there have not been errors, we can now update product configs - // First initialise the listener + if (sets_are_indentical_for_subbed_products( + all_products_, new_configs, last_configs_)) { + SPDLOG_DEBUG("Configuration is identical for the subscribed products. " + "Skipping update"); + return false; + } - for (auto &listener : listeners_) { listener->init(); } + return process_response(std::move(new_configs)); +} - for (auto &[name, product] : products_) { - const auto product_configs = configs.find(name); - if (product_configs != configs.end()) { - product.assign_configs(product_configs->second); - } else { - product.assign_configs({}); +bool client::process_response(std::set new_configs) +{ + for (auto &listener : listeners_) { + try { + listener->init(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to init listener: {}", e.what()); } } - for (auto &listener : listeners_) { listener->commit(); } - - targets_version_ = response.targets->version; - opaque_backend_state_ = response.targets->opaque_backend_state; + // unapply should happen first, because asm_dd aggregator ignores the key... + for (const auto &cfg : last_configs_) { + if (new_configs.contains(cfg)) { + continue; + } - return true; -} + const product p = cfg.get_product(); + auto it = listeners_per_product_.find(p); + if (it == listeners_per_product_.end()) { + continue; + } -bool client::is_remote_config_available() -{ - auto response_body = api_->get_info(); - try { - SPDLOG_TRACE("Received info response: {}", response_body); - auto response = protocol::parse_info(response_body); - - return std::find(response.endpoints.begin(), response.endpoints.end(), - "/v0.7/config") != response.endpoints.end(); - } catch (protocol::parser_exception &e) { - SPDLOG_ERROR("Error parsing info response - {}", e.what()); - return false; + SPDLOG_DEBUG("Unapplying config {}", cfg); + for (listener_base *listener : it->second) { + try { + listener->on_unapply(cfg); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to unapply config {}: {}", cfg, e.what()); + } + } } -} -bool client::poll() -{ - // Wait until we have a valid runtime ID, once this ID is available, - // it'll always have a value, even if all extensions have disconnected - if (api_ == nullptr || !ids_.has_value()) { - return false; - } + for (const auto &cfg : new_configs) { + const product p = cfg.get_product(); + if (p == known_products::UNKNOWN) { + SPDLOG_INFO("Ignoring config with key {}; unsupported product", + cfg.rc_path); + continue; + } + auto it = listeners_per_product_.find(p); + if (it == listeners_per_product_.end()) { + SPDLOG_INFO( + "No listeners for product {}; skipping key {}", p, cfg.rc_path); + continue; + } - auto request = generate_request(); + SPDLOG_DEBUG("Applying config {}", cfg); + for (listener_base *listener : it->second) { + try { + listener->on_update(cfg); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to apply config {}: {}", cfg, e.what()); + } + } + } - std::string serialized_request; - try { - serialized_request = protocol::serialize(request); - SPDLOG_TRACE("Sending request: {}", serialized_request); - } catch (protocol::serializer_exception &e) { - return false; + for (auto &listener : listeners_) { + try { + listener->commit(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Failed to commit listener: {}", e.what()); + } } - auto response_body = api_->get_configs(std::move(serialized_request)); + last_configs_ = std::move(new_configs); - try { - SPDLOG_TRACE("Received response: {}", response_body); - auto response = protocol::parse(response_body); - last_poll_error_.clear(); - return process_response(response); - } catch (protocol::parser_exception &e) { - SPDLOG_ERROR("Error parsing remote config response - {}", e.what()); - return false; - } + return true; } } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client.hpp b/appsec/src/helper/remote_config/client.hpp index 542d6ff216..7d03c0d94d 100644 --- a/appsec/src/helper/remote_config/client.hpp +++ b/appsec/src/helper/remote_config/client.hpp @@ -6,26 +6,28 @@ #pragma once #include +#include +#include #include #include #include -#include "../service_identifier.hpp" -#include "engine.hpp" -#include "engine_settings.hpp" -#include "http_api.hpp" +#include "../engine.hpp" +#include "../engine_settings.hpp" +#include "../service_config.hpp" +#include "../utils.hpp" #include "listeners/listener.hpp" #include "product.hpp" -#include "protocol/client.hpp" -#include "protocol/tuf/get_configs_request.hpp" -#include "protocol/tuf/get_configs_response.hpp" -#include "runtime_id_pool.hpp" -#include "service_config.hpp" #include "settings.hpp" -#include "utils.hpp" + +extern "C" { +struct ddog_RemoteConfigReader; +} namespace dds::remote_config { +void resolve_symbols(); + struct config_path { static config_path from_path(const std::string &path); @@ -34,63 +36,38 @@ struct config_path { }; class client { + client(remote_config::settings settings, + std::vector> listeners); + public: - using ptr = std::unique_ptr; - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - client(std::unique_ptr &&arg_api, service_identifier &&sid, - remote_config::settings settings, - std::vector listeners = {}); - virtual ~client() = default; + ~client() = default; client(const client &) = delete; client(client &&) = delete; client &operator=(const client &) = delete; client &operator=(client &&) = delete; - static client::ptr from_settings(service_identifier &&sid, + static std::unique_ptr from_settings( const remote_config::settings &settings, - std::vector listeners); - - virtual bool poll(); - virtual bool is_remote_config_available(); - [[nodiscard]] virtual const std::unordered_map & - get_products() - { - return products_; - } - - [[nodiscard]] const service_identifier &get_service_identifier() - { - return sid_; - } - - virtual void register_runtime_id(const std::string &id) { ids_.add(id); } - virtual void unregister_runtime_id(const std::string &id) - { - ids_.remove(id); - } + std::vector> listeners); -protected: - [[nodiscard]] protocol::get_configs_request generate_request() const; - bool process_response(const protocol::get_configs_response &response); - - std::unique_ptr api_; + bool poll(); - std::string id_; - runtime_id_pool ids_; - const service_identifier sid_; - const remote_config::settings settings_; +protected: + bool process_response(std::set new_configs); - // remote config state - std::string last_poll_error_; - std::string opaque_backend_state_; - int targets_version_{0}; + std::unique_ptr + reader_; + remote_config::settings settings_; // just for logging - // supported products - std::vector listeners_; - std::unordered_map products_; + std::vector> listeners_; + std::unordered_map> + listeners_per_product_; // non-owning index of listeners_ + std::unordered_set all_products_; // keys of listeners_per_product_ - protocol::capabilities_e capabilities_ = {protocol::capabilities_e::NONE}; + std::set last_configs_; + std::mutex mutex_; }; } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client_handler.cpp b/appsec/src/helper/remote_config/client_handler.cpp index c0e33ba829..d61f999255 100644 --- a/appsec/src/helper/remote_config/client_handler.cpp +++ b/appsec/src/helper/remote_config/client_handler.cpp @@ -13,30 +13,17 @@ namespace dds::remote_config { static constexpr std::chrono::milliseconds default_max_interval = 5min; -client_handler::client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval) - : service_config_(std::move(service_config)), - rc_client_(std::move(rc_client)), poll_interval_(poll_interval), - interval_(poll_interval), max_interval(default_max_interval) -{ - // It starts checking if rc is available - rc_action_ = [this] { discover(); }; -} +client_handler::client_handler(std::unique_ptr &&rc_client, + std::shared_ptr service_config) + : rc_client_{std::move(rc_client)}, + service_config_{std::move(service_config)} +{} -client_handler::~client_handler() -{ - if (handler_.joinable()) { - exit_.set_value(true); - handler_.join(); - } -} - -client_handler::ptr client_handler::from_settings(service_identifier &&id, +std::unique_ptr client_handler::from_settings( const dds::engine_settings &eng_settings, std::shared_ptr service_config, - const remote_config::settings &rc_settings, const engine::ptr &engine_ptr, - bool dynamic_enablement) + const remote_config::settings &rc_settings, + const std::shared_ptr &engine_ptr, bool dynamic_enablement) { if (!rc_settings.enabled) { return {}; @@ -46,7 +33,7 @@ client_handler::ptr client_handler::from_settings(service_identifier &&id, return {}; } - std::vector listeners = {}; + std::vector> listeners = {}; if (dynamic_enablement) { listeners.emplace_back( std::make_shared( @@ -59,82 +46,26 @@ client_handler::ptr client_handler::from_settings(service_identifier &&id, } if (listeners.empty()) { + SPDLOG_DEBUG( + "Not enabling remote config for this service as no " + "listeners are available (no dynamic enablement and no rules " + "file set)"); return {}; } - auto rc_client = remote_config::client::from_settings(std::move(id), - remote_config::settings(rc_settings), std::move(listeners)); - - return std::make_shared(std::move(rc_client), - std::move(service_config), - std::chrono::milliseconds{rc_settings.poll_interval}); -} - -bool client_handler::start() -{ - if (rc_client_) { - handler_ = std::thread(&client_handler::run, this, exit_.get_future()); - return true; - } + auto rc_client = + remote_config::client::from_settings(rc_settings, std::move(listeners)); - return false; -} - -void client_handler::handle_error() -{ - rc_action_ = [this] { discover(); }; - - if (errors_ < std::numeric_limits::max() - 1) { - errors_++; - } - - if (interval_ < max_interval) { - auto new_interval = - std::chrono::duration_cast( - poll_interval_ * pow(2, errors_)); - interval_ = std::min(max_interval, new_interval); - } + return std::make_unique( + std::move(rc_client), std::move(service_config)); } void client_handler::poll() { try { rc_client_->poll(); - } catch (dds::remote_config::network_exception & /** e */) { - handle_error(); - } -} -void client_handler::discover() -{ - try { - if (rc_client_->is_remote_config_available()) { - // Remote config is available. Start polls - rc_action_ = [this] { poll(); }; - errors_ = 0; - interval_ = poll_interval_; - return; - } - } catch (dds::remote_config::network_exception & /** e */) {} - handle_error(); -} - -void client_handler::tick() { rc_action_(); } - -// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) -void client_handler::run(std::future &&exit_signal) -{ - std::chrono::time_point before{0s}; - std::future_status fs = exit_signal.wait_for(0s); - while (fs == std::future_status::timeout) { - // If the thread is interrupted somehow, make sure to check that - // the polling interval has actually elapsed. - auto now = std::chrono::steady_clock::now(); - if ((now - before) >= interval_) { - tick(); - before = now; - } - - fs = exit_signal.wait_for(interval_); + } catch (const std::exception &e) { + SPDLOG_WARN("Error polling remote config: {}", e.what()); } } } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/client_handler.hpp b/appsec/src/helper/remote_config/client_handler.hpp index 4fea0e2a06..2a88649b00 100644 --- a/appsec/src/helper/remote_config/client_handler.hpp +++ b/appsec/src/helper/remote_config/client_handler.hpp @@ -5,17 +5,11 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "engine.hpp" -#include "remote_config/client.hpp" -#include "remote_config/settings.hpp" -#include "service_config.hpp" -#include "service_identifier.hpp" -#include "std_logging.hpp" -#include "utils.hpp" -#include +#include "../engine.hpp" +#include "../service_config.hpp" +#include "client.hpp" +#include "settings.hpp" #include -#include -#include namespace dds::remote_config { @@ -23,12 +17,9 @@ using namespace std::chrono_literals; class client_handler { public: - using ptr = std::shared_ptr; - - client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval = 1s); - ~client_handler(); + client_handler(std::unique_ptr &&rc_client, + std::shared_ptr service_config); + ~client_handler() = default; client_handler(const client_handler &) = delete; client_handler &operator=(const client_handler &) = delete; @@ -36,48 +27,17 @@ class client_handler { client_handler(client_handler &&) = delete; client_handler &operator=(client_handler &&) = delete; - static client_handler::ptr from_settings(service_identifier &&id, + static std::unique_ptr from_settings( const dds::engine_settings &eng_settings, std::shared_ptr service_config, const remote_config::settings &rc_settings, - const engine::ptr &engine_ptr, bool dynamic_enablement); - - bool start(); - - remote_config::client *get_client() { return rc_client_.get(); } + const std::shared_ptr &engine_ptr, bool dynamic_enablement); - void register_runtime_id(const std::string &id) - { - if (rc_client_) { - rc_client_->register_runtime_id(id); - } - } - void unregister_runtime_id(const std::string &id) - { - if (rc_client_) { - rc_client_->unregister_runtime_id(id); - } - } + void poll(); protected: - void run(std::future &&exit_signal); - void handle_error(); - - remote_config::client::ptr rc_client_; std::shared_ptr service_config_; - - std::chrono::milliseconds poll_interval_; - std::chrono::milliseconds interval_; - std::chrono::milliseconds max_interval; - void poll(); - void discover(); - void tick(); - std::function rc_action_; - - std::uint16_t errors_ = {0}; - - std::promise exit_; - std::thread handler_; + std::unique_ptr rc_client_; }; } // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.cpp b/appsec/src/helper/remote_config/config.cpp new file mode 100644 index 0000000000..852c63615d --- /dev/null +++ b/appsec/src/helper/remote_config/config.cpp @@ -0,0 +1,105 @@ +#include "config.hpp" +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +using namespace std::literals; + +namespace dds::remote_config { + +[[nodiscard]] product config::get_product() const +{ + // A configuration key has the form: + // (datadog/ | employee)///" + std::string_view sv{rc_path}; + if (sv.starts_with("datadog/"sv)) { + sv.remove_prefix("datadog/"sv.length()); + auto org_id_end = sv.find('/'); + if (org_id_end != std::string_view::npos) { + sv.remove_prefix(org_id_end + 1); + auto product_end = sv.find('/'); + if (product_end != std::string_view::npos) { + return known_products::for_name(sv.substr(0, product_end)); + } + } + } else if (sv.starts_with("employee/"sv)) { + sv.remove_prefix("employee/"sv.length()); + auto product_end = sv.find('/'); + if (product_end != std::string::npos) { + return known_products::for_name(sv.substr(0, product_end)); + } + } + + return known_products::UNKNOWN; +} + +config config::from_line(std::string_view line) +{ + // split by : + auto pos = line.find(':'); + if (pos == std::string_view::npos) { + throw std::runtime_error{"invalid shmem config line (no colon)"}; + } + + const std::string_view shm_path{line.substr(0, pos)}; + auto pos2 = line.find(':', pos + 1); + if (pos2 == std::string_view::npos) { + throw std::runtime_error{"invalid shmem config line (no second colon)"}; + } + + std::uint32_t limiter_idx; + auto res = + std::from_chars(line.data() + pos + 1, line.data() + pos2, limiter_idx); + if (res.ec != std::errc{} || res.ptr != line.data() + pos2) { + throw std::runtime_error{"invalid shmem config line (limiter_idx)"}; + } + + const std::string_view rc_path_encoded{line.substr(pos2 + 1)}; + // base64 decode rc_path (no padding): + std::string rc_path = base64_decode(rc_path_encoded); + + return {std::string{shm_path}, std::move(rc_path)}; +} + +mapped_memory config::read() const +{ + // open shared memory segment at rc_path: + const int fd = ::shm_open(shm_path.c_str(), O_RDONLY, 0); + if (fd == -1) { + throw std::runtime_error{ + "shm_open failed for " + shm_path + " : " + strerror_ts(errno)}; + } + + auto close_fs = defer{[fd]() { ::close(fd); }}; + + // check that the uid of the shared memory segment is the same as ours + struct ::stat shm_stat {}; + if (::fstat(fd, &shm_stat) == -1) { + throw std::runtime_error{ + "Call to fstat on memory segment failed: " + strerror_ts(errno)}; + } + if (shm_stat.st_uid != ::geteuid()) { + throw std::runtime_error{"Shared memory segment does not have the " + "expected owner. Expect our uid " + + std::to_string(::geteuid()) + " but found " + + std::to_string(shm_stat.st_uid)}; + } + + void *shm_ptr = + ::mmap(nullptr, shm_stat.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (shm_ptr == MAP_FAILED) { // NOLINT + throw std::runtime_error( + "Failed to map shared memory: " + std::string{strerror_ts(errno)}); + } + + return mapped_memory{shm_ptr, static_cast(shm_stat.st_size)}; +} +} // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/config.hpp b/appsec/src/helper/remote_config/config.hpp index 839c51e6f3..05e5e226c7 100644 --- a/appsec/src/helper/remote_config/config.hpp +++ b/appsec/src/helper/remote_config/config.hpp @@ -5,48 +5,98 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "protocol/config_state.hpp" -#include +#include "../utils.hpp" +#include "product.hpp" #include -#include +#include #include +extern "C" { +#include +} + namespace dds::remote_config { -struct config { - std::string product; - std::string id; - std::string contents; - std::string path; - std::unordered_map hashes; - int version; - int length; - protocol::config_state::applied_state apply_state; - std::string apply_error; - - friend auto &operator<<( - std::ostream &os, const dds::remote_config::config &c) - { - os << "Product: " << c.product << std::endl; - os << "id: " << c.id << std::endl; - os << "contents: " << c.contents << std::endl; - os << "path: " << c.path << std::endl; - // os << "hashes: " << c.hashes << std::endl; - os << "version: " << c.version << std::endl; - os << "length: " << c.length << std::endl; - os << "apply_state: " << (int)c.apply_state << std::endl; - os << "apply_error: " << c.apply_error << std::endl; - return os; +class mapped_memory { +public: + mapped_memory(void *ptr, std::size_t size) : ptr_{ptr}, size_{size} {} + mapped_memory(const mapped_memory &) = delete; + mapped_memory(mapped_memory &&mm) noexcept : ptr_{mm.ptr_}, size_{mm.size_} + { + mm.ptr_ = nullptr; + mm.size_ = 0; + } + mapped_memory &operator=(const mapped_memory &) = delete; + mapped_memory &operator=(mapped_memory &&mm) noexcept + { + ptr_ = mm.ptr_; + size_ = mm.size_; + mm.ptr_ = nullptr; + mm.size_ = 0; + return *this; + } + ~mapped_memory() noexcept + { + if (ptr_ != nullptr) { + if (::munmap(ptr_, size_) == -1) { + SPDLOG_WARN( + "Failed to unmap shared memory: {}", strerror_ts(errno)); + }; + } } + + operator std::string_view() const // NOLINT + { + return std::string_view{static_cast(ptr_), size_}; + } + +private: + void *ptr_; + std::size_t size_; }; -inline bool operator==(const config &rhs, const config &lhs) -{ - return rhs.product == lhs.product && rhs.id == lhs.id && - rhs.contents == lhs.contents && rhs.hashes == lhs.hashes && - rhs.version == lhs.version && rhs.path == lhs.path && - rhs.length == lhs.length && rhs.apply_state == lhs.apply_state && - rhs.apply_error == lhs.apply_error; -} +struct config { + // from a line provided by the RC config reader + static config from_line(std::string_view line); + + std::string shm_path; + std::string rc_path; + + [[nodiscard]] mapped_memory read() const; + + [[nodiscard]] product get_product() const; + + bool operator==(const config &b) const + { + return shm_path == b.shm_path && rc_path == b.rc_path; + } + + friend std::ostream &operator<<(std::ostream &os, const config &c) + { + return os << c.shm_path << ":" << c.rc_path; + } +}; } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::config &key) const + { + return dds::hash(key.shm_path, key.rc_path); + } +}; +template <> struct less { + bool operator()(const dds::remote_config::config &lhs, + const dds::remote_config::config &rhs) const + { + if (lhs.rc_path < rhs.rc_path) { + return true; + }; + if (lhs.rc_path > rhs.rc_path) { + return false; + } + return lhs.shm_path < rhs.shm_path; + } +}; +} // namespace std diff --git a/appsec/src/helper/remote_config/http_api.cpp b/appsec/src/helper/remote_config/http_api.cpp deleted file mode 100644 index 9f97b123dd..0000000000 --- a/appsec/src/helper/remote_config/http_api.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#include "http_api.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast = boost::beast; // from -namespace http = beast::http; // from -namespace net = boost::asio; // from -using tcp = net::ip::tcp; // from - -static const int version = 11; - -std::string execute_request(const std::string &host, const std::string &port, - const http::request &request) -{ - std::string result; - - try { - // The io_context is required for all I/O - net::io_context ioc; - - // These objects perform our I/O - tcp::resolver resolver(ioc); - beast::tcp_stream stream(ioc); - - // Look up the domain name - auto const results = resolver.resolve(host, port); - - // Make the connection on the IP address we get from a lookup - stream.connect(results); - - // Send the HTTP request to the remote host - http::write(stream, request); - - // This buffer is used for reading and must be persisted - beast::flat_buffer buffer; - - // Declare a container to hold the response - http::response res; - - // Receive the HTTP response - http::read(stream, buffer, res); - - // Write the message to standard out - result = boost::beast::buffers_to_string(res.body().data()); - - // Gracefully close the socket - beast::error_code ec; - // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c) - stream.socket().shutdown(tcp::socket::shutdown_both, ec); - - // not_connected happens sometimes - // so don't bother reporting it. - // - if (ec && ec != beast::errc::not_connected) { - throw beast::system_error{ec}; - } - - // If we get here then the connection is closed gracefully - } catch (std::exception const &e) { - auto sv = request.target(); - const std::string err{sv.data(), sv.size()}; - SPDLOG_ERROR("Connection error - {} - {}", err, e.what()); - throw dds::remote_config::network_exception( - "Connection error - " + err + " - " + e.what()); - } - - return result; -} - -std::string dds::remote_config::http_api::get_info() const -{ - http::request req{http::verb::get, "/info", version}; - req.set(http::field::host, host_); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - - return execute_request(host_, port_, req); -} - -std::string dds::remote_config::http_api::get_configs( - std::string &&request) const -{ - // Set up an HTTP POST request message - http::request req; - req.method(http::verb::post); - req.target("/v0.7/config"); - req.version(version); - req.set(http::field::host, host_); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - req.set(http::field::content_length, std::to_string(request.size())); - req.set(http::field::accept, "*/*"); - req.set(http::field::content_type, "application/x-www-form-urlencoded"); - req.body() = std::move(request); - req.keep_alive(true); - - return execute_request(host_, port_, req); -}; diff --git a/appsec/src/helper/remote_config/http_api.hpp b/appsec/src/helper/remote_config/http_api.hpp deleted file mode 100644 index f83fb2e527..0000000000 --- a/appsec/src/helper/remote_config/http_api.hpp +++ /dev/null @@ -1,47 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -namespace dds::remote_config { - -class network_exception : public std::exception { -public: - explicit network_exception(std::string what) : what_(std::move(what)) {} - [[nodiscard]] const char *what() const noexcept override - { - return what_.c_str(); - } - -protected: - const std::string what_; -}; - -class http_api { -public: - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - http_api(std::string host, std::string port) - : host_(std::move(host)), port_(std::move(port)){}; - - http_api(const http_api &) = delete; - http_api(http_api &&) = delete; - - http_api &operator=(const http_api &) = delete; - http_api &operator=(http_api &&) = delete; - - virtual ~http_api() = default; - - virtual std::string get_info() const; - virtual std::string get_configs(std::string &&request) const; - -protected: - std::string host_; - std::string port_; -}; - -} // namespace dds::remote_config diff --git a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp index edd8ae0f66..71c293a04d 100644 --- a/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp +++ b/appsec/src/helper/remote_config/listeners/asm_features_listener.cpp @@ -4,19 +4,21 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_features_listener.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "utils.hpp" +#include "../../json_helper.hpp" +#include "../../utils.hpp" +#include "../exception.hpp" #include #include void dds::remote_config::asm_features_listener::on_update(const config &config) { rapidjson::Document serialized_doc; - if (!json_helper::get_json_base64_encoded_content( - config.contents, serialized_doc)) { - throw error_applying_config("Invalid config contents"); + + { + const mapped_memory contents{config.read()}; + if (!json_helper::parse_json(contents, serialized_doc)) { + throw error_applying_config("Invalid config contents"); + } } auto asm_itr = json_helper::get_field_of_type( diff --git a/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp b/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp index ab91cc42be..9ca500549c 100644 --- a/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp +++ b/appsec/src/helper/remote_config/listeners/asm_features_listener.hpp @@ -5,9 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../config.hpp" +#include "../../service_config.hpp" +#include "../product.hpp" #include "listener.hpp" -#include "service_config.hpp" namespace dds::remote_config { @@ -22,10 +23,9 @@ class asm_features_listener : public listener_base { service_config_->unset_asm(); } - [[nodiscard]] std::unordered_map - get_supported_products() override + [[nodiscard]] std::unordered_set get_supported_products() override { - return {{"ASM_FEATURES", protocol::capabilities_e::ASM_ACTIVATION}}; + return {known_products::ASM_FEATURES}; } void init() override {} diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp index 0fb56fd106..f8d5f3b9b1 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.cpp @@ -4,12 +4,11 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_aggregator.hpp" -#include "exception.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" +#include "../../exception.hpp" #include #include #include +#include namespace dds::remote_config { @@ -31,7 +30,7 @@ void asm_aggregator::init(rapidjson::Document::AllocatorType *allocator) void asm_aggregator::add(const config &config) { rapidjson::Document doc(&ruleset_.GetAllocator()); - if (!json_helper::get_json_base64_encoded_content(config.contents, doc)) { + if (!json_helper::parse_json(config.read(), doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp index 46dfdf4b6a..d4998f3836 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_aggregator.hpp @@ -5,11 +5,11 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../config.hpp" +#include "../../../engine.hpp" +#include "../../../json_helper.hpp" +#include "../../../parameter.hpp" #include "config_aggregator.hpp" -#include "engine.hpp" -#include "json_helper.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp index 90ef911524..b0f6805872 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.cpp @@ -4,13 +4,12 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_data_aggregator.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" +#include "../../../json_helper.hpp" +#include "../../exception.hpp" #include #include #include +#include namespace dds::remote_config { @@ -81,8 +80,7 @@ void extract_data( void asm_data_aggregator::add(const config &config) { rapidjson::Document serialized_doc; - if (!json_helper::get_json_base64_encoded_content( - config.contents, serialized_doc)) { + if (!json_helper::parse_json(config.read(), serialized_doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp index 4dba85460f..a570c6eea4 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_data_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../engine.hpp" +#include "../../../parameter.hpp" +#include "../../config.hpp" #include "config_aggregator.hpp" -#include "engine.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp index ab5c8ace09..ec0458b34a 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.cpp @@ -4,14 +4,14 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "asm_dd_aggregator.hpp" -#include "exception.hpp" -#include "remote_config/exception.hpp" +#include "../../../exception.hpp" +#include "../../exception.hpp" #include void dds::remote_config::asm_dd_aggregator::add(const config &config) { rapidjson::Document doc(&ruleset_.GetAllocator()); - if (!json_helper::get_json_base64_encoded_content(config.contents, doc)) { + if (!json_helper::parse_json(config.read(), doc)) { throw error_applying_config("Invalid config contents"); } diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp index 937a4d1c69..2951005e71 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../../config.hpp" +#include "../../../json_helper.hpp" +#include "../../../parameter.hpp" #include "config_aggregator.hpp" -#include "json_helper.hpp" -#include "parameter.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp b/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp index 52bcd41e08..381eed0bb0 100644 --- a/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp +++ b/appsec/src/helper/remote_config/listeners/config_aggregators/config_aggregator.hpp @@ -5,10 +5,10 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" -#include "engine.hpp" -#include "parameter.hpp" -#include "remote_config/listeners/listener.hpp" +#include "../../../config.hpp" +#include "../../../engine.hpp" +#include "../../../parameter.hpp" +#include "../listener.hpp" #include #include #include diff --git a/appsec/src/helper/remote_config/listeners/engine_listener.cpp b/appsec/src/helper/remote_config/listeners/engine_listener.cpp index 761f6be0e8..29d92f1c6e 100644 --- a/appsec/src/helper/remote_config/listeners/engine_listener.cpp +++ b/appsec/src/helper/remote_config/listeners/engine_listener.cpp @@ -4,29 +4,30 @@ // This product includes software developed at Datadog // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "engine_listener.hpp" +#include "../../json_helper.hpp" +#include "../exception.hpp" +#include "../product.hpp" #include "config_aggregators/asm_aggregator.hpp" #include "config_aggregators/asm_data_aggregator.hpp" #include "config_aggregators/asm_dd_aggregator.hpp" -#include "exception.hpp" -#include "json_helper.hpp" -#include "remote_config/exception.hpp" -#include "spdlog/spdlog.h" #include #include #include +#include #include namespace dds::remote_config { engine_listener::engine_listener( - engine::ptr engine, const std::string &rules_file) + std::shared_ptr engine, const std::string &rules_file) : engine_(std::move(engine)) { - aggregators_.emplace(asm_product, std::make_unique()); aggregators_.emplace( - asm_dd_product, std::make_unique(rules_file)); + known_products::ASM, std::make_unique()); + aggregators_.emplace(known_products::ASM_DD, + std::make_unique(rules_file)); aggregators_.emplace( - asm_data_product, std::make_unique()); + known_products::ASM_DATA, std::make_unique()); } void engine_listener::init() @@ -37,9 +38,10 @@ void engine_listener::init() void engine_listener::on_update(const config &config) { - auto it = aggregators_.find(config.product); + auto it = aggregators_.find(config.get_product()); if (it == aggregators_.end()) { - throw error_applying_config("unknown product: " + config.product); + throw error_applying_config( + "unknown product: " + std::string{config.get_product().name()}); } auto &aggregator = it->second; @@ -53,9 +55,10 @@ void engine_listener::on_update(const config &config) void engine_listener::on_unapply(const config &config) { - auto it = aggregators_.find(config.product); + auto it = aggregators_.find(config.get_product()); if (it == aggregators_.end()) { - throw error_applying_config("unknown product: " + config.product); + throw error_applying_config( + "unknown product: " + std::string{config.get_product().name()}); } auto &aggregator = it->second; diff --git a/appsec/src/helper/remote_config/listeners/engine_listener.hpp b/appsec/src/helper/remote_config/listeners/engine_listener.hpp index 1ef7847ed5..6f961b6c2c 100644 --- a/appsec/src/helper/remote_config/listeners/engine_listener.hpp +++ b/appsec/src/helper/remote_config/listeners/engine_listener.hpp @@ -5,12 +5,12 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" +#include "../../config.hpp" +#include "../../engine.hpp" +#include "../../parameter.hpp" +#include "../product.hpp" #include "config_aggregators/config_aggregator.hpp" -#include "engine.hpp" #include "listener.hpp" -#include "parameter.hpp" -#include "remote_config/protocol/client.hpp" #include #include #include @@ -21,7 +21,7 @@ namespace dds::remote_config { class engine_listener : public listener_base { public: explicit engine_listener( - engine::ptr engine, const std::string &rules_file = {}); + std::shared_ptr engine, const std::string &rules_file = {}); engine_listener(const engine_listener &) = delete; engine_listener(engine_listener &&) = default; engine_listener &operator=(const engine_listener &) = delete; @@ -34,30 +34,16 @@ class engine_listener : public listener_base { void on_unapply(const config &config) override; void commit() override; - [[nodiscard]] std::unordered_map - get_supported_products() override + [[nodiscard]] std::unordered_set get_supported_products() override { - return {{asm_product, - protocol::capabilities_e::ASM_EXCLUSIONS | - protocol::capabilities_e::ASM_CUSTOM_BLOCKING_RESPONSE | - protocol::capabilities_e::ASM_REQUEST_BLOCKING | - protocol::capabilities_e::ASM_RESPONSE_BLOCKING | - protocol::capabilities_e::ASM_CUSTOM_RULES | - protocol::capabilities_e::ASM_TRUSTED_IPS}, - {asm_dd_product, protocol::capabilities_e::ASM_DD_RULES}, - {asm_data_product, - protocol::capabilities_e::ASM_IP_BLOCKING | - protocol::capabilities_e::ASM_USER_BLOCKING}}; + return {known_products::ASM, known_products::ASM_DD, + known_products::ASM_DATA}; } protected: - static constexpr std::string_view asm_product = "ASM"; - static constexpr std::string_view asm_dd_product = "ASM_DD"; - static constexpr std::string_view asm_data_product = "ASM_DATA"; - - std::unordered_map + std::unordered_map aggregators_; - engine::ptr engine_; + std::shared_ptr engine_; rapidjson::Document ruleset_; std::unordered_set to_commit_; }; diff --git a/appsec/src/helper/remote_config/listeners/listener.hpp b/appsec/src/helper/remote_config/listeners/listener.hpp index df17d900e5..91bb93af1a 100644 --- a/appsec/src/helper/remote_config/listeners/listener.hpp +++ b/appsec/src/helper/remote_config/listeners/listener.hpp @@ -5,17 +5,17 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "remote_config/config.hpp" -#include "remote_config/protocol/client.hpp" +#include "../config.hpp" +#include "../product.hpp" #include +#include +#include #include namespace dds::remote_config { class listener_base { public: - using shared_ptr = std::shared_ptr; - listener_base() = default; listener_base(const listener_base &) = default; listener_base(listener_base &&) = default; @@ -25,8 +25,7 @@ class listener_base { virtual void on_update(const config &config) = 0; virtual void on_unapply(const config &config) = 0; - [[nodiscard]] virtual std::unordered_map + [[nodiscard]] virtual std::unordered_set get_supported_products() = 0; // Stateful listeners need to override these methods diff --git a/appsec/src/helper/remote_config/product.cpp b/appsec/src/helper/remote_config/product.cpp deleted file mode 100644 index 2a604c62d0..0000000000 --- a/appsec/src/helper/remote_config/product.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#include "product.hpp" -#include "exception.hpp" - -void dds::remote_config::product::update_configs( - std::unordered_map &to_update) -{ - for (auto &[name, config] : to_update) { - try { - listener_->on_update(config); - config.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ACKNOWLEDGED; - config.apply_error = ""; - } catch (dds::remote_config::error_applying_config &e) { - config.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ERROR; - config.apply_error = e.what(); - } - } -} - -void dds::remote_config::product::unapply_configs( - std::unordered_map &to_unapply) -{ - for (auto &[path, conf] : to_unapply) { - try { - listener_->on_unapply(conf); - conf.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ACKNOWLEDGED; - conf.apply_error = ""; - } catch (dds::remote_config::error_applying_config &e) { - conf.apply_state = dds::remote_config::protocol::config_state:: - applied_state::ERROR; - conf.apply_error = e.what(); - } - } -} - -void dds::remote_config::product::assign_configs( - const std::unordered_map &configs) -{ - std::unordered_map to_update; - bool changes = false; - - // determine what each config given is - for (const auto &[name, config] : configs) { - auto previous_config = configs_.find(name); - if (previous_config == configs_.end()) { // New config - changes = true; - auto config_to_update = config; - config_to_update.apply_state = dds::remote_config::protocol:: - config_state::applied_state::UNACKNOWLEDGED; - to_update.emplace(name, config_to_update); - } else { // Already existed - if (config.hashes == - previous_config->second.hashes) { // No changes in config - to_update.emplace(name, previous_config->second); - } else { // Config updated - changes = true; - auto config_to_update = config; - config_to_update.apply_state = dds::remote_config::protocol:: - config_state::applied_state::UNACKNOWLEDGED; - to_update.emplace(name, config_to_update); - } - // configs_ at the end of this loop will contain only configs - // which have to be unapply. This one has been classified as - // something else and therefore, it has to be removed - configs_.erase(previous_config); - } - } - - if (changes || !configs_.empty()) { - update_configs(to_update); - unapply_configs(configs_); - } - - // Save new state of configs - configs_ = std::move(to_update); -}; diff --git a/appsec/src/helper/remote_config/product.hpp b/appsec/src/helper/remote_config/product.hpp index 5c04f14904..a9b70fe60f 100644 --- a/appsec/src/helper/remote_config/product.hpp +++ b/appsec/src/helper/remote_config/product.hpp @@ -5,50 +5,62 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include "config.hpp" -#include "listeners/listener.hpp" -#include "remote_config/protocol/client.hpp" -#include -#include -#include -#include -#include -#include +#include "../config.hpp" +#include "../utils.hpp" +#include namespace dds::remote_config { class product { public: - explicit product(std::string_view name, listener_base::shared_ptr listener) - : name_(name), listener_(std::move(listener)) + explicit constexpr product(std::string_view name) : name_{name} {} + + [[nodiscard]] const std::string_view &name() const { return name_; } + + bool operator==(const product &other) const { return name_ == other.name_; } + + friend std::ostream &operator<<(std::ostream &os, const product &p) { - if (listener_ == nullptr) { - throw std::runtime_error("invalid listener"); - } + return os << p.name_; } - void assign_configs(const std::unordered_map &configs); - [[nodiscard]] const std::unordered_map & - get_configs() const - { - return configs_; - }; - bool operator==(product const &b) const +private: + std::string_view name_; +}; + +struct known_products { + static inline constexpr product ASM{std::string_view{"ASM"}}; + static inline constexpr product ASM_DD{std::string_view{"ASM_DD"}}; + static inline constexpr product ASM_DATA{std::string_view{"ASM_DATA"}}; + static inline constexpr product ASM_FEATURES{ + std::string_view{"ASM_FEATURES"}}; + static inline constexpr product UNKNOWN{std::string_view{"UNKOWN"}}; + + static product for_name(std::string_view name) { - return name_ == b.name_ && configs_ == b.configs_; + if (name == ASM.name()) { + return ASM; + } + if (name == ASM_DD.name()) { + return ASM_DD; + } + if (name == ASM_DATA.name()) { + return ASM_DATA; + } + if (name == ASM_FEATURES.name()) { + return ASM_FEATURES; + } + + return UNKNOWN; } - [[nodiscard]] const std::string &get_name() const { return name_; } - -protected: - void update_configs( - std::unordered_map &to_update); - void unapply_configs( - std::unordered_map - &to_unapply); - - std::string name_; - std::unordered_map configs_; - std::shared_ptr listener_; }; - } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::product &product) const + { + return dds::hash(product.name()); + } +}; +} // namespace std diff --git a/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp b/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp deleted file mode 100644 index 84a4b6dc2e..0000000000 --- a/appsec/src/helper/remote_config/protocol/cached_target_file_hash.hpp +++ /dev/null @@ -1,22 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include - -namespace dds::remote_config::protocol { - -struct cached_target_files_hash { - std::string algorithm; - std::string hash; -}; - -inline bool operator==( - const cached_target_files_hash &rhs, const cached_target_files_hash &lhs) -{ - return rhs.algorithm == lhs.algorithm && rhs.hash == lhs.hash; -} -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/cached_target_files.hpp b/appsec/src/helper/remote_config/protocol/cached_target_files.hpp deleted file mode 100644 index dfda0c496b..0000000000 --- a/appsec/src/helper/remote_config/protocol/cached_target_files.hpp +++ /dev/null @@ -1,27 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include - -#include "cached_target_file_hash.hpp" - -namespace dds::remote_config::protocol { - -struct cached_target_files { - std::string path; - int length; - std::vector hashes; -}; - -inline bool operator==( - const cached_target_files &rhs, const cached_target_files &lhs) -{ - return rhs.path == lhs.path && rhs.length == lhs.length && - rhs.hashes == lhs.hashes; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client.hpp b/appsec/src/helper/remote_config/protocol/client.hpp deleted file mode 100644 index 1e13d08a7c..0000000000 --- a/appsec/src/helper/remote_config/protocol/client.hpp +++ /dev/null @@ -1,70 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include -#include - -#include "client_state.hpp" -#include "client_tracer.hpp" - -namespace dds::remote_config::protocol { - -enum class capabilities_e : uint16_t { - NONE = 0, - RESERVED = 1, - ASM_ACTIVATION = 1 << 1, - ASM_IP_BLOCKING = 1 << 2, - ASM_DD_RULES = 1 << 3, - ASM_EXCLUSIONS = 1 << 4, - ASM_REQUEST_BLOCKING = 1 << 5, - ASM_RESPONSE_BLOCKING = 1 << 6, - ASM_USER_BLOCKING = 1 << 7, - ASM_CUSTOM_RULES = 1 << 8, - ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9, - ASM_TRUSTED_IPS = 1 << 10, -}; - -constexpr capabilities_e operator|( - const capabilities_e &lhs, capabilities_e rhs) -{ - return static_cast( - static_cast::type>(lhs) | - static_cast::type>(rhs)); -} - -constexpr capabilities_e &operator|=( - capabilities_e &lhs, const capabilities_e rhs) -{ - lhs = lhs | rhs; - return lhs; -} - -constexpr capabilities_e operator&(capabilities_e lhs, capabilities_e rhs) -{ - return static_cast( - static_cast::type>(lhs) & - static_cast::type>(rhs)); -} - -struct client { - std::string id; - std::vector products; - protocol::client_tracer client_tracer; - protocol::client_state client_state; - capabilities_e capabilities; -}; - -inline bool operator==(const client &rhs, const client &lhs) -{ - return rhs.id == lhs.id && rhs.products == lhs.products && - rhs.client_tracer == lhs.client_tracer && - rhs.client_state == lhs.client_state && - rhs.capabilities == lhs.capabilities; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client_state.hpp b/appsec/src/helper/remote_config/protocol/client_state.hpp deleted file mode 100644 index 369736ce8e..0000000000 --- a/appsec/src/helper/remote_config/protocol/client_state.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include "config_state.hpp" -#include -#include - -namespace dds::remote_config::protocol { - -struct client_state { - int targets_version; - std::vector config_states; - bool has_error; - std::string error; - std::string backend_client_state; -}; - -inline bool operator==(const client_state &rhs, const client_state &lhs) -{ - return rhs.targets_version == lhs.targets_version && - rhs.config_states == lhs.config_states && - rhs.has_error == lhs.has_error && rhs.error == lhs.error && - rhs.backend_client_state == lhs.backend_client_state; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/client_tracer.hpp b/appsec/src/helper/remote_config/protocol/client_tracer.hpp deleted file mode 100644 index 8fb4313583..0000000000 --- a/appsec/src/helper/remote_config/protocol/client_tracer.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -namespace dds::remote_config::protocol { - -struct client_tracer { - std::string runtime_id; - std::string tracer_version; - std::string service; - std::vector extra_services; - std::string env; - std::string app_version; -}; - -inline bool operator==(const client_tracer &rhs, const client_tracer &lhs) -{ - return rhs.runtime_id == lhs.runtime_id && - rhs.tracer_version == lhs.tracer_version && - rhs.service == lhs.service && - rhs.extra_services == lhs.extra_services && rhs.env == lhs.env && - rhs.app_version == lhs.app_version; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/config_state.hpp b/appsec/src/helper/remote_config/protocol/config_state.hpp deleted file mode 100644 index 791a895668..0000000000 --- a/appsec/src/helper/remote_config/protocol/config_state.hpp +++ /dev/null @@ -1,32 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include - -namespace dds::remote_config::protocol { - -struct config_state { - std::string id; - int version; - std::string product; - enum class applied_state : int { - UNKNOWN = 0, - UNACKNOWLEDGED = 1, - ACKNOWLEDGED = 2, - ERROR = 3 - } apply_state; - std::string apply_error; -}; - -inline bool operator==(const config_state &rhs, const config_state &lhs) -{ - return rhs.id == lhs.id && rhs.version == lhs.version && - rhs.product == lhs.product && rhs.apply_state == lhs.apply_state && - rhs.apply_error == lhs.apply_error; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/path.hpp b/appsec/src/helper/remote_config/protocol/path.hpp deleted file mode 100644 index a902efa0ae..0000000000 --- a/appsec/src/helper/remote_config/protocol/path.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include -#include - -namespace dds::remote_config::protocol { - -struct path { - int custom_v; - std::unordered_map hashes; - int length; -}; - -inline bool operator==(const path &rhs, const path &lhs) -{ - return rhs.custom_v == lhs.custom_v && rhs.hashes == lhs.hashes && - rhs.length == lhs.length; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/target_file.hpp b/appsec/src/helper/remote_config/protocol/target_file.hpp deleted file mode 100644 index 170f742ddd..0000000000 --- a/appsec/src/helper/remote_config/protocol/target_file.hpp +++ /dev/null @@ -1,23 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -namespace dds::remote_config::protocol { - -struct target_file { - std::string path; - std::string raw; -}; - -inline bool operator==(const target_file &rhs, const target_file &lhs) -{ - return rhs.path == lhs.path && rhs.raw == lhs.raw; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/targets.hpp b/appsec/src/helper/remote_config/protocol/targets.hpp deleted file mode 100644 index 2844c25954..0000000000 --- a/appsec/src/helper/remote_config/protocol/targets.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -#include "path.hpp" - -namespace dds::remote_config::protocol { - -struct targets { - int version; - std::string opaque_backend_state; - std::unordered_map paths; -}; - -inline bool operator==(const targets &rhs, const targets &lhs) -{ - return rhs.version == lhs.version && - rhs.opaque_backend_state == lhs.opaque_backend_state && - std::equal(lhs.paths.begin(), lhs.paths.end(), rhs.paths.begin()); -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp b/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp deleted file mode 100644 index dd7a6523f2..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/get_configs_request.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include - -#include "../cached_target_files.hpp" -#include "../client.hpp" - -namespace dds::remote_config::protocol { - -struct get_configs_request { -public: - protocol::client client; - std::vector cached_target_files; -}; - -inline bool operator==( - const get_configs_request &rhs, const get_configs_request &lhs) -{ - return rhs.client == lhs.client && - rhs.cached_target_files == lhs.cached_target_files; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp b/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp deleted file mode 100644 index 19a68e37f9..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/get_configs_response.hpp +++ /dev/null @@ -1,24 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -#include "../cached_target_files.hpp" -#include "../client.hpp" -#include "../target_file.hpp" -#include "../targets.hpp" - -namespace dds::remote_config::protocol { - -struct get_configs_response { - std::unordered_map target_files; - std::vector client_configs; - std::optional targets; -}; - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp b/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp deleted file mode 100644 index 5e47531f62..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/info_response.hpp +++ /dev/null @@ -1,22 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -#include "../cached_target_files.hpp" -#include "../client.hpp" -#include "../target_file.hpp" -#include "../targets.hpp" - -namespace dds::remote_config::protocol { - -struct info_response { - std::vector endpoints; -}; - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/parser.cpp b/appsec/src/helper/remote_config/protocol/tuf/parser.cpp deleted file mode 100644 index 4e327fb5f1..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/parser.cpp +++ /dev/null @@ -1,357 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include -#include -#include - -#include "parser.hpp" -#include -#include - -using namespace std::literals; - -namespace dds::remote_config::protocol { - -bool validate_field_is_present(const rapidjson::Value &parent_field, - const char *key, rapidjson::Type type, - rapidjson::Value::ConstMemberIterator &output_itr, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result missing, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result invalid) -{ - output_itr = parent_field.FindMember(key); - - if (output_itr == parent_field.MemberEnd()) { - if (missing == remote_config_parser_result::allow_missing) { - return false; - } - throw parser_exception(missing); - } - - if (type == output_itr->value.GetType()) { - return true; - } - throw parser_exception(invalid); -} - -bool validate_field_is_present( - rapidjson::Value::ConstMemberIterator &parent_field, const char *key, - rapidjson::Type type, rapidjson::Value::ConstMemberIterator &output_itr, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result &missing, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - const remote_config_parser_result &invalid) -{ - output_itr = parent_field->value.FindMember(key); - - if (output_itr == parent_field->value.MemberEnd()) { - throw parser_exception(missing); - } - - if (type == output_itr->value.GetType()) { - return true; - } - - throw parser_exception(invalid); -} - -std::unordered_map parse_target_files( - rapidjson::Value::ConstMemberIterator target_files_itr) -{ - std::unordered_map result; - - for (rapidjson::Value::ConstValueIterator itr = - target_files_itr->value.Begin(); - itr != target_files_itr->value.End(); ++itr) { - if (!itr->IsObject()) { - throw parser_exception( - remote_config_parser_result::target_files_object_invalid); - } - - // Path checks - rapidjson::Value::ConstMemberIterator const path_itr = - itr->GetObject().FindMember("path"); - if (path_itr == itr->GetObject().MemberEnd()) { - throw parser_exception( - remote_config_parser_result::target_files_path_field_missing); - } - if (!path_itr->value.IsString()) { - throw parser_exception(remote_config_parser_result:: - target_files_path_field_invalid_type); - } - - // Raw checks - rapidjson::Value::ConstMemberIterator const raw_itr = - itr->GetObject().FindMember("raw"); - if (raw_itr == itr->GetObject().MemberEnd()) { - throw parser_exception( - remote_config_parser_result::target_files_raw_field_missing); - } - if (!raw_itr->value.IsString()) { - throw parser_exception(remote_config_parser_result:: - target_files_raw_field_invalid_type); - } - result.insert({path_itr->value.GetString(), - {path_itr->value.GetString(), raw_itr->value.GetString()}}); - } - - return result; -} - -std::vector parse_client_configs( - rapidjson::Value::ConstMemberIterator client_configs_itr) -{ - std::vector result; - - for (rapidjson::Value::ConstValueIterator itr = - client_configs_itr->value.Begin(); - itr != client_configs_itr->value.End(); ++itr) { - if (!itr->IsString()) { - throw parser_exception( - remote_config_parser_result::client_config_field_invalid_entry); - } - - result.emplace_back(itr->GetString()); - } - - return result; -} - -std::pair parse_target( - rapidjson::Value::ConstMemberIterator target_itr) -{ - rapidjson::Value::ConstMemberIterator custom_itr; - validate_field_is_present(target_itr, "custom", rapidjson::kObjectType, - custom_itr, - remote_config_parser_result::custom_path_targets_field_missing, - remote_config_parser_result::custom_path_targets_field_invalid); - rapidjson::Value::ConstMemberIterator v_itr; - validate_field_is_present(custom_itr, "v", rapidjson::kNumberType, v_itr, - remote_config_parser_result::v_path_targets_field_missing, - remote_config_parser_result::v_path_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator hashes_itr; - validate_field_is_present(target_itr, "hashes", rapidjson::kObjectType, - hashes_itr, - remote_config_parser_result::hashes_path_targets_field_missing, - remote_config_parser_result::hashes_path_targets_field_invalid); - - std::unordered_map hashes_mapped; - auto hashes_object = hashes_itr->value.GetObject(); - for (rapidjson::Value::ConstMemberIterator itr = - hashes_object.MemberBegin(); - itr != hashes_object.MemberEnd(); ++itr) { - if (itr->value.GetType() != rapidjson::kStringType) { - throw parser_exception(remote_config_parser_result:: - hash_hashes_path_targets_field_invalid); - } - - std::pair const hash_pair( - itr->name.GetString(), itr->value.GetString()); - hashes_mapped.insert(hash_pair); - } - - if (hashes_mapped.empty()) { - throw parser_exception( - remote_config_parser_result::hashes_path_targets_field_empty); - } - - rapidjson::Value::ConstMemberIterator length_itr; - validate_field_is_present(target_itr, "length", rapidjson::kNumberType, - length_itr, - remote_config_parser_result::length_path_targets_field_missing, - remote_config_parser_result::length_path_targets_field_invalid); - - std::string const target_name(target_itr->name.GetString()); - path const path_object = { - v_itr->value.GetInt(), hashes_mapped, length_itr->value.GetInt()}; - - return {target_name, path_object}; -} - -targets parse_targets_signed( - rapidjson::Value::ConstMemberIterator targets_signed_itr) -{ - rapidjson::Value::ConstMemberIterator version_itr; - validate_field_is_present(targets_signed_itr, "version", - rapidjson::kNumberType, version_itr, - remote_config_parser_result::version_signed_targets_field_missing, - remote_config_parser_result::version_signed_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator targets_itr; - validate_field_is_present(targets_signed_itr, "targets", - rapidjson::kObjectType, targets_itr, - remote_config_parser_result::targets_signed_targets_field_missing, - remote_config_parser_result::targets_signed_targets_field_invalid); - - std::vector> paths; - for (rapidjson::Value::ConstMemberIterator current_target = - targets_itr->value.MemberBegin(); - current_target != targets_itr->value.MemberEnd(); ++current_target) { - auto path = parse_target(current_target); - paths.push_back(path); - } - - rapidjson::Value::ConstMemberIterator type_itr; - validate_field_is_present(targets_signed_itr, "_type", - rapidjson::kStringType, type_itr, - remote_config_parser_result::type_signed_targets_field_missing, - remote_config_parser_result::type_signed_targets_field_invalid); - if ("targets"sv != type_itr->value.GetString()) { - throw parser_exception(remote_config_parser_result:: - type_signed_targets_field_invalid_type); - } - - rapidjson::Value::ConstMemberIterator custom_itr; - validate_field_is_present(targets_signed_itr, "custom", - rapidjson::kObjectType, custom_itr, - remote_config_parser_result::custom_signed_targets_field_missing, - remote_config_parser_result::custom_signed_targets_field_invalid); - - rapidjson::Value::ConstMemberIterator opaque_backend_state_itr; - validate_field_is_present(custom_itr, "opaque_backend_state", - rapidjson::kStringType, opaque_backend_state_itr, - remote_config_parser_result::obs_custom_signed_targets_field_missing, - remote_config_parser_result::obs_custom_signed_targets_field_invalid); - std::unordered_map final_paths; - for (auto &[path_str, path] : paths) { - final_paths.emplace(path_str, path); - } - return {version_itr->value.GetInt(), - opaque_backend_state_itr->value.GetString(), final_paths}; -} - -targets parse_targets(rapidjson::Value::ConstMemberIterator targets_itr) -{ - std::string const targets_encoded_content = targets_itr->value.GetString(); - - if (targets_encoded_content.empty()) { - throw parser_exception( - remote_config_parser_result::targets_field_empty); - } - - std::string base64_decoded; - try { - base64_decoded = base64_decode(targets_encoded_content, true); - } catch (std::runtime_error &error) { - throw parser_exception( - remote_config_parser_result::targets_field_invalid_base64); - } - - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(base64_decoded).HasParseError()) { - throw parser_exception( - remote_config_parser_result::targets_field_invalid_json); - } - - rapidjson::Value::ConstMemberIterator signed_itr; - - // Lets validate the data and since we are there we get the iterators - validate_field_is_present(serialized_doc, "signed", rapidjson::kObjectType, - signed_itr, remote_config_parser_result::signed_targets_field_missing, - remote_config_parser_result::signed_targets_field_invalid); - - return parse_targets_signed(signed_itr); -} - -get_configs_response parse(const std::string &body) -{ - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(body).HasParseError()) { - throw parser_exception(remote_config_parser_result::invalid_json); - } - if (!serialized_doc.IsObject()) { - throw parser_exception(remote_config_parser_result::invalid_response); - } - - rapidjson::Value::ConstMemberIterator target_files_itr; - rapidjson::Value::ConstMemberIterator client_configs_itr; - rapidjson::Value::ConstMemberIterator targets_itr; - - // Lets validate the data and since we are here as we get the iterators - auto validated_target_files = validate_field_is_present(serialized_doc, - "target_files", rapidjson::kArrayType, target_files_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::target_files_field_invalid_type); - - auto validated_client_configs = validate_field_is_present(serialized_doc, - "client_configs", rapidjson::kArrayType, client_configs_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::client_config_field_invalid_type); - - auto validated_targets = validate_field_is_present(serialized_doc, - "targets", rapidjson::kStringType, targets_itr, - remote_config_parser_result::allow_missing, - remote_config_parser_result::targets_field_invalid_type); - - std::unordered_map target_files; - if (validated_target_files) { - target_files = parse_target_files(target_files_itr); - } - std::vector client_configs; - if (validated_client_configs) { - client_configs = parse_client_configs(client_configs_itr); - } - - std::optional targets; - if (validated_targets) { - targets = parse_targets(targets_itr); - } - - return {target_files, client_configs, targets}; -} - -info_response parse_info(const std::string &body) -{ - info_response response; - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(body).HasParseError()) { - throw parser_exception(remote_config_parser_result::invalid_json); - } - if (!serialized_doc.IsObject()) { - throw parser_exception(remote_config_parser_result::invalid_response); - } - - rapidjson::Value::ConstMemberIterator endpoints_itr; - validate_field_is_present(serialized_doc, "endpoints", - rapidjson::kArrayType, endpoints_itr, - remote_config_parser_result::endpoints_field_missing, - remote_config_parser_result::endpoints_field_invalid); - - for (rapidjson::Value::ConstValueIterator itr = - endpoints_itr->value.Begin(); - itr != endpoints_itr->value.End(); ++itr) { - if (itr->GetType() != rapidjson::kStringType) { - throw parser_exception( - remote_config_parser_result::invalid_endpoint); - } - - response.endpoints.emplace_back(itr->GetString()); - } - - return response; -} - -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define RESULT_AS_STR(entry) #entry, -namespace { -constexpr std::array - results_as_str = {PARSER_RESULTS(RESULT_AS_STR)}; -} // anonymous namespace -std::string_view remote_config_parser_result_to_str( - const remote_config_parser_result &result) -{ - if (result == remote_config_parser_result::num_of_values) { - return ""; - } - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - return results_as_str[(size_t)result]; -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/parser.hpp b/appsec/src/helper/remote_config/protocol/tuf/parser.hpp deleted file mode 100644 index 4693199654..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/parser.hpp +++ /dev/null @@ -1,86 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include -#include - -#include "get_configs_response.hpp" -#include "info_response.hpp" - -namespace dds::remote_config::protocol { -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define PARSER_RESULTS(X) \ - X(success) \ - X(allow_missing) \ - X(invalid_json) \ - X(targets_field_empty) \ - X(targets_field_invalid_base64) \ - X(targets_field_invalid_json) \ - X(targets_field_invalid_type) \ - X(signed_targets_field_invalid) \ - X(signed_targets_field_missing) \ - X(type_signed_targets_field_invalid) \ - X(type_signed_targets_field_invalid_type) \ - X(type_signed_targets_field_missing) \ - X(version_signed_targets_field_invalid) \ - X(version_signed_targets_field_missing) \ - X(custom_signed_targets_field_invalid) \ - X(custom_signed_targets_field_missing) \ - X(obs_custom_signed_targets_field_invalid) \ - X(obs_custom_signed_targets_field_missing) \ - X(target_files_object_invalid) \ - X(target_files_field_invalid_type) \ - X(target_files_path_field_missing) \ - X(target_files_path_field_invalid_type) \ - X(target_files_raw_field_missing) \ - X(target_files_raw_field_invalid_type) \ - X(client_config_field_invalid_type) \ - X(client_config_field_invalid_entry) \ - X(targets_signed_targets_field_invalid) \ - X(targets_signed_targets_field_missing) \ - X(custom_path_targets_field_invalid) \ - X(custom_path_targets_field_missing) \ - X(v_path_targets_field_invalid) \ - X(v_path_targets_field_missing) \ - X(hashes_path_targets_field_invalid) \ - X(hashes_path_targets_field_missing) \ - X(hashes_path_targets_field_empty) \ - X(hash_hashes_path_targets_field_invalid) \ - X(length_path_targets_field_invalid) \ - X(length_path_targets_field_missing) \ - X(invalid_response) \ - X(endpoints_field_missing) \ - X(endpoints_field_invalid) \ - X(invalid_endpoint) - -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define RESULT_AS_ENUM_ENTRY(entry) entry, -enum class remote_config_parser_result : size_t { - PARSER_RESULTS(RESULT_AS_ENUM_ENTRY) num_of_values -}; - -std::string_view remote_config_parser_result_to_str( - const remote_config_parser_result &result); - -class parser_exception : public std::exception { -public: - explicit parser_exception(remote_config_parser_result error) - : message_(remote_config_parser_result_to_str(error)), error_(error) - {} - virtual const char *what() { return message_.c_str(); } - remote_config_parser_result get_error() { return error_; } - -protected: - std::string message_; - remote_config_parser_result error_; -}; - -get_configs_response parse(const std::string &body); -info_response parse_info(const std::string &body); - -} // namespace dds::remote_config::protocol \ No newline at end of file diff --git a/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp b/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp deleted file mode 100644 index ec69b5d3ae..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/serializer.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include -#include - -#include "../../../json_helper.hpp" -#include "../cached_target_files.hpp" -#include "base64.h" -#include "exception.hpp" -#include "serializer.hpp" - -namespace dds::remote_config::protocol { - -void serialize_client_tracer(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, const client_tracer &client_tracer) -{ - rapidjson::Value tracer_object(rapidjson::kObjectType); - - tracer_object.AddMember("language", "php", alloc); - tracer_object.AddMember("runtime_id", client_tracer.runtime_id, alloc); - tracer_object.AddMember( - "tracer_version", client_tracer.tracer_version, alloc); - tracer_object.AddMember("service", client_tracer.service, alloc); - tracer_object.AddMember("env", client_tracer.env, alloc); - tracer_object.AddMember("app_version", client_tracer.app_version, alloc); - - rapidjson::Value extra_services_array(rapidjson::kArrayType); - - for (auto const &service_name : client_tracer.extra_services) { - rapidjson::Value service(rapidjson::kStringType); - service.SetString(service_name.c_str(), service_name.length(), alloc); - extra_services_array.PushBack(service, alloc); - } - - tracer_object.AddMember("extra_services", extra_services_array, alloc); - client_field.AddMember("client_tracer", tracer_object, alloc); -} - -void serialize_config_states(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, - const std::vector &config_states) -{ - rapidjson::Value config_states_object(rapidjson::kArrayType); - - for (const auto &config_state : config_states) { - rapidjson::Value config_state_object(rapidjson::kObjectType); - config_state_object.AddMember("id", config_state.id, alloc); - config_state_object.AddMember("version", config_state.version, alloc); - config_state_object.AddMember("product", config_state.product, alloc); - config_state_object.AddMember( - "apply_state", static_cast(config_state.apply_state), alloc); - config_state_object.AddMember( - "apply_error", config_state.apply_error, alloc); - config_states_object.PushBack(config_state_object, alloc); - } - - client_field.AddMember("config_states", config_states_object, alloc); -} - -void serialize_client_state(rapidjson::Document::AllocatorType &alloc, - rapidjson::Value &client_field, const client_state &client_state) -{ - rapidjson::Value client_state_object(rapidjson::kObjectType); - - client_state_object.AddMember( - "targets_version", client_state.targets_version, alloc); - client_state_object.AddMember("root_version", 1, alloc); - client_state_object.AddMember("has_error", client_state.has_error, alloc); - client_state_object.AddMember("error", client_state.error, alloc); - client_state_object.AddMember( - "backend_client_state", client_state.backend_client_state, alloc); - - serialize_config_states( - alloc, client_state_object, client_state.config_states); - - client_field.AddMember("state", client_state_object, alloc); -} - -void serialize_client(rapidjson::Document::AllocatorType &alloc, - rapidjson::Document &document, const client &client) -{ - rapidjson::Value client_object(rapidjson::kObjectType); - - client_object.AddMember("id", client.id, alloc); - client_object.AddMember("is_tracer", true, alloc); - - // NOLINTBEGIN - auto capabilities_int = - static_cast::type>( - client.capabilities); - char bytes[2] = {static_cast(capabilities_int >> 8), - static_cast(capabilities_int & 0x00FF)}; - - client_object.AddMember("capabilities", - base64_encode(std::string_view(bytes, 2), false), alloc); - // NOLINTEND - - rapidjson::Value products(rapidjson::kArrayType); - for (const std::string &product_str : client.products) { - products.PushBack(rapidjson::Value(product_str, alloc).Move(), alloc); - } - client_object.AddMember("products", products, alloc); - - serialize_client_tracer(alloc, client_object, client.client_tracer); - serialize_client_state(alloc, client_object, client.client_state); - - document.AddMember("client", client_object, alloc); -} - -void serialize_cached_target_files_hashes( - rapidjson::Document::AllocatorType &alloc, rapidjson::Value &parent, - const std::vector &cached_target_files_hash_list) -{ - rapidjson::Value cached_target_files_array(rapidjson::kArrayType); - - for (const cached_target_files_hash &ctfh : cached_target_files_hash_list) { - rapidjson::Value cached_target_file_hash_object(rapidjson::kObjectType); - cached_target_file_hash_object.AddMember( - "algorithm", ctfh.algorithm, alloc); - cached_target_file_hash_object.AddMember("hash", ctfh.hash, alloc); - cached_target_files_array.PushBack( - cached_target_file_hash_object, alloc); - } - - parent.AddMember("hashes", cached_target_files_array, alloc); -} - -void serialize_cached_target_files(rapidjson::Document::AllocatorType &alloc, - rapidjson::Document &document, - const std::vector &cached_target_files_list) -{ - rapidjson::Value cached_target_files_array(rapidjson::kArrayType); - - for (const cached_target_files &ctf : cached_target_files_list) { - rapidjson::Value cached_target_file_object(rapidjson::kObjectType); - cached_target_file_object.AddMember("path", ctf.path, alloc); - cached_target_file_object.AddMember("length", ctf.length, alloc); - serialize_cached_target_files_hashes( - alloc, cached_target_file_object, ctf.hashes); - cached_target_files_array.PushBack(cached_target_file_object, alloc); - } - - document.AddMember("cached_target_files", cached_target_files_array, alloc); -} - -std::string serialize(const get_configs_request &request) -{ - rapidjson::Document document; - rapidjson::Document::AllocatorType &alloc = document.GetAllocator(); - - document.SetObject(); - - serialize_client(alloc, document, request.client); - serialize_cached_target_files(alloc, document, request.cached_target_files); - - dds::string_buffer buffer; - rapidjson::Writer writer(buffer); - - // This has to be tested - if (!document.Accept(writer)) { - throw serializer_exception(); - } - - return buffer.get_string_ref(); -} - -} // namespace dds::remote_config::protocol diff --git a/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp b/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp deleted file mode 100644 index 4e0bf009ef..0000000000 --- a/appsec/src/helper/remote_config/protocol/tuf/serializer.hpp +++ /dev/null @@ -1,19 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include -#include - -#include "get_configs_request.hpp" - -namespace dds::remote_config::protocol { - -class serializer_exception : public std::exception {}; - -std::string serialize(const get_configs_request &request); - -} // namespace dds::remote_config::protocol \ No newline at end of file diff --git a/appsec/src/helper/remote_config/settings.hpp b/appsec/src/helper/remote_config/settings.hpp index 955ed14a5b..43a7b8340d 100644 --- a/appsec/src/helper/remote_config/settings.hpp +++ b/appsec/src/helper/remote_config/settings.hpp @@ -6,7 +6,7 @@ #pragma once -#include "utils.hpp" +#include "../utils.hpp" #include #include #include @@ -15,45 +15,31 @@ namespace dds::remote_config { -/* client_settings are currently the same for the whole client session. - * If this changes in the future, it will make sense to create a separation - * between 1) settings used for creating the engine and 2) settings used - * after, possibly when creating the subscriber listeners on every request - */ struct settings { - static constexpr uint32_t default_poll_interval{1000}; - static constexpr unsigned default_port{8126}; - // Remote config settings - bool enabled{false}; - std::string host; - unsigned port = default_port; - std::uint32_t poll_interval = default_poll_interval; - - // these two are specified in RCTE1 - // std::string targets_key; - // std::string targets_key_id; - // bool integrity_check_enabled{false}; - - MSGPACK_DEFINE_MAP(enabled, host, port, poll_interval); + bool enabled{}; + std::string shmem_path; bool operator==(const settings &oth) const noexcept { - return enabled == oth.enabled && host == oth.host && port == oth.port && - poll_interval == oth.poll_interval; + return enabled == oth.enabled && shmem_path == oth.shmem_path; } friend auto &operator<<(std::ostream &os, const settings &c) { return os << "{enabled=" << std::boolalpha << c.enabled - << ", host=" << c.host << ", port=" << c.port - << ", poll_interval=" << c.poll_interval << "}"; + << ", shmem_path=" << c.shmem_path << "}"; } - struct settings_hash { - std::size_t operator()(const settings &s) const noexcept - { - return hash(s.enabled, s.host, s.port, s.poll_interval); - } - }; + MSGPACK_DEFINE_MAP(enabled, shmem_path); }; } // namespace dds::remote_config + +namespace std { +template <> struct hash { + std::size_t operator()(const dds::remote_config::settings &s) const noexcept + { + return dds::hash(s.enabled, s.shmem_path); + } +}; + +} // namespace std diff --git a/appsec/src/helper/runner.cpp b/appsec/src/helper/runner.cpp index 042cbccbd0..dee62304fd 100644 --- a/appsec/src/helper/runner.cpp +++ b/appsec/src/helper/runner.cpp @@ -7,19 +7,36 @@ #include "client.hpp" #include "subscriber/waf.hpp" +#include #include #include #include #include +extern "C" { +#include +} + +namespace { +struct ConfigInvariants; +struct Arc_Target; + +using in_proc_notify_fn = void (*)( + const ConfigInvariants *invariants, const Arc_Target *target); + +void (*ddog_set_rc_notify_fn)(in_proc_notify_fn notify_fn); +char *(*ddog_remote_config_path)(const ConfigInvariants *, const Arc_Target *); +void (*ddog_remote_config_path_free)(char *); +} // namespace + namespace dds { namespace { network::base_acceptor::ptr acceptor_from_config(const config::config &cfg) { - auto value{cfg.get("socket_path")}; - if (value.size() >= 4 && value.substr(0, 3) == "fd:") { - auto rest{value.substr(3)}; + std::string_view const sock_path{cfg.socket_file_path()}; + if (sock_path.size() >= 4 && sock_path.substr(0, 3) == "fd:") { + auto rest{sock_path.substr(3)}; int const fd = std::stoi(std::string{rest}); // can throw struct stat statbuf {}; int const res = fstat(fd, &statbuf); @@ -30,19 +47,51 @@ network::base_acceptor::ptr acceptor_from_config(const config::config &cfg) return std::make_unique(fd); } - return std::make_unique(value); + return std::make_unique(sock_path); +} + +void block_sigusr1() +{ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (pthread_sigmask(SIG_UNBLOCK, &mask, nullptr) != 0) { + throw std::runtime_error{ + "Failed to block SIGUSR1: errno " + std::to_string(errno)}; + } +} + +void unblock_sigusr1() +{ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (pthread_sigmask(SIG_UNBLOCK, &mask, nullptr) != 0) { + throw std::runtime_error{ + "Failed to unblock SIGUSR1: errno " + std::to_string(errno)}; + } +} + +void handle_sigusr1() +{ + // the signal handler need not do anything (just interrupt accept()) + struct sigaction alarmer = {}; + alarmer.sa_handler = [](int) {}; + if (sigaction(SIGUSR1, &alarmer, nullptr) < 0) { + throw std::runtime_error{ + "Failed to set SIGUSR1 handler: errno " + std::to_string(errno)}; + } } } // namespace -runner::runner(const config::config &cfg) - : runner(cfg, acceptor_from_config(cfg)) +runner::runner(const config::config &cfg, std::atomic &interrupted) + : runner{cfg, acceptor_from_config(cfg), interrupted} {} -runner::runner( - const config::config &cfg, network::base_acceptor::ptr &&acceptor) +runner::runner(const config::config &cfg, + network::base_acceptor::ptr &&acceptor, std::atomic &interrupted) : cfg_(cfg), service_manager_{std::make_shared()}, - acceptor_(std::move(acceptor)), - idle_timeout_(cfg.get("runner_idle_timeout")) + acceptor_(std::move(acceptor)), interrupted_{interrupted} { try { acceptor_->set_accept_timeout(1min); @@ -52,39 +101,70 @@ runner::runner( } } +// NOLINTNEXTLINE +std::shared_ptr runner::RUNNER_FOR_NOTIFICATIONS{nullptr}; + +void runner::register_for_rc_notifications() +{ + SPDLOG_INFO("Register RC update callback"); + std::atomic_store(&runner::RUNNER_FOR_NOTIFICATIONS, shared_from_this()); + + ddog_set_rc_notify_fn( + [](const ConfigInvariants *invariants, const Arc_Target *target) { + char *path = ddog_remote_config_path(invariants, target); + + if (path == nullptr) { + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_ERROR("Failed to get remote config path"); + return; + } + + const std::shared_ptr runner = + std::atomic_load(&RUNNER_FOR_NOTIFICATIONS); + if (!runner) { + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_ERROR("No runner to notify of remote config updates"); + ddog_remote_config_path_free(path); + return; + } + + // NOLINTNEXTLINE(bugprone-lambda-function-name) + SPDLOG_INFO("Remote config updated notification for {}", path); + // TODO: move the updates to a separate thread + runner->service_manager_->notify_of_rc_updates(path); + ddog_remote_config_path_free(path); + }); +} + +runner::~runner() noexcept +{ + try { + std::shared_ptr expected = shared_from_this(); + std::atomic_compare_exchange_strong(&RUNNER_FOR_NOTIFICATIONS, + &expected, std::shared_ptr(nullptr)); + } catch (...) { + // can only happened if there is no shared_ptr for the runner + // in this case a std::bad_weak_ptr is thrown + std::abort(); + } +} + void runner::run() { try { - auto last_not_idle = std::chrono::steady_clock::now(); - SPDLOG_INFO("Running"); - while (running_) { - network::base_socket::ptr socket; - try { - socket = acceptor_->accept(); - } catch (const timeout_error &e) { - // If there are clients running, we don't - if (worker_pool_.worker_count() > 0) { - // We are not idle, update - last_not_idle = std::chrono::steady_clock::now(); - continue; - } - - auto elapsed = std::chrono::steady_clock::now() - last_not_idle; - if (elapsed >= idle_timeout_) { - SPDLOG_INFO("Runner idle for {} minutes, exiting", - idle_timeout_.count()); - break; - } - - continue; - } + SPDLOG_INFO("Runner running"); + handle_sigusr1(); + + while (!interrupted()) { + unblock_sigusr1(); + network::base_socket::ptr socket = acceptor_->accept(); + block_sigusr1(); if (!socket) { - SPDLOG_CRITICAL("Acceptor returned invalid socket. Bug."); - break; + continue; // interrupted / timeout } - if (!running_) { + if (interrupted()) { break; } @@ -95,12 +175,41 @@ void runner::run() worker_pool_.launch( [c](worker::queue_consumer &q) mutable { c->run(q); }); - - last_not_idle = std::chrono::steady_clock::now(); } } catch (const std::exception &e) { SPDLOG_ERROR("exception: {}", e.what()); } + + SPDLOG_INFO("Runner exiting, stopping pool"); + worker_pool_.stop(); + SPDLOG_INFO("Pool stopped"); +} + +void runner::resolve_symbols() +{ + // NOLINTNEXTLINE + ddog_set_rc_notify_fn = reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_set_rc_notify_fn")); + if (ddog_set_rc_notify_fn == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_set_rc_notify_fn"}; + } + + ddog_remote_config_path = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_path")); + if (ddog_remote_config_path == nullptr) { + throw std::runtime_error{"Failed to resolve ddog_remote_config_path"}; + } + + ddog_remote_config_path_free = + // NOLINTNEXTLINE + reinterpret_cast( + dlsym(RTLD_DEFAULT, "ddog_remote_config_path_free")); + if (ddog_remote_config_path_free == nullptr) { + throw std::runtime_error{ + "Failed to resolve ddog_remote_config_path_free"}; + } } } // namespace dds diff --git a/appsec/src/helper/runner.hpp b/appsec/src/helper/runner.hpp index 6c01fd9432..2c495b3910 100644 --- a/appsec/src/helper/runner.hpp +++ b/appsec/src/helper/runner.hpp @@ -5,8 +5,9 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #pragma once -#include +#include #include +#include #include "config.hpp" #include "network/acceptor.hpp" @@ -16,29 +17,38 @@ namespace dds { -class runner { +class runner : public std::enable_shared_from_this { public: - explicit runner(const config::config &cfg); - runner(const config::config &cfg, network::base_acceptor::ptr &&acceptor); + runner(const config::config &cfg, std::atomic &interrupted); + runner(const config::config &cfg, network::base_acceptor::ptr &&acceptor, + std::atomic &interrupted); runner(const runner &) = delete; runner &operator=(const runner &) = delete; runner(runner &&) = delete; runner &operator=(runner &&) = delete; - ~runner() = default; + ~runner() noexcept; + + static void resolve_symbols(); void run() noexcept(false); - void exit() { running_ = false; } + void register_for_rc_notifications(); + + [[nodiscard]] bool interrupted() const + { + return interrupted_.load(std::memory_order_acquire); + } private: - const config::config &cfg_; + static std::shared_ptr RUNNER_FOR_NOTIFICATIONS; + + const config::config &cfg_; // NOLINT std::shared_ptr service_manager_; worker::pool worker_pool_; // Server variables network::base_acceptor::ptr acceptor_; - std::chrono::minutes idle_timeout_; - std::atomic running_{true}; + std::atomic &interrupted_; // NOLINT }; } // namespace dds diff --git a/appsec/src/helper/service.cpp b/appsec/src/helper/service.cpp index 561675deb3..bedb5e1e2d 100644 --- a/appsec/src/helper/service.cpp +++ b/appsec/src/helper/service.cpp @@ -10,20 +10,17 @@ namespace dds { service::service(std::shared_ptr engine, std::shared_ptr service_config, - dds::remote_config::client_handler::ptr &&client_handler, + std::unique_ptr &&client_handler, + std::string rc_path, const schema_extraction_settings &schema_extraction_settings) - : engine_(std::move(engine)), service_config_(std::move(service_config)), - client_handler_(std::move(client_handler)) + : engine_{std::move(engine)}, service_config_{std::move(service_config)}, + client_handler_{std::move(client_handler)}, rc_path_{std::move(rc_path)} { // The engine should always be valid if (!engine_) { throw std::runtime_error("invalid engine"); } - if (client_handler_) { - client_handler_->start(); - } - double sample_rate = schema_extraction_settings.sample_rate; if (!schema_extraction_settings.enabled) { @@ -31,23 +28,29 @@ service::service(std::shared_ptr engine, } schema_sampler_ = std::make_shared(sample_rate); + + if (client_handler_) { + client_handler_->poll(); + } } -service::ptr service::from_settings(service_identifier &&id, +std::shared_ptr service::from_settings( const dds::engine_settings &eng_settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement) { - auto engine_ptr = engine::from_settings(eng_settings, meta, metrics); + const std::shared_ptr engine_ptr = + engine::from_settings(eng_settings, meta, metrics); auto service_config = std::make_shared(); - auto client_handler = remote_config::client_handler::from_settings( - std::move(id), eng_settings, service_config, rc_settings, engine_ptr, - dynamic_enablement); + auto client_handler = + remote_config::client_handler::from_settings(eng_settings, + service_config, rc_settings, engine_ptr, dynamic_enablement); return std::make_shared(engine_ptr, std::move(service_config), - std::move(client_handler), eng_settings.schema_extraction); + std::move(client_handler), rc_settings.shmem_path, + eng_settings.schema_extraction); } } // namespace dds diff --git a/appsec/src/helper/service.hpp b/appsec/src/helper/service.hpp index 72e3a8a5dd..233926367d 100644 --- a/appsec/src/helper/service.hpp +++ b/appsec/src/helper/service.hpp @@ -10,7 +10,6 @@ #include "remote_config/client_handler.hpp" #include "sampler.hpp" #include "service_config.hpp" -#include "service_identifier.hpp" #include "std_logging.hpp" #include "utils.hpp" #include @@ -23,11 +22,10 @@ using namespace std::chrono_literals; class service { public: - using ptr = std::shared_ptr; - service(std::shared_ptr engine, std::shared_ptr service_config, - dds::remote_config::client_handler::ptr &&client_handler, + std::unique_ptr &&client_handler, + std::string rc_path, const schema_extraction_settings &schema_extraction_settings = {}); service(const service &) = delete; @@ -38,26 +36,12 @@ class service { virtual ~service() = default; - static service::ptr from_settings(service_identifier &&id, + static std::shared_ptr from_settings( const dds::engine_settings &eng_settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement); - virtual void register_runtime_id(const std::string &id) - { - if (client_handler_) { - client_handler_->register_runtime_id(id); - } - } - - virtual void unregister_runtime_id(const std::string &id) - { - if (client_handler_) { - client_handler_->unregister_runtime_id(id); - } - } - [[nodiscard]] std::shared_ptr get_engine() const { // TODO make access atomic? @@ -75,11 +59,19 @@ class service { return schema_sampler_; } + [[nodiscard]] bool is_remote_config_shmem_path(std::string_view path) + { + return rc_path_ == path; + } + + void notify_of_rc_updates() { client_handler_->poll(); } + protected: std::shared_ptr engine_{}; std::shared_ptr service_config_{}; - dds::remote_config::client_handler::ptr client_handler_{}; + std::unique_ptr client_handler_{}; std::shared_ptr schema_sampler_; + std::string rc_path_; }; } // namespace dds diff --git a/appsec/src/helper/service_identifier.hpp b/appsec/src/helper/service_identifier.hpp deleted file mode 100644 index fb967cf820..0000000000 --- a/appsec/src/helper/service_identifier.hpp +++ /dev/null @@ -1,55 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -#pragma once - -#include "utils.hpp" -#include -#include - -namespace dds { -struct service_identifier { - std::string service; - std::vector extra_services; - std::string env; - std::string tracer_version; - std::string app_version; - std::string runtime_id; - - MSGPACK_DEFINE_MAP( - service, extra_services, env, tracer_version, app_version, runtime_id); - - bool operator==(const service_identifier &oth) const noexcept - { - return service == oth.service && env == oth.env; - } - - friend auto &operator<<(std::ostream &os, const service_identifier &id) - { - os << "{service=" << id.service << ", env=" << id.env - << ", tracer_version=" << id.tracer_version - << ", app_version=" << id.app_version - << ", runtime_id=" << id.runtime_id; - - os << ", extra_services=["; - for (int i = 0; i < id.extra_services.size(); i++) { - os << id.extra_services[i]; - if (i + 1 < id.extra_services.size()) { - os << ", "; - } - } - os << "]}"; - - return os; - } - - struct hash { - std::size_t operator()(const service_identifier &id) const noexcept - { - return dds::hash(id.service, id.env); - } - }; -}; -} // namespace dds diff --git a/appsec/src/helper/service_manager.cpp b/appsec/src/helper/service_manager.cpp index 6bcf70b2d3..d4c058265d 100644 --- a/appsec/src/helper/service_manager.cpp +++ b/appsec/src/helper/service_manager.cpp @@ -8,28 +8,31 @@ namespace dds { std::shared_ptr service_manager::create_service( - service_identifier &&id, const engine_settings &settings, - const remote_config::settings &rc_settings, + const engine_settings &settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement) { - const std::lock_guard guard{mutex_}; + const cache_key key{settings, rc_settings}; - auto hit = cache_.find(id); + const std::lock_guard guard{mutex_}; + auto hit = cache_.find(key); if (hit != cache_.end()) { auto service_ptr = hit->second.lock(); if (service_ptr) { // not expired SPDLOG_DEBUG( - "Found an existing service for {}::{}", id.service, id.env); + "Found an existing service for settings={} rc_settings={}", + settings, rc_settings); return service_ptr; } } - SPDLOG_DEBUG("Creating a service for {}::{}", id.service, id.env); + SPDLOG_DEBUG("Creating a service for settings={} rc_settings={}", settings, + rc_settings); + + auto service_ptr = service::from_settings( + settings, rc_settings, meta, metrics, dynamic_enablement); + cache_.emplace(key, std::move(service_ptr)); - auto service_ptr = service::from_settings(service_identifier(id), settings, - rc_settings, meta, metrics, dynamic_enablement); - cache_.emplace(std::move(id), std::move(service_ptr)); last_service_ = service_ptr; cleanup_cache(); @@ -37,6 +40,27 @@ std::shared_ptr service_manager::create_service( return service_ptr; } +void service_manager::notify_of_rc_updates(std::string_view shmem_path) +{ + std::vector> services_to_notify; + { + const std::lock_guard guard{mutex_}; + for (auto &[key, service_ptr] : cache_) { + if (key.get_shmem_path() == shmem_path) { + if (std::shared_ptr service = service_ptr.lock()) { + services_to_notify.emplace_back(std::move(service)); + } + } + } + } // release lock + + SPDLOG_DEBUG( + "Notifying {} services of RC updates", services_to_notify.size()); + for (auto &service : services_to_notify) { + service->notify_of_rc_updates(); + } +} + void service_manager::cleanup_cache() { for (auto it = cache_.begin(); it != cache_.end();) { diff --git a/appsec/src/helper/service_manager.hpp b/appsec/src/helper/service_manager.hpp index ce0561b282..d4cb7a2186 100644 --- a/appsec/src/helper/service_manager.hpp +++ b/appsec/src/helper/service_manager.hpp @@ -6,6 +6,7 @@ #pragma once #include "engine.hpp" +#include "engine_settings.hpp" #include "exception.hpp" #include "network/proto.hpp" #include "service.hpp" @@ -21,25 +22,63 @@ namespace dds { class service_manager { public: - virtual ~service_manager() = default; service_manager() = default; + service_manager(const service_manager &) = delete; + service_manager &operator=(const service_manager &) = delete; + service_manager(service_manager &&) = delete; + service_manager &operator=(service_manager &&) = delete; + virtual ~service_manager() = default; - virtual std::shared_ptr create_service(service_identifier &&id, + virtual std::shared_ptr create_service( const engine_settings &settings, const remote_config::settings &rc_settings, std::map &meta, std::map &metrics, bool dynamic_enablement); + void notify_of_rc_updates(std::string_view shmem_path); + protected: - using cache_t = std::unordered_map, service_identifier::hash>; + class cache_key { + public: + cache_key(engine_settings engine_settings, + remote_config::settings config_settings) + : engine_settings_{std::move(engine_settings)}, + config_settings_{std::move(config_settings)}, + hash_{dds::hash(engine_settings_, config_settings_)} + {} + + bool operator==(const cache_key &other) const + { + return engine_settings_ == other.engine_settings_ && + config_settings_ == other.config_settings_; + } + + struct hash { + std::size_t operator()(const cache_key &key) const + { + return key.hash_; + } + }; + + [[nodiscard]] const std::string &get_shmem_path() const + { + return config_settings_.shmem_path; + } + + private: + engine_settings engine_settings_; + remote_config::settings config_settings_; + std::size_t hash_; + }; + + using cache_t = + std::unordered_map, cache_key::hash>; void cleanup_cache(); // mutex_ must be held when calling this - // TODO this should be some sort of time-based LRU cache - service::ptr last_service_; std::mutex mutex_; cache_t cache_; + std::shared_ptr last_service_; // keep always one }; } // namespace dds diff --git a/appsec/src/helper/subscriber/base.hpp b/appsec/src/helper/subscriber/base.hpp index 1ae415b4a9..addfc6752c 100644 --- a/appsec/src/helper/subscriber/base.hpp +++ b/appsec/src/helper/subscriber/base.hpp @@ -17,12 +17,8 @@ namespace dds { class subscriber { public: - using ptr = std::shared_ptr; - class listener { public: - using ptr = std::shared_ptr; - listener() = default; listener(const listener &) = default; listener &operator=(const listener &) = delete; @@ -49,8 +45,8 @@ class subscriber { virtual std::string_view get_name() = 0; virtual std::unordered_set get_subscriptions() = 0; - virtual listener::ptr get_listener() = 0; - virtual subscriber::ptr update(parameter &rule, + virtual std::unique_ptr get_listener() = 0; + virtual std::unique_ptr update(parameter &rule, std::map &meta, std::map &metrics) = 0; }; diff --git a/appsec/src/helper/subscriber/waf.cpp b/appsec/src/helper/subscriber/waf.cpp index 03970a1920..4a5062c006 100644 --- a/appsec/src/helper/subscriber/waf.cpp +++ b/appsec/src/helper/subscriber/waf.cpp @@ -15,13 +15,14 @@ #include #include +#include "../compression.hpp" #include "../json_helper.hpp" #include "../std_logging.hpp" #include "../tags.hpp" -#include "base64.h" -#include "compression.hpp" -#include "ddwaf.h" +#include "base.hpp" #include "waf.hpp" +#include +#include namespace dds::waf { @@ -337,10 +338,10 @@ instance::~instance() } } -instance::listener::ptr instance::get_listener() +std::unique_ptr instance::get_listener() { - return listener::ptr(new listener( - ddwaf_context_init(handle_), waf_timeout_, ruleset_version_)); + return std::make_unique( + ddwaf_context_init(handle_), waf_timeout_, ruleset_version_); } instance::instance( @@ -355,7 +356,7 @@ instance::instance( for (uint32_t i = 0; i < size; i++) { addresses_.emplace(addrs[i]); } } -subscriber::ptr instance::update(parameter &rule, +std::unique_ptr instance::update(parameter &rule, std::map &meta, std::map &metrics) { @@ -376,28 +377,29 @@ subscriber::ptr instance::update(parameter &rule, throw invalid_object(); } - return subscriber::ptr( + return std::unique_ptr( new instance(new_handle, waf_timeout_, std::move(version))); } -instance::ptr instance::from_settings(const engine_settings &settings, - const engine_ruleset &ruleset, std::map &meta, +std::unique_ptr instance::from_settings( + const engine_settings &settings, const engine_ruleset &ruleset, + std::map &meta, std::map &metrics) { dds::parameter param = json_to_parameter(ruleset.get_document()); - return std::make_shared(param, meta, metrics, + return std::make_unique(param, meta, metrics, settings.waf_timeout_us, settings.obfuscator_key_regex, settings.obfuscator_value_regex); } -instance::ptr instance::from_string(std::string_view rule, +std::unique_ptr instance::from_string(std::string_view rule, std::map &meta, std::map &metrics, std::uint64_t waf_timeout_us, std::string_view key_regex, std::string_view value_regex) { engine_ruleset const ruleset{rule}; dds::parameter param = json_to_parameter(ruleset.get_document()); - return std::make_shared( + return std::make_unique( param, meta, metrics, waf_timeout_us, key_regex, value_regex); } diff --git a/appsec/src/helper/subscriber/waf.hpp b/appsec/src/helper/subscriber/waf.hpp index cafc5922df..513855ddf6 100644 --- a/appsec/src/helper/subscriber/waf.hpp +++ b/appsec/src/helper/subscriber/waf.hpp @@ -24,7 +24,7 @@ class instance : public dds::subscriber { static constexpr int default_waf_timeout_us = 10000; static constexpr int max_plain_schema_allowed = 260; static constexpr int max_schema_size = 25000; - using ptr = std::shared_ptr; + class listener : public dds::subscriber::listener { public: listener(ddwaf_context ctx, std::chrono::microseconds waf_timeout, @@ -68,18 +68,19 @@ class instance : public dds::subscriber { return addresses_; } - listener::ptr get_listener() override; + std::unique_ptr get_listener() override; - subscriber::ptr update(parameter &rule, + std::unique_ptr update(parameter &rule, std::map &meta, std::map &metrics) override; - static instance::ptr from_settings(const engine_settings &settings, - const engine_ruleset &ruleset, std::map &meta, + static std::unique_ptr from_settings( + const engine_settings &settings, const engine_ruleset &ruleset, + std::map &meta, std::map &metrics); // testing only - static instance::ptr from_string(std::string_view rule, + static std::unique_ptr from_string(std::string_view rule, std::map &meta, std::map &metrics, std::uint64_t waf_timeout_us = default_waf_timeout_us, diff --git a/appsec/src/helper/utils.hpp b/appsec/src/helper/utils.hpp index ad657beae0..5d07ae5544 100644 --- a/appsec/src/helper/utils.hpp +++ b/appsec/src/helper/utils.hpp @@ -52,4 +52,22 @@ inline std::string dd_tolower(std::string string) std::string read_file(std::string_view filename); +#ifdef __linux__ +extern "C" int __xpg_strerror_r(int, char *, size_t); +#endif +inline std::string strerror_ts(int errnum) +{ + std::string buf(256, '\0'); // NOLINT + +#ifdef __linux__ + (void)__xpg_strerror_r(errnum, buf.data(), buf.size()); +#else + (void)strerror_r(errnum, buf.data(), buf.size()); +#endif + + buf.resize(std::strlen(buf.data())); + + return buf; +} + } // namespace dds diff --git a/appsec/tests/extension/client_init_record_span_tags.phpt b/appsec/tests/extension/client_init_record_span_tags.phpt index 8a86280ff4..5455aeafd6 100644 --- a/appsec/tests/extension/client_init_record_span_tags.phpt +++ b/appsec/tests/extension/client_init_record_span_tags.phpt @@ -109,4 +109,6 @@ Array [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %f + [php.memory.peak_usage_bytes] => %f + [php.memory.peak_real_usage_bytes] => %f ) diff --git a/appsec/tests/extension/generate_backtrace_01.phpt b/appsec/tests/extension/generate_backtrace_01.phpt new file mode 100644 index 0000000000..2b8cb37ae7 --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_01.phpt @@ -0,0 +1,54 @@ +--TEST-- +Generate backtrace +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(3) "two" + ["file"]=> + string(25) "generate_backtrace_01.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(15) + ["function"]=> + string(3) "one" + ["file"]=> + string(25) "generate_backtrace_01.php" + ["id"]=> + int(1) + } + } +} diff --git a/appsec/tests/extension/generate_backtrace_02.phpt b/appsec/tests/extension/generate_backtrace_02.phpt new file mode 100644 index 0000000000..4ef30633f8 --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_02.phpt @@ -0,0 +1,45 @@ +--TEST-- +Number of frames can be configured with DD_APPSEC_MAX_STACK_TRACE_DEPTH +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_MAX_STACK_TRACE_DEPTH=1 +--FILE-- + +--EXPECTF-- +array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(1) { + [0]=> + array(4) { + ["line"]=> + int(15) + ["function"]=> + string(3) "one" + ["file"]=> + string(25) "generate_backtrace_02.php" + ["id"]=> + int(1) + } + } +} diff --git a/appsec/tests/extension/generate_backtrace_03.phpt b/appsec/tests/extension/generate_backtrace_03.phpt new file mode 100644 index 0000000000..bff2244861 --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_03.phpt @@ -0,0 +1,24 @@ +--TEST-- +By default DD_APPSEC_MAX_STACK_TRACE_DEPTH is 32 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +int(32) diff --git a/appsec/tests/extension/generate_backtrace_04.phpt b/appsec/tests/extension/generate_backtrace_04.phpt new file mode 100644 index 0000000000..fbecbc7f76 --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_04.phpt @@ -0,0 +1,474 @@ +--TEST-- +When DD_APPSEC_MAX_STACK_TRACE_DEPTH is lower than the number of frames. 0.25% are picked from top and 75% from bottom +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_MAX_STACK_TRACE_DEPTH=40 +--FILE-- + +--EXPECTF-- +array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(40) { + [0]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(1) + } + [2]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(2) + } + [3]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(3) + } + [4]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(4) + } + [5]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(5) + } + [6]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(6) + } + [7]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(7) + } + [8]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(8) + } + [9]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(9) + } + [10]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(20) + } + [11]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(21) + } + [12]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(22) + } + [13]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(23) + } + [14]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(24) + } + [15]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(25) + } + [16]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(26) + } + [17]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(27) + } + [18]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(28) + } + [19]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(29) + } + [20]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(30) + } + [21]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(31) + } + [22]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(32) + } + [23]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(33) + } + [24]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(34) + } + [25]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(35) + } + [26]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(36) + } + [27]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(37) + } + [28]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(38) + } + [29]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(39) + } + [30]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(40) + } + [31]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(41) + } + [32]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(42) + } + [33]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(43) + } + [34]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(44) + } + [35]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(45) + } + [36]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(46) + } + [37]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(47) + } + [38]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(48) + } + [39]=> + array(4) { + ["line"]=> + int(15) + ["function"]=> + string(18) "recursive_function" + ["file"]=> + string(25) "generate_backtrace_04.php" + ["id"]=> + int(49) + } + } +} diff --git a/appsec/tests/extension/generate_backtrace_05.phpt b/appsec/tests/extension/generate_backtrace_05.phpt new file mode 100644 index 0000000000..89cc1dcafe --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_05.phpt @@ -0,0 +1,26 @@ +--TEST-- +DD_APPSEC_MAX_STACK_TRACE_DEPTH can be set to unlimited with 0 +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_MAX_STACK_TRACE_DEPTH=0 +--FILE-- + +--EXPECTF-- +int(50) diff --git a/appsec/tests/extension/generate_backtrace_06.phpt b/appsec/tests/extension/generate_backtrace_06.phpt new file mode 100644 index 0000000000..5a93bbf125 --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_06.phpt @@ -0,0 +1,91 @@ +--TEST-- +Backtrace do not contains datadog frames +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +--INI-- +extension=ddtrace.so +--FILE-- +init(); + + DDTrace\start_span(); + $root = DDTrace\active_span(); + one("foo01"); +} + +?> +--EXPECTF-- +array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(3) { + [0]=> + array(4) { + ["line"]=> + int(24) + ["function"]=> + string(5) "ltrim" + ["file"]=> + string(25) "generate_backtrace_06.php" + ["id"]=> + int(1) + } + [1]=> + array(4) { + ["line"]=> + int(29) + ["function"]=> + string(3) "two" + ["file"]=> + string(25) "generate_backtrace_06.php" + ["id"]=> + int(2) + } + [2]=> + array(4) { + ["line"]=> + int(37) + ["function"]=> + string(3) "one" + ["file"]=> + string(25) "generate_backtrace_06.php" + ["id"]=> + int(3) + } + } +} +string(33) "Verify the wrapped function works" diff --git a/appsec/tests/extension/generate_backtrace_07.phpt b/appsec/tests/extension/generate_backtrace_07.phpt new file mode 100644 index 0000000000..307d01327d --- /dev/null +++ b/appsec/tests/extension/generate_backtrace_07.phpt @@ -0,0 +1,81 @@ +--TEST-- +Functions are fully qualified names +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +--INI-- +extension=ddtrace.so +--FILE-- +two($param01, "other"); + } + } +} + +namespace { + include __DIR__ . '/inc/ddtrace_version.php'; + + DDTrace\start_span(); + $root = DDTrace\active_span(); + + $class = new Some\Package\Foo(); + $class->one("foo01"); +} +?> +--EXPECTF-- +array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(3) { + [0]=> + array(4) { + ["line"]=> + int(12) + ["function"]=> + string(23) "Some\Package\Foo::three" + ["file"]=> + string(25) "generate_backtrace_07.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(17) + ["function"]=> + string(21) "Some\Package\Foo::two" + ["file"]=> + string(25) "generate_backtrace_07.php" + ["id"]=> + int(1) + } + [2]=> + array(4) { + ["line"]=> + int(29) + ["function"]=> + string(21) "Some\Package\Foo::one" + ["file"]=> + string(25) "generate_backtrace_07.php" + ["id"]=> + int(2) + } + } +} diff --git a/appsec/tests/extension/headers_collection_01.phpt b/appsec/tests/extension/headers_collection_01.phpt index ade7dafe21..290bb5183d 100644 --- a/appsec/tests/extension/headers_collection_01.phpt +++ b/appsec/tests/extension/headers_collection_01.phpt @@ -68,32 +68,33 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(11) { + ["http.request.headers.accept"]=> + string(3) "*/*" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" ["http.request.headers.content-type"]=> string(10) "text/plain" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_02.phpt b/appsec/tests/extension/headers_collection_02.phpt index 065fb507df..67f5bb9023 100644 --- a/appsec/tests/extension/headers_collection_02.phpt +++ b/appsec/tests/extension/headers_collection_02.phpt @@ -68,60 +68,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_03.phpt b/appsec/tests/extension/headers_collection_03.phpt index d0854f6737..add14327c1 100644 --- a/appsec/tests/extension/headers_collection_03.phpt +++ b/appsec/tests/extension/headers_collection_03.phpt @@ -73,60 +73,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_04.phpt b/appsec/tests/extension/headers_collection_04.phpt index d067f60fad..bad781630a 100644 --- a/appsec/tests/extension/headers_collection_04.phpt +++ b/appsec/tests/extension/headers_collection_04.phpt @@ -73,60 +73,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_05.phpt b/appsec/tests/extension/headers_collection_05.phpt index 66da7497ec..b0164b7d87 100644 --- a/appsec/tests/extension/headers_collection_05.phpt +++ b/appsec/tests/extension/headers_collection_05.phpt @@ -73,60 +73,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_06.phpt b/appsec/tests/extension/headers_collection_06.phpt index 25a364490a..2b7ae5a853 100644 --- a/appsec/tests/extension/headers_collection_06.phpt +++ b/appsec/tests/extension/headers_collection_06.phpt @@ -1,12 +1,12 @@ --TEST-- -All headers are collected when track_user_signup_event is triggered by automation and extended mode is set +All headers are collected when track_user_signup_event is triggered by automation and identification mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,60 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_07.phpt b/appsec/tests/extension/headers_collection_07.phpt index 9bc25a497e..7dd33113f3 100644 --- a/appsec/tests/extension/headers_collection_07.phpt +++ b/appsec/tests/extension/headers_collection_07.phpt @@ -1,12 +1,12 @@ --TEST-- -All headers are collected when track_user_login_success_event is triggered by automation and extended mode is set +All headers are collected when track_user_login_success_event is triggered by automation and identification mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,60 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_08.phpt b/appsec/tests/extension/headers_collection_08.phpt index 19e060657d..adf34edef3 100644 --- a/appsec/tests/extension/headers_collection_08.phpt +++ b/appsec/tests/extension/headers_collection_08.phpt @@ -1,12 +1,12 @@ --TEST-- -All headers are collected when track_user_login_failure_event is triggered by automation and extended mode is set +All headers are collected when track_user_login_failure_event is triggered by automation and identification mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,60 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_09.phpt b/appsec/tests/extension/headers_collection_09.phpt index b92e1d00cf..f55f03e657 100644 --- a/appsec/tests/extension/headers_collection_09.phpt +++ b/appsec/tests/extension/headers_collection_09.phpt @@ -1,12 +1,12 @@ --TEST-- -Basic headers are collected when track_user_signup_event is triggered by automation and extended mode is not set +All headers are collected when track_user_signup_event is triggered by automation and anonymization mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,32 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- -array(11) { +array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.forwarded-for"]=> + string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_10.phpt b/appsec/tests/extension/headers_collection_10.phpt index f67f0ce96e..ad6791bfa7 100644 --- a/appsec/tests/extension/headers_collection_10.phpt +++ b/appsec/tests/extension/headers_collection_10.phpt @@ -1,12 +1,12 @@ --TEST-- -Basic headers are collected when track_user_login_success_event is triggered by automation and extended mode is not set +All headers are collected when track_user_login_success_event is triggered by automation and anonymization mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,32 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- -array(11) { +array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.forwarded-for"]=> + string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_11.phpt b/appsec/tests/extension/headers_collection_11.phpt index c722afbf22..c2c9104fd5 100644 --- a/appsec/tests/extension/headers_collection_11.phpt +++ b/appsec/tests/extension/headers_collection_11.phpt @@ -1,12 +1,12 @@ --TEST-- -Basic headers are collected when track_user_login_failure_event is triggered by automation and extended mode is not set +All headers are collected when track_user_login_failure_event is triggered by automation and anonymization mode is set --INI-- extension=ddtrace.so datadog.appsec.log_file=/tmp/php_appsec_test.log datadog.appsec.log_level=debug datadog.appsec.enabled=1 --ENV-- -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon HTTP_X_FORWARDED_FOR=7.7.7.7 DD_TRACE_CLIENT_IP_HEADER_DISABLED=true HTTP_X_FORWARDED_FOR=7.7.7.6,10.0.0.1 @@ -74,32 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- -array(11) { +array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.forwarded-for"]=> + string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_12.phpt b/appsec/tests/extension/headers_collection_12.phpt index c39eccbd1a..d93073d78e 100644 --- a/appsec/tests/extension/headers_collection_12.phpt +++ b/appsec/tests/extension/headers_collection_12.phpt @@ -74,60 +74,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/headers_collection_13.phpt b/appsec/tests/extension/headers_collection_13.phpt index f41f53ab20..d32870831d 100644 --- a/appsec/tests/extension/headers_collection_13.phpt +++ b/appsec/tests/extension/headers_collection_13.phpt @@ -66,7 +66,6 @@ rshutdown(); $helper->get_commands(); //ignore - ddtrace_rshutdown(); dd_trace_internal_fn('synchronous_flush'); @@ -74,60 +73,61 @@ $commands = $helper->get_commands(); $tags = $commands[0]['payload'][0][0]['meta']; $headers = array_filter($tags, function ($key) { return strpos($key, "http.request.headers.") === 0;}, ARRAY_FILTER_USE_KEY); +ksort($headers); var_dump($headers); $helper->finished_with_commands(); ?> --EXPECTF-- array(25) { + ["http.request.headers.accept"]=> + string(3) "*/*" + ["http.request.headers.accept-encoding"]=> + string(4) "gzip" + ["http.request.headers.accept-language"]=> + string(5) "pt-PT" ["http.request.headers.akamai-user-risk"]=> string(13) "akamaiuserisk" - ["http.request.headers.x-forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.x-cluster-client-ip"]=> - string(7) "7.7.7.9" ["http.request.headers.cf-ray"]=> string(5) "cfray" - ["http.request.headers.user-agent"]=> - string(13) "my user agent" - ["http.request.headers.accept"]=> - string(3) "*/*" - ["http.request.headers.host"]=> - string(11) "myhost:8888" - ["http.request.headers.forwarded"]=> - string(9) "for="foo"" - ["http.request.headers.content-encoding"]=> - string(5) "utf-8" - ["http.request.headers.x-forwarded-for"]=> - string(16) "7.7.7.6,10.0.0.1" ["http.request.headers.cloudfront-viewer-ja3-fingerprint"]=> string(16) "cloudfrontviewer" - ["http.request.headers.accept-language"]=> - string(5) "pt-PT" - ["http.request.headers.x-appgw-trace-id"]=> - string(12) "appgvtraceid" - ["http.request.headers.x-sigsci-tags"]=> - string(10) "sigscitags" - ["http.request.headers.true-client-ip"]=> - string(8) "7.7.7.11" - ["http.request.headers.accept-encoding"]=> - string(4) "gzip" - ["http.request.headers.x-sigsci-requestid"]=> - string(15) "sigscirequestid" - ["http.request.headers.x-client-ip"]=> - string(7) "7.7.7.7" + ["http.request.headers.content-encoding"]=> + string(5) "utf-8" + ["http.request.headers.content-length"]=> + string(1) "0" ["http.request.headers.content-type"]=> string(10) "text/plain" - ["http.request.headers.x-real-ip"]=> - string(7) "7.7.7.8" + ["http.request.headers.forwarded"]=> + string(9) "for="foo"" ["http.request.headers.forwarded-for"]=> string(17) "7.7.7.10,10.0.0.1" + ["http.request.headers.host"]=> + string(11) "myhost:8888" + ["http.request.headers.true-client-ip"]=> + string(8) "7.7.7.11" + ["http.request.headers.user-agent"]=> + string(13) "my user agent" + ["http.request.headers.via"]=> + string(12) "HTTP/1.1 GWA" ["http.request.headers.x-amzn-trace-id"]=> string(13) "amazontraceid" + ["http.request.headers.x-appgw-trace-id"]=> + string(12) "appgvtraceid" + ["http.request.headers.x-client-ip"]=> + string(7) "7.7.7.7" ["http.request.headers.x-cloud-trace-context"]=> string(17) "cloudtracecontext" - ["http.request.headers.via"]=> - string(12) "HTTP/1.1 GWA" - ["http.request.headers.content-length"]=> - string(1) "0" + ["http.request.headers.x-cluster-client-ip"]=> + string(7) "7.7.7.9" + ["http.request.headers.x-forwarded"]=> + string(9) "for="foo"" + ["http.request.headers.x-forwarded-for"]=> + string(16) "7.7.7.6,10.0.0.1" + ["http.request.headers.x-real-ip"]=> + string(7) "7.7.7.8" + ["http.request.headers.x-sigsci-requestid"]=> + string(15) "sigscirequestid" + ["http.request.headers.x-sigsci-tags"]=> + string(10) "sigscitags" } diff --git a/appsec/tests/extension/helper_extra_args_parsing.phpt b/appsec/tests/extension/helper_extra_args_parsing.phpt deleted file mode 100644 index 510781acf4..0000000000 --- a/appsec/tests/extension/helper_extra_args_parsing.phpt +++ /dev/null @@ -1,166 +0,0 @@ ---TEST-- -Parsing of datadog.appsec.helper_extra_flags ---INI-- ---FILE-- - ---EXPECTF-- -: -array(1) { - [0]=> - string(%d) "%s" -} - -"": -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(0) "" -} - - : -array(1) { - [0]=> - string(%d) "%s" -} - -foo bar: -array(3) { - [0]=> - string(%d) "%s" - [1]=> - string(3) "foo" - [2]=> - string(3) "bar" -} - -\foo \bar: -array(3) { - [0]=> - string(%d) "%s" - [1]=> - string(3) "foo" - [2]=> - string(3) "bar" -} - - foo bar : -array(3) { - [0]=> - string(%d) "%s" - [1]=> - string(3) "foo" - [2]=> - string(3) "bar" -} - -foo\ bar\ : -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(8) "foo bar " -} - -foo""'': -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(3) "foo" -} - -foo"'"'"': -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(5) "foo'"" -} - -foo"bar\""'bar\'': -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(11) "foobar"bar'" -} - -"foo bar": -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(7) "foo bar" -} - -'foo bar': -array(2) { - [0]=> - string(%d) "%s" - [1]=> - string(7) "foo bar" -} - - -With errors - -bar': - -Warning: datadog\appsec\testing\get_helper_argv(): [ddappsec] datadog.appsec.helper_extra_args has unmatched quotes: bar' in %s on line %d -array(0) { -} - -bar": - -Warning: datadog\appsec\testing\get_helper_argv(): [ddappsec] datadog.appsec.helper_extra_args has unmatched quotes: bar" in %s on line %d -array(0) { -} - -'bar: - -Warning: datadog\appsec\testing\get_helper_argv(): [ddappsec] datadog.appsec.helper_extra_args has unmatched quotes: 'bar in %s on line %d -array(0) { -} - -"bar: - -Warning: datadog\appsec\testing\get_helper_argv(): [ddappsec] datadog.appsec.helper_extra_args has unmatched quotes: "bar in %s on line %d -array(0) { -} - -bar\: - -Warning: datadog\appsec\testing\get_helper_argv(): [ddappsec] datadog.appsec.helper_extra_args has an unpaired \ at the end: bar\ in %s on line %d -array(0) { -} diff --git a/appsec/tests/extension/helper_launch_failure.phpt b/appsec/tests/extension/helper_launch_failure.phpt deleted file mode 100644 index 51cf9ecfd8..0000000000 --- a/appsec/tests/extension/helper_launch_failure.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -The helper exits immediately ---SKIPIF-- - ---INI-- -datadog.appsec.helper_path=/usr/bin/true -datadog.appsec.helper_launch=1 -datadog.appsec.log_level=debug -datadog.appsec.log_file=/tmp/php_appsec_test.log ---FILE-- - ---EXPECTF-- -bool(false) -array(2) { - ["failed_count"]=> - int(1) - ["next_retry"]=> - float(%f) -} -found message in log matching /%s/ diff --git a/appsec/tests/extension/helper_launch_lock_taken.phpt b/appsec/tests/extension/helper_launch_lock_taken.phpt deleted file mode 100644 index 01fee870f8..0000000000 --- a/appsec/tests/extension/helper_launch_lock_taken.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Can't launch helper because the lock is taken ---INI-- -datadog.appsec.helper_runtime_path=/tmp/appsec-ext-test/ -datadog.appsec.helper_path=/usr/bin/true -datadog.appsec.helper_launch=1 -datadog.appsec.log_file=/tmp/php_appsec_test.log -datadog.appsec.log_level=info ---FILE-- - ---EXPECTF-- -bool(true) -bool(false) -found message in log matching /Attempting to connect to UNIX socket \/tmp\/appsec-ext-test\/ddappsec_%s.sock/ -found message in log matching /The helper lock on \/tmp\/appsec-ext-test\/ddappsec_%s.lock is already being held/ -array(2) { - ["failed_count"]=> - int(1) - ["next_retry"]=> - float(%f) -} diff --git a/appsec/tests/extension/helper_launch_ok.phpt b/appsec/tests/extension/helper_launch_ok.phpt deleted file mode 100644 index c307d79b74..0000000000 --- a/appsec/tests/extension/helper_launch_ok.phpt +++ /dev/null @@ -1,95 +0,0 @@ ---TEST-- -Test that the helper is launched with the correct properties ---SKIPIF-- - ---INI-- -datadog.appsec.helper_log_file=/tmp/helper_test.log -datadog.appsec.helper_launch=1 -datadog.appsec.log_file=/tmp/php_appsec_test.log -datadog.appsec.log_level=debug ---FILE-- - ---EXPECTF-- -helper_mgr_acquire_conn run -bool(true) - -Connected? -bool(true) - -Contents of helper log: -[%s] pre-exec: Going for second fork -[%s] pre-exec: Intermediate process exiting -[%s] pre-exec: About to call execv -Checking open file descriptors -* has file descriptor 0 -* has file descriptor 1 -* has file descriptor 2 -Checking procmask -array ( -) -Checking umask -0 -Checking parent uid (should be 1) -1 -Checking process group id == pid (is a process group leader) -OK -Checking session id == pid (is a session leader) -OK - -Checking socket id -file descriptor is %d -opened fd %d - -Accepting a connection: -accepted a new connection -read initial message from extension (size %d) -read remaining data diff --git a/appsec/tests/extension/inc/mock_helper.php b/appsec/tests/extension/inc/mock_helper.php index e25ea575b7..7a9a75594a 100644 --- a/appsec/tests/extension/inc/mock_helper.php +++ b/appsec/tests/extension/inc/mock_helper.php @@ -13,7 +13,7 @@ class Helper { function __construct($opts = array()) { $runtime_path = key_exists('runtime_path', $opts) ? $opts['runtime_path'] : ini_get('datadog.appsec.helper_runtime_path'); - $sock_path = $runtime_path . "/ddappsec_" . phpversion('ddappsec') . "_" . getmyuid() . "." . getmygid() . ".sock"; + $sock_path = $runtime_path . "/ddappsec_" . phpversion('ddappsec') . "_" . getmyuid() . ".sock"; $received_pipe = key_exists('received_pipe', $opts) ? $opts['received_pipe'] : true; $this->mock_helper_path = key_exists('mock_helper_path', $opts) ? $opts['mock_helper_path'] : getenv('MOCK_HELPER_BINARY'); $this->lock_path = $runtime_path . "/ddappsec_" . phpversion('ddappsec') . ".lock"; diff --git a/appsec/tests/extension/report_backtrace_01.phpt b/appsec/tests/extension/report_backtrace_01.phpt new file mode 100644 index 0000000000..8be9d133c9 --- /dev/null +++ b/appsec/tests/extension/report_backtrace_01.phpt @@ -0,0 +1,71 @@ +--TEST-- +Report backtrace +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +bool(true) +array(1) { + ["exploit"]=> + array(1) { + [0]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(7) "some id" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_01.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(18) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_01.php" + ["id"]=> + int(1) + } + } + } + } +} diff --git a/appsec/tests/extension/report_backtrace_02.phpt b/appsec/tests/extension/report_backtrace_02.phpt new file mode 100644 index 0000000000..83571791af --- /dev/null +++ b/appsec/tests/extension/report_backtrace_02.phpt @@ -0,0 +1,106 @@ +--TEST-- +DD_APPSEC_MAX_STACK_TRACES by default is 2 so only two backtraces are reported +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +array(1) { + ["_dd.stack"]=> + array(1) { + ["exploit"]=> + array(2) { + [0]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo01" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_02.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(20) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_02.php" + ["id"]=> + int(1) + } + } + } + [1]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo02" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_02.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(21) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_02.php" + ["id"]=> + int(1) + } + } + } + } + } +} diff --git a/appsec/tests/extension/report_backtrace_03.phpt b/appsec/tests/extension/report_backtrace_03.phpt new file mode 100644 index 0000000000..71a046b74a --- /dev/null +++ b/appsec/tests/extension/report_backtrace_03.phpt @@ -0,0 +1,139 @@ +--TEST-- +DD_APPSEC_MAX_STACK_TRACES can be configured +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_APPSEC_MAX_STACK_TRACES=3 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +array(1) { + ["_dd.stack"]=> + array(1) { + ["exploit"]=> + array(3) { + [0]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo01" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(20) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(1) + } + } + } + [1]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo02" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(21) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(1) + } + } + } + [2]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo03" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(22) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_03.php" + ["id"]=> + int(1) + } + } + } + } + } +} diff --git a/appsec/tests/extension/report_backtrace_04.phpt b/appsec/tests/extension/report_backtrace_04.phpt new file mode 100644 index 0000000000..5f749c4d1d --- /dev/null +++ b/appsec/tests/extension/report_backtrace_04.phpt @@ -0,0 +1,139 @@ +--TEST-- +DD_APPSEC_MAX_STACK_TRACES can be set to unlimited with 0 +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_APPSEC_MAX_STACK_TRACES=0 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +array(1) { + ["_dd.stack"]=> + array(1) { + ["exploit"]=> + array(3) { + [0]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo01" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(20) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(1) + } + } + } + [1]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo02" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(21) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(1) + } + } + } + [2]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(5) "foo03" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(13) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(22) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_04.php" + ["id"]=> + int(1) + } + } + } + } + } +} diff --git a/appsec/tests/extension/report_backtrace_05.phpt b/appsec/tests/extension/report_backtrace_05.phpt new file mode 100644 index 0000000000..5f24dc18d6 --- /dev/null +++ b/appsec/tests/extension/report_backtrace_05.phpt @@ -0,0 +1,84 @@ +--TEST-- +Trace are reported when helper indicates so +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--INI-- +extension=ddtrace.so +datadog.appsec.enabled=1 +--FILE-- + '1234']]], []])), +]); + +function two($param01, $param02) +{ + push_address("irrelevant", ["some" => "params", "more" => "parameters"]); +} + +function one($param01) +{ + two($param01, "other"); +} + +rinit(); + +DDTrace\start_span(); +$root = DDTrace\active_span(); +one("foo"); + +DDTrace\close_span(0); + +$span = dd_trace_serialize_closed_spans(); +$meta_struct = $span[0]["meta_struct"]; + +var_dump(decode_msgpack($meta_struct["_dd.stack"])); +DDTrace\flush(); + +?> +--EXPECTF-- +array(1) { + ["exploit"]=> + array(1) { + [0]=> + array(3) { + ["language"]=> + string(3) "php" + ["id"]=> + string(4) "1234" + ["frames"]=> + array(2) { + [0]=> + array(4) { + ["line"]=> + int(20) + ["function"]=> + string(3) "two" + ["file"]=> + string(23) "report_backtrace_05.php" + ["id"]=> + int(0) + } + [1]=> + array(4) { + ["line"]=> + int(27) + ["function"]=> + string(3) "one" + ["file"]=> + string(23) "report_backtrace_05.php" + ["id"]=> + int(1) + } + } + } + } +} \ No newline at end of file diff --git a/appsec/tests/extension/report_backtrace_06.phpt b/appsec/tests/extension/report_backtrace_06.phpt new file mode 100644 index 0000000000..d5d1222907 --- /dev/null +++ b/appsec/tests/extension/report_backtrace_06.phpt @@ -0,0 +1,33 @@ +--TEST-- +DD_APPSEC_STACK_TRACE_ENABLED can be disabled +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_APPSEC_STACK_TRACE_ENABLED=0 +--INI-- +extension=ddtrace.so +--FILE-- + +--EXPECTF-- +bool(false) diff --git a/appsec/tests/extension/rinit_agent_host_port_01.phpt b/appsec/tests/extension/rinit_agent_host_port_01.phpt deleted file mode 100644 index 2d4d297c62..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_01.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Agent host and port are taken from ENV ---ENV-- -DD_AGENT_HOST=1.2.3.4 -DD_TRACE_AGENT_PORT=567 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_02.phpt b/appsec/tests/extension/rinit_agent_host_port_02.phpt deleted file mode 100644 index 963b8bd136..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_02.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Fallback to default port if given not valid ---ENV-- -DD_TRACE_AGENT_PORT=99999 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_03.phpt b/appsec/tests/extension/rinit_agent_host_port_03.phpt deleted file mode 100644 index 4390878fa2..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_03.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Fallback to default port if given not valid ---ENV-- -DD_TRACE_AGENT_PORT=0 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["port"]); - -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_04.phpt b/appsec/tests/extension/rinit_agent_host_port_04.phpt deleted file mode 100644 index 38623573bf..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_04.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Agent host and port are taken from INI ---INI-- -datadog.agent_host=1.2.3.4 -datadog.trace.agent_port=567 ---ENV-- -DD_TRACE_AGENT_PORT= -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_05.phpt b/appsec/tests/extension/rinit_agent_host_port_05.phpt deleted file mode 100644 index a00a4d6b07..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_05.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Agent host and port can be taken from agent url on INI ---INI-- -datadog.trace.agent_url=http://1.2.3.4:567 ---ENV-- -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_06.phpt b/appsec/tests/extension/rinit_agent_host_port_06.phpt deleted file mode 100644 index 3e0f33105a..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_06.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Agent host and port can be taken from agent url on ENV ---ENV-- -DD_TRACE_AGENT_URL=http://1.2.3.4:567 -DD_AGENT_HOST= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_agent_host_port_07.phpt b/appsec/tests/extension/rinit_agent_host_port_07.phpt deleted file mode 100644 index 51825dc6a7..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_07.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Agent host and port fallback to default when agent url ones are not valid ---ENV-- -DD_TRACE_AGENT_URL=http://:99999 -DD_TRACE_AGENT_PORT= ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "127.0.0.1" -int(8126) diff --git a/appsec/tests/extension/rinit_agent_host_port_08.phpt b/appsec/tests/extension/rinit_agent_host_port_08.phpt deleted file mode 100644 index 7b1876d3ee..0000000000 --- a/appsec/tests/extension/rinit_agent_host_port_08.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -DD_AGENT_HOST and DD_TRACE_AGENT_PORT take priority over DD_TRACE_AGENT_URL ---ENV-- -DD_TRACE_AGENT_URL=http://2.2.2.2:1234 -DD_AGENT_HOST=1.2.3.4 -DD_TRACE_AGENT_PORT=567 ---FILE-- -get_commands(); -var_dump($commands[0][0]); //Command name - client_init -var_dump($commands[0][1][6]["host"]); -var_dump($commands[0][1][6]["port"]); -?> ---EXPECTF-- -bool(true) -bool(true) -string(%d) "client_init" -string(%d) "1.2.3.4" -int(567) diff --git a/appsec/tests/extension/rinit_record_span_tags.phpt b/appsec/tests/extension/rinit_record_span_tags.phpt index 981f0ade01..3ffe1d1f79 100644 --- a/appsec/tests/extension/rinit_record_span_tags.phpt +++ b/appsec/tests/extension/rinit_record_span_tags.phpt @@ -102,4 +102,6 @@ Array [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %f + [php.memory.peak_usage_bytes] => %f + [php.memory.peak_real_usage_bytes] => %f ) diff --git a/appsec/tests/extension/rinit_rshutdown_basic.phpt b/appsec/tests/extension/rinit_rshutdown_basic.phpt index e26443a567..3a85e3a3c7 100644 Binary files a/appsec/tests/extension/rinit_rshutdown_basic.phpt and b/appsec/tests/extension/rinit_rshutdown_basic.phpt differ diff --git a/appsec/tests/extension/root_span_add_tag.phpt b/appsec/tests/extension/root_span_add_tag.phpt index fbb7086f03..a5fb6112e1 100644 --- a/appsec/tests/extension/root_span_add_tag.phpt +++ b/appsec/tests/extension/root_span_add_tag.phpt @@ -2,6 +2,7 @@ Test ddtrace_root_span_add_tag --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_SERVICE=appsec_tests --INI-- extension=ddtrace.so @@ -56,7 +57,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { [%s"]=> float(%d) ["_dd.agent_psr"]=> @@ -65,6 +66,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%s) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt b/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt index fe8b0b9b3f..1a6f89f10c 100644 --- a/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt +++ b/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt @@ -84,4 +84,6 @@ Array [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %s + [php.memory.peak_usage_bytes] => %f + [php.memory.peak_real_usage_bytes] => %f ) diff --git a/appsec/tests/extension/tracking_functions_disabled_01.phpt b/appsec/tests/extension/track_custom_event_functions_appsec_disabled.phpt similarity index 100% rename from appsec/tests/extension/tracking_functions_disabled_01.phpt rename to appsec/tests/extension/track_custom_event_functions_appsec_disabled.phpt diff --git a/appsec/tests/extension/track_user_login_failure_event.phpt b/appsec/tests/extension/track_user_login_failure_event.phpt index 32a8f11502..fef52ad536 100644 --- a/appsec/tests/extension/track_user_login_failure_event.phpt +++ b/appsec/tests/extension/track_user_login_failure_event.phpt @@ -37,10 +37,10 @@ Array [appsec.events.users.login.failure.usr.id] => Admin [appsec.events.users.login.failure.track] => true [_dd.appsec.events.users.login.failure.sdk] => true - [appsec.events.users.login.failure.usr.exists] => false [appsec.events.users.login.failure.value] => something [appsec.events.users.login.failure.metadata] => some other metadata [appsec.events.users.login.failure.email] => noneofyour@business.com + [appsec.events.users.login.failure.usr.exists] => false [_dd.runtime_family] => php [_dd.p.dm] => -4 ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode.phpt new file mode 100644 index 0000000000..8a5cd6ea96 --- /dev/null +++ b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode.phpt @@ -0,0 +1,38 @@ +--TEST-- +Track automated user login failure with anonymization mode and verify the tags in the root span +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon +--FILE-- + "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" + ] + , true +); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [appsec.events.users.login.failure.usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 + [appsec.events.users.login.failure.track] => true + [_dd.appsec.events.users.login.failure.auto.mode] => anonymization + [appsec.events.users.login.failure.usr.exists] => true +) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_06.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_compat.phpt similarity index 70% rename from appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_06.phpt rename to appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_compat.phpt index c3153a3f0b..bdc990bd65 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_06.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Metadata is discarded on automated safe mode +Track automated user login failure with anonymization mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -13,8 +13,7 @@ include __DIR__ . '/inc/ddtrace_version.php'; ddtrace_version_at_least('0.79.0'); -track_user_login_failure_event( - "8d701714-5b26-4113-a8bf-ea7a681bcc3e", +track_user_login_failure_event("1234", true, [ "value" => "something", @@ -32,8 +31,8 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [appsec.events.users.login.failure.usr.id] => 8d701714-5b26-4113-a8bf-ea7a681bcc3e + [appsec.events.users.login.failure.usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => anonymization [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_full_name.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_full_name.phpt new file mode 100644 index 0000000000..3996d5bbbd --- /dev/null +++ b/appsec/tests/extension/track_user_login_failure_event_automated_anon_mode_full_name.phpt @@ -0,0 +1,38 @@ +--TEST-- +Track automated user login failure with anonymization mode, using the full name as configuration +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anonymization +--FILE-- + "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" + ] + , true +); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [appsec.events.users.login.failure.usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 + [appsec.events.users.login.failure.track] => true + [_dd.appsec.events.users.login.failure.auto.mode] => anonymization + [appsec.events.users.login.failure.usr.exists] => true +) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_default_mode.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_default_mode.phpt index 7cb2a8bf88..532242aecb 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_default_mode.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_default_mode.phpt @@ -1,5 +1,5 @@ --TEST-- -Metadata is discarded on automated safe mode +Metadata is kept on automated (default) identification mode --INI-- extension=ddtrace.so --ENV-- @@ -24,6 +24,6 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => 1234 [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_02.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_disabled_config.phpt similarity index 55% rename from appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_02.phpt rename to appsec/tests/extension/track_user_login_failure_event_automated_disabled_config.phpt index 291741a1ea..f7c8285ee1 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_02.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_disabled_config.phpt @@ -1,10 +1,11 @@ --TEST-- -Safe mode does not allow sensitive ids +Ensure automated user login failure is disabled through configuration --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident +DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED=0 --FILE-- "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" +], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,7 +29,4 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe - [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_disabled_mode.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_disabled_mode.phpt index ba15a9438c..6e55520b0c 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_disabled_mode.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_disabled_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Track automated user login failure with disabled mode and verify there is no tags +Track automated user login failure with disabled mode and verify there are no tags --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=disabled +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=disabled --FILE-- 'some@email.com'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -25,6 +25,6 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => 1234 [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_extended_mode_02.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_02.phpt similarity index 74% rename from appsec/tests/extension/track_user_login_failure_event_automated_extended_mode_02.phpt rename to appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_02.phpt index 2e805fbef6..780034a358 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_extended_mode_02.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_02.phpt @@ -1,10 +1,10 @@ --TEST-- -Verify on extended mode sensitive ids are not discarded +Verify on identification mode sensitive ids are not discarded --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- %s [appsec.events.users.login.failure.usr.id] => sensitiveId [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => extended + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true - [appsec.events.users.login.failure.email] => some@email.com ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_extended_mode.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_compat.phpt similarity index 75% rename from appsec/tests/extension/track_user_login_failure_event_automated_extended_mode.phpt rename to appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_compat.phpt index a7cfbabfc3..df397c733c 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_extended_mode.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Track automated user login failure with extended mode mode event and verify the tags in the root span +Track automated user login failure with identification mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -25,7 +25,6 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => 1234 [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => extended + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true - [appsec.events.users.login.failure.email] => some@email.com ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_03.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_full_name.phpt similarity index 64% rename from appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_03.phpt rename to appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_full_name.phpt index 68ef44246e..2796a3e5d9 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_03.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_ident_mode_full_name.phpt @@ -1,10 +1,10 @@ --TEST-- -Safe mode allows numeric ids +Track automated user login failure event with identification mode, using the full name as configuration --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=identification --FILE-- 'some@email.com'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -25,6 +25,6 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => 1234 [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_invalid_mode.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_invalid_mode.phpt index f83ae2d536..98af06f8bc 100644 --- a/appsec/tests/extension/track_user_login_failure_event_automated_invalid_mode.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_automated_invalid_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Track automated user login failure with invalid mode mode event and verify the tags in the root span +Validate that when the mode is set to an invalid value, collection is disabled --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=invalid +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=invalid --FILE-- %s - [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe - [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_04.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_04.phpt deleted file mode 100644 index 25c8adec2d..0000000000 --- a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_04.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Safe mode allows uuid v1 ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [appsec.events.users.login.failure.usr.id] => 85e37758-0b85-11ee-be56-0242ac120002 - [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe - [appsec.events.users.login.failure.usr.exists] => true -) diff --git a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_05.phpt b/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_05.phpt deleted file mode 100644 index 039e29c6d6..0000000000 --- a/appsec/tests/extension/track_user_login_failure_event_automated_safe_mode_05.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Safe mode allows uuid v4 ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [appsec.events.users.login.failure.usr.id] => 8d701714-5b26-4113-a8bf-ea7a681bcc3e - [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe - [appsec.events.users.login.failure.usr.exists] => true -) diff --git a/appsec/tests/extension/track_user_login_failure_event_existing_user.phpt b/appsec/tests/extension/track_user_login_failure_event_existing_user.phpt index 188a712be3..60fbe15c89 100644 --- a/appsec/tests/extension/track_user_login_failure_event_existing_user.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_existing_user.phpt @@ -30,8 +30,8 @@ Array [appsec.events.users.login.failure.usr.id] => Admin [appsec.events.users.login.failure.track] => true [_dd.appsec.events.users.login.failure.sdk] => true - [appsec.events.users.login.failure.usr.exists] => true [appsec.events.users.login.failure.value] => something [appsec.events.users.login.failure.metadata] => some other metadata [appsec.events.users.login.failure.email] => noneofyour@business.com + [appsec.events.users.login.failure.usr.exists] => true ) diff --git a/appsec/tests/extension/track_user_login_failure_event_no_user.phpt b/appsec/tests/extension/track_user_login_failure_event_no_user.phpt index 5116a252ba..ec10309588 100644 --- a/appsec/tests/extension/track_user_login_failure_event_no_user.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_no_user.phpt @@ -23,6 +23,6 @@ Array ( [runtime-id] => %s [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => false ) diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority.phpt index d77e3ac7d7..e5de6e72f3 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority.phpt @@ -26,7 +26,7 @@ Array [appsec.events.users.login.failure.usr.id] => Admin [appsec.events.users.login.failure.track] => true [_dd.appsec.events.users.login.failure.sdk] => true - [appsec.events.users.login.failure.usr.exists] => true [appsec.events.users.login.failure.value] => something-from-sdk - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [appsec.events.users.login.failure.usr.exists] => true + [_dd.appsec.events.users.login.failure.auto.mode] => identification ) diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_02.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_02.phpt index 745edbdd4a..e0ed50ca3c 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_02.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_02.phpt @@ -25,7 +25,7 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => Admin [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true [_dd.appsec.events.users.login.failure.sdk] => true [appsec.events.users.login.failure.value] => something-from-sdk diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_03.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_03.phpt index 5c5f6f7de5..7fc0ecbf69 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_03.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_03.phpt @@ -27,7 +27,7 @@ Array [runtime-id] => %s [appsec.events.users.login.failure.usr.id] => Other [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => safe + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true [_dd.appsec.events.users.login.failure.sdk] => true [appsec.events.users.login.failure.value] => something-from-sdk-2 diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_04.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_04.phpt index 28a9531a25..bdef3bf6a1 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_04.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_04.phpt @@ -1,10 +1,10 @@ --TEST-- -When values are set with automated event and with sdk, SDK takes priority on extended mode +When values are set with automated event and with sdk, SDK takes priority on identification mode --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- Admin [appsec.events.users.login.failure.track] => true [_dd.appsec.events.users.login.failure.sdk] => true - [appsec.events.users.login.failure.usr.exists] => true [appsec.events.users.login.failure.value] => something-from-sdk - [_dd.appsec.events.users.login.failure.auto.mode] => extended + [appsec.events.users.login.failure.usr.exists] => true + [_dd.appsec.events.users.login.failure.auto.mode] => identification ) diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_05.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_05.phpt index ca7904599b..bb5ca10fe7 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_05.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_05.phpt @@ -1,10 +1,10 @@ --TEST-- -When values are set with automated event and with sdk, SDK takes priority on extended mode +When values are set with automated event and with sdk, SDK takes priority on identification mode --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- %s [appsec.events.users.login.failure.usr.id] => Admin [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => extended + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true - [appsec.events.users.login.failure.value] => something-from-sdk [_dd.appsec.events.users.login.failure.sdk] => true + [appsec.events.users.login.failure.value] => something-from-sdk ) diff --git a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_06.phpt b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_06.phpt index a81bd57b8f..05a68b8fb5 100644 --- a/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_06.phpt +++ b/appsec/tests/extension/track_user_login_failure_event_sdk_takes_priority_06.phpt @@ -1,10 +1,10 @@ --TEST-- -Latest sdk values take priority on extended mode +Latest sdk values take priority on identification mode --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- %s [appsec.events.users.login.failure.usr.id] => Other [appsec.events.users.login.failure.track] => true - [_dd.appsec.events.users.login.failure.auto.mode] => extended + [_dd.appsec.events.users.login.failure.auto.mode] => identification [appsec.events.users.login.failure.usr.exists] => true - [appsec.events.users.login.failure.value] => something-from-sdk-2 [_dd.appsec.events.users.login.failure.sdk] => true + [appsec.events.users.login.failure.value] => something-from-sdk-2 ) diff --git a/appsec/tests/extension/track_user_login_success_event.phpt b/appsec/tests/extension/track_user_login_success_event.phpt index b256a20be5..ae3c5b2914 100644 --- a/appsec/tests/extension/track_user_login_success_event.phpt +++ b/appsec/tests/extension/track_user_login_success_event.phpt @@ -36,10 +36,10 @@ Array [runtime-id] => %s [usr.id] => Admin [_dd.appsec.events.users.login.success.sdk] => true - [appsec.events.users.login.success.track] => true [appsec.events.users.login.success.value] => something [appsec.events.users.login.success.metadata] => some other metadata [appsec.events.users.login.success.email] => noneofyour@business.com + [appsec.events.users.login.success.track] => true [_dd.runtime_family] => php [_dd.p.dm] => -4 ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_02.phpt b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode.phpt similarity index 57% rename from appsec/tests/extension/track_user_login_success_event_automated_safe_mode_02.phpt rename to appsec/tests/extension/track_user_login_success_event_automated_anon_mode.phpt index 5b9f3abef1..e387d0e4c1 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_02.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Safe mode does not allow sensitive ids +Track automated user login success event with anonymization mode and verify the tags in the root span --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon --FILE-- 'discarded'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,6 +23,7 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [_dd.appsec.events.users.login.success.auto.mode] => safe + [usr.id] => anon_8c6976e5b5410415bde908bd4dee15df + [_dd.appsec.events.users.login.success.auto.mode] => anonymization [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_03.phpt b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode_compat.phpt similarity index 62% rename from appsec/tests/extension/track_user_login_success_event_automated_safe_mode_03.phpt rename to appsec/tests/extension/track_user_login_success_event_automated_anon_mode_compat.phpt index 6d266808a9..d340fdd843 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_03.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Safe mode allows numeric ids +Track automated user login success event with anonymization mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -13,7 +13,7 @@ include __DIR__ . '/inc/ddtrace_version.php'; ddtrace_version_at_least('0.79.0'); -track_user_login_success_event("1234", [], true); +track_user_login_success_event("admin", ['something' => 'discarded'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,7 +23,7 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.login.success.auto.mode] => safe + [usr.id] => anon_8c6976e5b5410415bde908bd4dee15df + [_dd.appsec.events.users.login.success.auto.mode] => anonymization [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_anon_mode_full_name.phpt b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode_full_name.phpt new file mode 100644 index 0000000000..39ea6130c8 --- /dev/null +++ b/appsec/tests/extension/track_user_login_success_event_automated_anon_mode_full_name.phpt @@ -0,0 +1,29 @@ +--TEST-- +Track automated user login success with anonymization mode, using the full name as configuration +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anonymization +--FILE-- + 'discarded'], true); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [usr.id] => anon_8c6976e5b5410415bde908bd4dee15df + [_dd.appsec.events.users.login.success.auto.mode] => anonymization + [appsec.events.users.login.success.track] => true +) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_default_mode.phpt b/appsec/tests/extension/track_user_login_success_event_automated_default_mode.phpt index f92df7f877..1db2098598 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_default_mode.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_default_mode.phpt @@ -23,6 +23,6 @@ Array ( [runtime-id] => %s [usr.id] => 1234 - [_dd.appsec.events.users.login.success.auto.mode] => safe + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_06.phpt b/appsec/tests/extension/track_user_login_success_event_automated_disabled_config.phpt similarity index 68% rename from appsec/tests/extension/track_user_login_success_event_automated_safe_mode_06.phpt rename to appsec/tests/extension/track_user_login_success_event_automated_disabled_config.phpt index 9a66e2cd79..236952150c 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_06.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_disabled_config.phpt @@ -1,10 +1,11 @@ --TEST-- -Metadata is discarded in +Ensure automated user login success is disabled through configuration --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident +DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED=0 --FILE-- "something", "metadata" => "some other metadata", @@ -29,7 +30,4 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.login.success.auto.mode] => safe - [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_disabled_mode.phpt b/appsec/tests/extension/track_user_login_success_event_automated_disabled_mode.phpt index c57e6d2c9e..a75e1bd8c9 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_disabled_mode.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_disabled_mode.phpt @@ -4,7 +4,7 @@ Track automated user login success event with disabled mode and verify there is extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=disabled +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=disabled --FILE-- 'discarded'], true); +track_user_login_success_event("1234", ['email' => 'some@email.com'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -24,6 +24,6 @@ Array ( [runtime-id] => %s [usr.id] => 1234 - [_dd.appsec.events.users.login.success.auto.mode] => safe + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_extended_mode_02.phpt b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_02.phpt similarity index 71% rename from appsec/tests/extension/track_user_login_success_event_automated_extended_mode_02.phpt rename to appsec/tests/extension/track_user_login_success_event_automated_ident_mode_02.phpt index dfac4d3913..f9f2b469a3 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_extended_mode_02.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_02.phpt @@ -1,10 +1,10 @@ --TEST-- -Verify on extended mode sensitive ids are not discarded +Verify on identification mode sensitive ids are not discarded --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=extended +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- %s [usr.id] => sensitiveId - [_dd.appsec.events.users.login.success.auto.mode] => extended + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true - [appsec.events.users.login.success.email] => some@email.com ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_extended_mode.phpt b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_compat.phpt similarity index 73% rename from appsec/tests/extension/track_user_login_success_event_automated_extended_mode.phpt rename to appsec/tests/extension/track_user_login_success_event_automated_ident_mode_compat.phpt index 847943f906..88874a4d58 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_extended_mode.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Track automated user login success event with safe mode and verify the tags in the root span +Track automated user login success event with identification mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -24,7 +24,6 @@ Array ( [runtime-id] => %s [usr.id] => 1234 - [_dd.appsec.events.users.login.success.auto.mode] => extended + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true - [appsec.events.users.login.success.email] => some@email.com ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_full_name.phpt b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_full_name.phpt new file mode 100644 index 0000000000..156c884711 --- /dev/null +++ b/appsec/tests/extension/track_user_login_success_event_automated_ident_mode_full_name.phpt @@ -0,0 +1,29 @@ +--TEST-- +Track automated user login success event with identification mode, using the full name as configuration +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=identification +--FILE-- + 'some@email.com'], true); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [usr.id] => 1234 + [_dd.appsec.events.users.login.success.auto.mode] => identification + [appsec.events.users.login.success.track] => true +) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_invalid_mode.phpt b/appsec/tests/extension/track_user_login_success_event_automated_invalid_mode.phpt index 31bbaee3cb..e9972158e7 100644 --- a/appsec/tests/extension/track_user_login_success_event_automated_invalid_mode.phpt +++ b/appsec/tests/extension/track_user_login_success_event_automated_invalid_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Track automated user login success event with safe mode and verify the tags in the root span +Track automated user login success event, validate that an invalid mode disables collection --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=invalid +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=invalid --FILE-- %s - [_dd.appsec.events.users.login.success.auto.mode] => safe - [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_04.phpt b/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_04.phpt deleted file mode 100644 index 76a61f26a1..0000000000 --- a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_04.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Safe mode allows uuid v1 ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [usr.id] => 85e37758-0b85-11ee-be56-0242ac120002 - [_dd.appsec.events.users.login.success.auto.mode] => safe - [appsec.events.users.login.success.track] => true -) diff --git a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_05.phpt b/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_05.phpt deleted file mode 100644 index 1bc9765c6c..0000000000 --- a/appsec/tests/extension/track_user_login_success_event_automated_safe_mode_05.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Safe mode allows uuid v4 ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [usr.id] => 8d701714-5b26-4113-a8bf-ea7a681bcc3e - [_dd.appsec.events.users.login.success.auto.mode] => safe - [appsec.events.users.login.success.track] => true -) diff --git a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority.phpt b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority.phpt index 2924541003..f1c2faed96 100644 --- a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority.phpt +++ b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority.phpt @@ -25,7 +25,7 @@ Array [runtime-id] => %s [usr.id] => Admin [_dd.appsec.events.users.login.success.sdk] => true - [appsec.events.users.login.success.track] => true [appsec.events.users.login.success.value] => something-from-sdk - [_dd.appsec.events.users.login.success.auto.mode] => safe + [appsec.events.users.login.success.track] => true + [_dd.appsec.events.users.login.success.auto.mode] => identification ) diff --git a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_02.phpt b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_02.phpt index be38b41e8c..d21274d619 100644 --- a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_02.phpt +++ b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_02.phpt @@ -24,7 +24,7 @@ Array ( [runtime-id] => %s [usr.id] => Admin - [_dd.appsec.events.users.login.success.auto.mode] => safe + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true [_dd.appsec.events.users.login.success.sdk] => true [appsec.events.users.login.success.value] => something-from-sdk diff --git a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_03.phpt b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_03.phpt index 94d395aafd..530f2ac8e4 100644 --- a/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_03.phpt +++ b/appsec/tests/extension/track_user_login_success_event_sdk_takes_priority_03.phpt @@ -26,7 +26,7 @@ Array ( [runtime-id] => %s [usr.id] => OtherUser - [_dd.appsec.events.users.login.success.auto.mode] => safe + [_dd.appsec.events.users.login.success.auto.mode] => identification [appsec.events.users.login.success.track] => true [_dd.appsec.events.users.login.success.sdk] => true [appsec.events.users.login.success.value] => something-from-sdk-2 diff --git a/appsec/tests/extension/track_user_signup_event_automated_anon_mode.phpt b/appsec/tests/extension/track_user_signup_event_automated_anon_mode.phpt new file mode 100644 index 0000000000..0d7c26fb3a --- /dev/null +++ b/appsec/tests/extension/track_user_signup_event_automated_anon_mode.phpt @@ -0,0 +1,35 @@ +--TEST-- +Track automated user sign up event with anonymization mode and verify the tags in the root span +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anon +--FILE-- + "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" +], +true); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 + [_dd.appsec.events.users.signup.auto.mode] => anonymization + [appsec.events.users.signup.track] => true +) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_06.phpt b/appsec/tests/extension/track_user_signup_event_automated_anon_mode_compat.phpt similarity index 74% rename from appsec/tests/extension/track_user_signup_event_automated_safe_mode_06.phpt rename to appsec/tests/extension/track_user_signup_event_automated_anon_mode_compat.phpt index cb1d9f37a4..1319f23d53 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_06.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_anon_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Metadata is discarded in +Track automated user sign up event with anonymization mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -29,7 +29,7 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.signup.auto.mode] => safe + [usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 + [_dd.appsec.events.users.signup.auto.mode] => anonymization [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_anon_mode_full_name.phpt b/appsec/tests/extension/track_user_signup_event_automated_anon_mode_full_name.phpt new file mode 100644 index 0000000000..76fd4a6a55 --- /dev/null +++ b/appsec/tests/extension/track_user_signup_event_automated_anon_mode_full_name.phpt @@ -0,0 +1,35 @@ +--TEST-- +Track automated user signup with anonymization mode, using the full name as configuration +--INI-- +extension=ddtrace.so +--ENV-- +DD_APPSEC_ENABLED=1 +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=anonymization +--FILE-- + "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" +], +true); + +echo "root_span_get_meta():\n"; +print_r(root_span_get_meta()); +?> +--EXPECTF-- +root_span_get_meta(): +Array +( + [runtime-id] => %s + [usr.id] => anon_03ac674216f3e15c761ee1a5e255f067 + [_dd.appsec.events.users.signup.auto.mode] => anonymization + [appsec.events.users.signup.track] => true +) diff --git a/appsec/tests/extension/track_user_signup_event_automated_default_mode.phpt b/appsec/tests/extension/track_user_signup_event_automated_default_mode.phpt index 4dd9d939d5..bb544de19c 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_default_mode.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_default_mode.phpt @@ -23,6 +23,6 @@ Array ( [runtime-id] => %s [usr.id] => 1234 - [_dd.appsec.events.users.signup.auto.mode] => safe + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_04.phpt b/appsec/tests/extension/track_user_signup_event_automated_disabled_config.phpt similarity index 56% rename from appsec/tests/extension/track_user_signup_event_automated_safe_mode_04.phpt rename to appsec/tests/extension/track_user_signup_event_automated_disabled_config.phpt index 0233cf2e50..9e8ce5a750 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_04.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_disabled_config.phpt @@ -1,10 +1,11 @@ --TEST-- -Safe mode allows uuid v1 +Ensure automated user signup is disabled through configuration --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident +DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING_ENABLED=0 --FILE-- "something", + "metadata" => "some other metadata", + "email" => "noneofyour@business.com" +], +true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,7 +30,4 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 85e37758-0b85-11ee-be56-0242ac120002 - [_dd.appsec.events.users.signup.auto.mode] => safe - [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_disabled_mode.phpt b/appsec/tests/extension/track_user_signup_event_automated_disabled_mode.phpt index 87893cc30c..915b295a1d 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_disabled_mode.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_disabled_mode.phpt @@ -4,7 +4,7 @@ Track automated user login success event with disabled mode and verify there is extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=disabled +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=disabled --FILE-- 'some@email.com'], true); - -echo "root_span_get_meta():\n"; -print_r(root_span_get_meta()); -?> ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.signup.auto.mode] => extended - [appsec.events.users.signup.email] => some@email.com - [appsec.events.users.signup.track] => true -) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_03.phpt b/appsec/tests/extension/track_user_signup_event_automated_ident_mode.phpt similarity index 59% rename from appsec/tests/extension/track_user_signup_event_automated_safe_mode_03.phpt rename to appsec/tests/extension/track_user_signup_event_automated_ident_mode.phpt index 5a5cc7957c..17e79fe0c6 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_03.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_ident_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Safe mode allows numeric ids +Track automated user sign up event with identification mode and verify the tags in the root span --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=ident --FILE-- 'some@email.com'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,7 +23,7 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.signup.auto.mode] => safe + [usr.id] => sensitiveId + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_extended_mode_02.phpt b/appsec/tests/extension/track_user_signup_event_automated_ident_mode_compat.phpt similarity index 78% rename from appsec/tests/extension/track_user_signup_event_automated_extended_mode_02.phpt rename to appsec/tests/extension/track_user_signup_event_automated_ident_mode_compat.phpt index 0ca11f26a0..ef68b6c59c 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_extended_mode_02.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_ident_mode_compat.phpt @@ -1,5 +1,5 @@ --TEST-- -Verify on extended mode sensitive ids are not discarded +Track automated user sign up event with identification mode, configured through the deprecated variable --INI-- extension=ddtrace.so --ENV-- @@ -24,7 +24,6 @@ Array ( [runtime-id] => %s [usr.id] => sensitiveId - [_dd.appsec.events.users.signup.auto.mode] => extended - [appsec.events.users.signup.email] => some@email.com + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode.phpt b/appsec/tests/extension/track_user_signup_event_automated_ident_mode_full_name.phpt similarity index 58% rename from appsec/tests/extension/track_user_signup_event_automated_safe_mode.phpt rename to appsec/tests/extension/track_user_signup_event_automated_ident_mode_full_name.phpt index f0afe5e569..9511cd5937 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_ident_mode_full_name.phpt @@ -1,10 +1,10 @@ --TEST-- -Track automated user sign up event with safe mode and verify the tags in the root span +Track automated user signup with identification mode, using the full name as configuration --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=identification --FILE-- 'discarded'], true); +track_user_signup_event("sensitiveId", ['email' => 'some@email.com'], true); echo "root_span_get_meta():\n"; print_r(root_span_get_meta()); @@ -23,7 +23,7 @@ root_span_get_meta(): Array ( [runtime-id] => %s - [usr.id] => 1234 - [_dd.appsec.events.users.signup.auto.mode] => safe + [usr.id] => sensitiveId + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_invalid_mode.phpt b/appsec/tests/extension/track_user_signup_event_automated_invalid_mode.phpt index 9cabe5f6b7..1dc84ac48b 100644 --- a/appsec/tests/extension/track_user_signup_event_automated_invalid_mode.phpt +++ b/appsec/tests/extension/track_user_signup_event_automated_invalid_mode.phpt @@ -1,10 +1,10 @@ --TEST-- -Track automated user signup event with safe mode and verify the tags in the root span +Track automated user signup event, verify that an invalid mode disables collection --INI-- extension=ddtrace.so --ENV-- DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=invalid +DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE=invalid --FILE-- %s - [_dd.appsec.events.users.signup.auto.mode] => safe - [appsec.events.users.signup.track] => true ) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_02.phpt b/appsec/tests/extension/track_user_signup_event_automated_safe_mode_02.phpt deleted file mode 100644 index 244ebe868e..0000000000 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_02.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -Safe mode does not allow sensitive ids ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [_dd.appsec.events.users.signup.auto.mode] => safe - [appsec.events.users.signup.track] => true -) diff --git a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_05.phpt b/appsec/tests/extension/track_user_signup_event_automated_safe_mode_05.phpt deleted file mode 100644 index 462b6a71d3..0000000000 --- a/appsec/tests/extension/track_user_signup_event_automated_safe_mode_05.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Safe mode allows uuid v4 ---INI-- -extension=ddtrace.so ---ENV-- -DD_APPSEC_ENABLED=1 -DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING=safe ---FILE-- - ---EXPECTF-- -root_span_get_meta(): -Array -( - [runtime-id] => %s - [usr.id] => 8d701714-5b26-4113-a8bf-ea7a681bcc3e - [_dd.appsec.events.users.signup.auto.mode] => safe - [appsec.events.users.signup.track] => true -) diff --git a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority.phpt b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority.phpt index 9ead34fb87..b80c3f8a75 100644 --- a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority.phpt +++ b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority.phpt @@ -27,5 +27,5 @@ Array [_dd.appsec.events.users.signup.sdk] => true [appsec.events.users.signup.value] => something-from-sdk [appsec.events.users.signup.track] => true - [_dd.appsec.events.users.signup.auto.mode] => safe + [_dd.appsec.events.users.signup.auto.mode] => identification ) diff --git a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_02.phpt b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_02.phpt index ed543ecfc1..edd7d8612c 100644 --- a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_02.phpt +++ b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_02.phpt @@ -24,7 +24,7 @@ Array ( [runtime-id] => %s [usr.id] => Admin - [_dd.appsec.events.users.signup.auto.mode] => safe + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true [_dd.appsec.events.users.signup.sdk] => true [appsec.events.users.signup.value] => something-from-sdk diff --git a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_03.phpt b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_03.phpt index f2426c0c1e..e84a564ead 100644 --- a/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_03.phpt +++ b/appsec/tests/extension/track_user_signup_event_sdk_takes_priority_03.phpt @@ -26,7 +26,7 @@ Array ( [runtime-id] => %s [usr.id] => OtherUser - [_dd.appsec.events.users.signup.auto.mode] => safe + [_dd.appsec.events.users.signup.auto.mode] => identification [appsec.events.users.signup.track] => true [_dd.appsec.events.users.signup.sdk] => true [appsec.events.users.signup.value] => something-from-sdk-2 diff --git a/appsec/tests/extension/user_tracking_do_nothing_from_login_success.phpt b/appsec/tests/extension/user_tracking_do_nothing_from_login_success.phpt index c2a3f0f19d..02524c88bc 100644 --- a/appsec/tests/extension/user_tracking_do_nothing_from_login_success.phpt +++ b/appsec/tests/extension/user_tracking_do_nothing_from_login_success.phpt @@ -48,8 +48,8 @@ Array [runtime-id] => %s [usr.id] => Admin [_dd.appsec.events.users.login.success.sdk] => true - [appsec.events.users.login.success.track] => true [appsec.events.users.login.success.value] => something [appsec.events.users.login.success.metadata] => some other metadata [appsec.events.users.login.success.email] => noneofyour@business.com + [appsec.events.users.login.success.track] => true ) diff --git a/appsec/tests/fuzzer/CMakeLists.txt b/appsec/tests/fuzzer/CMakeLists.txt index 6fd6970fd6..eacd786caf 100644 --- a/appsec/tests/fuzzer/CMakeLists.txt +++ b/appsec/tests/fuzzer/CMakeLists.txt @@ -4,8 +4,11 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSIO set_target_properties(RapidJSON::rapidjson PROPERTIES INTERFACE_COMPILE_DEFINITIONS "RAPIDJSON_HAS_STDSTRING=1") add_executable(ddappsec_helper_fuzzer ${HELPER_SOURCE} main.cpp mutators.cpp) - set_target_properties(ddappsec_helper_fuzzer PROPERTIES COMPILE_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping") - set_target_properties(ddappsec_helper_fuzzer PROPERTIES LINK_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping") + set_target_properties(ddappsec_helper_fuzzer PROPERTIES + COMPILE_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping" + LINK_FLAGS "-fsanitize=fuzzer-no-link,address,leak -fprofile-instr-generate -fcoverage-mapping" + CXX_STANDARD 20 + ) target_include_directories(ddappsec_helper_fuzzer PRIVATE ${HELPER_INCLUDE_DIR}) execute_process( diff --git a/appsec/tests/fuzzer/main.cpp b/appsec/tests/fuzzer/main.cpp index 7f09fc2611..f6add1dfea 100644 --- a/appsec/tests/fuzzer/main.cpp +++ b/appsec/tests/fuzzer/main.cpp @@ -3,51 +3,157 @@ // // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2021 Datadog, Inc. -#include -#include -#include -#include + #include "mutators.hpp" #include "network.hpp" +#include #include +#include +#include +#include +#include dds::fuzzer::acceptor *acceptor; -std::function mutator; +std::function mutator; // NOLINT -extern "C" int LLVMFuzzerRunDriver(int *argc, char ***argv, - int (*UserCb)(const uint8_t *Data, size_t Size)); +extern "C" int LLVMFuzzerRunDriver( + int *argc, char ***argv, int (*UserCb)(const uint8_t *Data, size_t Size)); -extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) +extern "C" int LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) { return 0; } -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* bytes, size_t size) +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *bytes, size_t size) { - acceptor->push_socket(std::make_unique(bytes, size)); + acceptor->push_socket( + std::make_unique(bytes, size)); return 0; } // The custom mutator: -extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, - size_t MaxSize, unsigned int Seed) +extern "C" size_t LLVMFuzzerCustomMutator( + uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed) { return mutator(Data, Size, MaxSize, Seed); } +namespace { + +class ext_config : public dds::config::config { + static auto args_to_map(int argc, char **argv) + { + auto kv{std::map{}}; + for (int i = 1; i < argc; ++i) { + std::string_view arg(argv[i]); + if (arg.size() < 2 || arg.substr(0, 2) != "--") { + // Not an option, weird + continue; + } + arg.remove_prefix(2); + + // Check if the option has an assignment + auto pos = arg.find('='); + if (pos != std::string::npos) { + kv[arg.substr(0, pos)] = arg.substr(pos + 1); + continue; + } + + // Check the next argument + if ((i + 1) < argc) { + const std::string_view value(argv[i + 1]); + if (arg.size() < 2 || arg.substr(0, 2) != "--") { + // Not an option, so we assume it's a value + kv[arg] = value; + // Skip on next iteration + ++i; + continue; + } + } + + // If the next argument is an option or this is the last argument, + // we assume it's just a modifier. + kv[arg] = std::string_view(); + } + + return kv; + } + + std::function(std::string_view)> + make_resolve_func(int argc, char **argv) + { + auto args = args_to_map(argc, argv); + + // also go for this side effect + auto fm = args.find("fuzz-mode"); + if (fm != args.end()) { + fuzz_mode = fm->second; + } else { + fuzz_mode = "raw"; + } + + return + [args = std::move(args)]( + std::string_view env_name) -> std::optional { + auto it = args.find(map_to_arg_name(env_name)); + if (it == args.end()) { + if (env_name == env_log_file_path) { + return {"/dev/stderr"}; + } + return std::nullopt; + } + return it->second; + }; + } + + static std::string_view map_to_arg_name(std::string_view env_name) + { + if (env_name == env_socket_file_path) { + return "socket_path"; + } + if (env_name == env_lock_file_path) { + return "lock_path"; + } + if (env_name == env_log_file_path) { + return "log_path"; + } + if (env_name == env_log_level) { + return "log_level"; + } + return ""; + } + +public: + std::string_view fuzz_mode; + + ext_config(int argc, char **argv) + : dds::config::config{make_resolve_func(argc, argv)} + { + auto args = args_to_map(argc, argv); + + // also go for this side effect + auto fm = args.find("fuzz-mode"); + if (fm != args.end()) { + fuzz_mode = fm->second; + } else { + fuzz_mode = "raw"; + } + } +}; +} // namespace + int main(int argc, char **argv) { - dds::config::config config(argc, argv); + ext_config const config{argc, argv}; auto logger = spdlog::stderr_color_mt("ddappsec"); spdlog::set_default_logger(logger); logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l][%t] %v"); - spdlog::set_level( - spdlog::level::from_str(config.get("log_level"))); + spdlog::set_level(config.log_level()); std::string fuzz_mode; try { - fuzz_mode = config.get("fuzz-mode"); + fuzz_mode = config.fuzz_mode; } catch (...) { fuzz_mode = "raw"; } @@ -60,19 +166,20 @@ int main(int argc, char **argv) mutator = RawMutator; } else if (fuzz_mode == "off") { mutator = NopMutator; - }else { + } else { std::cerr << "Unsupported fuzzing mode, using raw mutator" << std::endl; } auto acceptor_ptr = std::make_unique(); acceptor = acceptor_ptr.get(); - dds::runner runner(config, std::move(acceptor_ptr)); + std::atomic interrupted; + dds::runner runner{config, std::move(acceptor_ptr), interrupted}; - std::thread runner_thread([&runner]{ runner.run(); }); + std::thread runner_thread([&runner] { runner.run(); }); - int result = LLVMFuzzerRunDriver(&argc, &argv, LLVMFuzzerTestOneInput); + int result = LLVMFuzzerRunDriver(&argc, &argv, LLVMFuzzerTestOneInput); - runner.exit(); + interrupted.store(true, std::memory_order_release); acceptor->exit(); runner_thread.join(); diff --git a/appsec/tests/fuzzer/mutators.hpp b/appsec/tests/fuzzer/mutators.hpp index 2ed9f2b53c..b8ea1100cf 100644 --- a/appsec/tests/fuzzer/mutators.hpp +++ b/appsec/tests/fuzzer/mutators.hpp @@ -5,6 +5,9 @@ // Copyright 2021 Datadog, Inc. #pragma once +#include +#include + // Forward-declare the libFuzzer's mutator callback. extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); diff --git a/appsec/tests/helper/CMakeLists.txt b/appsec/tests/helper/CMakeLists.txt index ea7c536cf1..ea7f878fca 100644 --- a/appsec/tests/helper/CMakeLists.txt +++ b/appsec/tests/helper/CMakeLists.txt @@ -4,3 +4,7 @@ target_link_libraries(ddappsec_helper_test PRIVATE helper_objects libddwaf_objects pthread spdlog gtest gmock) target_include_directories(ddappsec_helper_test PRIVATE ${CMAKE_SOURCE_DIR}/third_party/) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_options(ddappsec_helper_test PRIVATE -Wl,--export-dynamic) +endif() diff --git a/appsec/tests/helper/broker_test.cpp b/appsec/tests/helper/broker_test.cpp index 110382f30c..a1085af5cb 100644 --- a/appsec/tests/helper/broker_test.cpp +++ b/appsec/tests/helper/broker_test.cpp @@ -266,33 +266,13 @@ TEST(BrokerTest, RecvClientInit) pack_str(packer, "client_init"); // Message contents - packer.pack_array(7); + packer.pack_array(6); packer.pack_unsigned_int(20); // 1. PID pack_str(packer, "one"); // 2. client_version pack_str(packer, "two"); // 3. runtime_version packer.pack_nil(); // 4. enabled_configuration - packer.pack_map(6); // 5. service_identifier - pack_str(packer, "service"); - pack_str(packer, "api"); - - pack_str(packer, "extra_services"); - packer.pack_array(0); - - pack_str(packer, "env"); - pack_str(packer, "prod"); - - pack_str(packer, "tracer_version"); - pack_str(packer, "9.99.9"); - - pack_str(packer, "app_version"); - pack_str(packer, "1.23.4"); - - pack_str(packer, "runtime_id"); - pack_str(packer, - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - - packer.pack_map(6); // 6. engine_settings + packer.pack_map(6); // 5. engine_settings pack_str(packer, "rules_file"); pack_str(packer, "three"); @@ -315,15 +295,11 @@ TEST(BrokerTest, RecvClientInit) pack_str(packer, "sample_rate"); packer.pack_double(0.5); - packer.pack_map(4); // 7. rc_settings + packer.pack_map(2); // 6. rc_settings pack_str(packer, "enabled"); packer.pack_true(); - pack_str(packer, "host"); - pack_str(packer, "datadog.host"); - pack_str(packer, "port"); - packer.pack_uint32(1025); - pack_str(packer, "poll_interval"); - packer.pack_uint32(2222); + pack_str(packer, "shmem_path"); + pack_str(packer, "/shmem_path_test"); const std::string &expected_data = ss.str(); @@ -343,15 +319,6 @@ TEST(BrokerTest, RecvClientInit) EXPECT_STREQ(command.runtime_version.c_str(), "two"); EXPECT_FALSE(command.enabled_configuration.has_value()); - // Service Identifier - EXPECT_STREQ(command.service.service.c_str(), "api"); - EXPECT_EQ(command.service.extra_services.size(), 0); - EXPECT_STREQ(command.service.env.c_str(), "prod"); - EXPECT_STREQ(command.service.tracer_version.c_str(), "9.99.9"); - EXPECT_STREQ(command.service.app_version.c_str(), "1.23.4"); - EXPECT_STREQ(command.service.runtime_id.c_str(), - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - // Engine settings EXPECT_EQ(command.engine_settings.rules_file, std::string{"three"}); EXPECT_EQ(command.engine_settings.waf_timeout_us, 42ul); @@ -365,9 +332,7 @@ TEST(BrokerTest, RecvClientInit) // RC settings EXPECT_EQ(command.rc_settings.enabled, true); - EXPECT_STREQ(command.rc_settings.host.c_str(), "datadog.host"); - EXPECT_EQ(command.rc_settings.port, 1025); - EXPECT_EQ(command.rc_settings.poll_interval, 2222); + EXPECT_EQ(command.rc_settings.shmem_path, std::string{"/shmem_path_test"}); } TEST(BrokerTest, RecvRequestInit) diff --git a/appsec/tests/helper/client_test.cpp b/appsec/tests/helper/client_test.cpp index 11a3b7d98d..50e8c2c117 100644 --- a/appsec/tests/helper/client_test.cpp +++ b/appsec/tests/helper/client_test.cpp @@ -30,7 +30,7 @@ class broker : public dds::network::base_broker { class service_manager : public dds::service_manager { public: MOCK_METHOD(std::shared_ptr, create_service, - (dds::service_identifier && id, const dds::engine_settings &settings, + (const dds::engine_settings &settings, const dds::remote_config::settings &rc_settings, (std::map & meta), (std::map & metrics), @@ -42,12 +42,8 @@ class service : public dds::service { public: service(std::shared_ptr engine, std::shared_ptr service_config) - : dds::service(engine, service_config, {}) + : dds::service(engine, service_config, {}, "/rc_path") {} - - MOCK_METHOD(void, register_runtime_id, (const std::string &id), (override)); - MOCK_METHOD( - void, unregister_runtime_id, (const std::string &id), (override)); }; } // namespace mock @@ -187,7 +183,6 @@ TEST(ClientTest, ClientInitRegisterRuntimeId) msg.pid = 1729; msg.runtime_version = "1.0"; msg.client_version = "2.0"; - msg.service.runtime_id = "thisisaruntimeid"; msg.engine_settings.rules_file = fn; network::request req(std::move(msg)); @@ -198,17 +193,11 @@ TEST(ClientTest, ClientInitRegisterRuntimeId) send(testing::An &>())) .WillOnce(DoAll(testing::SaveArg<0>(&res), Return(true))); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); - std::string runtime_id; - EXPECT_CALL(*service, register_runtime_id(_)) - .Times(1) - .WillOnce(testing::SaveArg<0>(&runtime_id)); - EXPECT_TRUE(c.run_client_init()); - EXPECT_STREQ(runtime_id.c_str(), "thisisaruntimeid"); } TEST(ClientTest, ClientInitGeneratesRuntimeId) @@ -238,17 +227,11 @@ TEST(ClientTest, ClientInitGeneratesRuntimeId) send(testing::An &>())) .WillOnce(DoAll(testing::SaveArg<0>(&res), Return(true))); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); - std::string runtime_id; - EXPECT_CALL(*service, register_runtime_id(_)) - .Times(1) - .WillOnce(testing::SaveArg<0>(&runtime_id)); - EXPECT_TRUE(c.run_client_init()); - EXPECT_STRNE(runtime_id.c_str(), ""); } TEST(ClientTest, ClientInitInvalidRules) @@ -1847,7 +1830,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, true)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, true)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); @@ -1863,7 +1846,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) send( testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, false)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, false)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); @@ -1879,7 +1862,7 @@ TEST(ClientTest, ServiceIsCreatedDependingOnEnabledConfigurationValue) send( testing::An &>())) .WillRepeatedly(Return(true)); - EXPECT_CALL(*smanager, create_service(_, _, _, _, _, false)) + EXPECT_CALL(*smanager, create_service(_, _, _, _, false)) .Times(1) .WillOnce(Return(service)); client c(smanager, std::unique_ptr(broker)); diff --git a/appsec/tests/helper/config_test.cpp b/appsec/tests/helper/config_test.cpp index bba4e1e210..c37aad7b65 100644 --- a/appsec/tests/helper/config_test.cpp +++ b/appsec/tests/helper/config_test.cpp @@ -5,6 +5,8 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "common.hpp" #include +#include +#include namespace dds { @@ -15,74 +17,35 @@ template constexpr size_t vsize(T (&)[size]) } } // namespace -TEST(ConfigTest, ValidConstruction) -{ - char *argv[] = {const_cast("tester"), const_cast("--key"), - const_cast("value"), nullptr}; - EXPECT_NO_THROW(config::config(vsize(argv) - 1, argv)); -} - -TEST(ConfigTest, NonNullTerminatedListConstruction) -{ - char *argv[] = {const_cast("a"), const_cast("b")}; - EXPECT_NO_THROW(config::config(vsize(argv), argv)); -} - -TEST(ConfigTest, InvalidParameter) -{ - char *argv[] = {const_cast("tester"), - const_cast("parameter_missing_dashes"), nullptr}; - EXPECT_NO_THROW(config::config(vsize(argv) - 1, argv)); -} - TEST(ConfigTest, TestDefaultKeys) { - config::config cfg(0, nullptr); - EXPECT_NO_THROW(cfg.get("lock_path")); - EXPECT_NO_THROW(cfg.get("socket_path")); - EXPECT_NO_THROW(cfg.get("log_level")); + config::config cfg{[](std::string_view) { return std::nullopt; }}; + EXPECT_EQ(cfg.lock_file_path(), "/tmp/ddappsec.lock"sv); + EXPECT_EQ(cfg.socket_file_path(), "/tmp/ddappsec.sock"sv); + EXPECT_EQ(cfg.log_file_path(), "/tmp/ddappsec_helper.log"sv); + EXPECT_EQ(cfg.log_level(), spdlog::level::level_enum::warn); } TEST(ConfigTest, TestDefaultOverride) { - char *argv[] = {const_cast("tester"), - const_cast("--lock_path"), const_cast("unknown"), - const_cast("--socket_path"), const_cast("unknown"), - const_cast("--log_level"), const_cast("unknown"), - nullptr}; + static std::unordered_map defaults = { + {"_DD_SIDECAR_APPSEC_LOCK_FILE_PATH", "/foo/ddappsec.lock"}, + {"_DD_SIDECAR_APPSEC_SOCKET_FILE_PATH", "/foo/ddappsec.sock"}, + {"_DD_SIDECAR_APPSEC_LOG_FILE_PATH", "/foo/ddappsec_helper.log"}, + {"_DD_SIDECAR_APPSEC_LOG_LEVEL", "debug"}}; - config::config cfg(vsize(argv) - 1, argv); - EXPECT_TRUE(cfg.get("lock_path") == "unknown"); - EXPECT_TRUE(cfg.get("socket_path") == "unknown"); - EXPECT_TRUE(cfg.get("log_level") == "unknown"); -} + config::config cfg{[&](std::string_view key) { + auto it = defaults.find(key); + if (it != defaults.end()) { + return std::optional{it->second}; + } + return std::optional{}; + }}; -TEST(ConfigTest, TestInvalidKeys) -{ - config::config cfg(0, nullptr); - EXPECT_THROW(cfg.get("invalid"), std::out_of_range); + EXPECT_EQ(cfg.lock_file_path(), "/foo/ddappsec.lock"sv); + EXPECT_EQ(cfg.log_file_path(), "/foo/ddappsec_helper.log"sv); + EXPECT_EQ(cfg.socket_file_path(), "/foo/ddappsec.sock"sv); + EXPECT_EQ(cfg.log_level(), spdlog::level::level_enum::debug); } -TEST(ConfigTest, TestKeyValue) -{ - char *argv[] = {const_cast("tester"), const_cast("--a_key"), - const_cast("a_value"), const_cast("--b_key"), - const_cast("b_value"), const_cast("--c_key=c_value"), - nullptr}; - - config::config cfg(vsize(argv) - 1, argv); - EXPECT_TRUE(cfg.get("a_key") == "a_value"); - EXPECT_TRUE(cfg.get("b_key") == "b_value"); - EXPECT_TRUE(cfg.get("c_key") == "c_value"); -} - -TEST(ConfigTest, TestModifiers) -{ - int argc = 2; - char *argv[] = {const_cast("tester"), - const_cast("--modifier"), nullptr}; - - config::config cfg(argc, argv); - EXPECT_TRUE(cfg.get("modifier")); -} } // namespace dds diff --git a/appsec/tests/helper/engine_test.cpp b/appsec/tests/helper/engine_test.cpp index eaee4d231b..1778e17337 100644 --- a/appsec/tests/helper/engine_test.cpp +++ b/appsec/tests/helper/engine_test.cpp @@ -6,6 +6,7 @@ #include "common.hpp" #include "json_helper.hpp" #include +#include #include #include @@ -19,8 +20,6 @@ namespace dds { namespace mock { class listener : public dds::subscriber::listener { public: - typedef std::shared_ptr ptr; - MOCK_METHOD2(call, void(dds::parameter_view &, dds::event &)); MOCK_METHOD2( get_meta_and_metrics, void(std::map &, @@ -29,12 +28,10 @@ class listener : public dds::subscriber::listener { class subscriber : public dds::subscriber { public: - typedef std::shared_ptr ptr; - MOCK_METHOD0(get_name, std::string_view()); - MOCK_METHOD0(get_listener, dds::subscriber::listener::ptr()); + MOCK_METHOD0(get_listener, std::unique_ptr()); MOCK_METHOD0(get_subscriptions, std::unordered_set()); - MOCK_METHOD3(update, dds::subscriber::ptr(dds::parameter &, + MOCK_METHOD3(update, std::unique_ptr(dds::parameter &, std::map &meta, std::map &metrics)); }; @@ -55,17 +52,18 @@ TEST(EngineTest, SingleSubscriptor) { auto e{engine::create()}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .WillRepeatedly( - Invoke([](dds::parameter_view &data, dds::event &event_) -> void { - event_.actions.push_back({dds::action_type::block, {}}); - })); - - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .WillRepeatedly(Invoke( + [](dds::parameter_view &data, dds::event &event_) -> void { + event_.actions.push_back({dds::action_type::block, {}}); + })); + return listener; + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -87,7 +85,8 @@ using namespace std::literals; TEST(EngineTest, MultipleSubscriptors) { auto e{engine::create()}; - mock::listener::ptr blocker = mock::listener::ptr(new mock::listener()); + + auto blocker = std::make_unique(); EXPECT_CALL(*blocker, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -98,7 +97,7 @@ TEST(EngineTest, MultipleSubscriptors) } })); - mock::listener::ptr recorder = mock::listener::ptr(new mock::listener()); + auto recorder = std::make_unique(); EXPECT_CALL(*recorder, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -108,21 +107,31 @@ TEST(EngineTest, MultipleSubscriptors) } })); - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); + std::unique_ptr ignorer = + std::unique_ptr(new mock::listener()); EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub1, get_listener()).WillRepeatedly(Return(blocker)); + std::unique_ptr sub1 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub1, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(blocker); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub2, get_listener()).WillRepeatedly(Return(recorder)); + std::unique_ptr sub2 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub2, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(recorder); + })); - mock::subscriber::ptr sub3 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub3, get_listener()).WillRepeatedly(Return(ignorer)); + std::unique_ptr sub3 = + std::unique_ptr(new mock::subscriber()); + EXPECT_CALL(*sub3, get_listener()).WillRepeatedly(Invoke([&]() { + return std::move(ignorer); + })); - e->subscribe(sub1); - e->subscribe(sub2); - e->subscribe(sub3); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); + e->subscribe(std::move(sub3)); auto ctx = e->get_context(); @@ -194,21 +203,23 @@ TEST(EngineTest, StatefulSubscriptor) auto e{engine::create()}; int attempt = 0; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .Times(6) - .WillRepeatedly(Invoke( - [&attempt](dds::parameter_view &data, dds::event &event_) -> void { + + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([&]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .Times(3) + .WillRepeatedly(Invoke([&attempt](dds::parameter_view &data, + dds::event &event_) -> void { if (attempt == 2 || attempt == 5) { event_.actions.push_back({dds::action_type::block, {}}); } attempt++; })); + return listener; + })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); - - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -251,7 +262,7 @@ TEST(EngineTest, WafDefaultActions) { auto e{engine::create(engine_settings::default_trace_rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly(Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -261,10 +272,12 @@ TEST(EngineTest, WafDefaultActions) event_.actions.push_back({dds::action_type::extract_schema, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -293,7 +306,7 @@ TEST(EngineTest, InvalidActionsAreDiscarded) { auto e{engine::create(engine_settings::default_trace_rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { @@ -301,10 +314,12 @@ TEST(EngineTest, InvalidActionsAreDiscarded) event_.actions.push_back({dds::action_type::block, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); auto ctx = e->get_context(); @@ -330,8 +345,10 @@ TEST(EngineTest, WafSubscriptorBasic) auto e{engine::create()}; - auto waf_ptr = waf::instance::from_string(waf_rule, meta, metrics); - e->subscribe(waf_ptr); + auto waf_uniq_ptr = waf::instance::from_string(waf_rule, meta, metrics); + auto *waf_ptr = waf_uniq_ptr.get(); + + e->subscribe(std::move(waf_uniq_ptr)); EXPECT_STREQ(waf_ptr->get_name().data(), "waf"); @@ -390,27 +407,36 @@ TEST(EngineTest, MockSubscriptorsUpdateRuleData) { auto e{engine::create()}; - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); - - mock::subscriber::ptr new_sub1 = - mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*new_sub1, get_listener()).WillOnce(Return(ignorer)); - - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub1, update(_, _, _)).WillOnce(Return(new_sub1)); + auto ignorer = []() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)).Times(testing::AnyNumber()); + return listener; + }; + + auto new_sub1 = std::make_unique(); + EXPECT_CALL(*new_sub1, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); + + auto sub1 = std::make_unique(); + EXPECT_CALL(*sub1, update(_, _, _)).WillOnce(Invoke([&]() { + return std::move(new_sub1); + })); EXPECT_CALL(*sub1, get_name()).WillRepeatedly(Return("")); - mock::subscriber::ptr new_sub2 = - mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*new_sub2, get_listener()).WillOnce(Return(ignorer)); + auto new_sub2 = std::make_unique(); + EXPECT_CALL(*new_sub2, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub2, update(_, _, _)).WillOnce(Return(new_sub2)); + auto sub2 = std::make_unique(); + EXPECT_CALL(*sub2, update(_, _, _)).WillOnce(Invoke([&]() { + return std::move(new_sub2); + })); EXPECT_CALL(*sub2, get_name()).WillRepeatedly(Return("")); - e->subscribe(sub1); - e->subscribe(sub2); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); std::map meta; std::map metrics; @@ -433,21 +459,28 @@ TEST(EngineTest, MockSubscriptorsInvalidRuleData) { auto e{engine::create()}; - mock::listener::ptr ignorer = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*ignorer, call(_, _)).Times(testing::AnyNumber()); + auto ignorer = []() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)).Times(testing::AnyNumber()); + return listener; + }; - mock::subscriber::ptr sub1 = mock::subscriber::ptr(new mock::subscriber()); + auto sub1 = std::make_unique(); EXPECT_CALL(*sub1, update(_, _, _)).WillRepeatedly(Throw(std::exception())); EXPECT_CALL(*sub1, get_name()).WillRepeatedly(Return("")); - EXPECT_CALL(*sub1, get_listener()).WillOnce(Return(ignorer)); + EXPECT_CALL(*sub1, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - mock::subscriber::ptr sub2 = mock::subscriber::ptr(new mock::subscriber()); + auto sub2 = std::make_unique(); EXPECT_CALL(*sub2, update(_, _, _)).WillRepeatedly(Throw(std::exception())); EXPECT_CALL(*sub2, get_name()).WillRepeatedly(Return("")); - EXPECT_CALL(*sub2, get_listener()).WillOnce(Return(ignorer)); + EXPECT_CALL(*sub2, get_listener()).WillOnce(Invoke([&]() { + return ignorer(); + })); - e->subscribe(sub1); - e->subscribe(sub2); + e->subscribe(std::move(sub1)); + e->subscribe(std::move(sub2)); std::map meta; std::map metrics; @@ -857,17 +890,19 @@ TEST(EngineTest, RateLimiterForceKeep) int rate_limit = 0; auto e{engine::create(rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); + auto listener = std::make_unique(); EXPECT_CALL(*listener, call(_, _)) .WillRepeatedly( Invoke([](dds::parameter_view &data, dds::event &event_) -> void { event_.actions.push_back({dds::action_type::redirect, {}}); })); - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillOnce(Invoke([&]() { + return std::move(listener); + })); - e->subscribe(sub); + e->subscribe(std::move(sub)); parameter p = parameter::map(); p.add("a", parameter::string("value"sv)); @@ -881,17 +916,18 @@ TEST(EngineTest, RateLimiterDoNotForceKeep) int rate_limit = 1; auto e{engine::create(rate_limit)}; - mock::listener::ptr listener = mock::listener::ptr(new mock::listener()); - EXPECT_CALL(*listener, call(_, _)) - .WillRepeatedly( - Invoke([](dds::parameter_view &data, dds::event &event_) -> void { - event_.actions.push_back({dds::action_type::redirect, {}}); - })); - - mock::subscriber::ptr sub = mock::subscriber::ptr(new mock::subscriber()); - EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Return(listener)); - - e->subscribe(sub); + auto sub = std::make_unique(); + EXPECT_CALL(*sub, get_listener()).WillRepeatedly(Invoke([&]() { + auto listener = std::make_unique(); + EXPECT_CALL(*listener, call(_, _)) + .WillOnce(Invoke( + [](dds::parameter_view &data, dds::event &event_) -> void { + event_.actions.push_back({dds::action_type::redirect, {}}); + })); + return listener; + })); + + e->subscribe(std::move(sub)); parameter p = parameter::map(); p.add("a", parameter::string("value"sv)); diff --git a/appsec/tests/helper/remote_config/client_handler_test.cpp b/appsec/tests/helper/remote_config/client_handler_test.cpp deleted file mode 100644 index aefaf40f41..0000000000 --- a/appsec/tests/helper/remote_config/client_handler_test.cpp +++ /dev/null @@ -1,331 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include "../common.hpp" -#include "mocks.hpp" -#include "remote_config/client_handler.hpp" - -namespace dds { - -namespace mock { - -class client_handler : public remote_config::client_handler { -public: - client_handler(remote_config::client::ptr &&rc_client, - std::shared_ptr service_config, - const std::chrono::milliseconds &poll_interval) - : remote_config::client_handler( - std::move(rc_client), service_config, poll_interval) - {} - void set_max_interval(std::chrono::milliseconds new_interval) - { - max_interval = new_interval; - } - auto get_max_interval() { return max_interval; } - const std::chrono::milliseconds get_current_interval() { return interval_; } - void tick() { remote_config::client_handler::tick(); } - - auto get_errors() { return errors_; } -}; - -} // namespace mock - -ACTION_P(SignalCall, promise) { promise->set_value(true); } - -class ClientHandlerTest : public ::testing::Test { -public: - service_identifier sid{"service", {"extra_service01", "extra_service02"}, - "env", "tracer_version", "app_version", "runtime_id"}; - dds::engine_settings settings; - remote_config::settings rc_settings; - std::shared_ptr service_config; - service_identifier id; - std::shared_ptr engine; - - void SetUp() - { - service_config = std::make_shared(); - id = sid; - engine = engine::create(); - rc_settings.enabled = true; - } -}; - -TEST_F(ClientHandlerTest, IfRemoteConfigDisabledItDoesNotGenerateHandler) -{ - rc_settings.enabled = false; - - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, false); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, IfNoServiceConfigProvidedItDoesNotGenerateHandler) -{ - std::shared_ptr null_service_config = {}; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, null_service_config, rc_settings, - engine, false); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, RuntimeIdIsNotGeneratedIfProvided) -{ - const char *runtime_id = "some runtime id"; - id.runtime_id = runtime_id; - - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, false); - - EXPECT_STREQ(runtime_id, client_handler->get_client() - ->get_service_identifier() - .runtime_id.c_str()); -} - -TEST_F(ClientHandlerTest, AsmFeatureProductIsAddeWhenDynamicEnablement) -{ - auto dynamic_enablement = true; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_FEATURES") != products_list.end()); -} - -TEST_F( - ClientHandlerTest, AsmFeatureProductIsNotAddeWhenDynamicEnablementDisabled) -{ - auto dynamic_enablement = false; - - // Clear rules file so at least some other products are added - settings.rules_file.clear(); - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_FEATURES") == products_list.end()); -} - -TEST_F(ClientHandlerTest, SomeProductsDependOnDynamicEngineBeingSet) -{ - { // When rules file is not set, products are added - settings.rules_file.clear(); - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, true); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_DATA") != products_list.end()); - EXPECT_TRUE(products_list.find("ASM_DD") != products_list.end()); - EXPECT_TRUE(products_list.find("ASM") != products_list.end()); - } - - { // When rules file is set, products not are added - settings.rules_file = "/some/file"; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, true); - - auto products_list = client_handler->get_client()->get_products(); - EXPECT_TRUE(products_list.find("ASM_DATA") == products_list.end()); - EXPECT_TRUE(products_list.find("ASM_DD") == products_list.end()); - EXPECT_TRUE(products_list.find("ASM") == products_list.end()); - } -} - -TEST_F(ClientHandlerTest, IfNoProductsAreRequiredRemoteClientIsNotGenerated) -{ - settings.rules_file = "/some/file"; - auto dynamic_enablement = false; - auto client_handler = remote_config::client_handler::from_settings( - dds::service_identifier(id), settings, service_config, rc_settings, - engine, dynamic_enablement); - - EXPECT_FALSE(client_handler); -} - -TEST_F(ClientHandlerTest, ValidateRCThread) -{ - std::promise poll_call_promise; - auto poll_call_future = poll_call_promise.get_future(); - std::promise available_call_promise; - auto available_call_future = available_call_promise.get_future(); - - auto rc_client = std::make_unique(sid); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(1) - .WillOnce(DoAll(SignalCall(&available_call_promise), Return(true))); - EXPECT_CALL(*rc_client, poll) - .Times(1) - .WillOnce(DoAll(SignalCall(&poll_call_promise), Return(true))); - - auto client_handler = remote_config::client_handler( - std::move(rc_client), service_config, 200ms); - - client_handler.start(); - - // wait a little bit - this might end up being flaky - poll_call_future.wait_for(400ms); - available_call_future.wait_for(200ms); -} - -TEST_F(ClientHandlerTest, WhenRcNotAvailableItKeepsDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(2) - .WillOnce(Return(false)) - .WillOnce(Return(false)); - EXPECT_CALL(*rc_client, poll).Times(0); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, WhenPollFailsItGoesBackToDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(2) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - EXPECT_CALL(*rc_client, poll) - .Times(1) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, WhenDiscoverFailsItStaysOnDiscovering) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillOnce(Return(false)) - .WillOnce(Throw(dds::remote_config::network_exception("some"))) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - EXPECT_CALL(*rc_client, poll).Times(0); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 50ms); - client_handler.set_max_interval(100ms); - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, ItKeepsPollingWhileNoError) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(*rc_client, poll) - .Times(2) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 500ms); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); -} - -TEST_F(ClientHandlerTest, ItDoesNotStartIfNoRcClientGiven) -{ - auto rc_client = nullptr; - auto client_handler = - remote_config::client_handler(rc_client, service_config, 500ms); - - EXPECT_FALSE(client_handler.start()); -} - -TEST_F(ClientHandlerTest, ItDoesNotGoOverMaxIfGivenInitialIntervalIsLower) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillRepeatedly(Return(false)); - - auto max_interval = 300ms; - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 299ms); - client_handler.set_max_interval(max_interval); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); - - EXPECT_EQ(max_interval, client_handler.get_current_interval()); - EXPECT_EQ(3, client_handler.get_errors()); -} - -TEST_F(ClientHandlerTest, IfInitialIntervalIsHigherThanMaxItBecomesNewMax) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, is_remote_config_available) - .Times(3) - .WillRepeatedly(Return(false)); - - auto interval = 200ms; - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, interval); - client_handler.set_max_interval(100ms); - - client_handler.tick(); - client_handler.tick(); - client_handler.tick(); - - EXPECT_EQ(interval, client_handler.get_current_interval()); - EXPECT_EQ(3, client_handler.get_errors()); -} - -TEST_F(ClientHandlerTest, ByDefaultMaxIntervalisFiveMinutes) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - auto client_handler = - mock::client_handler(std::move(rc_client), service_config, 200ms); - - EXPECT_EQ(5min, client_handler.get_max_interval()); -} - -TEST_F(ClientHandlerTest, RegisterAndUnregisterRuntimeID) -{ - auto rc_client = std::make_unique( - dds::service_identifier(sid)); - EXPECT_CALL(*rc_client, register_runtime_id).Times(1); - EXPECT_CALL(*rc_client, unregister_runtime_id).Times(1); - - auto client_handler = remote_config::client_handler( - std::move(rc_client), service_config, 200ms); - - client_handler.register_runtime_id("something"); - client_handler.unregister_runtime_id("something"); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/client_test.cpp b/appsec/tests/helper/remote_config/client_test.cpp deleted file mode 100644 index 262960d9c6..0000000000 --- a/appsec/tests/helper/remote_config/client_test.cpp +++ /dev/null @@ -1,1348 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../common.hpp" -#include "base64.h" -#include "json_helper.hpp" -#include "remote_config/client.hpp" -#include "remote_config/exception.hpp" -#include "remote_config/listeners/listener.hpp" -#include "remote_config/product.hpp" -#include "remote_config/protocol/client.hpp" -#include "remote_config/protocol/client_state.hpp" -#include "remote_config/protocol/client_tracer.hpp" -#include "remote_config/protocol/config_state.hpp" -#include "remote_config/protocol/tuf/get_configs_request.hpp" -#include "remote_config/protocol/tuf/serializer.hpp" -#include "service_identifier.hpp" -#include "spdlog/fmt/bundled/core.h" - -using capabilities_e = dds::remote_config::protocol::capabilities_e; - -namespace dds { -class dummy_listener : public remote_config::listener_base { -public: - explicit dummy_listener(std::string_view name_ = "MOCK_PRODUCT") - : name(name_) - {} - void on_update(const remote_config::config &config) override {} - void on_unapply(const remote_config::config &config) override {} - void init() override {} - void commit() override {} - - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return { - {name, remote_config::protocol::capabilities_e::ASM_ACTIVATION}}; - } - - std::string name; -}; - -namespace mock { - -// The simple custom action -ACTION_P(set_response_body, response) { arg1.assign(response); } - -ACTION(ThrowErrorApplyingConfig) -{ - throw remote_config::error_applying_config("some error"); -} - -class api : public remote_config::http_api { -public: - api() : http_api("0.0.0.0", "1234"){}; - MOCK_METHOD(std::string, get_configs, (std::string && request), (const)); -}; - -class listener_mock : public remote_config::listener_base { -public: - listener_mock() = default; - listener_mock(std::string_view name_) : name(name_) {} - ~listener_mock() override = default; - MOCK_METHOD( - void, on_update, ((const remote_config::config &config)), (override)); - MOCK_METHOD( - void, on_unapply, ((const remote_config::config &config)), (override)); - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return { - {name, remote_config::protocol::capabilities_e::ASM_ACTIVATION}}; - } - - MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(void, commit, (), (override)); - std::string name{"MOCK_PRODUCT"}; -}; -} // namespace mock - -namespace test_helpers { -std::string sha256_from_path(std::string path) { return path + "_sha256"; } - -std::string raw_from_path(std::string path) { return path + "_raw"; } - -// Just a deterministic way of asserting this and avoid hadcoding much -int version_from_path(std::string path) { return path.length() + 55; } - -int length_from_path(std::string path) { return path.length(); } -} // namespace test_helpers - -class test_client : public remote_config::client { -public: - test_client(std::string id, - std::unique_ptr &&arg_api, - service_identifier &&sid, remote_config::settings &&settings, - std::vector listeners = {}) - : remote_config::client(std::move(arg_api), std::move(sid), - std::move(settings), listeners) - { - id_ = std::move(id); - } -}; - -class RemoteConfigClient : public ::testing::Test { -public: - std::string id; - std::string runtime_id; - std::string tracer_version; - std::string service; - std::vector extra_services; - std::string env; - std::string app_version; - std::string backend_client_state; - int target_version; - std::string asm_features; - std::string asm_dd; - std::string apm_sampling; - std::vector products_str; - std::vector listeners_; - std::string first_product_product; - std::string first_product_id; - std::string second_product_product; - std::string second_product_id; - std::string first_path; - std::string second_path; - std::vector paths; - remote_config::settings settings; - remote_config::protocol::capabilities_e capabilities; - - void SetUp() - { - // Since most values are moved to the classes, they need to be generated - // again on each set up - id = "some id"; - runtime_id = "some runtime id"; - tracer_version = "some tracer version"; - service = "some service"; - extra_services = {"service01", "service02"}; - env = "some env"; - app_version = "some app version"; - backend_client_state = "some backend state here"; - target_version = 123; - asm_features = "ASM_FEATURES"; - asm_dd = "ASM_DD"; - apm_sampling = "APM_SAMPLING"; - products_str = {asm_dd, asm_features}; - - first_product_product = asm_features; - first_product_id = "2.test1.config"; - first_path = "employee/" + first_product_product + "/" + - first_product_id + "/config"; - second_product_product = asm_features; - second_product_id = "luke.steensen"; - second_path = "datadog/2/" + second_product_product + "/" + - second_product_id + "/config"; - paths = {first_path, second_path}; - capabilities = remote_config::protocol::capabilities_e::ASM_ACTIVATION; - generate_listeners(); - } - - void generate_listeners() - { - for (std::string_view p_str : products_str) { - listeners_.push_back(std::make_shared(p_str)); - } - } - - remote_config::protocol::client generate_client(bool generate_state) - { - remote_config::protocol::client_tracer client_tracer = {runtime_id, - tracer_version, service, extra_services, env, app_version}; - - std::vector config_states; - int _target_version; - std::string _backend_client_state; - if (generate_state) { - // All these states are extracted from the harcoded request/response - std::string product00(first_product_product); - std::string product00_id(first_product_id); - remote_config::protocol::config_state cs00 = {product00_id, - test_helpers::version_from_path(first_path), product00, - remote_config::protocol::config_state::applied_state:: - ACKNOWLEDGED, - ""}; - std::string product01(second_product_product); - std::string product01_id(second_product_id); - remote_config::protocol::config_state cs01 = {product01_id, - test_helpers::version_from_path(second_path), product01, - remote_config::protocol::config_state::applied_state:: - ACKNOWLEDGED, - ""}; - - config_states.push_back(cs00); - config_states.push_back(cs01); - _target_version = target_version; - // This field is extracted from the harcoded response - _backend_client_state = backend_client_state; - } else { - _target_version = 0; // Default target version - _backend_client_state = ""; - } - remote_config::protocol::client_state client_state = { - _target_version, config_states, false, "", _backend_client_state}; - - auto products_str_cpy = products_str; - auto id_cpy = id; - remote_config::protocol::client c = {id_cpy, products_str_cpy, - client_tracer, client_state, capabilities}; - - return c; - } - - std::string generate_targets( - std::vector paths, std::string opaque_backend_state) - { - std::string targets_str; - for (int i = 0; i < paths.size(); i++) { - std::string path = paths[i]; - std::string sha256 = test_helpers::sha256_from_path(path); - targets_str.append( - ("\"" + path + "\": {\"custom\": {\"v\": " + - std::to_string(test_helpers::version_from_path(path)) + - " }, \"hashes\": {\"sha256\": \"" + sha256 + - "\"}, \"length\": " + - std::to_string(test_helpers::length_from_path(paths[i])) + - " }")); - if (i + 1 < paths.size()) { - targets_str.append(","); - } - } - - std::string targets_json = - ("{\"signatures\": [], \"signed\": {\"_type\": \"targets\", " - "\"custom\": {\"opaque_backend_state\": \"" + - opaque_backend_state + - "\"}, " - "\"expires\": \"2022-11-04T13:31:59Z\", \"spec_version\": " - "\"1.0.0\", \"targets\": {" + - targets_str + - "}, \"version\": " + std::to_string(target_version) + " } }"); - - return base64_encode(targets_json); - } - - std::string generate_example_response( - std::vector client_configs, - std::vector target_files, - std::vector target_paths) - { - std::string client_configs_str = ""; - std::string target_files_str = ""; - for (int i = 0; i < client_configs.size(); i++) { - client_configs_str.append("\"" + client_configs[i] + "\""); - if (i + 1 < client_configs.size()) { - client_configs_str.append(", "); - } - } - for (int i = 0; i < target_files.size(); i++) { - target_files_str.append( - "{\"path\": \"" + target_files[i] + "\", \"raw\": \"" + - test_helpers::raw_from_path(target_files[i]) + "\"}"); - if (i + 1 < target_files.size()) { - target_files_str.append(","); - } - } - return ("{\"roots\": [], \"targets\": \"" + - generate_targets(target_paths, backend_client_state) + - "\", \"target_files\": [" + target_files_str + - "], " - "\"client_configs\": [" + - client_configs_str + - "] " - "}"); - } - - std::string generate_example_response(std::vector paths) - { - return generate_example_response(paths, paths, paths); - } - - remote_config::protocol::get_configs_request generate_request( - bool generate_state, bool generate_cache) - { - dds::remote_config::protocol::client protocol_client = - generate_client(generate_state); - std::vector files; - if (generate_cache) { - // First cached file - remote_config::protocol::cached_target_files_hash hash01{ - "sha256", test_helpers::sha256_from_path(paths[0])}; - std::string path01 = paths[0]; - remote_config::protocol::cached_target_files file01 = { - path01, test_helpers::length_from_path(path01), {hash01}}; - files.push_back(file01); - - // Second cached file - remote_config::protocol::cached_target_files_hash hash02{ - "sha256", test_helpers::sha256_from_path(paths[1])}; - std::string path02 = paths[1]; - remote_config::protocol::cached_target_files file02{ - path02, test_helpers::length_from_path(path02), {hash02}}; - files.push_back(file02); - } - return {std::move(protocol_client), std::move(files)}; - } - - std::string generate_request_serialized( - bool generate_state, bool generate_cache) - { - std::optional request_serialized; - - request_serialized = remote_config::protocol::serialize( - generate_request(generate_state, generate_cache)); - - return request_serialized.value(); - } - - bool validate_request_has_error( - std::string request_serialized, bool has_error, std::string error_msg) - { - rapidjson::Document serialized_doc; - if (serialized_doc.Parse(request_serialized).HasParseError()) { - return false; - } - - rapidjson::Value::ConstMemberIterator state_itr = - serialized_doc.FindMember("client")->value.FindMember("state"); - - // Has error field - rapidjson::Value::ConstMemberIterator itr = - state_itr->value.FindMember("has_error"); - rapidjson::Type expected_type = - has_error ? rapidjson::kTrueType : rapidjson::kFalseType; - if (itr->value.GetType() != expected_type) { - return false; - } - - // Error field - itr = state_itr->value.FindMember("error"); - if (itr->value.GetType() != rapidjson::kStringType || - error_msg != itr->value.GetString()) { - return false; - } - - return true; - } -}; - -TEST_F(RemoteConfigClient, OnNetworkApiErrorTheExceptionsFlows) -{ - bool exception_thrown = false; - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs) - .WillOnce(Throw(dds::remote_config::network_exception("some"))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - try { - api_client.poll(); - } catch (dds::remote_config::network_exception & /** e*/) { - exception_thrown = true; - } - - EXPECT_TRUE(exception_thrown); -} - -std::string sort_arrays(std::string json) -{ - rapidjson::Document doc; - doc.Parse(json); - - // Sorting products - auto products = doc.FindMember("client") - ->value.FindMember("products") - ->value.GetArray(); - std::sort(products.begin(), products.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - return strcmp(lhs.GetString(), rhs.GetString()) < 0; - }); - - // Sorting config_states - auto config_states = doc.FindMember("client") - ->value.FindMember("state") - ->value.FindMember("config_states") - ->value.GetArray(); - std::sort(config_states.begin(), config_states.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - auto first = lhs.FindMember("id")->value.GetString(); - auto second = rhs.FindMember("id")->value.GetString(); - return strcmp(first, second) < 0; - }); - - // Sorting cached_target_files - auto cached_target_files = - doc.FindMember("cached_target_files")->value.GetArray(); - std::sort(cached_target_files.begin(), cached_target_files.end(), - [](const rapidjson::Value &lhs, const rapidjson::Value &rhs) { - auto first = lhs.FindMember("path")->value.GetString(); - auto second = rhs.FindMember("path")->value.GetString(); - return strcmp(first, second) < 0; - }); - - // Generate string - dds::string_buffer buffer; - rapidjson::Writer writer(buffer); - if (!doc.Accept(writer)) { - return json; - } - - return buffer.get_string_ref(); -} - -TEST_F(RemoteConfigClient, ItCallsToApiOnPoll) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, PollFailsWithoutRuntimeID) -{ - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs(_)).Times(0); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, ReplaceRuntimeID) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Unregister the old ID and register a new one - api_client.unregister_runtime_id(runtime_id); - runtime_id = "something else"; - api_client.register_runtime_id(runtime_id); - api_client.register_runtime_id("dummy"); - api_client.register_runtime_id("unused"); - api_client.register_runtime_id("irrelevant"); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, RemoveRuntimeID) -{ - auto api = std::make_unique(); - std::string request_sent = ""; - EXPECT_CALL(*api, get_configs(_)) - .Times(AtLeast(1)) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response(paths)))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Unregister the ID, it should still be used - api_client.unregister_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(request_sent)); -} - -TEST_F(RemoteConfigClient, ItReturnErrorWhenApiNotProvided) -{ - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, nullptr, std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, ItReturnErrorWhenResponseIsInvalidJson) -{ - auto api = std::make_unique(); - EXPECT_CALL(*api, get_configs).WillOnce(Return("invalid json here")); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_FALSE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, - ItReturnErrorAndSaveLastErrorWhenClientConfigPathNotInTargetPaths) -{ - std::string response = generate_example_response(paths, paths, {}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config " + paths[0] + - " in " - "targets")); -} - -TEST_F(RemoteConfigClient, - ItReturnErrorAndSaveLastErrorWhenClientConfigPathNotInTargetFiles) -{ - std::string response = generate_example_response(paths, {}, paths); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config " + paths[0] + - " in " - "target files and in cache files")); -} - -TEST(ClientConfig, ItGetGeneratedFromString) -{ - std::string apm_sampling = "APM_SAMPLING"; - auto cp = remote_config::config_path::from_path( - "datadog/2/LIVE_DEBUGGING/9e413cda-647b-335b-adcd-7ce453fc2284/config"); - EXPECT_EQ("LIVE_DEBUGGING", cp.product); - EXPECT_EQ("9e413cda-647b-335b-adcd-7ce453fc2284", cp.id); - - cp = remote_config::config_path::from_path( - "employee/DEBUG_DD/2.test1.config/config"); - EXPECT_EQ("DEBUG_DD", cp.product); - EXPECT_EQ("2.test1.config", cp.id); - - cp = remote_config::config_path::from_path( - "datadog/55/APM_SAMPLING/dynamic_rates/something"); - EXPECT_EQ(apm_sampling, cp.product); - EXPECT_EQ("dynamic_rates", cp.id); -} - -TEST(ClientConfig, ItDoesNotGetGeneratedFromStringIfNotValidMatch) -{ - bool exception = false; - - try { - remote_config::config_path::from_path(""); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path("invalid"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path("datadog/55/APM_SAMPLING/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "datadog/55/APM_SAMPLING//config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "datadog/aa/APM_SAMPLING/dynamic_rates/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); - exception = false; - try { - remote_config::config_path::from_path( - "something/APM_SAMPLING/dynamic_rates/config"); - } catch (remote_config::invalid_path e) { - exception = true; - } - EXPECT_TRUE(exception); -} - -TEST_F(RemoteConfigClient, ItReturnsErrorWhenClientConfigPathCantBeParsed) -{ - std::string invalid_path = "invalid/path/dynamic_rates/config"; - std::string response = generate_example_response({invalid_path}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error( - request_sent, true, "error parsing path " + invalid_path)); -} - -TEST_F(RemoteConfigClient, ItReturnsErrorIfProductOnPathNotRequested) -{ - std::string path_of_no_requested_product = - "datadog/2/APM_SAMPLING/dynamic_rates/config"; - std::string response = - generate_example_response({path_of_no_requested_product}); - - auto api = std::make_unique(); - std::string request_sent; - EXPECT_CALL(*api, get_configs) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings)); - api_client.register_runtime_id(runtime_id); - - // Validate first request does not contain any error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, false, "")); - - // Validate second request contains error - EXPECT_FALSE(api_client.poll()); - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "received config " + path_of_no_requested_product + - " for a " - "product that was not requested")); -} - -TEST_F(RemoteConfigClient, ItGeneratesClientStateAndCacheFromResponse) -{ - auto api = std::make_unique(); - - std::string first_request = ""; - std::string second_request = ""; - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(DoAll(testing::SaveArg<0>(&first_request), - Return(generate_example_response(paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&second_request), - Return(generate_example_response(paths)))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - // First call should not contain state neither cache - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(first_request)); - // Second call. This should contain state and cache from previous - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(second_request)); -} - -TEST_F(RemoteConfigClient, WhenANewConfigIsAddedItCallsOnUpdateOnPoll) -{ - auto api = std::make_unique(); - - std::string response = generate_example_response({first_path}); - - EXPECT_CALL(*api, get_configs(_)).Times(1).WillOnce(Return(response)); - - std::string content = test_helpers::raw_from_path(first_path); - std::unordered_map hashes = { - std::pair( - "sha256", test_helpers::sha256_from_path(first_path))}; - remote_config::config expected_config = {first_product_product, - first_product_id, content, first_path, hashes, - test_helpers::version_from_path(first_path), - test_helpers::length_from_path(first_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(1); - EXPECT_CALL(*listener01, on_update(expected_config)).Times(1); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener01, commit()).Times(1); - listener01->name = first_product_product; - - // Product on response - auto listener_called_no_configs01 = std::make_shared(); - EXPECT_CALL(*listener_called_no_configs01, init()).Times(1); - EXPECT_CALL(*listener_called_no_configs01, on_update(_)).Times(0); - EXPECT_CALL(*listener_called_no_configs01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener_called_no_configs01, commit()).Times(1); - std::string product_str_not_in_response = "NOT_IN_RESPONSE"; - listener_called_no_configs01->name = product_str_not_in_response; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client(id, std::move(api), std::move(sid), - std::move(settings), {listener01, listener_called_no_configs01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, WhenAConfigDissapearOnFollowingPollsItCallsToUnApply) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string response02 = generate_example_response({second_path}); - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(Return(response01)) - .WillOnce(Return(response02)); - - std::string content01 = test_helpers::raw_from_path(first_path); - std::unordered_map hashes01 = { - std::pair( - "sha256", test_helpers::sha256_from_path(first_path))}; - remote_config::config expected_config01 = {first_product_product, - first_product_id, content01, first_path, hashes01, - test_helpers::version_from_path(first_path), - test_helpers::length_from_path(first_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - remote_config::config expected_config01_at_unapply = expected_config01; - expected_config01_at_unapply.apply_state = - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED; - - std::string content02 = test_helpers::raw_from_path(second_path); - std::unordered_map hashes02 = { - std::pair( - "sha256", test_helpers::sha256_from_path(second_path))}; - remote_config::config expected_config02 = {first_product_product, - second_product_id, content02, second_path, hashes02, - test_helpers::version_from_path(second_path), - test_helpers::length_from_path(second_path), - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(2); - // First poll expectations - EXPECT_CALL(*listener01, on_update(expected_config01)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - // Second poll expectations - EXPECT_CALL(*listener01, on_update(expected_config02)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(expected_config01_at_unapply)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, commit()).Times(2); - // First poll expectations - listener01->name = first_product_product; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), {listener01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); -} - -TEST_F( - RemoteConfigClient, WhenAConfigGetsUpdatedOnFollowingPollsItCallsToUnUpdate) -{ - auto api = std::make_unique(); - - std::string response01( - "{\"roots\": [], \"targets\": " - "\"eyAgIAogICAgInNpZ25lZCI6IHsKICAgICAgICAiX3R5cGUiOiAidGFyZ2V0cyIsCiAg" - "ICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIj" - "ogInNvbWV0aGluZyIKICAgICAgICB9LAogICAgICAgICJ0YXJnZXRzIjogewogICAgICAg" - "ICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZpZyI6IH" - "sKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYi" - "OiAzNjc0MAogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOi" - "B7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJj" - "MGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgIC" - "AgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogICAgICAg" - "ICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn" - "0=\", \"target_files\": [{\"path\": " - "\"datadog/2/APM_SAMPLING/dynamic_rates/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"} ], " - "\"client_configs\": " - "[\"datadog/2/APM_SAMPLING/dynamic_rates/config\"] " - "}"); - - std::string response02( - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduZWQiOiB7CiAgICAgICAgICAgICAgICAiX3R5cGUiOiAidGFy" - "Z2V0cyIsCiAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgIC" - "AgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAic29tZXRoaW5nIgogICAgICAgICAg" - "ICAgICAgfSwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgICAgICAgIC" - "AgICAgICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZp" - "ZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogIC" - "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzNjc0MAogICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICJzaGEyNTYiOiAiYW5vdGhlcl9oYXNoX2hlcmUiCiAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgIC" - "AgICAgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAg" - "ICB9Cn0=\", \"target_files\": [{\"path\": " - "\"datadog/2/APM_SAMPLING/dynamic_rates/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"} ], " - "\"client_configs\": " - "[\"datadog/2/APM_SAMPLING/dynamic_rates/config\"] " - "}"); - - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillOnce(Return(response01)) - .WillOnce(Return(response02)); - - std::string product_str = "APM_SAMPLING"; - std::string product_str_01 = product_str; - std::string product_str_02 = product_str; - std::string id_product = "dynamic_rates"; - std::string id_product_01 = id_product; - std::string id_product_02 = id_product; - std::string path = "datadog/2/APM_SAMPLING/dynamic_rates/config"; - std::string path_01 = path; - std::string path_02 = path; - std::string content = - "UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo="; - std::string content_01 = content; - std::string content_02 = content; - std::unordered_map hashes_01 = {std::pair< - std::string, std::string>("sha256", - "07465cece47e4542abc0da040d9ebb42ec97224920d6870651dc3316528609d5")}; - remote_config::config expected_config = {product_str_01, id_product_01, - content_01, path_01, hashes_01, 36740, 66399, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - std::unordered_map hashes_02 = { - std::pair("sha256", "another_hash_here")}; - remote_config::config expected_config_02 = {product_str_02, id_product_02, - content_02, path_02, hashes_02, 36740, 66399, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - - // Product on response - auto listener01 = std::make_shared(); - EXPECT_CALL(*listener01, init()).Times(2); - // Second poll expectations - EXPECT_CALL(*listener01, on_update(expected_config_02)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - // First poll expectations - EXPECT_CALL(*listener01, on_update(expected_config)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*listener01, on_unapply(_)).Times(0); - EXPECT_CALL(*listener01, commit()).Times(2); - listener01->name = apm_sampling; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), {listener01}); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); -} - -TEST_F(RemoteConfigClient, FilesThatAreInCacheAreUsedWhenNotInTargetFiles) -{ - auto api = std::make_unique(); - - std::string first_request = ""; - std::string second_request = ""; - std::string third_request = ""; - - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(DoAll(testing::SaveArg<0>(&first_request), - Return(generate_example_response(paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&second_request), - Return(generate_example_response(paths, {}, paths)))) - .WillOnce(DoAll(testing::SaveArg<0>(&third_request), - Return(generate_example_response(paths, {}, paths)))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // First call should not contain state neither cache - EXPECT_EQ(sort_arrays(generate_request_serialized(false, false)), - sort_arrays(first_request)); - // Second call. Since this call has cache, response comes without - // target_files - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(second_request)); - // Third call. Cache and state should be kept even though - // target_files came empty on second - EXPECT_EQ(sort_arrays(generate_request_serialized(true, true)), - sort_arrays(third_request)); -} - -TEST_F(RemoteConfigClient, NotTrackedFilesAreDeletedFromCache) -{ - auto api = std::make_unique(); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(generate_example_response(paths))) - .WillOnce(Return(generate_example_response({}))) - .WillOnce(DoAll(testing::SaveArg<0>(&request_sent), - Return(generate_example_response({})))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // Lets validate cached_target_files is empty - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto output_itr = serialized_doc.FindMember("cached_target_files"); - - EXPECT_FALSE(output_itr == serialized_doc.MemberEnd()); - EXPECT_TRUE(rapidjson::kArrayType == output_itr->value.GetType()); - EXPECT_EQ(0, output_itr->value.GetArray().Size()); -} - -TEST_F(RemoteConfigClient, TestHashIsDifferentFromTheCache) -{ - auto api = std::make_unique(); - - std::string first_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [{\"path\": " - "\"employee/ASM_FEATURES/2.test1.config/config\", \"raw\": " - "\"some_raw=\"}" - "], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"]" - "}"; - - // This response has a cached file with different hash, it should not be - // used - std::string second_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX290aGVyX2hhc2giCiAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9" - "Cn0=" - "\", \"target_files\": [], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"] }"; - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(first_response)) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(second_response))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_FALSE(api_client.poll()); - EXPECT_FALSE(api_client.poll()); - - EXPECT_TRUE(validate_request_has_error(request_sent, true, - "missing config employee/ASM_FEATURES/2.test1.config/config in " - "target files and in cache files")); -} - -TEST_F(RemoteConfigClient, TestWhenFileGetsFromCacheItsCachedLenUsed) -{ - auto api = std::make_unique(); - - std::string first_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [{\"path\": " - "\"employee/ASM_FEATURES/2.test1.config/config\", \"raw\": " - "\"some_raw=\"}" - "], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"]" - "}"; - - // This response has a cached file with different len and version, it - // should not be used - std::string second_response = - "{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICAg" - "ICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZG" - "Y3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMj" - "JiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2" - "NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgIC" - "AgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgICAgICAg" - "ICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3RvbSI6IH" - "sKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNv" - "bWV0aGluZyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcy" - "I6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJz" - "aW9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZW1wbG95ZWUvQVNNX0ZFQVRVUkVTLzIudGVzdDEuY29uZmln" - "L2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIj" - "ogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiA0CiAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgInNoYTI1NiI6ICJzb21lX2hhc2giCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNTUKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgICB9Cn0=\", " - "\"target_files\": [], \"client_configs\": " - "[\"employee/ASM_FEATURES/2.test1.config/config\"] }"; - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(3) - .WillOnce(Return(first_response)) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(second_response))) - .RetiresOnSaturation(); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - // Lets validate cached_target_files is empty - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto output_itr = serialized_doc.FindMember("cached_target_files"); - - auto files_cached = output_itr->value.GetArray(); - EXPECT_FALSE(output_itr == serialized_doc.MemberEnd()); - EXPECT_TRUE(rapidjson::kArrayType == output_itr->value.GetType()); - EXPECT_EQ(1, files_cached.Size()); - - auto len_itr = files_cached[0].FindMember("length"); - EXPECT_FALSE(len_itr == files_cached[0].MemberEnd()); - EXPECT_TRUE(rapidjson::kNumberType == len_itr->value.GetType()); - EXPECT_EQ(41, len_itr->value.GetInt()); -} - -rapidjson::GenericArray>::ValueType> -get_config_states(const rapidjson::Document &serialized_doc) -{ - return serialized_doc.FindMember("client") - ->value.FindMember("state") - ->value.FindMember("config_states") - ->value.GetArray(); -} - -TEST_F(RemoteConfigClient, ProductsWithAListenerAcknowledgeUpdates) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - - auto config_states_arr = get_config_states(serialized_doc); - - EXPECT_EQ(1, config_states_arr.Size()); - EXPECT_EQ( - (int)remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, - config_states_arr[0].FindMember("apply_state")->value.GetInt()); - EXPECT_EQ("", - std::string( - config_states_arr[0].FindMember("apply_error")->value.GetString())); -} - -TEST_F(RemoteConfigClient, WhenAListerCanProccesAnUpdateTheConfigStateGetsError) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(2) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - auto listener = std::make_shared(); - EXPECT_CALL(*listener, init()).Times(2); - EXPECT_CALL(*listener, on_update(_)) - .WillRepeatedly(mock::ThrowErrorApplyingConfig()); - EXPECT_CALL(*listener, commit()).Times(2); - - listener->name = first_product_product; - std::vector listeners = { - listener}; - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client(id, std::move(api), std::move(sid), - std::move(settings), std::move(listeners)); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - - auto config_states_arr = get_config_states(serialized_doc); - - EXPECT_EQ(1, config_states_arr.Size()); - EXPECT_EQ((int)remote_config::protocol::config_state::applied_state::ERROR, - config_states_arr[0].FindMember("apply_state")->value.GetInt()); - EXPECT_EQ("some error", - std::string( - config_states_arr[0].FindMember("apply_error")->value.GetString())); -} - -TEST_F(RemoteConfigClient, OneClickActivationIsSetAsCapability) -{ - auto api = std::make_unique(); - - std::string response01 = generate_example_response({first_path}); - - std::string request_sent; - EXPECT_CALL(*api, get_configs(_)) - .Times(1) - .WillRepeatedly( - DoAll(testing::SaveArg<0>(&request_sent), Return(response01))); - - service_identifier sid{ - service, extra_services, env, tracer_version, app_version, runtime_id}; - dds::test_client api_client( - id, std::move(api), std::move(sid), std::move(settings), listeners_); - api_client.register_runtime_id(runtime_id); - - EXPECT_TRUE(api_client.poll()); - - rapidjson::Document serialized_doc; - serialized_doc.Parse(request_sent); - auto capabilities = - serialized_doc.FindMember("client")->value.FindMember("capabilities"); - - EXPECT_STREQ("AAI=", capabilities->value.GetString()); -} -} // namespace dds diff --git a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp index 5d1af34b73..35c6e249ba 100644 --- a/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/asm_features_listener_test.cpp @@ -5,6 +5,7 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "../../common.hpp" +#include "../mocks.hpp" #include "base64.h" #include "remote_config/exception.hpp" #include "remote_config/listeners/asm_features_listener.hpp" @@ -12,22 +13,12 @@ namespace dds { -remote_config::config get_config(const std::string &content, bool encode = true) -{ - std::string encoded_content = content; - if (encode) { - encoded_content = base64_encode(content); - } - - return {"some product", "some id", encoded_content, "some path", {}, 123, - 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} +namespace mock = remote_config::mock; remote_config::config get_config_with_status(std::string status) { - return get_config("{\"asm\":{\"enabled\":" + status + "}}"); + return mock::get_config( + "ASM_FEATURES", "{\"asm\":{\"enabled\":" + status + "}}"); } remote_config::config get_enabled_config(bool as_string = true) @@ -138,7 +129,7 @@ TEST(RemoteConfigAsmFeaturesListener, std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; remote_config::config non_base_64_content_config = - get_config(invalid_content, false); + mock::get_config("ASM_FEATURES", invalid_content); try { listener.on_update(non_base_64_content_config); @@ -160,7 +151,8 @@ TEST(RemoteConfigAsmFeaturesListener, auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); std::string invalid_content = "invalidJsonContent"; - remote_config::config config = get_config(invalid_content); + remote_config::config config = + mock::get_config("ASM_FEATURES", invalid_content); try { listener.on_update(config); @@ -181,7 +173,8 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmKeyMissing) "Invalid config json encoded contents: asm key missing or invalid"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config asm_key_missing = get_config("{}"); + remote_config::config asm_key_missing = + mock::get_config("ASM_FEATURES", "{}"); try { listener.on_update(asm_key_missing); @@ -201,7 +194,8 @@ TEST(RemoteConfigAsmFeaturesListener, ListenerThrowsAnErrorWhenAsmIsNotValid) "Invalid config json encoded contents: asm key missing or invalid"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config invalid_asm_key = get_config("{ \"asm\": 123}"); + remote_config::config invalid_asm_key = + mock::get_config("ASM_FEATURES", "{ \"asm\": 123}"); try { listener.on_update(invalid_asm_key); @@ -222,7 +216,8 @@ TEST( "Invalid config json encoded contents: enabled key missing"; auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); - remote_config::config enabled_key_missing = get_config("{ \"asm\": {}}"); + remote_config::config enabled_key_missing = + mock::get_config("ASM_FEATURES", "{ \"asm\": {}}"); try { listener.on_update(enabled_key_missing); @@ -244,7 +239,7 @@ TEST(RemoteConfigAsmFeaturesListener, auto remote_config_service = std::make_shared(); remote_config::asm_features_listener listener(remote_config_service); remote_config::config enabled_key_invalid = - get_config("{ \"asm\": { \"enabled\": 123}}"); + mock::get_config("ASM_FEATURES", "{ \"asm\": { \"enabled\": 123}}"); try { listener.on_update(enabled_key_invalid); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp index 987d0edfd0..43c0974819 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_aggregator_test.cpp @@ -9,12 +9,13 @@ #include "json_helper.hpp" #include "remote_config/exception.hpp" #include "remote_config/listeners/config_aggregators/asm_aggregator.hpp" +#include "remote_config/product.hpp" #include namespace dds::remote_config { namespace { -using mock::generate_config; +using mock::get_config; const std::string waf_rule = R"({"version":"2.1","rules":[{"id":"1","name":"rule1","tags":{"type":"flow1","category":"category1"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg1","key_path":[]}],"regex":".*"}}]}]})"; @@ -59,8 +60,8 @@ TEST(RemoteConfigAsmAggregator, EmptyConfigThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(generate_config("ASM", {})), - remote_config::error_applying_config); + EXPECT_THROW(aggregator.add(get_config("ASM", {})), + std::runtime_error); // mmap failure aggregator.aggregate(doc); @@ -91,7 +92,7 @@ TEST(RemoteConfigAsmAggregator, IncorrectTypeThrows) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - EXPECT_THROW(aggregator.add(generate_config("ASM", rule_override)), + EXPECT_THROW(aggregator.add(get_config("ASM", rule_override)), remote_config::error_applying_config); aggregator.aggregate(doc); @@ -121,7 +122,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideEmpty) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -150,7 +151,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideSingleConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -193,10 +194,10 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideMultipleConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -241,15 +242,15 @@ TEST(RemoteConfigAsmAggregator, RulesOverrideIgnoreInvalidConfigs) rapidjson::Document doc(rapidjson::kObjectType); remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); { const std::string invalid = R"({"rules_override": {"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -295,7 +296,7 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -335,9 +336,9 @@ TEST(RemoteConfigAsmAggregator, RulesOverridesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); - aggregator.add(generate_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); + aggregator.add(get_config("ASM", rule_override)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -384,7 +385,7 @@ TEST(RemoteConfigAsmAggregator, ActionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -413,9 +414,9 @@ TEST(RemoteConfigAsmAggregator, ActionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -444,15 +445,15 @@ TEST(RemoteConfigAsmAggregator, ActionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); { const std::string invalid = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -482,9 +483,9 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -507,7 +508,7 @@ TEST(RemoteConfigAsmAggregator, ActionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", action_definitions)); + aggregator.add(get_config("ASM", action_definitions)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -537,7 +538,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -566,10 +567,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -598,15 +599,15 @@ TEST(RemoteConfigAsmAggregator, ExclusionsIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); { const std::string invalid = R"({"exclusions": {"id":1,"rules_target":[{"rule_id":1}]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -636,10 +637,10 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -662,7 +663,7 @@ TEST(RemoteConfigAsmAggregator, ExclusionsConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -692,7 +693,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesSingleConfig) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -721,15 +722,15 @@ TEST(RemoteConfigAsmAggregator, CustomRulesMultipleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); { const std::string invalid = R"({"custom_rules": {"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM", invalid)), remote_config::error_applying_config); } - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -758,10 +759,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesIgnoreInvalidConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -791,10 +792,10 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -817,7 +818,7 @@ TEST(RemoteConfigAsmAggregator, CustomRulesConfigCycling) { rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -847,7 +848,7 @@ TEST(RemoteConfigAsmAggregator, AllSingleConfigs) remote_config::asm_aggregator aggregator; aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); aggregator.aggregate(doc); const auto &overrides = doc["rules_override"]; @@ -876,23 +877,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); } aggregator.aggregate(doc); @@ -922,23 +923,23 @@ TEST(RemoteConfigAsmAggregator, IgnoreInvalidOverlappingConfigs) { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}],"custom_rules":[{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}]})"; - aggregator.add(generate_config("ASM", update)); + aggregator.add(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"tags": {"confidence": "1"}}], "on_match": ["block"]}],"actions": {"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } { const std::string update = R"({"custom_rules":{"id":"1","name":"custom_rule1","tags":{"type":"custom","category":"custom"},"conditions":[{"operator":"match_regex","parameters":{"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}],"on_match":["block"]}})"; - EXPECT_THROW(aggregator.add(generate_config("ASM", update)), + EXPECT_THROW(aggregator.add(get_config("ASM", update)), remote_config::error_applying_config); } aggregator.aggregate(doc); diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp index 30a17de218..776bc51efd 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_data_aggregator_test.cpp @@ -15,7 +15,7 @@ namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; struct test_rule_data_data { std::optional expiration; @@ -62,7 +62,7 @@ remote_config::config get_rules_data(std::vector data) rapidjson::Writer writer(buffer); document.Accept(writer); - return generate_config("ASM_DATA", buffer.get_string_ref()); + return get_config("ASM_DATA", buffer.get_string_ref()); } TEST(RemoteConfigAsmDataAggregator, ParseRulesData) @@ -335,7 +335,7 @@ TEST(RemoteConfigAsmDataAggregator, IgnoreInvalidConfigs) { const std::string &invalid = R"({"rules_data": [{"id": "id01", "data": [{"expiration": 11, "value": "1.2.3.5"} ], "type": "ip_with_expiration"},{"data": [{"expiration": 11111, "value": "1.2.3.4"} ], "type": "ip_with_expiration"}]})"; - EXPECT_THROW(aggregator.add(generate_config("ASM_DATA", invalid)), + EXPECT_THROW(aggregator.add(get_config("ASM_DATA", invalid)), remote_config::error_applying_config); } aggregator.aggregate(doc); @@ -414,8 +414,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotInBase64) { std::string invalid_content = "&&&"; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, false); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -438,8 +437,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfContentNotValidJsonContent) { std::string invalid_content = "InvalidJsonContent"; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -464,8 +462,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoRulesDataKey) std::string expected_error_message = "Invalid config json contents: rules_data key missing or " "invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -490,8 +487,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataNotArray) std::string expected_error_message = "Invalid config json contents: rules_data key missing or " "invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -515,8 +511,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfRulesDataEntryNotObject) std::string invalid_content = "{\"rules_data\": [\"invalid\"] }"; std::string expected_error_message = "Invalid config json contents: rules_data entry invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -543,8 +538,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoId) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -571,8 +565,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfIdNotString) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -599,8 +592,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoType) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -627,8 +619,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfTypeNotString) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -654,8 +645,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfNoData) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -682,8 +672,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataNotArray) std::string expected_error_message = "Invalid config json contents: rules_data missing a field or " "field is invalid"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -708,8 +697,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataEntryNotObject) R"({"rules_data": [{"data": [ "invalid" ], "id": "some_id", "type": "ip_with_expiration"} ] })"; std::string expected_error_message = "Invalid config json contents: Entry on data not a valid object"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -733,8 +721,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataExpirationHasInvalidType) std::string invalid_content = R"({"rules_data": [{"data": [{"expiration": "invalid", "value": "1.2.3.4"}], "id": "some_id", "type": "data_with_expiration"}]})"; std::string expected_error_message = "Invalid type for expiration entry"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -759,8 +746,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueMissing) "{\"rules_data\": [{\"data\": [{\"expiration\": 11} ], \"id\": " "\"some_id\", \"type\": \"data_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; @@ -786,8 +772,7 @@ TEST(RemoteConfigAsmDataAggregator, ThrowsAnErrorIfDataValueHasInvalidType) "\"value\": 1234} ], \"id\": \"some_id\", \"type\": " "\"ip_with_expiration\"} ] }"; std::string expected_error_message = "Invalid value of data entry"; - remote_config::config config = - generate_config("ASM_DATA", invalid_content, true); + remote_config::config config = get_config("ASM_DATA", invalid_content); remote_config::asm_data_aggregator aggregator; diff --git a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp index 964ab22b3b..1c9dd18f23 100644 --- a/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/config_aggregators/asm_dd_aggregator_test.cpp @@ -16,7 +16,7 @@ const std::string waf_rule = namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; TEST(RemoteConfigAsmDdAggregator, AddConfig) { @@ -24,7 +24,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.add(generate_config("ASM_DD", waf_rule)); + aggregator.add(get_config("ASM_DD", waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -38,7 +38,7 @@ TEST(RemoteConfigAsmDdAggregator, RemoveConfig) rapidjson::Document doc(rapidjson::kObjectType); aggregator.init(&doc.GetAllocator()); - aggregator.remove(generate_config("ASM_DD", waf_rule)); + aggregator.remove(get_config("ASM_DD", waf_rule)); aggregator.aggregate(doc); const auto &rules = doc["rules"]; @@ -55,8 +55,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidBase64Content) std::string invalid_content = "&&&"; std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - generate_config("ASM_DD", invalid_content, false); + remote_config::config config = get_config("ASM_DD", invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); @@ -79,8 +78,7 @@ TEST(RemoteConfigAsmDdAggregator, AddConfigInvalidJsonContent) std::string invalid_content = "InvalidJsonContent"; std::string error_message = ""; std::string expected_error_message = "Invalid config contents"; - remote_config::config config = - generate_config("ASM_DD", invalid_content, true); + remote_config::config config = get_config("ASM_DD", invalid_content); remote_config::asm_dd_aggregator aggregator; rapidjson::Document doc(rapidjson::kObjectType); diff --git a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp index 44905a1c37..1e3a3d4bb1 100644 --- a/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp +++ b/appsec/tests/helper/remote_config/listeners/engine_listener_test.cpp @@ -7,11 +7,13 @@ #include "../../common.hpp" #include "../mocks.hpp" #include "base64.h" +#include "engine.hpp" #include "json_helper.hpp" #include "remote_config/exception.hpp" #include "remote_config/listeners/engine_listener.hpp" #include "remote_config/product.hpp" #include "subscriber/waf.hpp" +#include #include const std::string waf_rule = @@ -19,7 +21,7 @@ const std::string waf_rule = namespace dds::remote_config { -using mock::generate_config; +using mock::get_config; namespace { @@ -55,7 +57,7 @@ TEST(RemoteConfigEngineListener, UnknownConfig) remote_config::engine_listener listener(engine); listener.init(); - EXPECT_THROW(listener.on_update(generate_config("UNKNOWN", waf_rule)), + EXPECT_THROW(listener.on_update(get_config("UNKNOWN", waf_rule)), error_applying_config); listener.commit(); } @@ -72,7 +74,7 @@ TEST(RemoteConfigEngineListener, RuleUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); listener.commit(); { @@ -102,7 +104,7 @@ TEST(RemoteConfigEngineListener, RuleUpdateFallback) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_unapply(generate_config("ASM_DD", waf_rule)); + listener.on_unapply(get_config("ASM_DD", waf_rule)); listener.commit(); { @@ -135,7 +137,7 @@ TEST(RemoteConfigEngineListener, RulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -176,8 +178,8 @@ TEST(RemoteConfigEngineListener, RulesAndRulesOverrideUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -225,7 +227,7 @@ TEST(RemoteConfigEngineListener, ExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -267,8 +269,8 @@ TEST(RemoteConfigEngineListener, RulesAndExclusionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -317,7 +319,7 @@ TEST(RemoteConfigEngineListener, ActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -360,8 +362,8 @@ TEST(RemoteConfigEngineListener, RulesAndActionsUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -412,7 +414,7 @@ TEST(RemoteConfigEngineListener, CustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -457,8 +459,8 @@ TEST(RemoteConfigEngineListener, RulesAndCustomRulesUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM", update)); listener.commit(); { @@ -506,7 +508,7 @@ TEST(RemoteConfigEngineListener, RulesDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config("ASM_DATA", update)); listener.commit(); { @@ -540,8 +542,8 @@ TEST(RemoteConfigEngineListener, RulesAndRuleDataUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DATA", update)); listener.commit(); { @@ -578,11 +580,11 @@ TEST(RemoteConfigEngineListener, FullUpdate) remote_config::engine_listener listener(engine); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config("ASM_DATA", update)); } { const std::string update = @@ -591,23 +593,23 @@ TEST(RemoteConfigEngineListener, FullUpdate) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -634,11 +636,11 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) remote_config::engine_listener listener(engine, create_sample_rules_ok()); listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config("ASM_DATA", update)); } listener.commit(); @@ -673,12 +675,12 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -714,17 +716,17 @@ TEST(RemoteConfigEngineListener, MultipleInitCommitUpdates) } listener.init(); - listener.on_update(generate_config("ASM_DD", waf_rule)); + listener.on_update(get_config("ASM_DD", waf_rule)); { const std::string update = R"({"actions": [{"id": "redirect", "type": "redirect_request", "parameters": {"status_code": "303", "location": "localhost"}}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } { const std::string update = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); } listener.commit(); @@ -776,7 +778,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdate) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(rules, meta, metrics)); { @@ -797,7 +799,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdate) remote_config::engine_listener listener(e); listener.init(); - listener.on_update(generate_config("ASM_DD", new_rules)); + listener.on_update(get_config("ASM_DD", new_rules)); listener.commit(); { @@ -823,7 +825,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdateFallback) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(rules, meta, metrics)); { @@ -840,7 +842,7 @@ TEST(RemoteConfigEngineListener, EngineRuleUpdateFallback) remote_config::engine_listener listener(e, create_sample_rules_ok()); listener.init(); - listener.on_unapply(generate_config("ASM_DD", "")); + listener.on_unapply(get_config("ASM_DD", "")); listener.commit(); { @@ -859,7 +861,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideUpdateDisableRule) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -877,7 +879,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideUpdateDisableRule) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"rule_id": "1"}], "enabled":"false"}]})"; - listener.on_update(generate_config("ASM", rule_override)); + listener.on_update(get_config("ASM", rule_override)); { auto ctx = engine->get_context(); @@ -906,7 +908,7 @@ TEST(RemoteConfigEngineListener, RuleOverrideUpdateSetOnMatch) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -926,7 +928,7 @@ TEST(RemoteConfigEngineListener, RuleOverrideUpdateSetOnMatch) const std::string rule_override = R"({"rules_override": [{"rules_target": [{"tags": {"type": "flow1"}}], "on_match": ["block"]}]})"; - listener.on_update(generate_config("ASM", rule_override)); + listener.on_update(get_config("ASM", rule_override)); { auto ctx = engine->get_context(); @@ -957,7 +959,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideAndActionsUpdate) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -979,7 +981,7 @@ TEST(RemoteConfigEngineListener, EngineRuleOverrideAndActionsUpdate) {"status_code": "303", "location": "localhost"}}],"rules_override": [{"rules_target": [{"rule_id": "1"}], "on_match": ["redirect"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1010,7 +1012,7 @@ TEST(RemoteConfigEngineListener, EngineExclusionsUpdatePasslistRule) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -1029,7 +1031,7 @@ TEST(RemoteConfigEngineListener, EngineExclusionsUpdatePasslistRule) const std::string update = R"({"exclusions":[{"id":1,"rules_target":[{"rule_id":1}]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1058,7 +1060,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) std::map meta; std::map metrics; - auto engine{dds::engine::create()}; + std::shared_ptr engine{dds::engine::create()}; engine->subscribe(waf::instance::from_string(waf_rule, meta, metrics)); remote_config::engine_listener listener(engine); @@ -1090,7 +1092,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) "category":"custom"},"conditions":[{"operator":"match_regex","parameters": {"inputs":[{"address":"arg3","key_path":[]}],"regex":"^custom.*"}}], "on_match":["block"]}]})"; - listener.on_update(generate_config("ASM", update)); + listener.on_update(get_config("ASM", update)); { auto ctx = engine->get_context(); @@ -1134,7 +1136,7 @@ TEST(RemoteConfigEngineListener, EngineCustomRulesUpdate) } listener.init(); - listener.on_update(generate_config("ASM", R"({"custom_rules":[]})")); + listener.on_update(get_config("ASM", R"({"custom_rules":[]})")); listener.commit(); { @@ -1168,7 +1170,7 @@ TEST(RemoteConfigEngineListener, EngineRuleDataUpdate) std::map meta; std::map metrics; - auto e{engine::create()}; + std::shared_ptr e{engine::create()}; e->subscribe(waf::instance::from_string(waf_rule_with_data, meta, metrics)); remote_config::engine_listener listener(e); @@ -1186,7 +1188,7 @@ TEST(RemoteConfigEngineListener, EngineRuleDataUpdate) const std::string update = R"({"rules_data":[{"id":"blocked_ips","type":"ip_with_expiration","data":[{"value":"1.2.3.4","expiration":0}]}]})"; - listener.on_update(generate_config("ASM_DATA", update)); + listener.on_update(get_config("ASM_DATA", update)); { auto ctx = e->get_context(); diff --git a/appsec/tests/helper/remote_config/mocks.cpp b/appsec/tests/helper/remote_config/mocks.cpp new file mode 100644 index 0000000000..6aa0988781 --- /dev/null +++ b/appsec/tests/helper/remote_config/mocks.cpp @@ -0,0 +1,41 @@ +#include "mocks.hpp" +#include +#include +#include +#include + +namespace dds::remote_config::mock { +remote_config::config get_config( + std::string_view product_name, const std::string &content) +{ + static std::atomic id{0}; + + const int cur_id = id.fetch_add(1, std::memory_order_relaxed); + + std::string shm_path{"/test-rc-file-" + std::to_string(cur_id)}; + ::shm_unlink(shm_path.c_str()); + int shm_fd = ::shm_open(shm_path.c_str(), O_CREAT | O_RDWR, 0600); + if (shm_fd == -1) { + std::abort(); + } + ::ftruncate(shm_fd, content.size()); + + if (content.size() > 0) { + void *mem = + ::mmap(nullptr, content.size(), PROT_WRITE, MAP_SHARED, shm_fd, 0); + if (mem == MAP_FAILED) { + std::abort(); + } + std::copy_n(content.data(), content.size(), static_cast(mem)); + + if (::munmap(mem, content.size()) != 0) { + std::abort(); + } + } + + ::close(shm_fd); + + return {shm_path, std::string{"datadog/2/"} + std::string{product_name} + + "/foobar_" + std::to_string(cur_id) + "/config"}; +} +} // namespace dds::remote_config::mock diff --git a/appsec/tests/helper/remote_config/mocks.hpp b/appsec/tests/helper/remote_config/mocks.hpp index c142c89bf1..f34f1d7701 100644 --- a/appsec/tests/helper/remote_config/mocks.hpp +++ b/appsec/tests/helper/remote_config/mocks.hpp @@ -11,7 +11,6 @@ #include "engine.hpp" #include "remote_config/client.hpp" #include "remote_config/config.hpp" -#include "service_identifier.hpp" namespace dds::remote_config::mock { @@ -30,30 +29,7 @@ class engine : public dds::engine { static auto create() { return std::shared_ptr(new engine()); } }; -class client : public remote_config::client { -public: - client(service_identifier sid) - : remote_config::client(nullptr, std::move(sid), {}) - {} - ~client() override = default; - MOCK_METHOD0(poll, bool()); - MOCK_METHOD0(is_remote_config_available, bool()); - MOCK_METHOD(void, register_runtime_id, (const std::string &id), (override)); - MOCK_METHOD( - void, unregister_runtime_id, (const std::string &id), (override)); -}; - -inline remote_config::config generate_config( - const std::string &product, const std::string &content, bool encode = true) -{ - std::string encoded_content = content; - if (encode) { - encoded_content = base64_encode(content); - } - - return {product, "id", encoded_content, "path", {}, 123, 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} +remote_config::config get_config( + std::string_view product_name, const std::string &content); } // namespace dds::remote_config::mock diff --git a/appsec/tests/helper/remote_config/parser_test.cpp b/appsec/tests/helper/remote_config/parser_test.cpp deleted file mode 100644 index 64765834e4..0000000000 --- a/appsec/tests/helper/remote_config/parser_test.cpp +++ /dev/null @@ -1,1227 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include -#include -#include - -#include "../common.hpp" -#include "remote_config/protocol/tuf/parser.hpp" - -namespace dds { - -std::string get_example_response() -{ - std::string response( - "{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5aWQi" - "OiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0Mz" - "EyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdj" - "Y2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZj" - "c2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5" - "MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl" - "90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICJv" - "cGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWlhKemFXOXVJam94TENKemRHRjBaU0k2ZX" - "lKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pKRFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQy" - "RllhM1Z6TURGMWVsUTFNM3BuZW1sU1RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMF" - "ZFV2pZM1VYVjVXRWxyYTJjeGIyTkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4" - "WFUzQlJiRFpOYWt4UFUyTnZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMT" - "kiCiAgICAgICAgfSwKICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5" - "WiIsCiAgICAgICAgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldH" - "MiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0" - "ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAg" - "ICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3NDY1Y2" - "VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4" - "NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcm" - "UwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYz" - "OTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFkb2cvMi9ERUJVRy9sdWtlLn" - "N0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAg" - "ICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImM2YThj" - "ZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZW" - "E3ZDg3NDAiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhl" - "cmUwMiIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMT" - "MKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVz" - "dDEuY29uZmlnL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgIC" - "AgICAgICAgICAgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAg" - "ICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZW" - "Q4MjU2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaG" - "hlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjog" - "NDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4Nz" - "E1NgogICAgfQp9\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - - return response; -} - -void assert_parser_error(std::function parser, - std::string payload, - remote_config::protocol::remote_config_parser_result result) -{ - remote_config::protocol::remote_config_parser_result error; - try { - parser(payload); - } catch (remote_config::protocol::parser_exception &e) { - error = e.get_error(); - } - - EXPECT_EQ(result, error); -} - -TEST(RemoteConfigParser, ItReturnsErrorWhenInvalidBodyIsGiven) -{ - assert_parser_error(remote_config::protocol::parse, "invalid_json", - remote_config::protocol::remote_config_parser_result::invalid_json); -} - -TEST(RemoteConfigParser, ParsingNonObjectPayloadThrowsException) -{ - assert_parser_error(remote_config::protocol::parse, "[]", - remote_config::protocol::remote_config_parser_result::invalid_response); -} - -TEST(RemoteConfigParser, TargetsFieldMustBeString) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": [], \"target_files\": [], \"client_configs\": [] }", - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_type); -} - -TEST(RemoteConfigParser, targetFilesFieldCanBeMissed) -{ - auto response = remote_config::protocol::parse( - "{\"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5" - "aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNz" - "FiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBm" - "NWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOG" - "IzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMy" - "NWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaW" - "duZWQiOiB7CiAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0" - "b20iOiB7CiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWl" - "hKemFXOXVJam94TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pK" - "RFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQyRllhM1Z6TURGMWVsUTFNM3BuZW1sU1" - "RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMFZFV2pZM1VYVjVXRWxyYTJjeGIy" - "TkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4WFUzQlJiRFpOYWt4UFUyTn" - "ZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMTkiCiAgICAgICAgfSwK" - "ICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgIC" - "AgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldHMiOiB7CiAg" - "ICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY2" - "9uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAg" - "ICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3" - "NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZG" - "MzMzE2NTI4NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hh" - "NTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIC" - "AibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFk" - "b2cvMi9ERUJVRy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgIC" - "AgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAg" - "ICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5" - "Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiLAogICAgICAgICAgIC" - "AgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMiIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVzdDEuY29uZmlnL2Nv" - "bmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAg" - "Imhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4Mj" - "U2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaG" - "FzaGhlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb2" - "4iOiAyNzQ4NzE1NgogICAgfQp9\", \"client_configs\": [] }"); - ASSERT_TRUE(response.target_files.empty()); -} - -TEST(RemoteConfigParser, targetFilesFieldMustBeArray) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": \"\", \"target_files\": \"\", \"client_configs\": [] }", - remote_config::protocol::remote_config_parser_result:: - target_files_field_invalid_type); -} - -TEST(RemoteConfigParser, clientConfigsFieldCanBeMissed) -{ - auto response = remote_config::protocol::parse( - "{\"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5" - "aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNz" - "FiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBm" - "NWY0YmZjMjdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOG" - "IzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMy" - "NWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaW" - "duZWQiOiB7CiAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICJjdXN0" - "b20iOiB7CiAgICAgICAgICAgICJvcGFxdWVfYmFja2VuZF9zdGF0ZSI6ICJleUoyWl" - "hKemFXOXVJam94TENKemRHRjBaU0k2ZXlKbWFXeGxYMmhoYzJobGN5STZXeUpTS3pK" - "RFZtdGxkRVJ6WVc1cFdrZEphMFphWkZKTlQyRllhM1Z6TURGMWVsUTFNM3BuZW1sU1" - "RHRTBQU0lzSWtJd1dtTTNUMUlyVWxWTGNuZE9iMFZFV2pZM1VYVjVXRWxyYTJjeGIy" - "TkhWV1IzZWtac1MwZERaRlU5SWl3aWVIRnFUbFV4VFV4WFUzQlJiRFpOYWt4UFUyTn" - "ZTVUoyYjNsU2VsWnJkelp6TkdFcmRYVndPV2d3UVQwaVhYMTkiCiAgICAgICAgfSwK" - "ICAgICAgICAiZXhwaXJlcyI6ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgIC" - "AgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgInRhcmdldHMiOiB7CiAg" - "ICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY2" - "9uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAg" - "ICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3" - "NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZG" - "MzMzE2NTI4NjA5ZDUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hh" - "NTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIC" - "AibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgImRhdGFk" - "b2cvMi9ERUJVRy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI6IHsKICAgICAgICAgICAgIC" - "AgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAg" - "ICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgIC" - "AgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5" - "Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiLAogICAgICAgICAgIC" - "AgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMiIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgfS" - "wKICAgICAgICAgICAgImVtcGxveWVlL0RFQlVHX0RELzIudGVzdDEuY29uZmlnL2Nv" - "bmZpZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAg" - "Imhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4Mj" - "U2NDdhZDBlYzZhNzg5OTE4ODkwNTY1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgz" - "Mzg5MTJkYWUiLAogICAgICAgICAgICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaG" - "FzaGhlcmUwMyIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVu" - "Z3RoIjogNDEKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb2" - "4iOiAyNzQ4NzE1NgogICAgfQp9\", \"target_files\": [] }"); - - ASSERT_TRUE(response.client_configs.empty()); -} - -TEST(RemoteConfigParser, clientConfigsFieldMustBeArray) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"targets\": \"\", \"target_files\": [], \"client_configs\": \"\" }", - remote_config::protocol::remote_config_parser_result:: - client_config_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - EXPECT_EQ(2, gcr.target_files.size()); - - auto target_files = gcr.target_files; - - EXPECT_EQ("employee/DEBUG_DD/2.test1.config/config", - target_files.find("employee/DEBUG_DD/2.test1.config/config") - ->second.path); - EXPECT_EQ("UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=", - target_files.find("employee/DEBUG_DD/2.test1.config/config") - ->second.raw); - - EXPECT_EQ("datadog/2/DEBUG/luke.steensen/config", - target_files.find("datadog/2/DEBUG/luke.steensen/config")->second.path); - EXPECT_EQ("aGVsbG8gdmVjdG9yIQ==", - target_files.find("datadog/2/DEBUG/luke.steensen/config")->second.raw); -} - -TEST(RemoteConfigParser, TargetFilesWithoutPathAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{ \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_missing); -} - -TEST(RemoteConfigParser, TargetFilesWithNonStringPathAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": [], \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesWithoutRawAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\"} ], " - "\"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_raw_field_missing); -} - -TEST(RemoteConfigParser, TargetFilesWithNonNonStringRawAreInvalid) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": []} ], " - "\"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_raw_field_invalid_type); -} - -TEST(RemoteConfigParser, TargetFilesMustBeObjects) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", \"target_files\": [ " - "\"invalid\", " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - target_files_object_invalid); -} - -TEST(RemoteConfigParser, ClientConfigsAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - EXPECT_EQ(2, gcr.client_configs.size()); - - auto client_configs = gcr.client_configs; - - EXPECT_EQ("datadog/2/DEBUG/luke.steensen/config", client_configs[0]); - EXPECT_EQ("employee/DEBUG_DD/2.test1.config/config", client_configs[1]); -} - -TEST(RemoteConfigParser, ClientConfigsMustBeStrings) -{ - assert_parser_error(remote_config::protocol::parse, - "{\"roots\": [], \"targets\": \"b2s=\", " - "\"target_files\": [], \"client_configs\": " - "[[\"invalid\"], " - "\"employee/DEBUG_DD/2.test1.config/config\"] }", - remote_config::protocol::remote_config_parser_result:: - client_config_field_invalid_entry); -} - -TEST(RemoteConfigParser, TargetsMustBeNotEmpty) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"\", " - "\"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_empty); -} - -TEST(RemoteConfigParser, TargetsnMustBeValidBase64Encoded) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"nonValid%Base64Here\", " - "\"target_files\": [{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_base64); -} - -TEST(RemoteConfigParser, TargetsDecodedMustBeValidJson) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": \"nonJsonHere\", \"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_field_invalid_json); -} - -TEST(RemoteConfigParser, SignedFieldOnTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5aWQ" - "iOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0" - "MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjM" - "jdjY2JkODBkOWM4NDU4ZDdkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1Yj" - "FkZjc2MWI4N2I2YzBlYjliMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZ" - "kYTg5MjU4MDkwMGYiCiAgICAgICAgfQogICAgXSwKICAgICJzaWduZWQiOiAiaW52YWxp" - "ZCIKfQ==\", \"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, SignedFieldOnTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbICAgICAgICAKICAgIF0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - signed_targets_field_missing); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgImN" - "1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXRlIjogInNvbWV0" - "aGluZyIKICAgICAgICB9LAogICAgICAgICJleHBpcmVzIjogIjIwMjItMTEtMDRUMTM6M" - "zE6NTlaIiwKICAgICAgICAic3BlY192ZXJzaW9uIjogIjEuMC4wIiwKICAgICAgICAidG" - "FyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2cvMi9BUE1fU0FNUExJTkcvZHluYW1" - "pY19yYXRlcy9jb25maWciOiB7CiAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgI" - "CAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICJibG" - "FoIiwKICAgICAgICAgICAgICAgICAgICAic2hhNTEyIjogInNoYTUxMmhhc2hoZXJlMDE" - "iCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDIKICAg" - "ICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogI" - "CAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl9" - "0eXBlIjoge30sCiAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgIm9wYXF1ZV9i" - "YWNrZW5kX3N0YXRlIjogInNvbWV0aGluZyIKICAgICAgICB9LAogICAgICAgICJleHBpc" - "mVzIjogIjIwMjItMTEtMDRUMTM6MzE6NTlaIiwKICAgICAgICAic3BlY192ZXJzaW9uIj" - "ogIjEuMC4wIiwKICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgImRhdGFkb2c" - "vMi9BUE1fU0FNUExJTkcvZHluYW1pY19yYXRlcy9jb25maWciOiB7CiAgICAgICAgICAg" - "ICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgI" - "CAgICAgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgIC" - "AgICAgICAgInNoYTI1NiI6ICJibGFoIiwKICAgICAgICAgICAgICAgICAgICAic2hhNTE" - "yIjogInNoYTUxMmhhc2hoZXJlMDEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAg" - "ICAgICAgImxlbmd0aCI6IDIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgI" - "nZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, _TypeFieldOnSignedTargetsMustBeEqualToTargets) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgIl9" - "0eXBlIjogIm5vbl9hY2NlcHRlZF90eXBlIiwKICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAic29tZXRoaW5nIgogICAgICAgI" - "H0sCiAgICAgICAgImV4cGlyZXMiOiAiMjAyMi0xMS0wNFQxMzozMTo1OVoiLAogICAgIC" - "AgICJzcGVjX3ZlcnNpb24iOiAiMS4wLjAiLAogICAgICAgICJ0YXJnZXRzIjogewogICA" - "gICAgICAgICAiZGF0YWRvZy8yL0FQTV9TQU1QTElORy9keW5hbWljX3JhdGVzL2NvbmZp" - "ZyI6IHsKICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgI" - "CAgInYiOiAxCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgImhhc2hlcy" - "I6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImJsYWgiLAogICAgICAgICA" - "gICAgICAgICAgICJzaGE1MTIiOiAic2hhNTEyaGFzaGhlcmUwMSIKICAgICAgICAgICAg" - "ICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogMgogICAgICAgICAgICB9CiAgI" - "CAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}], " - "\"client_configs\": " - "[ \"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - type_signed_targets_field_invalid_type); -} - -TEST(RemoteConfigParser, VersionFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAi" - "ZXlKMlpYSnphVzl1SWpveExDSnpkR0YwWlNJNmV5Sm1hV3hsWDJoaGMyaGxjeUk2V3lKU" - "0t6SkRWbXRsZEVSellXNXBXa2RKYTBaYVpGSk5UMkZZYTNWek1ERjFlbFExTTNwbmVtbF" - "NUR0UwUFNJc0lrSXdXbU0zVDFJclVsVkxjbmRPYjBWRVdqWTNVWFY1V0VscmEyY3hiMk5" - "IVldSM2VrWnNTMGREWkZVOUlpd2llSEZxVGxVeFRVeFhVM0JSYkRaTmFreFBVMk52U1VK" - "MmIzbFNlbFpyZHpaek5HRXJkWFZ3T1dnd1FUMGlYWDE5IgogICAgICAgIH0sCiAgICAgI" - "CAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2" - "R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHs" - "KICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhM" - "jU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2OD" - "cwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICA" - "gICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkYXRh" - "ZG9nLzIvREVCVUcvbHVrZS5zdGVlbnNlbi9jb25maWciOiB7CiAgICAgICAgICAgICAgI" - "CAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMwogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICA" - "gICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgwNmZh" - "MzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgfSwKICAgI" - "CAgICAgICAgICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AiZW1wbG95ZWUvREVCVUdfREQvMi50ZXN0MS5jb25maWcvY29uZmlnIjogewogICAgICA" - "gICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAidiI6IDEKICAg" - "ICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgI" - "CAgICAgICAgICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1Nj" - "VkNDRjMzlhNWU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICA" - "gIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgfQogICAg" - "ICAgIH0KICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - version_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, VersionFieldOnSignedTargetsMustBeNumber) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiAi" - "ZXlKMlpYSnphVzl1SWpveExDSnpkR0YwWlNJNmV5Sm1hV3hsWDJoaGMyaGxjeUk2V3lKU" - "0t6SkRWbXRsZEVSellXNXBXa2RKYTBaYVpGSk5UMkZZYTNWek1ERjFlbFExTTNwbmVtbF" - "NUR0UwUFNJc0lrSXdXbU0zVDFJclVsVkxjbmRPYjBWRVdqWTNVWFY1V0VscmEyY3hiMk5" - "IVldSM2VrWnNTMGREWkZVOUlpd2llSEZxVGxVeFRVeFhVM0JSYkRaTmFreFBVMk52U1VK" - "MmIzbFNlbFpyZHpaek5HRXJkWFZ3T1dnd1FUMGlYWDE5IgogICAgICAgIH0sCiAgICAgI" - "CAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2" - "R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHs" - "KICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhM" - "jU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2OD" - "cwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICA" - "gICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJkYXRh" - "ZG9nLzIvREVCVUcvbHVrZS5zdGVlbnNlbi9jb25maWciOiB7CiAgICAgICAgICAgICAgI" - "CAiY3VzdG9tIjogewogICAgICAgICAgICAgICAgICAgICJ2IjogMwogICAgICAgICAgIC" - "AgICAgfSwKICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICA" - "gICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgwNmZh" - "MzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgfSwKICAgI" - "CAgICAgICAgICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICB9LAogICAgICAgICAgIC" - "AiZW1wbG95ZWUvREVCVUdfREQvMi50ZXN0MS5jb25maWcvY29uZmlnIjogewogICAgICA" - "gICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAidiI6IDEKICAg" - "ICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgI" - "CAgICAgICAgICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1Nj" - "VkNDRjMzlhNWU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICA" - "gIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgfQogICAg" - "ICAgIH0sCiAgICAgICAgInZlcnNpb24iOiB7fQogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - version_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, CustomFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImV4cG" - "lyZXMiOiAiMjAyMi0xMS0wNFQxMzozMTo1OVoiLAogICAgICAgICAgICAgICAgInNwZWN" - "fdmVyc2lvbiI6ICIxLjAuMCIsCiAgICAgICAgICAgICAgICAidGFyZ2V0cyI6IHsKICAg" - "ICAgICAgICAgICAgICAgICAgICAgImRhdGFkb2cvMi9GRUFUVVJFUy9keW5hbWljX3Jhd" - "GVzL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG" - "9tIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiA" - "zNjc0MAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYz" - "BkYTA0MGQ5ZWJiNDJlYzk3MjI0OTIwZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogICAgICAgICAgICAgICAgICAgICAgICB9L" - "AogICAgICAgICAgICAgICAgICAgICAgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2x1a2Uuc3" - "RlZW5zZW4vY29uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ" - "jdXN0b20iOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAi" - "diI6IDMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICAic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk" - "3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiCiAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAibGVuZ3RoIjogMTMKICAgICAgICAgICAgICAgICAgICAgICAgfSwKIC" - "AgICAgICAgICAgICAgICAgICAgICAgImVtcGxveWVlL0ZFQVRVUkVTLzIudGVzdDEuY29" - "uZmlnL2NvbmZpZyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3Vz" - "dG9tIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiO" - "iAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICI0N2VkODI1NjQ3YWQwZWM2YTc4OTkx" - "ODg5MDU2NWQ0NGMzOWE1ZTRiYWNkMzViYjM0ZjlkZjM4MzM4OTEyZGFlIgogICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgImxlbmd0aCI6IDQxCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICA" - "gICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAg" - "ICAgICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomFieldOnSignedTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJleHBpcmVzIjogIjIwMjItMTE" - "tMDRUMTM6MzE6NTlaIiwKICAgICAgICAgICAgICAgICJzcGVjX3ZlcnNpb24iOiAiMS4w" - "LjAiLAogICAgICAgICAgICAgICAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICAgICAgI" - "CAgICAgICJkYXRhZG9nLzIvRkVBVFVSRVMvZHluYW1pY19yYXRlcy9jb25maWciOiB7Ci" - "AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMzY3NDAKICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAic2hhMjU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM" - "5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVuZ" - "3RoIjogNjYzOTkKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgIC" - "AgICAgICAgICAgImRhdGFkb2cvMi9GRUFUVVJFUy9sdWtlLnN0ZWVuc2VuL2NvbmZpZyI" - "6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgInNoYTI1NiI6ICJjNmE4Y2Q1MzUzMGI1OTJhNTA5N2EzMjMyY2U0OWNhMDgw" - "NmZhMzI0NzM1NjRjM2FiMzg2YmViYWVhN2Q4NzQwIgogICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxlbm" - "d0aCI6IDEzCiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICA" - "gICAgICAgICJlbXBsb3llZS9GRUFUVVJFUy8yLnRlc3QxLmNvbmZpZy9jb25maWciOiB7" - "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMQogICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICJzaGEyNTYiOiAiNDdlZDgyNTY0N2FkMGVjNmE3ODk5MTg4OTA1NjVkNDRjMzlhN" - "WU0YmFjZDM1YmIzNGY5ZGYzODMzODkxMmRhZSIKICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsZW5ndGg" - "iOiA0MQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9LAog" - "ICAgICAgICAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, - OpaqueBackendStateCustomFieldOnSignedTargetsMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6IHt9LAogICAgICAgICAgICAgICAgImV4cGlyZXMiOiAiMjAyMi0xMS0wNFQxMzo" - "zMTo1OVoiLAogICAgICAgICAgICAgICAgInNwZWNfdmVyc2lvbiI6ICIxLjAuMCIsCiAg" - "ICAgICAgICAgICAgICAidGFyZ2V0cyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgI" - "mRhdGFkb2cvMi9GRUFUVVJFUy9keW5hbWljX3JhdGVzL2NvbmZpZyI6IHsKICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAzNjc0MAogICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "mhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "JzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYzBkYTA0MGQ5ZWJiNDJlYzk3MjI0OTI" - "wZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2N" - "jM5OQogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgIC" - "AgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2x1a2Uuc3RlZW5zZW4vY29uZmlnIjogewogICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOiB7CiAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDMKICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "JoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA" - "ic2hhMjU2IjogImM2YThjZDUzNTMwYjU5MmE1MDk3YTMyMzJjZTQ5Y2EwODA2ZmEzMjQ3" - "MzU2NGMzYWIzODZiZWJhZWE3ZDg3NDAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGVuZ3RoIjogMT" - "MKICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICA" - "gImVtcGxveWVlL0ZFQVRVUkVTLzIudGVzdDEuY29uZmlnL2NvbmZpZyI6IHsKICAgICAg" - "ICAgICAgICAgICAgICAgICAgICAgICAgICAiY3VzdG9tIjogewogICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgICAgICAgInYiOiAxCiAgICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaGF" - "zaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNo" - "YTI1NiI6ICI0N2VkODI1NjQ3YWQwZWM2YTc4OTkxODg5MDU2NWQ0NGMzOWE1ZTRiYWNkM" - "zViYjM0ZjlkZjM4MzM4OTEyZGFlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxlbmd0aCI6IDQxCiA" - "gICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAg" - "ICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgICAgICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - obs_custom_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, - OpaqueBackendStateCustomFieldOnSignedTargetsMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgICAgICJzaWduYXR1cmVzIjogWwogICAgICAgICAgICAgICAgewogICAgICA" - "gICAgICAgICAgICAgICAgICAia2V5aWQiOiAiNWM0ZWNlNDEyNDFhMWJiNTEzZjZlM2U1" - "ZGY3NGFiN2Q1MTgzZGZmZmJkNzFiZmQ0MzEyNzkyMGQ4ODA1NjlmZCIsCiAgICAgICAgI" - "CAgICAgICAgICAgICAgICJzaWciOiAiNDliOTBmNWY0YmZjMjdjY2JkODBkOWM4NDU4ZD" - "dkMjJiYTlmYTA4OTBmZDc3NWRkMTE2YzUyOGIzNmRkNjA1YjFkZjc2MWI4N2I2YzBlYjl" - "iMDI2NDA1YTEzZWZlZjQ4Mjc5MzRkNmMyNWE3ZDZiODkyNWZkYTg5MjU4MDkwMGYiCiAg" - "ICAgICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAic2lnbmVkIjogewogICAgI" - "CAgICAgICAgICAgIl90eXBlIjogInRhcmdldHMiLAogICAgICAgICAgICAgICAgImN1c3" - "RvbSI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgIm9wYXF1ZV9iYWNrZW5kX3N0YXR" - "lIjoge30KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiZXhwaXJlcyI6" - "ICIyMDIyLTExLTA0VDEzOjMxOjU5WiIsCiAgICAgICAgICAgICAgICAic3BlY192ZXJza" - "W9uIjogIjEuMC4wIiwKICAgICAgICAgICAgICAgICJ0YXJnZXRzIjogewogICAgICAgIC" - "AgICAgICAgICAgICAgICAiZGF0YWRvZy8yL0ZFQVRVUkVTL2R5bmFtaWNfcmF0ZXMvY29" - "uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOiB7" - "CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDM2NzQwC" - "iAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJjMGRhMDQw" - "ZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgICAgICAgI" - "CAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC" - "AgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICA" - "gICAgICAgICAgICAgICAgICAgICJkYXRhZG9nLzIvRkVBVFVSRVMvbHVrZS5zdGVlbnNl" - "bi9jb25maWciOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImN1c3Rvb" - "SI6IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2IjogMw" - "ogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICAgICJzaGEyNTYiOiAiYzZhOGNkNTM1MzBiNTkyYTUwOTdhMzIzM" - "mNlNDljYTA4MDZmYTMyNDczNTY0YzNhYjM4NmJlYmFlYTdkODc0MCIKICAgICAgICAgIC" - "AgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgICA" - "gICAgICJsZW5ndGgiOiAxMwogICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAg" - "ICAgICAgICAgICAgICAgICAiZW1wbG95ZWUvRkVBVFVSRVMvMi50ZXN0MS5jb25maWcvY" - "29uZmlnIjogewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjdXN0b20iOi" - "B7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidiI6IDEKICA" - "gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAg" - "ICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgI" - "CAgICAgICAgICAgICAic2hhMjU2IjogIjQ3ZWQ4MjU2NDdhZDBlYzZhNzg5OTE4ODkwNT" - "Y1ZDQ0YzM5YTVlNGJhY2QzNWJiMzRmOWRmMzgzMzg5MTJkYWUiCiAgICAgICAgICAgICA" - "gICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg" - "ICAibGVuZ3RoIjogNDEKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgI" - "CAgICAgfSwKICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgICAgIC" - "B9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - obs_custom_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsFieldOnSignedTargetsMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sCiAgICAgICAgInRhcmdldHMiOiBbXSwKICAgICAgICAidmVyc2lvb" - "iI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_signed_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsFieldOnSignedTargetsMustExists) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sICAgICAgICAKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiAgI" - "CB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - targets_signed_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImhhc2hlcyI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAic2hhMjU2IjogIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwND" - "BkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUxZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICA" - "gICAgICB9LAogICAgICAgICAgICAgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAg" - "IH0KICAgICAgICB9LAogICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==" - "\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_path_targets_field_missing); -} - -TEST(RemoteConfigParser, CustomOnPathMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbCiAgICBdLAogICAgInNpZ25lZCI6IHsKICAgICA" - "gICAiY3VzdG9tIjogewogICAgICAgICAgICAib3BhcXVlX2JhY2tlbmRfc3RhdGUiOiBb" - "XQogICAgICAgIH0sCiAgICAgICAgInRhcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZ" - "G9nLzIvQVBNX1NBTVBMSU5HL2R5bmFtaWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgIC" - "AgICAgICAgImN1c3RvbSI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJoYXNoZXM" - "iOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI1NiI6ICIwNzQ2NWNlY2U0N2U0NTQy" - "YWJjMGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3MDY1MWRjMzMxNjUyODYwOWQ1IgogI" - "CAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICJsZW5ndGgiOiA2NjM5OQogIC" - "AgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lvbiI6IDI3NDg3MTU2CiA" - "gICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - custom_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, VCustomOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAiaGFzaGVzIjogewogICAgICAgIC" - "AgICAgICAgICAgICJzaGEyNTYiOiAiMDc0NjVjZWNlNDdlNDU0MmFiYzBkYTA0MGQ5ZWJ" - "iNDJlYzk3MjI0OTIwZDY4NzA2NTFkYzMzMTY1Mjg2MDlkNSIKICAgICAgICAgICAgICAg" - "IH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfQogI" - "CAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - v_path_targets_field_missing); -} - -TEST(RemoteConfigParser, VCustomOnPathMustBeNumber) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6ICJpbnZhbGlkIgogICAgICAgICAgICAgICAgfSwKIC" - "AgICAgICAgICAgICAgICJoYXNoZXMiOiB7CiAgICAgICAgICAgICAgICAgICAgInNoYTI" - "1NiI6ICIwNzQ2NWNlY2U0N2U0NTQyYWJjMGRhMDQwZDllYmI0MmVjOTcyMjQ5MjBkNjg3" - "MDY1MWRjMzMxNjUyODYwOWQ1IgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgI" - "CAgICJsZW5ndGgiOiA2NjM5OQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgIC" - "AidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - v_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, HashesOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImxlbmd0aCI6IDY2Mzk5CiAgICAgICAgICAgIH0KICAgICAgICB9LAo" - "gICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_missing); -} - -TEST(RemoteConfigParser, HashesOnPathMustBeObject) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6ICJpbnZhbGlkIiwKICAgICAgICAgICAgICAgICJsZW5" - "ndGgiOiA2NjM5OQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAidmVyc2lv" - "biI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, AtLeastOneHashMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICA" - "gICAgICAibGVuZ3RoIjogNjYzOTkKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAg" - "ICAgInZlcnNpb24iOiAyNzQ4NzE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hashes_path_targets_field_empty); -} - -TEST(RemoteConfigParser, HashesOnPathMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "ge30KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGVuZ3RoIjogNjYz" - "OTkKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInZlcnNpb24iOiAyNzQ4N" - "zE1NgogICAgfQp9\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - hash_hashes_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, LengthOnPathMustBePresent) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "gIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUx" - "ZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgI" - "CAgICB9LAogICAgICAgICJ2ZXJzaW9uIjogMjc0ODcxNTYKICAgIH0KfQ==\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_missing); -} - -TEST(RemoteConfigParser, LengthOnPathMustBeString) -{ - std::string invalid_response = - ("{\"roots\": [], \"targets\": " - "\"ewogICAgInNpZ25hdHVyZXMiOiBbXSwKICAgICJzaWduZWQiOiB7CiAgICAgICAgInR" - "hcmdldHMiOiB7CiAgICAgICAgICAgICJkYXRhZG9nLzIvQVBNX1NBTVBMSU5HL2R5bmFt" - "aWNfcmF0ZXMvY29uZmlnIjogewogICAgICAgICAgICAgICAgImN1c3RvbSI6IHsKICAgI" - "CAgICAgICAgICAgICAgICAidiI6IDM2NzQwCiAgICAgICAgICAgICAgICB9LAogICAgIC" - "AgICAgICAgICAgImhhc2hlcyI6IHsKICAgICAgICAgICAgICAgICAgICAic2hhMjU2Ijo" - "gIjA3NDY1Y2VjZTQ3ZTQ1NDJhYmMwZGEwNDBkOWViYjQyZWM5NzIyNDkyMGQ2ODcwNjUx" - "ZGMzMzE2NTI4NjA5ZDUiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgI" - "mxlbmd0aCI6ICJpbnZhbGlkIgogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgIC" - "AidmVyc2lvbiI6IDI3NDg3MTU2CiAgICB9Cn0=\", " - "\"target_files\": " - "[{\"path\": " - "\"employee/DEBUG_DD/2.test1.config/config\", \"raw\": " - "\"UmVtb3RlIGNvbmZpZ3VyYXRpb24gaXMgc3VwZXIgc3VwZXIgY29vbAo=\"}, " - "{\"path\": \"datadog/2/DEBUG/luke.steensen/config\", \"raw\": " - "\"aGVsbG8gdmVjdG9yIQ==\"} ], \"client_configs\": " - "[\"datadog/2/DEBUG/luke.steensen/config\", " - "\"employee/DEBUG_DD/2.test1.config/config\"] }"); - assert_parser_error(remote_config::protocol::parse, invalid_response, - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_invalid); -} - -TEST(RemoteConfigParser, TargetsAreParsed) -{ - std::string response = get_example_response(); - - auto gcr = remote_config::protocol::parse(response); - - std::optional _targets = gcr.targets; - - EXPECT_EQ(27487156, _targets->version); - - std::unordered_map paths = - _targets->paths; - - EXPECT_EQ(3, paths.size()); - - auto path_itr = paths.find("datadog/2/APM_SAMPLING/dynamic_rates/config"); - auto temp_path = path_itr->second; - EXPECT_EQ(36740, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "07465cece47e4542abc0da040d9ebb42ec97224920d6870651dc3316528609d5", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere01", temp_path.hashes["sha512"]); - EXPECT_EQ(66399, temp_path.length); - - path_itr = paths.find("datadog/2/DEBUG/luke.steensen/config"); - temp_path = path_itr->second; - EXPECT_EQ(3, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "c6a8cd53530b592a5097a3232ce49ca0806fa32473564c3ab386bebaea7d8740", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere02", temp_path.hashes["sha512"]); - EXPECT_EQ(13, temp_path.length); - - path_itr = paths.find("employee/DEBUG_DD/2.test1.config/config"); - temp_path = path_itr->second; - EXPECT_EQ(1, temp_path.custom_v); - EXPECT_EQ(2, temp_path.hashes.size()); - EXPECT_EQ( - "47ed825647ad0ec6a789918890565d44c39a5e4bacd35bb34f9df38338912dae", - temp_path.hashes["sha256"]); - EXPECT_EQ("sha512hashhere03", temp_path.hashes["sha512"]); - EXPECT_EQ(41, temp_path.length); -} - -TEST(RemoteConfigParser, RemoteConfigParserResultCanBeCastToString) -{ - EXPECT_EQ("success", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result::success)); - EXPECT_EQ("target_files_path_field_invalid_type", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - target_files_path_field_invalid_type)); - EXPECT_EQ("length_path_targets_field_missing", - remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - length_path_targets_field_missing)); - EXPECT_EQ("", remote_config::protocol::remote_config_parser_result_to_str( - remote_config::protocol::remote_config_parser_result:: - num_of_values)); -} - -TEST(RemoteConfigParser, ParseEmptyResponses) -{ - auto gcr = remote_config::protocol::parse("{}"); - - EXPECT_FALSE(gcr.targets.has_value()); -} - -TEST(RemoteConfigParser, ParseInfoResponse) -{ - const std::string response = - R"({"endpoints": ["/some/endpoint", "/another/endpoint"] })"; - auto info_response = remote_config::protocol::parse_info(response); - - EXPECT_EQ(2, info_response.endpoints.size()); - EXPECT_TRUE(std::find(info_response.endpoints.begin(), - info_response.endpoints.end(), - "/some/endpoint") != info_response.endpoints.end()); - EXPECT_TRUE(std::find(info_response.endpoints.begin(), - info_response.endpoints.end(), - "/another/endpoint") != info_response.endpoints.end()); -} - -TEST(RemoteConfigParser, ParseInfoInvalidJsonThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, "invalid_json", - remote_config::protocol::remote_config_parser_result::invalid_json); -} - -TEST(RemoteConfigParser, ParseInfoNonObjectPayloadThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, "[]", - remote_config::protocol::remote_config_parser_result::invalid_response); -} - -TEST(RemoteConfigParser, ParseInfoWithoutEndpointsThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"some":"value"})", - remote_config::protocol::remote_config_parser_result:: - endpoints_field_missing); -} - -TEST(RemoteConfigParser, ParseInfoWithEndpointsNotArrayThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"endpoints":"invalid_type"})", - remote_config::protocol::remote_config_parser_result:: - endpoints_field_invalid); -} - -TEST(RemoteConfigParser, ParseInfoWithNonStringEndpointThrowsException) -{ - assert_parser_error(remote_config::protocol::parse_info, - R"({"endpoints": [ 1234 ]})", - remote_config::protocol::remote_config_parser_result::invalid_endpoint); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/product_test.cpp b/appsec/tests/helper/remote_config/product_test.cpp deleted file mode 100644 index 997cc403cc..0000000000 --- a/appsec/tests/helper/remote_config/product_test.cpp +++ /dev/null @@ -1,262 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include "../common.hpp" -#include "remote_config/config.hpp" -#include "remote_config/exception.hpp" -#include "remote_config/listeners/listener.hpp" -#include "remote_config/product.hpp" - -using capabilities_e = dds::remote_config::protocol::capabilities_e; - -namespace dds { - -namespace mock { - -ACTION(ThrowErrorApplyingConfig) -{ - throw remote_config::error_applying_config("some error"); -}; - -class listener_mock : public remote_config::listener_base { -public: - listener_mock(std::string name = "MOCK_PRODUCT", - remote_config::protocol::capabilities_e capability = - remote_config::protocol::capabilities_e::ASM_DD_RULES) - : name_(name), capability_(capability){}; - ~listener_mock() override = default; - - MOCK_METHOD( - void, on_update, ((const remote_config::config &config)), (override)); - MOCK_METHOD( - void, on_unapply, ((const remote_config::config &config)), (override)); - MOCK_METHOD(void, init, (), (override)); - MOCK_METHOD(void, commit, (), (override)); - - [[nodiscard]] std::unordered_map - get_supported_products() override - { - return {{name_, capability_}}; - } - -protected: - std::string name_; - remote_config::protocol::capabilities_e capability_; -}; -} // namespace mock - -remote_config::config get_config(std::string id) -{ - return {"some product", id, "some contents", "some path", {}, 123, 321, - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; -} - -remote_config::config get_config() { return get_config("some id"); } - -remote_config::config unacknowledged(remote_config::config c) -{ - c.apply_state = - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED; - return c; -} - -remote_config::config acknowledged(remote_config::config c) -{ - c.apply_state = - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED; - return c; -} - -TEST(RemoteConfigProduct, InvalidListener) -{ - EXPECT_THROW(remote_config::product("", nullptr), std::runtime_error); -} - -TEST(RemoteConfigProduct, NameFromListenerIsSaved) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - EXPECT_EQ("MOCK_PRODUCT", product.get_name()); -} - -TEST(RemoteConfigProduct, ConfigsAreEmptyByDefault) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - EXPECT_EQ(0, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, ConfigsAreSaved) -{ - auto listener = std::make_shared(); - remote_config::product product("MOCK_PRODUCT", listener); - - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(config)).Times(1); - - product.assign_configs({{"config name", config}}); - - auto configs_on_product = product.get_configs(); - auto config_saved = configs_on_product.find("config name"); - - EXPECT_EQ(1, configs_on_product.size()); - EXPECT_EQ("config name", config_saved->first); - EXPECT_EQ(acknowledged(config), config_saved->second); -} - -TEST( - RemoteConfigProduct, WhenAConfigIsSavedTheProductListenerIsCalledToOnUpdate) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - EXPECT_CALL(*listener, on_update(config)).Times(1); - EXPECT_CALL(*listener, on_unapply(_)).Times(0); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - - EXPECT_EQ( - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, - product.get_configs().find("config name")->second.apply_state); -} - -TEST(RemoteConfigProduct, - WhenAConfigIsRemovedTheProductListenerIsCalledToUnApply) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(1); - EXPECT_CALL(*listener, on_unapply(acknowledged(config))).Times(1); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", unacknowledged(config)}}); - product.assign_configs({}); - - EXPECT_EQ(0, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenConfigDoesNotChangeItsListenersShouldNotBeCalled) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - - EXPECT_EQ(2, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, EvenIfJustOneKeyConfigIsDiferentItCallsToAllListeners) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - remote_config::config config03 = get_config("id 03"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config03))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}, - {"config name 03", unacknowledged(config03)}}); - - EXPECT_EQ(3, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAConfigGetsDeletedItAlsoUpdateWaf) -{ - auto listener = std::make_shared(); - remote_config::config config01 = get_config("id 01"); - remote_config::config config02 = get_config("id 02"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(acknowledged(config01))).Times(1); - EXPECT_CALL(*listener, on_update(unacknowledged(config02))).Times(1); - EXPECT_CALL(*listener, on_unapply(acknowledged(config02))).Times(1); - - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", unacknowledged(config01)}, - {"config name 02", unacknowledged(config02)}}); - product.assign_configs({{"config name 01", unacknowledged(config01)}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAConfigChangeItsHashItsListenerUpdateIsCalled) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - remote_config::config same_config_different_hash = get_config(); - same_config_different_hash.hashes.emplace("hash key", "hash value"); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(1); - EXPECT_CALL( - *listener, on_update(unacknowledged(same_config_different_hash))) - .Times(1); - EXPECT_CALL(*listener, on_unapply(_)).Times(0); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - product.assign_configs({{"config name", same_config_different_hash}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, SameConfigWithDifferentNameItsTreatedAsNewConfig) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(unacknowledged(config))).Times(2); - EXPECT_CALL(*listener, on_unapply(acknowledged(config))).Times(1); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name 01", config}}); - product.assign_configs({{"config name 02", config}}); - - EXPECT_EQ(1, product.get_configs().size()); -} - -TEST(RemoteConfigProduct, WhenAListenerFailsUpdatingAConfigItsStateGetsError) -{ - auto listener = std::make_shared(); - remote_config::config config = get_config(); - - EXPECT_CALL(*listener, on_update(_)) - .WillRepeatedly(mock::ThrowErrorApplyingConfig()); - remote_config::product product("MOCK_PRODUCT", listener); - - product.assign_configs({{"config name", config}}); - - EXPECT_EQ(1, product.get_configs().size()); - EXPECT_EQ(remote_config::protocol::config_state::applied_state::ERROR, - product.get_configs().find("config name")->second.apply_state); - EXPECT_EQ("some error", - product.get_configs().find("config name")->second.apply_error); -} - -} // namespace dds diff --git a/appsec/tests/helper/remote_config/serializer_test.cpp b/appsec/tests/helper/remote_config/serializer_test.cpp deleted file mode 100644 index 7ed8d0f3a5..0000000000 --- a/appsec/tests/helper/remote_config/serializer_test.cpp +++ /dev/null @@ -1,351 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are -// dual-licensed under the Apache-2.0 License or BSD-3-Clause License. -// -// This product includes software developed at Datadog -// (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -#include -#include - -#include "../common.hpp" -#include "base64.h" -#include "remote_config/protocol/client.hpp" -#include "remote_config/protocol/client_state.hpp" -#include "remote_config/protocol/client_tracer.hpp" -#include "remote_config/protocol/config_state.hpp" -#include "remote_config/protocol/tuf/get_configs_request.hpp" -#include "remote_config/protocol/tuf/serializer.hpp" - -namespace dds { - -bool array_contains_string(const rapidjson::Value &array, const char *searched) -{ - if (!array.IsArray()) { - return false; - } - - bool result = false; - for (rapidjson::Value::ConstValueIterator itr = array.Begin(); - itr != array.End(); ++itr) { - if (itr->IsString() && strcmp(searched, itr->GetString()) == 0) { - result = true; - } - } - - return result; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_string( - const rapidjson::Value &parent_field, const char *key, const char *value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(rapidjson::kStringType, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_int( - const rapidjson::Value &parent_field, const char *key, int value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(rapidjson::kNumberType, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator assert_it_contains_bool( - const rapidjson::Value &parent_field, const char *key, bool value) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - rapidjson::Type type = rapidjson::kTrueType; - if (!value) { - type = rapidjson::kFalseType; - } - EXPECT_EQ(type, tmp_itr->value.GetType()); - EXPECT_EQ(value, tmp_itr->value); - - return tmp_itr; -} - -rapidjson::Value::ConstMemberIterator find_and_assert_type( - const rapidjson::Value &parent_field, const char *key, rapidjson::Type type) -{ - rapidjson::Value::ConstMemberIterator tmp_itr = - parent_field.FindMember(key); - bool found = false; - if (tmp_itr != parent_field.MemberEnd()) { - found = true; - } - EXPECT_TRUE(found) << "Key " << key << " not found"; - EXPECT_EQ(type, tmp_itr->value.GetType()) - << "Key " << key << " not matching expected type"; - - return tmp_itr; -} - -int config_state_version = 456; -int targets_version = 123; - -remote_config::protocol::client get_client() -{ - remote_config::protocol::client_tracer client_tracer = {"some runtime id", - "some tracer version", "some service", {"extra01", "extra02"}, - "some env", "some app version"}; - - std::vector config_states; - - remote_config::protocol::config_state cs_unknown = { - "unknown config_state id", 11, "unknown config_state product", - remote_config::protocol::config_state::applied_state::UNKNOWN, ""}; - remote_config::protocol::config_state cs_unacknowledged = { - "unacknowledged config_state id", 22, - "unacknowledged config_state product", - remote_config::protocol::config_state::applied_state::UNACKNOWLEDGED, - ""}; - remote_config::protocol::config_state cs_acknowledged = { - "acknowledged config_state id", 33, "acknowledged config_state product", - remote_config::protocol::config_state::applied_state::ACKNOWLEDGED, ""}; - remote_config::protocol::config_state cs_error = {"error config_state id", - 44, "error config_state product", - remote_config::protocol::config_state::applied_state::ERROR, - "error description"}; - config_states.push_back(cs_unknown); - config_states.push_back(cs_unacknowledged); - config_states.push_back(cs_acknowledged); - config_states.push_back(cs_error); - - remote_config::protocol::client_state client_s = { - targets_version, config_states, false, "", "some backend client state"}; - - return {"some_id", {"ASM_DD"}, client_tracer, client_s}; -} - -std::vector -get_cached_target_files() -{ - std::vector - cached_target_files; - - std::vector first_hashes; - remote_config::protocol::cached_target_files_hash first_hash{ - "first hash algorithm", "first hash hash"}; - first_hashes.push_back(first_hash); - remote_config::protocol::cached_target_files first{ - "first some path", 1, std::move(first_hashes)}; - cached_target_files.push_back(first); - - std::vector - second_hashes; - remote_config::protocol::cached_target_files_hash second_hash{ - "second hash algorithm", "second hash hash"}; - second_hashes.push_back(second_hash); - remote_config::protocol::cached_target_files second{ - "second some path", 1, std::move(second_hashes)}; - cached_target_files.push_back(second); - - return cached_target_files; -} - -TEST(RemoteConfigSerializer, RequestCanBeSerializedWithClientField) -{ - remote_config::protocol::get_configs_request request = { - get_client(), get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // Client fields - rapidjson::Value::ConstMemberIterator client_itr = - find_and_assert_type(serialized_doc, "client", rapidjson::kObjectType); - - assert_it_contains_string(client_itr->value, "id", "some_id"); - assert_it_contains_bool(client_itr->value, "is_tracer", true); - - // Client products fields - rapidjson::Value::ConstMemberIterator products_itr = find_and_assert_type( - client_itr->value, "products", rapidjson::kArrayType); - array_contains_string(products_itr->value, "ASM_DD"); - - // Client tracer fields - rapidjson::Value::ConstMemberIterator client_tracer_itr = - find_and_assert_type( - client_itr->value, "client_tracer", rapidjson::kObjectType); - assert_it_contains_string(client_tracer_itr->value, "language", "php"); - assert_it_contains_string( - client_tracer_itr->value, "runtime_id", "some runtime id"); - assert_it_contains_string( - client_tracer_itr->value, "tracer_version", "some tracer version"); - assert_it_contains_string( - client_tracer_itr->value, "service", "some service"); - assert_it_contains_string(client_tracer_itr->value, "env", "some env"); - assert_it_contains_string( - client_tracer_itr->value, "app_version", "some app version"); - - rapidjson::Value::ConstMemberIterator extra_services_itr = - find_and_assert_type( - client_tracer_itr->value, "extra_services", rapidjson::kArrayType); - EXPECT_EQ("extra01", extra_services_itr->value[0]); - EXPECT_EQ("extra02", extra_services_itr->value[1]); - - // Client state fields - rapidjson::Value::ConstMemberIterator client_state_itr = - find_and_assert_type( - client_itr->value, "state", rapidjson::kObjectType); - assert_it_contains_int( - client_state_itr->value, "targets_version", targets_version); - assert_it_contains_int(client_state_itr->value, "root_version", 1); - assert_it_contains_bool(client_state_itr->value, "has_error", false); - assert_it_contains_string(client_state_itr->value, "error", ""); - assert_it_contains_string(client_state_itr->value, "backend_client_state", - "some backend client state"); - - // Config state fields - rapidjson::Value::ConstMemberIterator config_states_itr = - find_and_assert_type( - client_state_itr->value, "config_states", rapidjson::kArrayType); - ; - - // UNKNOWN - rapidjson::Value::ConstValueIterator itr = config_states_itr->value.Begin(); - assert_it_contains_string(*itr, "id", "unknown config_state id"); - assert_it_contains_int(*itr, "version", 11); - assert_it_contains_string(*itr, "product", "unknown config_state product"); - assert_it_contains_int(*itr, "apply_state", 0); - assert_it_contains_string(*itr, "apply_error", ""); - // UNACKNOWLEDGED - itr++; - assert_it_contains_string(*itr, "id", "unacknowledged config_state id"); - assert_it_contains_int(*itr, "version", 22); - assert_it_contains_string( - *itr, "product", "unacknowledged config_state product"); - assert_it_contains_int(*itr, "apply_state", 1); - assert_it_contains_string(*itr, "apply_error", ""); - // ACKNOWLEDGED - itr++; - assert_it_contains_string(*itr, "id", "acknowledged config_state id"); - assert_it_contains_int(*itr, "version", 33); - assert_it_contains_string( - *itr, "product", "acknowledged config_state product"); - assert_it_contains_int(*itr, "apply_state", 2); - assert_it_contains_string(*itr, "apply_error", ""); - // ERROR - itr++; - assert_it_contains_string(*itr, "id", "error config_state id"); - assert_it_contains_int(*itr, "version", 44); - assert_it_contains_string(*itr, "product", "error config_state product"); - assert_it_contains_int(*itr, "apply_state", 3); - assert_it_contains_string(*itr, "apply_error", "error description"); -} - -TEST(RemoteConfigSerializer, RequestCanBeSerializedWithCachedTargetFields) -{ - remote_config::protocol::get_configs_request request = { - get_client(), get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // cached_target_files fields - rapidjson::Value::ConstMemberIterator cached_target_files_itr = - find_and_assert_type( - serialized_doc, "cached_target_files", rapidjson::kArrayType); - - EXPECT_EQ(2, cached_target_files_itr->value.Size()); - - rapidjson::Value::ConstValueIterator first = - cached_target_files_itr->value.Begin(); - assert_it_contains_string(*first, "path", "first some path"); - assert_it_contains_int(*first, "length", 1); - - // Cached target file hash of first - rapidjson::Value::ConstMemberIterator first_cached_target_files_hash = - find_and_assert_type(*first, "hashes", rapidjson::kArrayType); - EXPECT_EQ(1, first_cached_target_files_hash->value.Size()); - assert_it_contains_string(*first_cached_target_files_hash->value.Begin(), - "algorithm", "first hash algorithm"); - assert_it_contains_string(*first_cached_target_files_hash->value.Begin(), - "hash", "first hash hash"); - - rapidjson::Value::ConstValueIterator second = - std::next(cached_target_files_itr->value.Begin()); - assert_it_contains_string(*second, "path", "second some path"); - assert_it_contains_int(*second, "length", 1); - - // Cached target file hash of second - rapidjson::Value::ConstMemberIterator second_cached_target_files_hash = - find_and_assert_type(*second, "hashes", rapidjson::kArrayType); - EXPECT_EQ(1, second_cached_target_files_hash->value.Size()); - assert_it_contains_string(*second_cached_target_files_hash->value.Begin(), - "algorithm", "second hash algorithm"); - assert_it_contains_string(*second_cached_target_files_hash->value.Begin(), - "hash", "second hash hash"); -} - -TEST(RemoteConfigSerializer, CapabilitiesCanBeSet) -{ - auto client = get_client(); - client.capabilities = - remote_config::protocol::capabilities_e::RESERVED | - remote_config::protocol::capabilities_e::ASM_ACTIVATION | - remote_config::protocol::capabilities_e::ASM_IP_BLOCKING | - remote_config::protocol::capabilities_e::ASM_DD_RULES; - - remote_config::protocol::get_configs_request request = { - client, get_cached_target_files()}; - - std::optional serialised_string; - serialised_string = remote_config::protocol::serialize(std::move(request)); - - EXPECT_TRUE(serialised_string); - - // Lets transform the resulting string back to json so we can assert more - // easily - rapidjson::Document serialized_doc; - serialized_doc.Parse(serialised_string.value()); - - // Client fields - rapidjson::Value::ConstMemberIterator client_itr = - find_and_assert_type(serialized_doc, "client", rapidjson::kObjectType); - - rapidjson::Value::ConstMemberIterator capabilities_itr = - find_and_assert_type( - client_itr->value, "capabilities", rapidjson::kStringType); - - EXPECT_STREQ("AA8=", capabilities_itr->value.GetString()); -} - -} // namespace dds diff --git a/appsec/tests/helper/service_manager_test.cpp b/appsec/tests/helper/service_manager_test.cpp index 9fe5a9273e..bd1995dc85 100644 --- a/appsec/tests/helper/service_manager_test.cpp +++ b/appsec/tests/helper/service_manager_test.cpp @@ -33,21 +33,17 @@ TEST(ServiceManagerTest, LoadRulesOK) dds::engine_settings engine_settings; engine_settings.rules_file = fn; engine_settings.waf_timeout_us = 42; - auto service = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); + auto service = + manager.create_service(engine_settings, {}, meta, metrics, {}); + auto *service_rp = service.get(); EXPECT_EQ(manager.get_cache().size(), 1); EXPECT_EQ(metrics[tag::event_rules_loaded], 4); // loading again should take from the cache - auto service2 = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); - EXPECT_EQ(manager.get_cache().size(), 1); - - // Even with different extra services, it should get the same - auto service3 = manager.create_service( - {"service", {"some", "services"}, "env", "", "", ""}, engine_settings, - {}, meta, metrics, {}); + auto service2 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(manager.get_cache().size(), 1); + EXPECT_EQ(service, service2); // destroying the services should expire the cache ptr auto cache_it = manager.get_cache().begin(); @@ -58,20 +54,19 @@ TEST(ServiceManagerTest, LoadRulesOK) service.reset(); ASSERT_FALSE(weak_ptr.expired()); service2.reset(); - ASSERT_FALSE(weak_ptr.expired()); - service3.reset(); // the last one should be kept by the manager ASSERT_FALSE(weak_ptr.expired()); // loading another service should cleanup the cache - auto service4 = manager.create_service( - {"service2", {}, "env"}, engine_settings, {}, meta, metrics, {}); + auto service3 = + manager.create_service(engine_settings, {true}, meta, metrics, {}); + ASSERT_NE(service3.get(), service_rp); ASSERT_TRUE(weak_ptr.expired()); EXPECT_EQ(manager.get_cache().size(), 1); // another service identifier should result in another service - auto service5 = manager.create_service({"service", {}, "env", "", "", ""}, - engine_settings, {}, meta, metrics, {}); + auto service4 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(manager.get_cache().size(), 2); } @@ -86,8 +81,7 @@ TEST(ServiceManagerTest, LoadRulesFileNotFound) dds::engine_settings engine_settings; engine_settings.rules_file = "/file/that/does/not/exist"; engine_settings.waf_timeout_us = 42; - manager.create_service( - {"s", {}, "e"}, engine_settings, {}, meta, metrics, {}); + manager.create_service(engine_settings, {}, meta, metrics, {}); }, std::runtime_error); } @@ -103,8 +97,7 @@ TEST(ServiceManagerTest, BadRulesFile) dds::engine_settings engine_settings; engine_settings.rules_file = "/dev/null"; engine_settings.waf_timeout_us = 42; - manager.create_service( - {"s", {}, "e"}, engine_settings, {}, meta, metrics, {}); + manager.create_service(engine_settings, {}, meta, metrics, {}); }, dds::parsing_error); } @@ -119,12 +112,10 @@ TEST(ServiceManagerTest, UniqueServices) dds::engine_settings engine_settings; engine_settings.rules_file = fn; - auto service1 = manager.create_service( - {"service", {}, "env", "1.0", "2.0", "runtime ID 0"}, engine_settings, - {}, meta, metrics, {}); - auto service2 = manager.create_service( - {"service", {}, "env", "1.1", "3.0", "runtime ID 1"}, engine_settings, - {}, meta, metrics, {}); + auto service1 = + manager.create_service(engine_settings, {}, meta, metrics, {}); + auto service2 = + manager.create_service(engine_settings, {}, meta, metrics, {}); EXPECT_EQ(service1.get(), service2.get()); } diff --git a/appsec/tests/helper/service_test.cpp b/appsec/tests/helper/service_test.cpp index 9fd6bfc340..815fa0d03a 100644 --- a/appsec/tests/helper/service_test.cpp +++ b/appsec/tests/helper/service_test.cpp @@ -5,26 +5,62 @@ // (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. #include "common.hpp" #include "remote_config/mocks.hpp" +#include "remote_config/settings.hpp" #include #include #include +extern "C" { +struct ddog_CharSlice { + const char *ptr; + uintptr_t len; +}; +struct ddog_RemoteConfigReader { + std::string shm_path; + struct ddog_CharSlice next_line; +}; +__attribute__((visibility("default"))) ddog_RemoteConfigReader * +ddog_remote_config_reader_for_path(const char *path) +{ + return new ddog_RemoteConfigReader{path}; +} +__attribute__((visibility("default"))) bool ddog_remote_config_read( + ddog_RemoteConfigReader *reader, ddog_CharSlice *data) +{ + if (reader->next_line.len == 0) { + return false; + } + data->ptr = reader->next_line.ptr; + data->len = reader->next_line.len; + reader->next_line.len = 0; + return true; +} +__attribute__((visibility("default"))) void ddog_remote_config_reader_drop( + struct ddog_RemoteConfigReader *reader) +{ + delete reader; +} + +__attribute__((constructor)) void resolve_symbols() +{ + dds::remote_config::resolve_symbols(); +} +} + namespace dds { TEST(ServiceTest, NullEngine) { - service_identifier sid{"service", {"extra01", "extra02"}, "env", - "tracer_version", "app_version", "runtime_id"}; - std::shared_ptr engine; - auto client = std::make_unique(sid); - EXPECT_CALL(*client, poll).Times(0); + std::shared_ptr engine{}; + remote_config::settings rc_settings{true, ""}; + auto client = remote_config::client::from_settings(rc_settings, {}); auto service_config = std::make_shared(); - auto client_handler = std::make_shared( - std::move(client), service_config, 1s); + auto client_handler = std::make_unique( + std::move(client), service_config); EXPECT_THROW( - auto s = service(engine, service_config, std::move(client_handler)), + auto s = service(engine, service_config, std::move(client_handler), ""), std::runtime_error); } @@ -35,7 +71,7 @@ TEST(ServiceTest, NullServiceHandler) // A null service handler doesn't make a difference as remote config is // optional - service svc{engine, service_config, nullptr}; + service svc{engine, service_config, nullptr, ""}; EXPECT_EQ(engine.get(), svc.get_engine().get()); } @@ -43,9 +79,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { std::shared_ptr engine{engine::create()}; - service_identifier sid{"service", {"extra01", "extra02"}, "env", - "tracer_version", "app_version", "runtime_id"}; - auto client = std::make_unique(sid); + auto client = remote_config::client::from_settings({true}, {}); auto service_config = std::make_shared(); engine_settings engine_settings = {}; engine_settings.rules_file = create_sample_rules_ok(); @@ -54,8 +88,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It picks based on rate double all_requests_are_picked = 1.0; - auto s = service( - engine, service_config, nullptr, {true, all_requests_are_picked}); + auto s = service(engine, service_config, nullptr, "", + {true, all_requests_are_picked}); EXPECT_TRUE(s.get_schema_sampler()->picked()); } @@ -63,7 +97,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It does not pick based on rate double no_request_is_picked = 0.0; auto s = service( - engine, service_config, nullptr, {true, no_request_is_picked}); + engine, service_config, nullptr, "", {true, no_request_is_picked}); EXPECT_FALSE(s.get_schema_sampler()->picked()); } @@ -71,7 +105,7 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Constructor. It does not pick if disabled double all_requests_are_picked = 1.0; bool schema_extraction_disabled = false; - auto s = service(engine, service_config, nullptr, + auto s = service(engine, service_config, nullptr, "", {schema_extraction_disabled, all_requests_are_picked}); EXPECT_FALSE(s.get_schema_sampler()->picked()); @@ -80,8 +114,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It picks based on rate engine_settings.schema_extraction.enabled = true; engine_settings.schema_extraction.sample_rate = 1.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_TRUE(service->get_schema_sampler()->picked()); } @@ -89,8 +123,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It does not pick based on rate engine_settings.schema_extraction.enabled = true; engine_settings.schema_extraction.sample_rate = 0.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_FALSE(service->get_schema_sampler()->picked()); } @@ -98,8 +132,8 @@ TEST(ServiceTest, ServicePickSchemaExtractionSamples) { // Static constructor. It does not pick if disabled engine_settings.schema_extraction.enabled = false; engine_settings.schema_extraction.sample_rate = 1.0; - auto service = service::from_settings( - service_identifier(sid), engine_settings, {}, meta, metrics, false); + auto service = + service::from_settings(engine_settings, {}, meta, metrics, false); EXPECT_FALSE(service->get_schema_sampler()->picked()); } diff --git a/appsec/tests/helper/waf_test.cpp b/appsec/tests/helper/waf_test.cpp index 576d70de3c..0c3d533ce5 100644 --- a/appsec/tests/helper/waf_test.cpp +++ b/appsec/tests/helper/waf_test.cpp @@ -44,7 +44,7 @@ TEST(WafTest, InitWithInvalidRules) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_settings(cs, ruleset, meta, metrics)}; EXPECT_EQ(meta.size(), 2); @@ -70,7 +70,8 @@ TEST(WafTest, RunWithInvalidParam) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); parameter_view pv; dds::event e; @@ -82,7 +83,8 @@ TEST(WafTest, RunWithTimeout) std::map meta; std::map metrics; - subscriber::ptr wi(waf::instance::from_string(waf_rule, meta, metrics, 0)); + std::shared_ptr wi( + waf::instance::from_string(waf_rule, meta, metrics, 0)); auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -99,7 +101,8 @@ TEST(WafTest, ValidRunGood) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -119,7 +122,8 @@ TEST(WafTest, ValidRunMonitor) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(); @@ -148,8 +152,9 @@ TEST(WafTest, ValidRunMonitorObfuscated) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics, - waf::instance::default_waf_timeout_us, "password"sv, "string 3"sv)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics, + waf::instance::default_waf_timeout_us, "password"sv, "string 3"sv)}; auto ctx = wi->get_listener(); auto p = parameter::map(), sub_p = parameter::map(); @@ -189,7 +194,7 @@ TEST(WafTest, ValidRunMonitorObfuscatedFromSettings) cs.obfuscator_key_regex = "password"; auto ruleset = engine_ruleset::from_path(cs.rules_file); - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_settings(cs, ruleset, meta, metrics)}; auto ctx = wi->get_listener(); @@ -223,7 +228,7 @@ TEST(WafTest, UpdateRuleData) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(waf_rule_with_data, meta, metrics)}; ASSERT_TRUE(wi); @@ -282,7 +287,7 @@ TEST(WafTest, UpdateInvalid) std::map meta; std::map metrics; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(waf_rule_with_data, meta, metrics)}; ASSERT_TRUE(wi); @@ -307,7 +312,8 @@ TEST(WafTest, SchemasAreAdded) std::map meta; std::map metrics; - subscriber::ptr wi{waf::instance::from_string(waf_rule, meta, metrics)}; + std::shared_ptr wi{ + waf::instance::from_string(waf_rule, meta, metrics)}; auto ctx = wi->get_listener(); auto p = parameter::map(), sub_p = parameter::map(); @@ -343,7 +349,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"block_request","parameters":{"status_code":123,"grpc_status_code":321,"type":"json","custom_param":"foo"}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -382,7 +388,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"block_request","parameters":{}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -421,7 +427,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["custom"]}],"actions":[{"id":"custom","type":"custom_type","parameters":{"some":"parameter"}}],"rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); @@ -455,7 +461,7 @@ TEST(WafTest, ActionsAreSentAndParsed) std::string rules_with_actions = R"({"version":"2.1","rules":[{"id":"blk-001-001","name":"BlockIPAddresses","tags":{"type":"block_ip","category":"security_response"},"conditions":[{"parameters":{"inputs":[{"address":"http.client_ip"}],"data":"blocked_ips"},"operator":"ip_match"}],"transformers":[],"on_match":["block"]}], "rules_data":[{"id":"blocked_ips","type":"data_with_expiration","data":[{"value":"192.168.1.1","expiration":"9999999999"}]}]})"; - subscriber::ptr wi{ + std::shared_ptr wi{ waf::instance::from_string(rules_with_actions, meta, metrics)}; ASSERT_TRUE(wi); diff --git a/appsec/tests/integration/build.gradle b/appsec/tests/integration/build.gradle index fd20f3d6d7..2402f703be 100644 --- a/appsec/tests/integration/build.gradle +++ b/appsec/tests/integration/build.gradle @@ -1,3 +1,6 @@ +import java.nio.file.Files +import java.nio.file.Path + plugins { id 'groovy' id 'application' @@ -8,19 +11,27 @@ repositories { mavenCentral() } +sourceCompatibility = '11' +targetCompatibility = '11' + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + dependencies { - implementation 'ch.qos.logback:logback-classic:1.4.7' + implementation 'ch.qos.logback:logback-classic:1.5.6' implementation 'org.slf4j:jul-to-slf4j:1.7.36' - implementation 'org.apache.groovy:groovy:4.0.16' - implementation 'com.google.guava:guava:32.1.3-jre' - implementation 'org.msgpack:msgpack-core:0.9.6' - implementation 'io.javalin:javalin:5.4.2' + implementation 'org.apache.groovy:groovy:4.0.21' + implementation 'org.apache.groovy:groovy-json:4.0.21' + implementation 'com.google.guava:guava:33.2.1-jre' + implementation 'org.msgpack:msgpack-core:0.9.8' + implementation 'io.javalin:javalin:6.1.4' - implementation platform('org.testcontainers:testcontainers-bom:1.16.2') + implementation platform('org.testcontainers:testcontainers-bom:1.19.8') implementation "org.testcontainers:junit-jupiter" - implementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2' - implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.13' + implementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.16' } test { @@ -28,12 +39,14 @@ test { tasks['test'].enabled(false) ext.testMatrix = ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3'].collectMany { - [[it, 'release'], [it, 'release-zts']] + [[it, 'release'], [it, 'debug'], [it, 'release-zts']] } ext.uuid = "id -u".execute().text.trim() apply from: "$rootDir/gradle/images.gradle" +def arch = System.getProperty('os.arch') + def dockerPullTask(String tag) { String taskName = "dockerPull-${tag}" if (tasks.findByName(taskName) != null) { @@ -144,6 +157,9 @@ def buildRunInDockerTask = { Map options -> volumes['php-tracer-cargo-cache'] = [ mountPoint: '/root/.cargo/registry', ] + volumes['php-tracer-cargo-cache-git'] = [ + mountPoint: '/root/.cargo/git', + ] } def composerDlTask @@ -166,8 +182,21 @@ def buildRunInDockerTask = { Map options -> description = "${options['description']} for PHP $version $variant" def inputsSpec = options.get('inputs', [:]) - inputsSpec.get('dirs', []).each { - inputs.dir it + inputsSpec.get('dirs', []).each { dir -> + inputs.files fileTree(dir).matching { + include '**/*' + exclude { FileTreeElement element -> + Path p = element.file.toPath() + if (Files.isSymbolicLink(p)) { + // exclude if the target does not exist + if (!Files.exists(Files.readSymbolicLink(p))) { + logger.warn("Excluding broken symlink: $p") + return true + } + } + false // do not exclude + } + } } inputsSpec.get('files', []).each { inputs.file it @@ -253,7 +282,12 @@ def buildTracerTask = { String version, String variant -> needsAppsec: false, description: 'Build tracer for PHP', inputs: [ - dirs: ['../../../ext', '../../../zend_abstract_interface'], + dirs: [ + '../../../ext', + '../../../zend_abstract_interface', + '../../../libdatadog', + '../../../ddtrace.sym', + ], ], outputs: [ volume: 'php-tracer', @@ -263,7 +297,7 @@ def buildTracerTask = { String version, String variant -> '-e', '-c', ''' cd /project - PHPRC= make /project/tmp/build_extension/modules/ddtrace.so + PHPRC= RUST_DEBUG_BUILD=1 make /project/tmp/build_extension/modules/ddtrace.so ''' ] ) @@ -288,7 +322,7 @@ def buildAppSecTask = { String version, String variant -> ], outputs: [ volume: 'php-appsec', - files: ['ddappsec.so', 'ddappsec-helper'], + files: ['ddappsec.so', 'libddappsec-helper.so'], ], command: [ '-e', '-c', @@ -302,7 +336,7 @@ def buildAppSecTask = { String version, String variant -> -DCMAKE_TOOLCHAIN_FILE=/build/Toolchain.cmake \\ -DDD_APPSEC_TESTING=ON /project/appsec make -j extension ddappsec-helper && \\ - touch ddappsec.so ddappsec-helper + touch ddappsec.so libddappsec-helper.so ''' ] ) @@ -393,14 +427,14 @@ testMatrix.each { spec -> } task downloadCaches(type: Download) { - src 'https://sqreen-ci-java.s3.amazonaws.com/php-appsec-volume-caches.tar.gz' - dest 'build/php-appsec-volume-caches.tar.gz' + src "https://sqreen-ci-java.s3.amazonaws.com/php-appsec-volume-caches-${arch}.tar.gz" + dest "build/php-appsec-volume-caches-${arch}.tar.gz" overwrite false } task loadCaches(type: Exec) { description = "Load the docker caches" - inputs.file "${project.buildDir}/php-appsec-volume-caches.tar.gz" + inputs.file "${project.buildDir}/php-appsec-volume-caches-${arch}.tar.gz" commandLine 'docker', 'run', '--rm', '-v', 'php-tracer-cargo-cache:/caches/php-tracer-cargo-cache', @@ -408,11 +442,12 @@ task loadCaches(type: Exec) { '-v', "${project.buildDir}:/build", 'busybox', 'sh', '-c', - "tar -xzf /build/php-appsec-volume-caches.tar.gz -C /caches && \ + "tar -xzf /build/php-appsec-volume-caches-${arch}.tar.gz -C /caches && \ chown -R ${uuid} /caches" dependsOn downloadCaches dependsOn 'createVolume-php-tracer-cargo-cache' + dependsOn 'createVolume-php-tracer-cargo-cache-git' dependsOn 'createVolume-php-appsec-hunter-cache' } diff --git a/appsec/tests/integration/gradle/images.gradle b/appsec/tests/integration/gradle/images.gradle index cd6b9f2ef2..f590530456 100644 --- a/appsec/tests/integration/gradle/images.gradle +++ b/appsec/tests/integration/gradle/images.gradle @@ -13,6 +13,8 @@ def phpVersions = [ '8.3': '8.3.0', ] +def arch = System.getProperty('os.arch') + def imageUpToDate = { inputs, String image -> return { long volumeTime = creationDateOf(image) @@ -163,12 +165,21 @@ task buildAll { } def buildPushTask = { String tag, requirement -> - def task = tasks.register("pushImage-${tag}", Exec) { + tasks.register("pushImage-${tag}", Exec) { String image = "$repo:$tag" + String pushedImage = image + "-$arch" description = "Push image $image" + doFirst { + def proc = ['docker', 'tag', image, pushedImage].execute() + proc.waitForOrKill(5_000) + if (proc.exitValue() != 0) { + throw new GradleException("Failed to tag image $image with arch") + } + } + dependsOn requirement - commandLine 'docker', 'push', image + commandLine 'docker', 'push', pushedImage } } def allPushTasks = [ @@ -191,9 +202,34 @@ tasks.register('pushAll') { dependsOn allPushTasks } +def buildMultiArchTask = { String tag -> + tasks.register("pushImage-${tag}-multiarch", Exec) { + String image = "$repo:$tag" + commandLine 'docker', 'buildx', 'imagetools', 'create', + '-t', image, "$image-aarch64", "$image-amd64" + } +} +def allMultiArchTasks = [ + *testMatrix.collect { spec -> + buildMultiArchTask("php-${spec[0]}-${spec[1]}") + }, + *testMatrix.collect { spec -> + buildMultiArchTask("apache2-mod-php-${spec[0]}-${spec[1]}") + }, + *testMatrix.collect { spec -> + buildMultiArchTask("apache2-fpm-php-${spec[0]}-${spec[1]}") + }, + *testMatrix.collect { spec -> + buildMultiArchTask("nginx-fpm-php-${spec[0]}-${spec[1]}") + } +] +tasks.register('pushMultiArch') { + dependsOn allMultiArchTasks +} + task saveCaches(type: Exec) { description = "Save the docker caches" - outputs.file "${project.buildDir}/php-appsec-volume-caches.tar.gz" + outputs.file "${project.buildDir}/php-appsec-volume-caches-${arch}.tar.gz" commandLine 'docker', 'run', '--rm', '-e', "UUID=${uuid}", '-v', 'php-tracer-cargo-cache:/caches/php-tracer-cargo-cache', @@ -201,7 +237,7 @@ task saveCaches(type: Exec) { '-v', "${project.buildDir}:/build", 'busybox', 'sh', '-c', - 'tar -czf /build/php-appsec-volume-caches.tar.gz \ + """tar -czf /build/php-appsec-volume-caches-${arch}.tar.gz \ -C /caches php-tracer-cargo-cache php-appsec-hunter-cache && \ - chown \$UUID /build/php-appsec-volume-caches.tar.gz' + chown \$UUID /build/php-appsec-volume-caches-${arch}.tar.gz""" } diff --git a/appsec/tests/integration/src/docker/apache2-fpm/entrypoint.sh b/appsec/tests/integration/src/docker/apache2-fpm/entrypoint.sh index ca5cb7faac..4ef543e534 100755 --- a/appsec/tests/integration/src/docker/apache2-fpm/entrypoint.sh +++ b/appsec/tests/integration/src/docker/apache2-fpm/entrypoint.sh @@ -3,7 +3,13 @@ set -x mkdir -p /tmp/logs/apache2 -LOGS_PHP=(/tmp/logs/appsec.log /tmp/logs/helper.log /tmp/logs/php_error.log /tmp/logs/php_fpm_error.log) +LOGS_PHP=( + /tmp/logs/appsec.log + /tmp/logs/helper.log + /tmp/logs/php_error.log + /tmp/logs/php_fpm_error.log + /tmp/logs/sidecar.log +) touch "${LOGS_PHP[@]}" chown www-data:www-data "${LOGS_PHP[@]}" diff --git a/appsec/tests/integration/src/docker/apache2-mod/entrypoint.sh b/appsec/tests/integration/src/docker/apache2-mod/entrypoint.sh index 81b37162c8..b3aa853937 100755 --- a/appsec/tests/integration/src/docker/apache2-mod/entrypoint.sh +++ b/appsec/tests/integration/src/docker/apache2-mod/entrypoint.sh @@ -3,7 +3,7 @@ set -x mkdir -p /tmp/logs/apache2 -LOGS_PHP=(/tmp/logs/appsec.log /tmp/logs/helper.log /tmp/logs/php_error.log) +LOGS_PHP=(/tmp/logs/appsec.log /tmp/logs/helper.log /tmp/logs/php_error.log /tmp/logs/sidecar.log) touch "${LOGS_PHP[@]}" chown www-data:www-data "${LOGS_PHP[@]}" diff --git a/appsec/tests/integration/src/docker/nginx-fpm/entrypoint.sh b/appsec/tests/integration/src/docker/nginx-fpm/entrypoint.sh index d26b3152ee..521cafb1cd 100755 --- a/appsec/tests/integration/src/docker/nginx-fpm/entrypoint.sh +++ b/appsec/tests/integration/src/docker/nginx-fpm/entrypoint.sh @@ -3,7 +3,13 @@ set -x mkdir -p /tmp/logs/nginx -LOGS_PHP=(/tmp/logs/appsec.log /tmp/logs/helper.log /tmp/logs/php_error.log /tmp/logs/php_fpm_error.log) +LOGS_PHP=( + /tmp/logs/appsec.log + /tmp/logs/helper.log + /tmp/logs/php_error.log + /tmp/logs/php_fpm_error.log + /tmp/logs/sidecar.log +) touch "${LOGS_PHP[@]}" chown www-data "${LOGS_PHP[@]}" diff --git a/appsec/tests/integration/src/docker/php/Dockerfile b/appsec/tests/integration/src/docker/php/Dockerfile index c5fe969ce2..e85c7f012b 100644 --- a/appsec/tests/integration/src/docker/php/Dockerfile +++ b/appsec/tests/integration/src/docker/php/Dockerfile @@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \ apache2-dev \ libsqlite3-dev \ gdb \ + rust-gdb \ vim \ && rm -rf /var/lib/apt/lists/* ADD build_dev_php.sh /build/php/ diff --git a/appsec/tests/integration/src/docker/php/Dockerfile-php-deps b/appsec/tests/integration/src/docker/php/Dockerfile-php-deps index 3fd4b65888..dcbfec8ddd 100644 --- a/appsec/tests/integration/src/docker/php/Dockerfile-php-deps +++ b/appsec/tests/integration/src/docker/php/Dockerfile-php-deps @@ -14,11 +14,13 @@ RUN apt-get update && apt-get install -y \ netcat-openbsd \ gdb \ procps \ + python3-dev \ vim \ && rm -rf /var/lib/apt/lists/* + ADD build_dev_php.sh /build/php/ +RUN USER=root /build/php/build_dev_php.sh deps -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.73.0 -y +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain 1.81.0 -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN USER=root /build/php/build_dev_php.sh deps diff --git a/appsec/tests/integration/src/docker/php/build_dev_php.sh b/appsec/tests/integration/src/docker/php/build_dev_php.sh index 18a1821d2d..4b81f7e18c 100755 --- a/appsec/tests/integration/src/docker/php/build_dev_php.sh +++ b/appsec/tests/integration/src/docker/php/build_dev_php.sh @@ -137,12 +137,28 @@ function build_php { options+=(--with-iconv=shared) fi + if [[ -d /opt/homebrew/opt/zlib ]]; then + options+=(--with-zlib-dir=/opt/homebrew/opt/zlib) + fi + + if [[ -f /opt/homebrew/include/gmp.h ]]; then + options+=(--with-gmp=shared,/opt/homebrew) + else + options+=(--with-gmp=shared) + fi + + if [[ -d /opt/homebrew/opt/sqlite ]]; then + options+=(--with-pdo-sqlite=shared,/opt/homebrew/opt/sqlite) + else + options+=(--with-pdo-sqlite=shared,/usr) + fi + set -x - local -r libpq_dir=$(find /opt/homebrew/Cellar/libpq -depth 1 -type d 2>/dev/null | head -1) + local -r libpq_dir=/opt/homebrew/opt/libpq if [[ -n $libpq_dir ]]; then - #export LDFLAGS="${LDFLAGS:-} -L$libpq_dir/lib" - #export CPPFLAGS="${CPPFLAGS:-} -I$libpq_dir/include" - #export PATH="$libpq_dir/bin:$PATH" + export LDFLAGS="${LDFLAGS:-} -L$libpq_dir/lib" + export CPPFLAGS="${CPPFLAGS:-} -I$libpq_dir/include" + export PATH="$libpq_dir/bin:$PATH" options+=( "--with-pgsql=shared,$libpq_dir/bin" "--with-pdo-pgsql=shared,$libpq_dir/bin") @@ -160,11 +176,10 @@ function build_php { --enable-dom=shared --enable-fileinfo=shared --enable-filter - --with-gmp=shared --enable-intl=shared --enable-mbstring=shared --enable-opcache=shared - --enable-pdo=shared + "--enable-pdo=$([[ $(uname -o) != Darwin ]] && echo shared)" --enable-phar=shared --enable-xml --enable-simplexml=shared @@ -174,7 +189,6 @@ function build_php { --enable-ctype=shared --enable-session=shared --enable-tokenizer=shared - --with-pdo-sqlite=shared,/usr --with-curl=shared) fi @@ -312,15 +326,22 @@ function install_openssl { local -r build_dir="$HOME/php/build/openssl$major" mkdir -p "$build_dir" - cd "$build_dir" + pushd "$build_dir" if [[ $major = 1.0 ]]; then cp -a "$source_dir/." "$build_dir" fi - "$source_dir/config" --prefix="$install_dir" --openssldir="$install_dir" shared zlib no-tests + if [[ $version = '1.0.2u' && $(uname -o) = 'Darwin' && $(arch) = 'arm64' ]]; then + curl -Lf https://gist.githubusercontent.com/felixbuenemann/5f4dcb30ebb3b86e1302e2ec305bac89/raw/b339a33ff072c9747df21e2558c36634dd62c195/openssl-1.0.2u-darwin-arm64.patch | patch -p1 + "./Configure" shared zlib no-tests --prefix="$install_dir" \ + --openssldir="$install_dir" darwin64-arm64-cc + make depend + else + "$source_dir/config" --prefix="$install_dir" --openssldir="$install_dir" shared zlib no-tests + fi make -j && make install_sw touch "$install_dir/.installed" - cd - + popd rm -rf "$build_dir" echo "Installed OpenSSL $version in $install_dir" @@ -462,6 +483,8 @@ if [[ -d /opt/homebrew/lib ]]; then export LDFLAGS="${LDFLAGS:-} -L/opt/homebrew/lib" export CPPFLAGS="${CPPFLAGS:-} -I/opt/homebrew/include" fi +export CXXFLAGS="${CXXFLAGS:-} -std=c++11" +export CFLAGS="${CFLAGS:-} -Wno-implicit-function-declaration" install_openssl 1.0.2u install_openssl 1.1.1w diff --git a/appsec/tests/integration/src/docker/toolchain/CHECKSUMS b/appsec/tests/integration/src/docker/toolchain/CHECKSUMS index 362c2a99e1..8c7b6ff368 100644 --- a/appsec/tests/integration/src/docker/toolchain/CHECKSUMS +++ b/appsec/tests/integration/src/docker/toolchain/CHECKSUMS @@ -7,3 +7,4 @@ af5333da5b90f4a46a5184532164f4c6522e3c03a580131627c0f167ab98fb3e71b3e15518d6e224 07bf9973384151a18d5cc2892103e5f28a88c632e8e49662fde56d123632f2ed1b3710fa7a87b6b821955d0ec44160ff36f2aa4f233e389e14d628e9bf8dc764 llvm-11.1.0.src.tar.xz 5344b581bd6463d71af8c13e91792fa51f25a96a1ecbea81e42664b63d90b325aeb421dfbc8c22e187397ca08e84d9296a0c0c299ba04fa2b751d6864914bd82 musl-1.2.2.tar.gz 9591360672ba6192c606404caf70101538728a1cd5d548efcbb952f663f182bd1954d63743ffc9dd18f5c649a62a042c5e36d1ff423634dfd074f672dd1f4af9 cmake-3.28.0-linux-x86_64.tar.gz +48a20095711870b23bd5db342de0e058a7c6876bafad4c6ce9ff9bce672ca1e95ed9ac890d519b0884cd277d091575eda7e60a97cad377ee57c1e20dee25feb1 cmake-3.28.0-linux-aarch64.tar.gz diff --git a/appsec/tests/integration/src/docker/toolchain/Dockerfile b/appsec/tests/integration/src/docker/toolchain/Dockerfile index 21aff0c288..07867d0d93 100644 --- a/appsec/tests/integration/src/docker/toolchain/Dockerfile +++ b/appsec/tests/integration/src/docker/toolchain/Dockerfile @@ -6,9 +6,9 @@ RUN ln -s /bin/sed /usr/bin/sed RUN mkdir /build ADD . /build/ -RUN wget https://github.com/Kitware/CMake/releases/download/v3.28.0/cmake-3.28.0-linux-x86_64.tar.gz && \ - grep -F "cmake-3.28.0-linux-x86_64.tar.gz" ./build/CHECKSUMS | sha512sum --check && \ - tar --strip-components=1 -C /usr/local -xvzf cmake-3.28.0-linux-x86_64.tar.gz && \ - rm cmake-3.28.0-linux-x86_64.tar.gz +RUN wget https://github.com/Kitware/CMake/releases/download/v3.28.0/cmake-3.28.0-linux-$(arch | sed s/arm/aarch/).tar.gz && \ + grep -F "cmake-3.28.0-linux-$(arch | sed s/arm/aarch/).tar.gz" ./build/CHECKSUMS | sha512sum --check && \ + tar --strip-components=1 -C /usr/local -xvzf cmake-3.28.0-linux-$(arch | sed s/arm/aarch/).tar.gz && \ + rm cmake-3.28.0-linux-$(arch | sed s/arm/aarch/).tar.gz RUN cd /build && make install && make clean diff --git a/appsec/tests/integration/src/docker/toolchain/Makefile b/appsec/tests/integration/src/docker/toolchain/Makefile index 0334c4d365..cc09881c17 100644 --- a/appsec/tests/integration/src/docker/toolchain/Makefile +++ b/appsec/tests/integration/src/docker/toolchain/Makefile @@ -7,11 +7,11 @@ RELTYPE := RelWithDebInfo # need to be in sync with Toolchain*.cmake files MUSL_SYSROOT := $(CURDIR)/muslsysroot -TARGET_ARCH := x86_64 +TARGET_ARCH := $(shell arch) TARGET := $(TARGET_ARCH)-none-linux-musl -install: .libcxx-installed .libcxxabi-installed +install: $(MUSL_SYSROOT)/lib/libglibc_compat.a .libcxx-installed .libcxxabi-installed clean: rm -rf src/ build/ *.tar.xz *.tar.gz \ .compiler-rt-installed .gcc-toolchain-installed .libcxxabi-installed \ @@ -22,8 +22,15 @@ CC_TOOLCHAIN := /usr GCC_TOOL_PREFIX := /usr/bin/ GCC_TOOLCHAIN_SYSROOT := / +$(MUSL_SYSROOT)/lib/libglibc_compat.a: glibc_compat.c .musl-installed + mkdir -p $(MUSL_SYSROOT)/lib + clang --sysroot $(MUSL_SYSROOT) -fpie -O2 -fno-omit-frame-pointer \ + -ggdb3 -c glibc_compat.c -o /tmp/glibc_compat.o && \ + ar rcs $@ /tmp/glibc_compat.o && \ + rm /tmp/glibc_compat.o + .gcc-toolchain-installed: - cp -v /lib/x86_64-linux-gnu/libgcc_s.so.1 /usr/lib/gcc/x86_64-linux-gnu/10/libgcc_s.so.1 + cp -v /lib/$(TARGET_ARCH)-linux-gnu/libgcc_s.so.1 /usr/lib/gcc/$(TARGET_ARCH)-linux-gnu/10/libgcc_s.so.1 touch $@ musl-$(MUSL_VERSION).tar.gz: @@ -55,10 +62,14 @@ src/%/.finger: %-$(LLVM_VERSION).src.tar.xz CC=$(GCC_TOOL_PREFIX)gcc \ AR=$(GCC_TOOL_PREFIX)ar \ RANLIB=$(GCC_TOOL_PREFIX)ranlib \ - ../../src/musl/configure --prefix=$(MUSL_SYSROOT) && \ + ../../src/musl/configure --prefix=$(MUSL_SYSROOT) && \ $(MAKE) -j $(shell nproc) && \ $(MAKE) install && \ popd && \ + pushd $(MUSL_SYSROOT)/include && \ + patch -p0 < /build/locale.h.diff && \ + patch -p0 < /build/alltypes.h.diff && \ + popd && \ touch $@ VERBOSE := 1 @@ -148,14 +159,3 @@ COMMON_CMAKE_OPTIONS := -DCMAKE_BUILD_TYPE=$(RELTYPE) \ $(MAKE) -j $(shell nproc) && $(MAKE) install && \ popd && \ touch $@ - -libddwaf: - mkdir -p build/libddwaf && \ - pushd build/libddwaf && \ - cmake -DCMAKE_BUILD_TYPE=$(RELTYPE) -DCMAKE_TOOLCHAIN_FILE="$(realpath .)/Toolchain.cmake" \ - -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \ - -DLIBDDWAF_TEST_COVERAGE=OFF \ - ../../src/libddwaf && \ - make -j && make -j testPowerWAF && patchelf --remove-needed libc.so libddwaf.so && make package - -.PHONY: libddwaf diff --git a/appsec/tests/integration/src/docker/toolchain/Toolchain.cmake b/appsec/tests/integration/src/docker/toolchain/Toolchain.cmake index 2c6397706a..a444fc21c1 100644 --- a/appsec/tests/integration/src/docker/toolchain/Toolchain.cmake +++ b/appsec/tests/integration/src/docker/toolchain/Toolchain.cmake @@ -1,18 +1,29 @@ set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_PROCESSOR x86_64) +execute_process( + COMMAND arch + OUTPUT_VARIABLE ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(ARCHITECTURE MATCHES "x86_64") + set(ARCH x86_64) +else() + set(ARCH aarch64) +endif() set(CMAKE_SYSROOT /build/muslsysroot) set(CMAKE_AR /usr/bin/llvm-ar-11) -set(triple x86_64-none-linux-musl) +set(triple ${ARCH}-none-linux-musl) set(CMAKE_ASM_COMPILER_TARGET ${triple}) set(CMAKE_C_COMPILER /usr/bin/clang-11) set(CMAKE_C_COMPILER_TARGET ${triple}) set(c_cxx_flags "-nostdinc -isystem${CMAKE_SYSROOT}/include -isystem/usr/lib/llvm-11/lib/clang/11.0.1/include -resource-dir ${CMAKE_SYSROOT} -Qunused-arguments -rtlib=compiler-rt -unwindlib=libunwind -static-libgcc") -set(CMAKE_C_FLAGS ${c_cxx_flags}) +set(CMAKE_C_FLAGS_INIT ${c_cxx_flags}) set(CMAKE_CXX_COMPILER /usr/bin/clang++-11) set(CMAKE_CXX_COMPILER_TARGET ${triple}) -set(CMAKE_CXX_FLAGS "-stdlib=libc++ -isystem${CMAKE_SYSROOT}/include/c++/v1 ${c_cxx_flags}") -set(CMAKE_EXE_LINKER_FLAGS_INIT "-v -fuse-ld=lld -static -nodefaultlibs -lc++ -lc++abi ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-x86_64.a -lunwind -lc ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-x86_64.a") -set(CMAKE_SHARED_LINKER_FLAGS_INIT "-v -fuse-ld=lld -nodefaultlibs -Wl,-Bstatic -lc++ -lc++abi ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-x86_64.a -lunwind -Wl,-Bdynamic -lc ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-x86_64.a") +set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++ -isystem${CMAKE_SYSROOT}/include/c++/v1 ${c_cxx_flags}") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-v -fuse-ld=lld -static -nodefaultlibs -lc++ -lc++abi ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-${ARCH}.a -lunwind -lc ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-${ARCH}.a") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-v -fuse-ld=lld -nodefaultlibs -Wl,-Bstatic -lc++ -lc++abi ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-${ARCH}.a -lunwind -lglibc_compat -Wl,-Bdynamic ${CMAKE_SYSROOT}/lib/linux/libclang_rt.builtins-${ARCH}.a") +set(CMAKE_C_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") +set(CMAKE_CXX_STANDARD_LIBRARIES "-Wl,-Bdynamic -lc") set(CMAKE_NM /usr/bin/llvm-nm-11) set(CMAKE_RANLIB /usr/bin/llvm-ranlib-11) diff --git a/appsec/tests/integration/src/docker/toolchain/alltypes.h.diff b/appsec/tests/integration/src/docker/toolchain/alltypes.h.diff new file mode 100644 index 0000000000..bd9b86ffef --- /dev/null +++ b/appsec/tests/integration/src/docker/toolchain/alltypes.h.diff @@ -0,0 +1,25 @@ +--- bits/alltypes.h 2023-11-06 11:49:18.000000000 +0000 ++++ bits/alltypes.h 2024-04-22 09:30:09.927560000 +0000 +@@ -383,12 +383,20 @@ + + + #if defined(__NEED_pthread_attr_t) && !defined(__DEFINED_pthread_attr_t) +-typedef struct { union { int __i[sizeof(long)==8?14:9]; volatile int __vi[sizeof(long)==8?14:9]; unsigned long __s[sizeof(long)==8?7:9]; } __u; } pthread_attr_t; ++typedef struct { union { int __i[sizeof(long)==8?14:9]; volatile int __vi[sizeof(long)==8?14:9]; unsigned long __s[sizeof(long)==8?7:9]; ++#ifdef __aarch64__ ++ char __glibc_compat[64]; ++#endif ++} __u; } pthread_attr_t; + #define __DEFINED_pthread_attr_t + #endif + + #if defined(__NEED_pthread_mutex_t) && !defined(__DEFINED_pthread_mutex_t) +-typedef struct { union { int __i[sizeof(long)==8?10:6]; volatile int __vi[sizeof(long)==8?10:6]; volatile void *volatile __p[sizeof(long)==8?5:6]; } __u; } pthread_mutex_t; ++typedef struct { union { int __i[sizeof(long)==8?10:6]; volatile int __vi[sizeof(long)==8?10:6]; volatile void *volatile __p[sizeof(long)==8?5:6]; ++#ifdef __aarch64__ ++ char __glibc_compat[48]; ++#endif ++} __u; } pthread_mutex_t; + #define __DEFINED_pthread_mutex_t + #endif + diff --git a/appsec/tests/integration/src/docker/toolchain/glibc_compat.c b/appsec/tests/integration/src/docker/toolchain/glibc_compat.c new file mode 100644 index 0000000000..68d5e1f978 --- /dev/null +++ b/appsec/tests/integration/src/docker/toolchain/glibc_compat.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) && !defined(__GLIBC__) + +# ifdef __x86_64__ +float ceilf(float x) +{ + float result; + // NOLINTNEXTLINE(hicpp-no-assembler) + __asm__("roundss $0x0A, %[x], %[result]" + : [result] "=x"(result) + : [x] "x"(x)); + return result; +} +double ceil(double x) +{ + double result; + // NOLINTNEXTLINE(hicpp-no-assembler) + __asm__("roundsd $0x0A, %[x], %[result]" + : [result] "=x"(result) + : [x] "x"(x)); + return result; +} +# endif + +# ifdef __aarch64__ +float ceilf(float x) +{ + float result; + __asm__("frintp %s0, %s1\n" : "=w"(result) : "w"(x)); + return result; +} +double ceil(double x) +{ + double result; + __asm__("frintp %d0, %d1\n" : "=w"(result) : "w"(x)); + return result; +} +# endif + +# ifdef __aarch64__ +# define _STAT_VER 0 +# else +# define _STAT_VER 1 +# endif + +// glibc before 2.33 (2021) doesn't have these +int stat(const char *restrict path, void *restrict buf) +{ + int __xstat(int, const char *restrict, void *restrict); + return __xstat(_STAT_VER, path, buf); +} + +int fstat(int fd, void *buf) +{ + int __fxstat(int, int, void *); + return __fxstat(_STAT_VER, fd, buf); +} + +int lstat(const char *restrict path, void *restrict buf) +{ + int __lxstat(int, const char *restrict, void *restrict); + return __lxstat(_STAT_VER, path, buf); +} + +// glibc doesn't define pthread_atfork on aarch64. We need to delegate to +// glibc's __register_atfork() instead. __register_atfork() takes an extra +// argument, __dso_handle, which is a pointer to the DSO that is registering the +// fork handlers. This is used to ensure that the handlers are not called after +// the DSO is unloaded. glibc on amd64 also implements pthread_atfork() in terms +// of __register_atfork(). (musl never unloads modules so that potential +// problem doesn't exist) + +// On amd64, even though pthread_atfork is exported by glibc, it should not be +// used. Code that uses pthread_atfork will compile to an import to +// __register_atfork(), but here we're compiling against musl, resulting in an +// an import to pthread_atfork. This will cause a runtime error after the test +// that unloads our module. The reason is that when we call pthread_atfork in +// glibc, __register_atfork() is called with the __dso_handle of libc6.so, not +// the __dso_handle of our module. So the fork handler is not unregistered when +// our module is unloaded. + +extern void *__dso_handle __attribute__((weak)); +int __register_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void), void *__dso_handle) __attribute__((weak)); + +int pthread_atfork( + void (*prepare)(void), void (*parent)(void), void (*child)(void)) +{ + // glibc + if (__dso_handle && __register_atfork) { + return __register_atfork(prepare, parent, child, __dso_handle); + } + + static int (*real_atfork)(void (*)(void), void (*)(void), void (*)(void)); + + if (!real_atfork) { + // dlopen musl +# ifdef __aarch64__ + void *handle = dlopen("ld-musl-aarch64.so.1", RTLD_LAZY); + if (!handle) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlopen of ld-musl-aarch64.so.1 failed: %s\n", + dlerror()); + abort(); + } +# else + void *handle = dlopen("libc.musl-x86_64.so.1", RTLD_LAZY); + if (!handle) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlopen of libc.musl-x86_64.so.1 failed: %s\n", + dlerror()); + abort(); + } +# endif + real_atfork = dlsym(handle, "pthread_atfork"); + if (!real_atfork) { + (void)fprintf( + // NOLINTNEXTLINE(concurrency-mt-unsafe) + stderr, "dlsym of pthread_atfork failed: %s\n", dlerror()); + abort(); + } + } + + return real_atfork(prepare, parent, child); +} + +// the symbol strerror_r in glibc is not the POSIX version; it returns char * +// __xpg_sterror_r is exported by both glibc and musl +int strerror_r(int errnum, char *buf, size_t buflen) +{ + int __xpg_strerror_r(int, char *, size_t); + return __xpg_strerror_r(errnum, buf, buflen); +} + +// when compiling with --coverage, some references to atexit show up. +// glibc doesn't provide atexit for similar reasons as pthread_atfork presumably +int __cxa_atexit(void (*func)(void *), void *arg, void *dso_handle); +int atexit(void (*function)(void)) +{ + if (!__dso_handle) { + (void)fprintf(stderr, "Aborting because __dso_handle is NULL\n"); + abort(); + } + + // the cast is harmless on amd64 and aarch64. Passing an extra argument to a + // function that expects none causes no problems + return __cxa_atexit((void (*)(void *))function, 0, __dso_handle); +} + +// introduced in glibc 2.25 +ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) { + // SYS_getrandom is 318 (amd64) or 278 (aarch64) + // This was only added in Linux 3.17 (2014), so don't use it + // return syscall(SYS_getrandom, buf, buflen, flags); + int fd; + size_t bytes_read = 0; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return -1; + } + + while (bytes_read < buflen) { + ssize_t result = read(fd, (char*)buf + bytes_read, buflen - bytes_read); + if (result < 0) { + if (errno == EINTR) { + continue; + } + close(fd); + return -1; + } + bytes_read += result; + } + + close(fd); + return (ssize_t)bytes_read; +} + +#endif diff --git a/appsec/tests/integration/src/docker/toolchain/locale.h.diff b/appsec/tests/integration/src/docker/toolchain/locale.h.diff new file mode 100644 index 0000000000..36de614b57 --- /dev/null +++ b/appsec/tests/integration/src/docker/toolchain/locale.h.diff @@ -0,0 +1,11 @@ +-- locale.h ++++ locale.h +@@ -71,7 +71,7 @@ + #define LC_COLLATE_MASK (1< List filterMessages(List telemetryData, Class type) { + telemetryData.findAll { it['request_type'] == type.name } + + telemetryData.findAll { it['request_type'] == 'message-batch'} + *.payload*.findAll { it['request_type'] == 'generate-metrics' }.flatten() + .collect { type.newInstance([it['payload']] as Object[]) } + } + + static class GenerateMetrics { + static name = 'generate-metrics' + List series + + GenerateMetrics(Map m) { + series = m.series.collect { new Metric(it as Map) } + } + } + + static class Metric { + String namespace + String name + List points + List tags + boolean common + String type + long interval + + Metric(Map m) { + namespace = m.namespace + name = m.metric + points = m.points + tags = m.tags + common = m.common + type = m.type + interval = m.interval + } + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy index 97ea87064b..605f8450f5 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy @@ -1,6 +1,9 @@ package com.datadog.appsec.php.docker import com.datadog.appsec.php.mock_agent.MockDatadogAgent +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target import com.datadog.appsec.php.model.Span import com.datadog.appsec.php.model.Trace import com.github.dockerjava.api.command.CreateContainerCmd @@ -73,9 +76,16 @@ class AppSecContainer> extends GenericContain withEnv 'DD_TRACE_LOG_LEVEL', 'info,startup=off' withEnv 'DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS', '0' withEnv 'DD_TRACE_AGENT_FLUSH_INTERVAL', '0' + withEnv 'DD_TRACE_SIDECAR_TRACE_SENDER', '0' withEnv 'DD_TRACE_DEBUG', '1' withEnv 'DD_AUTOLOAD_NO_COMPILE', 'true' // must be exactly 'true' withEnv 'DD_TRACE_GIT_METADATA_ENABLED', '0' + withEnv 'DD_INSTRUMENTATION_TELEMETRY_ENABLED', '1' + withEnv '_DD_DEBUG_SIDECAR_LOG_METHOD', 'file:///tmp/logs/sidecar.log' + withEnv 'DD_SPAWN_WORKER_USE_EXEC', '1' // gdb fails following child with fdexec + withEnv 'DD_TELEMETRY_HEARTBEAT_INTERVAL', '10' + withEnv 'DD_TELEMETRY_EXTENDED_HEARTBEAT_INTERVAL', '10' + // withEnv '_DD_SHARED_LIB_DEBUG', '1' if (System.getProperty('XDEBUG') == '1') { Testcontainers.exposeHostPorts(9003) withEnv 'XDEBUG', '1' @@ -107,6 +117,18 @@ class AppSecContainer> extends GenericContain mockDatadogAgent.drainTraces() } + List drainTelemetry(int timeoutInMs) { + mockDatadogAgent.drainTelemetry(timeoutInMs) + } + + void setNextRCResponse(Target target, RemoteConfigResponse nextResponse) { + mockDatadogAgent.setNextRCResponse(target, nextResponse) + } + + RemoteConfigRequest waitForRCVersion(Target target, long version, long timeoutInMs) { + mockDatadogAgent.waitForRCVersion(target, version, timeoutInMs) + } + void close() { copyLogs() super.close() @@ -205,6 +227,7 @@ class AppSecContainer> extends GenericContain withFileSystemBind(wwwDir, '/test-resources', BindMode.READ_ONLY) withFileSystemBind('src/test/waf/recommended.json', '/etc/recommended.json', BindMode.READ_ONLY) + withFileSystemBind('src/test/resources/gdbinit', '/root/.gdbinit', BindMode.READ_ONLY) withFileSystemBind('src/test/bin/enable_extensions.sh', '/usr/local/bin/enable_extensions.sh', BindMode.READ_ONLY) addVolumeMount("php-appsec-$phpVersion-$phpVariant", '/appsec') @@ -218,6 +241,8 @@ class AppSecContainer> extends GenericContain ensureVolume('php-composer-cache') addVolumeMount('php-composer-cache', '/root/.composer/cache') + addVolumeMount('php-tracer-cargo-cache', '/root/.cargo/registry') + File composerFile if (phpVersion in ['7.0', '7.1']) { composerFile = new File('build/composer-2.2.x.phar') diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy new file mode 100644 index 0000000000..e4bca9a83a --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/ConfigV07Handler.groovy @@ -0,0 +1,76 @@ +package com.datadog.appsec.php.mock_agent + +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.Target +import com.google.common.collect.Lists +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import io.javalin.http.Context +import io.javalin.http.Handler +import org.jetbrains.annotations.NotNull + +@Slf4j +@CompileStatic +@Singleton +class ConfigV07Handler implements Handler { + private final Map nextResponse = [:] + final Map capturedRequests = [:] + + @Override + void handle(@NotNull Context context) throws Exception { + RemoteConfigRequest request = context.bodyStreamAsClass(RemoteConfigRequest) + log.debug("Received request with version ${request.client.clientState.targetsVersion}: {}", request.toString()) + Target target = request.extractTarget() + RemoteConfigResponse resp + synchronized (this) { + resp = nextResponse[target] + nextResponse[target] = (RemoteConfigResponse) null + } + synchronized (capturedRequests) { + capturedRequests[target] = request + capturedRequests.notify() + } + if (resp != null) { + log.info("Sending RC response for {}, req targets version={}, resp targets version={}", + target, request.client.clientState.targetsVersion, resp.targetsSigned.version) + context.json(resp) + } else { + context.json([:]) + } + } + + void setNextResponse(Target target, RemoteConfigResponse nextResponse) { + synchronized (this) { + this.nextResponse[target] = nextResponse + } + } + + RemoteConfigRequest waitForVersion(Target target, long version, long timeoutInMs) { + log.debug("waitForVersion start") + long deadline = System.currentTimeMillis() + timeoutInMs + synchronized (capturedRequests) { + while (System.currentTimeMillis() < deadline) { + RemoteConfigRequest request = capturedRequests[target] + if (request != null && request.client.clientState.targetsVersion == version) { + return request + } + capturedRequests.wait(deadline - System.currentTimeMillis()) + } + } + log.debug("waitForVersion timeout") + throw new AssertionError("No request with version $version gotten within " + + "$timeoutInMs ms for $target" as Object) + } + + List> drain(long timeoutInMs) { + synchronized (capturedRequests) { + if (capturedRequests.isEmpty()) { + capturedRequests.wait(timeoutInMs) + } + def requests = Lists.newArrayList(capturedRequests) + capturedRequests.clear() + requests + } + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/InfoHandler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/InfoHandler.groovy index 3ef291b295..4e953f3b8a 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/InfoHandler.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/InfoHandler.groovy @@ -15,7 +15,8 @@ class InfoHandler implements Handler { static class InfoResponse { String version = '7.49.0' List endpoints = [ - '/v0.4/traces' + '/v0.4/traces', + '/v0.7/config', ] } diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy index aa869535fb..1451e41588 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/MockDatadogAgent.groovy @@ -1,6 +1,8 @@ package com.datadog.appsec.php.mock_agent - +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target import com.datadog.appsec.php.model.Trace import groovy.transform.CompileStatic import groovy.util.logging.Slf4j @@ -25,6 +27,11 @@ class MockDatadogAgent implements Startable { this.httpServer.post('v0.4/traces', tracesHandler) this.httpServer.put('v0.4/traces', tracesHandler) this.httpServer.get('info', InfoHandler.instance) + this.httpServer.post('/telemetry/proxy/api/v2/apmtelemetry', TelemetryHandler.instance) + this.httpServer.post('v0.7/config', ConfigV07Handler.instance) + this.httpServer.error(404, ctx -> { + log.info("Unmatched request: ${ctx.method()} ${ctx.path()}") + }) this.httpServer.start(0) } @@ -60,4 +67,16 @@ class MockDatadogAgent implements Startable { List drainTraces() { tracesHandler.drainTraces() } + + List drainTelemetry(int timeoutInMs) { + TelemetryHandler.instance.drain(timeoutInMs) + } + + void setNextRCResponse(Target target, RemoteConfigResponse nextResponse) { + ConfigV07Handler.instance.setNextResponse(target, nextResponse) + } + + RemoteConfigRequest waitForRCVersion(Target target, long version, long timeoutInMs) { + ConfigV07Handler.instance.waitForVersion(target, version, timeoutInMs) + } } diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TelemetryHandler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TelemetryHandler.groovy new file mode 100644 index 0000000000..97fc0ab912 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TelemetryHandler.groovy @@ -0,0 +1,70 @@ +package com.datadog.appsec.php.mock_agent + +import groovy.json.JsonSlurper +import groovy.util.logging.Slf4j +import io.javalin.http.BadRequestResponse +import io.javalin.http.Context +import io.javalin.http.Handler +import org.jetbrains.annotations.NotNull + +@Singleton +@Slf4j +class TelemetryHandler implements Handler { + + private final static JsonSlurper jsonSlurper = new JsonSlurper() + final List capturedTelemetryMessages = [] + AssertionError savedError + + @Override + void handle(@NotNull Context ctx) throws Exception { + Object message + AssertionError error + try { + ctx.bodyInputStream().withCloseable { + message = readTelemetryMessage(it) + } + log.debug("Read telemetry message: ${message['request_type']}") + } catch (AssertionError e) { + log.error("Error reading traces: $e.message") + error = e + } + + // response + ctx.contentType('text/plain') + .result('') + + if (message) { + synchronized (capturedTelemetryMessages) { + capturedTelemetryMessages.add message + capturedTelemetryMessages.notify() + } + } + if (error) { + synchronized (capturedTelemetryMessages) { + savedError = error + capturedTelemetryMessages.notify() + throw new BadRequestResponse(error.message) + } + } + } + + private static Object readTelemetryMessage(InputStream is) { + jsonSlurper.parse(is) + } + + List drain(long timeoutInMs) { + synchronized (capturedTelemetryMessages) { + if (!savedError && capturedTelemetryMessages.isEmpty()) { + capturedTelemetryMessages.wait(timeoutInMs) + } + if (savedError) { + def e = savedError + savedError = null + throw e + } + def messages = [*capturedTelemetryMessages] + capturedTelemetryMessages.clear() + messages + } + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy index 72026e3614..b6882c8dc3 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/TracesV04Handler.groovy @@ -101,7 +101,9 @@ class TracesV04Handler implements Handler { List traces = [] MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(is) while (unpacker.hasNext()) { - traces << MsgpackHelper.unpackSingle(unpacker) + def trace = MsgpackHelper.unpackSingle(unpacker) + log.debug('Read submitted trace {}', trace) + traces << trace } traces.first() as List ?: [] diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy new file mode 100644 index 0000000000..c52e9c01fa --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Capability.groovy @@ -0,0 +1,51 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +enum Capability { + ASM_ACTIVATION(1), + ASM_IP_BLOCKING(2), + ASM_DD_RULES(3), + ASM_EXCLUSIONS(4), + ASM_REQUEST_BLOCKING(5), + ASM_RESPONSE_BLOCKING(6), + ASM_USER_BLOCKING(7), + ASM_CUSTOM_RULES(8), + ASM_CUSTOM_BLOCKING_RESPONSE(9), + ASM_TRUSTED_IPS(10), + ASM_API_SECURITY_SAMPLE_RATE(11), + APM_TRACING_SAMPLE_RATE(12), + APM_TRACING_LOGS_INJECTION(13), + APM_TRACING_HTTP_HEADER_TAGS(14), + APM_TRACING_CUSTOM_TAGS(15), + ASM_PROCESSOR_OVERRIDES(16), + ASM_CUSTOM_DATA_SCANNERS(17), + ASM_EXCLUSION_DATA(18), + APM_TRACING_ENABLED(19), + APM_TRACING_DATA_STREAMS_ENABLED(20), + ASM_RASP_SQLI(21), + ASM_RASP_LFI(22), + ASM_RASP_SSRF(23), + ASM_RASP_SHI(24), + ASM_RASP_XXE(25), + ASM_RASP_RCE(26), + ASM_RASP_NOSQLI(27), + ASM_RASP_XSS(28), + APM_TRACING_SAMPLE_RULES(29), + CSM_ACTIVATION(30) + + final int ordinal + + Capability(int ordinal) { + this.ordinal = ordinal + } + + static EnumSet forByteArray(byte[] arr) { + def capabilities = EnumSet.noneOf(Capability) + def bi = new BigInteger(arr) + for (Capability c: values()) { + if (bi.testBit(c.ordinal)) { + capabilities << c + } + } + capabilities + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/IntegrityCheckException.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/IntegrityCheckException.groovy new file mode 100644 index 0000000000..26a7e1ef13 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/IntegrityCheckException.groovy @@ -0,0 +1,11 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +class IntegrityCheckException extends RuntimeException { + IntegrityCheckException(String s) { + super(s) + } + + IntegrityCheckException(String s, Exception e) { + super(s, e) + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/MissingContentException.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/MissingContentException.groovy new file mode 100644 index 0000000000..1fb28f10f2 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/MissingContentException.groovy @@ -0,0 +1,7 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +class MissingContentException extends RuntimeException { + MissingContentException(String s) { + super(s) + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java new file mode 100644 index 0000000000..2481f2517a --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigRequest.java @@ -0,0 +1,336 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +public class RemoteConfigRequest { + + public static RemoteConfigRequest newRequest( + String clientId, + String runtimeId, + String tracerVersion, + Collection productNames, + String serviceName, + List extraServices, + String serviceEnv, + String serviceVersion, + List tags, + ClientInfo.ClientState clientState, + Collection cachedTargetFiles, + long capabilities) { + + ClientInfo.TracerInfo tracerInfo = + new RemoteConfigRequest.ClientInfo.TracerInfo(); + tracerInfo.runtimeId = runtimeId; + tracerInfo.tracerVersion = tracerVersion; + tracerInfo.serviceName = serviceName; + tracerInfo.extraServices = extraServices; + tracerInfo.serviceEnv = serviceEnv; + tracerInfo.serviceVersion = serviceVersion; + tracerInfo.tags = tags; + + ClientInfo clientInfo = + new RemoteConfigRequest.ClientInfo( + clientState, clientId, productNames, tracerInfo, capabilities); + + RemoteConfigRequest rcr = new RemoteConfigRequest(); + rcr.client = clientInfo; + rcr.cachedTargetFiles = cachedTargetFiles; + + return rcr; + } + + private ClientInfo client; + + @JsonProperty("cached_target_files") + private Collection cachedTargetFiles; + + public ClientInfo getClient() { + return this.client; + } + + public Target extractTarget() { + return Target.create( + client.tracerInfo.serviceName, + client.tracerInfo.serviceEnv, + client.tracerInfo.serviceVersion); + } + + /** Stores client information for Remote Configuration */ + public static class ClientInfo { + @JsonProperty("state") + public ClientState clientState; + + public String id; + public Collection products; + + @JsonProperty("client_tracer") + public TracerInfo tracerInfo; + + @JsonProperty("client_agent") + public AgentInfo agentInfo = null; // MUST NOT be set + + @JsonProperty("is_tracer") + public boolean isTracer = true; + + @JsonProperty("is_agent") + public Boolean isAgent = null; // MUST NOT be set; + + @JsonProperty("last_seen") + public long lastSeen; + + public byte[] capabilities; + + @JsonProperty("is_updater") + public Boolean isUpdater = null; // MUST NOT be set; + + @JsonProperty("client_updater") + public Map clientUpdater; + + public ClientInfo() {} + public ClientInfo( + ClientState clientState, + String id, + Collection productNames, + TracerInfo tracerInfo, + final long capabilities) { + this.clientState = clientState; + this.id = id; + this.products = productNames; + this.tracerInfo = tracerInfo; + + // Big-endian encoding of the `long` capabilities, stripping any trailing zero bytes + // (except the first one) + final int size = Math.max(1, Long.BYTES - Long.numberOfLeadingZeros(capabilities) / 8); + this.capabilities = new byte[size]; + for (int i = size - 1; i >= 0; i--) { + this.capabilities[size - i - 1] = (byte) (capabilities >>> (i * 8)); + } + } + + public TracerInfo getTracerInfo() { + return this.tracerInfo; + } + + public static class ClientState { + @JsonProperty("root_version") + public long rootVersion = 1L; + + @JsonProperty("targets_version") + public long targetsVersion; + + @JsonProperty("config_states") + public List configStates = new ArrayList<>(); + + @JsonProperty("has_error") + public boolean hasError; + + public String error; + + @JsonProperty("backend_client_state") + public byte[] backendClientState; + + public void setState( + long targetsVersion, + List configStates, + String error, + byte[] backendClientState) { + this.targetsVersion = targetsVersion; + this.configStates = configStates; + this.error = error; + this.hasError = error != null && !error.isEmpty(); + this.backendClientState = backendClientState; + } + + public static class ConfigState { + public static final int APPLY_STATE_ACKNOWLEDGED = 2; + public static final int APPLY_STATE_ERROR = 3; + + public String id; + public long version; + public String product; + + @JsonProperty("apply_state") + public int applyState; + + @JsonProperty("apply_error") + public String applyError; + + public void setState(String id, long version, String product, String error) { + this.id = id; + this.version = version; + this.product = product; + this.applyState = error == null ? APPLY_STATE_ACKNOWLEDGED : APPLY_STATE_ERROR; + this.applyError = error; + } + + @Override + public String toString() { + return new StringJoiner(", ", ConfigState.class.getSimpleName() + "[", "]") + .add("id='" + id + "'") + .add("version=" + version) + .add("product='" + product + "'") + .add("applyState=" + applyState) + .add("applyError='" + applyError + "'") + .toString(); + } + } + + @Override + public String toString() { + return new StringJoiner(", ", ClientState.class.getSimpleName() + "[", "]") + .add("rootVersion=" + rootVersion) + .add("targetsVersion=" + targetsVersion) + .add("configStates=" + configStates) + .add("hasError=" + hasError) + .add("error='" + error + "'") + .add("backendClientState='" + backendClientState + "'") + .toString(); + } + } + + public static class TracerInfo { + @JsonProperty("runtime_id") + public String runtimeId; + + public String language = "java"; + + public List tags; + + @JsonProperty("tracer_version") + public String tracerVersion; + + @JsonProperty("service") + public String serviceName; + + @JsonProperty("extra_services") + public List extraServices; + + @JsonProperty("env") + public String serviceEnv; + + @JsonProperty("app_version") + public String serviceVersion; + + @Override + public String toString() { + return new StringJoiner(", ", TracerInfo.class.getSimpleName() + "[", "]") + .add("runtimeId='" + runtimeId + "'") + .add("language='" + language + "'") + .add("tags=" + tags) + .add("tracerVersion='" + tracerVersion + "'") + .add("serviceName='" + serviceName + "'") + .add("extraServices=" + extraServices) + .add("serviceEnv='" + serviceEnv + "'") + .add("serviceVersion='" + serviceVersion + "'") + .toString(); + } + } + + private class AgentInfo { + String name; + String version; + + @Override + public String toString() { + return new StringJoiner(", ", AgentInfo.class.getSimpleName() + "[", "]") + .add("name='" + name + "'") + .add("version='" + version + "'") + .toString(); + } + } + + @Override + public String toString() { + return new StringJoiner(", ", ClientInfo.class.getSimpleName() + "[", "]") + .add("clientState=" + clientState) + .add("id='" + id + "'") + .add("products=" + products) + .add("tracerInfo=" + tracerInfo) + .add("agentInfo=" + agentInfo) + .add("isTracer=" + isTracer) + .add("isAgent=" + isAgent) + .add("capabilities=" + Arrays.toString(capabilities)) + .toString(); + } + } + + public static class CachedTargetFile { + public String path; + public long length; + public List hashes; + + public CachedTargetFile() {} + public CachedTargetFile( + String path, long length, Map hashes) { + this.path = path; + this.length = length; + List hashesList = + hashes.entrySet().stream() + .map(e -> new TargetFileHash(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + this.hashes = hashesList; + } + + public boolean hashesMatch(Map hashesMap) { + if (this.hashes == null) { + return false; + } + + if (hashesMap.size() != this.hashes.size()) { + return false; + } + + for (TargetFileHash tfh : hashes) { + String digest = hashesMap.get(tfh.algorithm); + if (!digest.equals(tfh.hash)) { + return false; + } + } + + return true; + } + + static public class TargetFileHash { + public String algorithm; + public String hash; + + TargetFileHash() {} + TargetFileHash(String algorithm, String hash) { + this.algorithm = algorithm; + this.hash = hash; + } + + @Override + public String toString() { + return new StringJoiner(", ", TargetFileHash.class.getSimpleName() + "[", "]") + .add("algorithm='" + algorithm + "'") + .add("hash='" + hash + "'") + .toString(); + } + } + + @Override + public String toString() { + return new StringJoiner(", ", CachedTargetFile.class.getSimpleName() + "[", "]") + .add("path='" + path + "'") + .add("length=" + length) + .add("hashes=" + hashes) + .toString(); + } + } + + @Override + public String toString() { + return new StringJoiner(", ", RemoteConfigRequest.class.getSimpleName() + "[", "]") + .add("client=" + client) + .add("cachedTargetFiles=" + cachedTargetFiles) + .toString(); + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java new file mode 100644 index 0000000000..4ce3f5e6b2 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/RemoteConfigResponse.java @@ -0,0 +1,220 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.reflect.UndeclaredThrowableException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Instant; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class RemoteConfigResponse { + @JsonProperty("client_configs") + public List clientConfigs; + + @JsonDeserialize(using = TargetsDeserializer.class) + @JsonSerialize(using = TargetsSerializer.class) + public Targets targets; + + @JsonProperty("target_files") + public List targetFiles; + + public Targets.ConfigTarget getTarget(String configKey) { + return this.targets.targetsSigned.targets.get(configKey); + } + + public String getTargetsSignature(String keyId) { + for (Targets.Signature signature : this.targets.signatures) { + if (keyId.equals(signature.keyId)) { + return signature.signature; + } + } + + throw new IntegrityCheckException("Missing signature for key " + keyId); + } + + public Targets.TargetsSigned getTargetsSigned() { + return this.targets.targetsSigned; + } + + public byte[] getFileContents(String configKey) { + + if (targetFiles == null) { + throw new MissingContentException("No content for " + configKey); + } + + try { + for (TargetFile targetFile : this.targetFiles) { + if (!configKey.equals(targetFile.path)) { + continue; + } + + Targets.ConfigTarget configTarget = getTarget(configKey); + String hashStr; + if (configTarget == null + || configTarget.hashes == null + || (hashStr = configTarget.hashes.get("sha256")) == null) { + throw new IntegrityCheckException("No sha256 hash present for " + configKey); + } + BigInteger expectedHash = new BigInteger(hashStr, 16); + + String raw = targetFile.raw; + byte[] decode = Base64.getDecoder().decode(raw); + BigInteger gottenHash = sha256(decode); + if (!expectedHash.equals(gottenHash)) { + throw new IntegrityCheckException( + "File " + + configKey + + " does not " + + "have the expected sha256 hash: Expected " + + expectedHash.toString(16) + + ", but got " + + gottenHash.toString(16)); + } + if (decode.length != configTarget.length) { + throw new IntegrityCheckException( + "File " + + configKey + + " does not " + + "have the expected length: Expected " + + configTarget.length + + ", but got " + + decode.length); + } + + return decode; + } + } catch (IntegrityCheckException e) { + throw e; + } catch (Exception exception) { + throw new IntegrityCheckException( + "Could not get file contents from remote config, file " + configKey, exception); + } + + throw new MissingContentException("No content for " + configKey); + } + + public static BigInteger sha256(byte[] bytes) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(bytes); + return new BigInteger(1, hash); + } catch (NoSuchAlgorithmException e) { + throw new UndeclaredThrowableException(e); + } + } + + public List getClientConfigs() { + return this.clientConfigs != null ? this.clientConfigs : Collections.emptyList(); + } + + public static class Targets { + public List signatures; + + @JsonProperty("signed") + public TargetsSigned targetsSigned; + + public static class Signature { + @JsonProperty("keyid") + public String keyId; + + @JsonProperty("sig") + public String signature; + } + + public static class TargetsSigned { + @JsonProperty("_type") + public String type; + + public TargetsCustom custom; + + @JsonSerialize(using=InstantSerializer.class) + public Instant expires; + + @JsonProperty("spec_version") + public String specVersion; + + public long version; + public Map targets; + + public static class TargetsCustom { + @JsonProperty("agent_request_interval") + public long agentRequestInterval; + + @JsonProperty("opaque_backend_state") + public String opaqueBackendState; + } + } + + public static class ConfigTarget { + public ConfigTargetCustom custom; + public Map hashes; + public long length; + + public static class ConfigTargetCustom { + @JsonProperty("v") + public long version; + } + } + } + + public static class TargetFile { + public String path; + public String raw; + } + + public static class TargetsDeserializer extends JsonDeserializer { + @Override + public Targets deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + String targetsJsonBase64 = node.asText(); + byte[] targetsJsonDecoded = + Base64.getDecoder().decode(targetsJsonBase64.getBytes(StandardCharsets.ISO_8859_1)); + + JsonParser defParser = jsonParser.getCodec().getFactory().createParser(targetsJsonDecoded); + + JsonDeserializer defaultDeserializer = deserializationContext.findRootValueDeserializer( + deserializationContext.constructType(Targets.class)); + return (Targets) defaultDeserializer.deserialize(defParser, deserializationContext); + } + } + + public static class TargetsSerializer extends JsonSerializer { + @Override + public void serialize(Targets value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + JsonSerializer defaultSerializer = serializers.findValueSerializer(Targets.class); + + StringWriter sw = new StringWriter(); + JsonGenerator defGen = gen.getCodec().getFactory().createGenerator(sw); + defaultSerializer.serialize(value, defGen, serializers); + defGen.flush(); + + byte[] base64 = Base64.getEncoder().encode(sw.toString().getBytes(StandardCharsets.UTF_8)); + + gen.writeString(new String(base64, StandardCharsets.ISO_8859_1)); + } + } + + public static class InstantSerializer extends JsonSerializer { + @Override + public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.toString()); + } + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy new file mode 100644 index 0000000000..92ecb4ad94 --- /dev/null +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/mock_agent/rem_cfg/Target.groovy @@ -0,0 +1,14 @@ +package com.datadog.appsec.php.mock_agent.rem_cfg + +import groovy.transform.Immutable + +@Immutable +class Target { + String service + String env + String appVersion + + static Target create(String service, String env, String appVersion) { + new Target(service, env, appVersion) + } +} diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/model/Span.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/model/Span.groovy index 3c43efd2d4..f55c7f83f6 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/model/Span.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/model/Span.groovy @@ -22,4 +22,5 @@ class Span { String type Map meta Map metrics + Map meta_struct } diff --git a/appsec/tests/integration/src/test/bin/enable_extensions.sh b/appsec/tests/integration/src/test/bin/enable_extensions.sh index ff3b67edcf..3d91dadc5f 100755 --- a/appsec/tests/integration/src/test/bin/enable_extensions.sh +++ b/appsec/tests/integration/src/test/bin/enable_extensions.sh @@ -21,9 +21,10 @@ if [[ -f /appsec/ddappsec.so && -d /project ]]; then { echo extension=/appsec/ddappsec.so echo datadog.appsec.enabled=true - echo datadog.appsec.helper_extra_args=--log_level info - echo datadog.appsec.helper_path=/appsec/ddappsec-helper + echo datadog.appsec.helper_path=/appsec/libddappsec-helper.so echo datadog.appsec.helper_log_file=/tmp/logs/helper.log + echo datadog.appsec.helper_log_level=info +# echo datadog.appsec.helper_log_level=debug echo datadog.appsec.rules=/etc/recommended.json echo datadog.appsec.log_file=/tmp/logs/appsec.log echo datadog.appsec.log_level=debug diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Apache2FpmTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Apache2FpmTests.groovy index 853ab1b90f..b7b9d44983 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Apache2FpmTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Apache2FpmTests.groovy @@ -12,7 +12,6 @@ import org.testcontainers.junit.jupiter.Testcontainers import java.net.http.HttpResponse import static com.datadog.appsec.php.integration.TestParams.getPhpVersion -import static com.datadog.appsec.php.integration.TestParams.getTracerVersion import static com.datadog.appsec.php.integration.TestParams.getVariant import static org.testcontainers.containers.Container.ExecResult diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy index e53bb3bab2..84ca54e07a 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/CommonTests.groovy @@ -1,6 +1,7 @@ package com.datadog.appsec.php.integration import com.datadog.appsec.php.docker.AppSecContainer +import com.datadog.appsec.php.mock_agent.MsgpackHelper import com.datadog.appsec.php.model.Span import com.datadog.appsec.php.model.Trace import org.junit.jupiter.api.Test @@ -8,6 +9,8 @@ import org.testcontainers.containers.Container import java.net.http.HttpRequest import java.net.http.HttpResponse +import org.msgpack.core.MessageUnpacker +import org.msgpack.core.MessagePack import static com.datadog.appsec.php.test.JsonMatcher.matchesJson import static java.net.http.HttpResponse.BodyHandlers.ofString @@ -148,6 +151,15 @@ trait CommonTests { assertThat appsecJson, matchesJson(expJson, false, true) } + Span assert_blocked_span(Span span) { + assert span.metrics."_dd.appsec.enabled" == 1.0d + assert span.metrics."_dd.appsec.waf.duration" > 0.0d + assert span.meta."_dd.appsec.event_rules.version" != '' + assert span.meta."appsec.blocked" == "true" + + return span + } + @Test void 'test blocking'() { // Set ip which is blocked @@ -159,10 +171,41 @@ trait CommonTests { } Span span = trace.first() - assert span.metrics."_dd.appsec.enabled" == 1.0d - assert span.metrics."_dd.appsec.waf.duration" > 0.0d - assert span.meta."_dd.appsec.event_rules.version" != '' - assert span.meta."appsec.blocked" == "true" + + this.assert_blocked_span(span) + } + + @Test + void 'test blocking and stack generation'() { + HttpRequest req = container.buildReq('/generate_stack.php?id=user2020').GET().build() + def trace = container.traceFromRequest(req, ofString()) { HttpResponse re -> + assert re.statusCode() == 403 + assert re.body().contains('blocked') + } + + Span span = trace.first() + assert_blocked_span(span) + + InputStream stream = new ByteArrayInputStream( span.meta_struct."_dd.stack".decodeBase64() ) + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(stream) + List stacks = [] + stacks << MsgpackHelper.unpackSingle(unpacker) + Object exploit = stacks.first().exploit.first() + + assert exploit.language == "php" + assert exploit.id ==~ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ + assert exploit.frames[0].file == "generate_stack.php" + assert exploit.frames[0].function == "one" + assert exploit.frames[0].id == 0 + assert exploit.frames[0].line == 8 + assert exploit.frames[1].file == "generate_stack.php" + assert exploit.frames[1].function == "two" + assert exploit.frames[1].id == 1 + assert exploit.frames[1].line == 12 + assert exploit.frames[2].file == "generate_stack.php" + assert exploit.frames[2].function == "three" + assert exploit.frames[2].id == 2 + assert exploit.frames[2].line == 15 } @Test @@ -349,7 +392,7 @@ trait CommonTests { void 'module does not have STATIC_TLS flag'() { Container.ExecResult res = container.execInContainer( 'bash', '-c', - '''! { readelf -d "$(php -r 'echo ini_get("extension_dir");')"/ddappsec.so | grep STATIC_TLS; }''') + '''! { readelf -d "$(DD_TRACE_CLI_ENABLED=0 php -r 'echo ini_get("extension_dir");')"/ddappsec.so | grep STATIC_TLS; }''') if (res.exitCode != 0) { throw new AssertionError("Module has STATIC_TLS flag: $res.stdout") } diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Laravel8xTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Laravel8xTests.groovy index 381a0d793c..86001ab57c 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Laravel8xTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Laravel8xTests.groovy @@ -50,7 +50,7 @@ class Laravel8xTests { Span span = trace.first() assert span.meta."appsec.events.users.login.failure.track" == "true" - assert span.meta."_dd.appsec.events.users.login.failure.auto.mode" == "safe" + assert span.meta."_dd.appsec.events.users.login.failure.auto.mode" == "identification" assert span.meta."appsec.events.users.login.failure.usr.exists" == "false" assert span.metrics._sampling_priority_v1 == 2.0d } @@ -66,7 +66,7 @@ class Laravel8xTests { //ciuser@example.com user id is 1 Span span = trace.first() assert span.meta."usr.id" == "1" - assert span.meta."_dd.appsec.events.users.login.success.auto.mode" == "safe" + assert span.meta."_dd.appsec.events.users.login.success.auto.mode" == "identification" assert span.meta."appsec.events.users.login.success.track" == "true" assert span.metrics._sampling_priority_v1 == 2.0d } @@ -81,7 +81,7 @@ class Laravel8xTests { Span span = trace.first() assert span.meta."usr.id" == "2" - assert span.meta."_dd.appsec.events.users.signup.auto.mode" == "safe" + assert span.meta."_dd.appsec.events.users.signup.auto.mode" == "identification" assert span.meta."appsec.events.users.signup.track" == "true" assert span.metrics._sampling_priority_v1 == 2.0d } diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy index f679392c45..25ec33cdd4 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/NginxFpmTests.groovy @@ -12,9 +12,7 @@ import org.testcontainers.junit.jupiter.Testcontainers import java.net.http.HttpResponse import static com.datadog.appsec.php.integration.TestParams.getPhpVersion -import static com.datadog.appsec.php.integration.TestParams.getTracerVersion import static com.datadog.appsec.php.integration.TestParams.getVariant -import static org.testcontainers.containers.Container.ExecResult @Testcontainers @Slf4j diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy new file mode 100644 index 0000000000..b06027dca1 --- /dev/null +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RemoteConfigTests.groovy @@ -0,0 +1,404 @@ +package com.datadog.appsec.php.integration + +import com.datadog.appsec.php.docker.AppSecContainer +import com.datadog.appsec.php.docker.FailOnUnmatchedTraces +import com.datadog.appsec.php.mock_agent.rem_cfg.Capability +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigRequest +import com.datadog.appsec.php.mock_agent.rem_cfg.RemoteConfigResponse +import com.datadog.appsec.php.mock_agent.rem_cfg.Target +import groovy.json.JsonOutput +import groovy.util.logging.Slf4j +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledIf +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers + +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.nio.charset.StandardCharsets +import java.time.Instant + +import static com.datadog.appsec.php.integration.TestParams.getPhpVersion +import static com.datadog.appsec.php.integration.TestParams.getVariant +import static java.net.http.HttpResponse.BodyHandlers.ofString +import static org.junit.jupiter.api.Assumptions.assumeTrue +import static org.testcontainers.containers.Container.ExecResult + +@Testcontainers +@Slf4j +@DisabledIf('isDisabled') +class RemoteConfigTests { + static boolean disabled = variant.contains('zts') || phpVersion != '8.3' + + private static final Target INITIAL_TARGET = new Target('some-name', 'none', '') + + @Container + @FailOnUnmatchedTraces + public static final AppSecContainer CONTAINER = + new AppSecContainer( + workVolume: this.name, + baseTag: 'nginx-fpm-php', + phpVersion: phpVersion, + phpVariant: variant, + www: 'base', + ) + + @BeforeAll + static void beforeAll() { + ExecResult res = CONTAINER.execInContainer( + 'bash', '-c', + '''sed -e '/appsec.enabled/d' -e '/appsec.rules=/d' /etc/php/php.ini > /etc/php/php-rc.ini; + kill -9 `pgrep php-fpm`; + export _DD_DEBUG_SIDECAR_RC_POLL_INTERVAL_MILLIS=1000 DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=1 DD_ENV=; + php-fpm -y /etc/php-fpm.conf -c /etc/php/php-rc.ini''') + assert res.exitCode == 0 + } + + @Test + void 'test remote activation and capabilities'() { + def doReq = { int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php') + .GET() + .header('User-agent', 'dd-test-scanner-log-block') + .build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call(200) + + RemoteConfigRequest rcr = applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [ + asm: [enabled: true] + ] + ]) + + def capSet = Capability.forByteArray(rcr.client.capabilities) + + [ + Capability.ASM_ACTIVATION, + Capability.ASM_IP_BLOCKING, + Capability.ASM_DD_RULES, + Capability.ASM_EXCLUSIONS, + Capability.ASM_REQUEST_BLOCKING, + Capability.ASM_RESPONSE_BLOCKING, + Capability.ASM_USER_BLOCKING, + Capability.ASM_CUSTOM_RULES, + Capability.ASM_CUSTOM_BLOCKING_RESPONSE, + Capability.ASM_TRUSTED_IPS, + ].each { assert it in capSet } + + doReq.call(403) + + dropRemoteConfig(INITIAL_TARGET) + + doReq.call(200) + } + + @Test + void 'test asm_data'() { + def doReq = { String ip, int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php') + .GET() + .header('X-Real-Ip', ip) + .build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call('1.2.3.4', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'datadog/2/ASM_DATA/mydata/config': [ + rules_data: [ + [ + id: 'blocked_ips', + type: 'ip_with_expiration', + data: [ + [ + expiration: 0, + value: '1.2.3.0/24' + ] + ] + ] + ] + + ] + ]) + + doReq.call('1.2.3.4', 403) + doReq.call('1.2.4.1', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call('1.2.3.4', 200) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test asm_dd'() { + def doReq = { int expectedStatus -> + HttpRequest req = CONTAINER.buildReq('/hello.php?a=matched+value').GET().build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call(200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'employee/ASM_DD/full_cfg/config': + [ + version: '2.1', + rules: [[ + id: 'partial_match_values', + name: 'Partially match values', + tags: [ + type: 'security_scanner', + category: 'attack_attempt' + ], + conditions: [[ + parameters: [ + inputs: [[ address: 'server.request.query' ]], + regex: '.*matched.+value.*' + ], + operator: 'match_regex' + ]], + transformers: ['values_only'], + on_match: ['block'] + ]] + ] + ]) + + doReq.call(403) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call(200) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test asm'() { + def doReq = { String path, int expectedStatus, Map headers = [:] -> + def br = CONTAINER.buildReq(path).GET() + headers.each { k, v -> br.header(k, v) } + HttpRequest req = br.build() + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + assert resp.statusCode() == expectedStatus + } + } + + doReq.call('/hello.php', 200) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]], + 'datadog/2/ASM/custom_user_cfg_2/config': [ + actions: [[ + id: 'block_custom', + type: 'block_request', + parameters: [ + status_code: '408' + ] + ]] + ], + 'datadog/2/ASM/custom_user_cfg_1/config': [ + custom_rules: [[ + id: 'partial_match_values', + name: 'Partially match values', + tags: [ + type: 'security_scanner', + category: 'attack_attempt' + ], + conditions: [[ + parameters: [ + inputs: [[ + address: 'server.request.query' + ]], + regex: '.*matched.+value.*' + ], + operator: 'match_regex' + ]], + transformers: ['values_only'], + on_match: ['block_custom'] + + ]], + exclusions: [[ + id: 'excl1', + rules_target: [[ + rule_id: 'ua0-600-56x' + ]], + conditions: [[ + operator: 'match_regex', + parameters: [ + inputs: [[ + address: 'server.request.query' + ]], + regex: 'excluded' + ] + ]] + ]], + rules_override: [[ + rules_target: [[ + rule_id: 'ua0-600-56x' + ]], + on_match: ['block_custom2'], + enabled: true + ]], + actions: [ + [ + id: 'block_custom2', + type: 'block_request', + parameters: [ + status_code: '410' + ] + ] + ] + ] + ]) + + // matches user rule 'partial_match_values' + doReq.call('/hello.php?a=matched+value1', 408) + + // matches exclusion rule 'excl1' + doReq.call('/hello.php?b=excluded', 200, ['User-agent': 'dd-test-scanner-log-block']) + + // action is overridden in rules_override to block_custom2 (code: 410) + doReq.call('/hello.php', 410, ['User-agent': 'dd-test-scanner-log-block']) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': null /* keep */, + 'datadog/2/ASM/custom_user_cfg_1/config': null /* keep */, + 'datadog/2/ASM/custom_user_cfg_2/config': [ + actions: [[ + id: 'block_custom', + type: 'block_request', + parameters: [ + status_code: '409' + ] + ]] + ], + ]) + + // status code changed to 409 + doReq.call('/hello.php?a=matched+value1', 409) + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + doReq.call('/hello.php?a=matched+value1', 200) + doReq.call('/hello.php?b=excluded', 403, ['User-agent': 'dd-test-scanner-log-block']) + doReq.call('/hello.php', 403, ['User-agent': 'dd-test-scanner-log-block']) + + dropRemoteConfig(INITIAL_TARGET) + } + + @Test + void 'test env change'() { + Target newTarget = new Target('some-name', 'another-env', '') + + def doReq = { Integer expectedStatus, String path, Map headers = [:] -> + def br = CONTAINER.buildReq(path).GET() + headers.each { k, v -> br.header(k, v) } + HttpRequest req = br.build() + def gottenStatus = null + CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + gottenStatus = resp.statusCode() + if (expectedStatus) { + assert gottenStatus == expectedStatus + } + } + gottenStatus + } + + doReq.call(200, '/hello.php') + + applyRemoteConfig(INITIAL_TARGET, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: false]]]) + + doReq.call(200, '/hello.php', ['User-agent': 'dd-test-scanner-log-block']) + + // changes env at the end of the request. The new rem cfg path is not transmitted + // to helper because appsec transmit rc path on req init + doReq.call(200, '/change_env.php?env=another-env') + + // new rem cfg path is transmitted to the helper on config_sync + doReq.call(200, '/change_env.php?env=another-env') + + applyRemoteConfig(newTarget, [ + 'datadog/2/ASM_FEATURES/asm_features_activation/config': [asm: [enabled: true]]]) + + def status = doReq.call(null, '/hello.php', ['User-agent': 'dd-test-scanner-log-block']) + if (status == 200) { + assumeTrue(false, "Test fails because env of rc client is reset on ddtrace_sidecar_rinit(), " + + "which runs before appsec rinit") + } + assert status == 403 + + dropRemoteConfig(INITIAL_TARGET) + dropRemoteConfig(newTarget) + } + + private RemoteConfigRequest applyRemoteConfig(Target target, Map files) { + Map encodedFiles = files + .findAll { it.value != null } + .collectEntries { + [ + it.key, + JsonOutput.toJson(it.value).getBytes(StandardCharsets.UTF_8) + ] + } + long newVersion = Instant.now().epochSecond + def rcr = new RemoteConfigResponse() + rcr.clientConfigs = files.keySet() as List + rcr.targetFiles = encodedFiles.collect { + new RemoteConfigResponse.TargetFile( + path: it.key, + raw: new String( + Base64.encoder.encode(it.value), + StandardCharsets.ISO_8859_1) + ) + } + rcr.targets = new RemoteConfigResponse.Targets( + signatures: [], + targetsSigned: new RemoteConfigResponse.Targets.TargetsSigned( + type: 'root', + custom: new RemoteConfigResponse.Targets.TargetsSigned.TargetsCustom( + opaqueBackendState: 'ABCDEF' + ), + specVersion:'1.0.0', + expires: Instant.parse('2030-01-01T00:00:00Z'), + version: newVersion, + targets: encodedFiles.collectEntries { + [ + it.key, + new RemoteConfigResponse.Targets.ConfigTarget( + hashes: [sha256: RemoteConfigResponse.sha256(it.value).toString(16).padLeft(64, '0')], + length: it.value.size(), + custom: new RemoteConfigResponse.Targets.ConfigTarget.ConfigTargetCustom( + version: newVersion + ) + ) + ] + } + ), + ) + + CONTAINER.setNextRCResponse(target, rcr) + CONTAINER.waitForRCVersion(target, newVersion, 5_000) + } + + RemoteConfigRequest dropRemoteConfig(Target target) { + applyRemoteConfig(target, [:]) + } + +} diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RoadRunnerTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RoadRunnerTests.groovy index b17d41fa60..4a5503e363 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RoadRunnerTests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/RoadRunnerTests.groovy @@ -8,7 +8,6 @@ import com.datadog.appsec.php.model.Span import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledIf -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy import org.testcontainers.containers.wait.strategy.WaitStrategy import org.testcontainers.containers.wait.strategy.WaitStrategyTarget import org.testcontainers.junit.jupiter.Container @@ -23,7 +22,6 @@ import static com.datadog.appsec.php.integration.TestParams.getVariant import static com.datadog.appsec.php.integration.TestParams.phpVersionAtLeast import static com.datadog.appsec.php.test.JsonMatcher.matchesJson import static java.net.http.HttpResponse.BodyHandlers.ofString -import static java.time.temporal.ChronoUnit.SECONDS import static org.hamcrest.MatcherAssert.assertThat @Testcontainers @@ -45,7 +43,9 @@ class RoadRunnerTests { phpVariant: variant, www: 'roadrunner', ).with { - // we only start listening on http after run.sh has finished + // we only start listening on http after run.sh has finished, + // so mark immediately the container as ready. We instead check for liveliness + // in beforeAll() setWaitStrategy(new WaitStrategy() { @Override void waitUntilReady(WaitStrategyTarget waitStrategyTarget) { @@ -62,7 +62,14 @@ class RoadRunnerTests { @BeforeAll static void beforeAll() { - new HostPortWaitStrategy().withStartupTimeout(Duration.of(300, SECONDS) ).waitUntilReady(CONTAINER) + // wait until roadrunner is running + long deadline = System.currentTimeMillis() + 300_000 + while (CONTAINER.execInContainer('grep', 'http server was started', '/tmp/logs/rr.log').exitCode != 0) { + if (System.currentTimeMillis() > deadline) { + throw new RuntimeException('Roadrunner did not start on time (see output of run.sh)') + } + Thread.sleep(500) + } } @Test diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/SidecarFeaturesDisabledTests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/SidecarFeaturesDisabledTests.groovy new file mode 100644 index 0000000000..acd6916355 --- /dev/null +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/SidecarFeaturesDisabledTests.groovy @@ -0,0 +1,66 @@ +package com.datadog.appsec.php.integration + +import com.datadog.appsec.php.docker.AppSecContainer +import com.datadog.appsec.php.docker.FailOnUnmatchedTraces +import com.datadog.appsec.php.docker.InspectContainerHelper +import groovy.util.logging.Slf4j +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.DisabledIf +import org.testcontainers.junit.jupiter.Container +import org.testcontainers.junit.jupiter.Testcontainers + +import java.net.http.HttpRequest +import java.net.http.HttpResponse + +import static com.datadog.appsec.php.integration.TestParams.getPhpVersion +import static com.datadog.appsec.php.integration.TestParams.getVariant +import static java.net.http.HttpResponse.BodyHandlers.ofString +import static org.testcontainers.containers.Container.ExecResult + +@Testcontainers +@Slf4j +@DisabledIf('isNotPhp83') +class SidecarFeaturesDisabledTests { + static boolean isNotPhp83() { + !getPhpVersion().startsWith('8.3') + } + + @Container + @FailOnUnmatchedTraces + public static final AppSecContainer CONTAINER = + new AppSecContainer( + workVolume: this.name, + baseTag: 'apache2-mod-php', + phpVersion: phpVersion, + phpVariant: variant, + www: 'base', + ) { + @Override + void configure() { + super.configure() + withEnv('DD_INSTRUMENTATION_TELEMETRY_ENABLED', 'false') + withEnv('DD_TRACE_SIDECAR_TRACE_SENDER', 'false') + } + } + + static void main(String[] args) { + InspectContainerHelper.run(CONTAINER) + } + + @Test + void 'appsec is enabled and sidecar is launched'() { + HttpRequest req = CONTAINER.buildReq('/hello.php') + .GET().build() + def trace = CONTAINER.traceFromRequest(req, ofString()) { HttpResponse resp -> + resp.body() == 'Hello world!' + } + + assert trace.first().metrics."_dd.appsec.enabled" == 1.0d + + ExecResult res = CONTAINER.execInContainer( + '/bin/bash', '-ce', 'ps auxww; pgrep -f datadog-ipc-helper') + if (res.exitCode != 0) { + throw new AssertionError("Could not find helper: $res.stdout\n$res.stderr") + } + } +} diff --git a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Symfony62Tests.groovy b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Symfony62Tests.groovy index 27f77c8c72..dd205114bb 100644 --- a/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Symfony62Tests.groovy +++ b/appsec/tests/integration/src/test/groovy/com/datadog/appsec/php/integration/Symfony62Tests.groovy @@ -53,7 +53,8 @@ class Symfony62Tests { assert resp.statusCode() == 302 } Span span = trace.first() - assert span.meta."_dd.appsec.events.users.login.success.auto.mode" == "safe" + assert span.meta."usr.id" != "" + assert span.meta."_dd.appsec.events.users.login.success.auto.mode" == "identification" assert span.meta."appsec.events.users.login.success.track" == "true" assert span.metrics._sampling_priority_v1 == 2.0d } @@ -69,7 +70,7 @@ class Symfony62Tests { } Span span = trace.first() assert span.meta."appsec.events.users.login.failure.track" == 'true' - assert span.meta."_dd.appsec.events.users.login.failure.auto.mode" == 'safe' + assert span.meta."_dd.appsec.events.users.login.failure.auto.mode" == 'identification' assert span.meta."appsec.events.users.login.failure.usr.exists" == 'false' assert span.metrics._sampling_priority_v1 == 2.0d } @@ -84,7 +85,8 @@ class Symfony62Tests { assert resp.statusCode() == 302 } Span span = trace.first() - assert span.meta."_dd.appsec.events.users.signup.auto.mode" == "safe" + assert span.meta."usr.id" != "" + assert span.meta."_dd.appsec.events.users.signup.auto.mode" == "identification" assert span.meta."appsec.events.users.signup.track" == "true" assert span.metrics._sampling_priority_v1 == 2.0d } diff --git a/appsec/tests/integration/src/test/resources/gdbinit b/appsec/tests/integration/src/test/resources/gdbinit new file mode 100644 index 0000000000..aaf915b3cc --- /dev/null +++ b/appsec/tests/integration/src/test/resources/gdbinit @@ -0,0 +1,4 @@ +set verbose off +set confirm off +handle SIGPIPE nostop print pass +catch throw diff --git a/appsec/tests/integration/src/test/waf/recommended.json b/appsec/tests/integration/src/test/waf/recommended.json index e4ff0748ce..f06f7f29fc 100644 --- a/appsec/tests/integration/src/test/waf/recommended.json +++ b/appsec/tests/integration/src/test/waf/recommended.json @@ -31,6 +31,32 @@ }, { "id": "blk-001-002", + "name": "Block user with stack", + "tags": { + "type": "block_user", + "category": "security_response" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "usr.id" + } + ], + "data": "blocked_users" + }, + "operator": "exact_match" + } + ], + "transformers": [], + "on_match": [ + "stack_trace", + "block" + ] + }, + { + "id": "blk-001-003", "name": "Block User Addresses", "tags": { "type": "block_user", diff --git a/appsec/tests/integration/src/test/www/base/public/change_env.php b/appsec/tests/integration/src/test/www/base/public/change_env.php new file mode 100644 index 0000000000..129a83b2fd --- /dev/null +++ b/appsec/tests/integration/src/test/www/base/public/change_env.php @@ -0,0 +1,7 @@ +env = $_GET['env']; + +var_dump($root_span); diff --git a/appsec/tests/integration/src/test/www/base/public/flush_telemetry.php b/appsec/tests/integration/src/test/www/base/public/flush_telemetry.php new file mode 100644 index 0000000000..52f5388e78 --- /dev/null +++ b/appsec/tests/integration/src/test/www/base/public/flush_telemetry.php @@ -0,0 +1,4 @@ + #include +#include #include namespace ip = asio::ip; @@ -22,26 +23,29 @@ using boost::system::error_code; class HttpClient { public: - HttpClient(EchoPipe &echo_pipe, ip::tcp::socket &&sock) - : echo_pipe_{echo_pipe}, sock_{std::move(sock)} - { - } - ~HttpClient() { SPDLOG_INFO("Closing HTTP connection"); } - - void do_request(yield_context yield) - { - try { - _do_request(yield); - } catch (const std::exception &e) { - SPDLOG_WARN("Error handling HTTP request: {}", e.what()); - } catch (...) { - SPDLOG_WARN("Error handling HTTP request"); - } - SPDLOG_DEBUG("Finished request handling"); + HttpClient(EchoPipe &echo_pipe, ip::tcp::socket &&sock) + : echo_pipe_{echo_pipe}, sock_{std::move(sock)} + {} + HttpClient(const HttpClient &) = delete; + HttpClient(HttpClient &&) = delete; + HttpClient &operator=(const HttpClient &) = delete; + HttpClient &operator=(HttpClient &&) = delete; + ~HttpClient() { SPDLOG_INFO("Closing HTTP connection"); } + + void do_request(const yield_context &yield) + { + try { + _do_request(yield); + } catch (const std::exception &e) { + SPDLOG_WARN("Error handling HTTP request: {}", e.what()); + } catch (...) { + SPDLOG_WARN("Error handling HTTP request"); + } + SPDLOG_DEBUG("Finished request handling"); } private: - void _do_request(yield_context yield) + void _do_request(const yield_context& yield) { SPDLOG_DEBUG("do_request_line"); do_request_line(yield); @@ -51,6 +55,9 @@ class HttpClient { if (method_ == "PUT" && uri_ == "/v0.4/traces") { SPDLOG_DEBUG("do_traces"); do_traces(yield); + } else if (method_ == "POST" && + uri_ == "/telemetry/proxy/api/v2/apmtelemetry") { + SPDLOG_INFO("Ignoring telemetry data"); } else { SPDLOG_WARN("Don't know how to handle {} {}", method_, uri_); } @@ -61,11 +68,12 @@ class HttpClient { void do_request_line(yield_context yield) { - size_t size = asio::async_read_until( + size_t const size = asio::async_read_until( sock_, asio::dynamic_buffer(buf_), '\r', yield); SPDLOG_DEBUG("Read request line with size {}", size - 1); - std::sregex_iterator it{buf_.begin(), buf_.begin() + size, word}; + std::sregex_iterator it{ + buf_.begin(), buf_.begin() + static_cast(size), word}; if (it != itend) { method_ = it->str(); SPDLOG_DEBUG("Method: {}", method_); @@ -96,14 +104,14 @@ class HttpClient { consume_chars(yield, '\n'); } - void do_headers(yield_context yield) + void do_headers(const yield_context& yield) { while (do_single_header(yield)) {} } bool do_single_header(yield_context yield) { - size_t read = asio::async_read_until( + size_t const read = asio::async_read_until( sock_, asio::dynamic_buffer(buf_), '\r', yield); if (read == 1) { @@ -112,8 +120,9 @@ class HttpClient { } std::smatch match; std::string header; - if (std::regex_search( - buf_.cbegin(), buf_.cbegin() + read, match, header_name)) { + if (std::regex_search(buf_.cbegin(), + buf_.cbegin() + static_cast(read), match, + header_name)) { header = match.str(); } else { throw std::runtime_error{"No header name found"}; @@ -134,7 +143,7 @@ class HttpClient { return true; } - void do_traces(yield_context yield) + void do_traces(const yield_context& yield) { auto count_header = headers_.find(trace_count_header); if (count_header == headers_.end()) { @@ -151,10 +160,10 @@ class HttpClient { void do_single_trace(yield_context yield) { auto dyn_buf = asio::dynamic_buffer(buf_); - size_t read = asio::async_read_until(sock_, dyn_buf, '\r', yield); + size_t const read = asio::async_read_until(sock_, dyn_buf, '\r', yield); // read the message length - std::string_view size_str{&buf_[0], read - 1}; + std::string_view const size_str{buf_.data(), read - 1}; SPDLOG_DEBUG("Trace size as string: {}", size_str); size_t msg_size; @@ -168,7 +177,7 @@ class HttpClient { if (buf_.size() < msg_size + 2 /* for \r\n */) { buf_.reserve(msg_size + 2); - size_t missing = msg_size + 2 - buf_.size(); + size_t const missing = msg_size + 2 - buf_.size(); asio::async_read(sock_, dyn_buf.prepare(missing), yield); } @@ -210,33 +219,31 @@ class HttpClient { } template - void check_n_chars(Tuple &&tuple, std::index_sequence) + void check_n_chars(Tuple &&tuple, std::index_sequence /*seq*/) { auto check_char = [](char read, char exp) { if (read != exp) { throw std::runtime_error{ std::string{"Expected read char to be "} + - boost::lexical_cast( - static_cast(exp)) + + std::to_string(static_cast(exp)) + " but it was " + - boost::lexical_cast( - static_cast(read))}; + std::to_string(static_cast(read))}; } }; - [[maybe_unused]] int dummy[] = { - (check_char(buf_[Is], std::get(tuple)), 0)...}; + (check_char(buf_[Is], std::get(tuple)), ...); } void do_response(yield_context yield) { - static const char resp[] = "HTTP/1.1 200 OK\r\n" - "Content-Type: application/json\r\n" - "Content-Length: 40\r\n" - "\r\n" - R"({"rate_by_service":{"service:,env:":1}}")" - "\n"; + static const std::array resp{ + "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 40\r\n" + "\r\n" + R"({"rate_by_service":{"service:,env:":1}}")" + "\n"}; asio::async_write( - sock_, asio::const_buffer(resp, sizeof(resp) - 1), yield); + sock_, asio::const_buffer(resp.data(), sizeof(resp) - 1), yield); } void consume(size_t bytes) @@ -249,8 +256,11 @@ class HttpClient { buf_.erase(0, bytes); } + // NOLINTNEXTLINE static inline const std::regex word{R"(\S+)"}; + // NOLINTNEXTLINE static inline const std::regex header_name{R"(^[^:]+(?=:))"}; + // NOLINTNEXTLINE static inline const std::sregex_iterator itend; static inline constexpr std::string_view trace_count_header{ "x-datadog-trace-count"}; @@ -286,7 +296,7 @@ void HttpServerDispatcher::start() acceptor_.listen(backlog); // synchronous; may throw SPDLOG_INFO("Listening for TCP connections (mock datadog agent)"); } -void HttpServerDispatcher::run_loop(yield_context yield) +void HttpServerDispatcher::run_loop(const yield_context& yield) { SPDLOG_INFO("Started HttpServerDisp:atcher loop"); while (!iocontext.stopped()) { diff --git a/appsec/tests/mock_helper/mock_dd_agent.hpp b/appsec/tests/mock_helper/mock_dd_agent.hpp index b60178fdd4..f6632e4683 100644 --- a/appsec/tests/mock_helper/mock_dd_agent.hpp +++ b/appsec/tests/mock_helper/mock_dd_agent.hpp @@ -11,15 +11,19 @@ #include class HttpServerDispatcher { +public: static constexpr int backlog = 1; - public: HttpServerDispatcher(EchoPipe &echo_pipe, asio::ip::port_type port); + HttpServerDispatcher(const HttpServerDispatcher&) = delete; + HttpServerDispatcher& operator=(const HttpServerDispatcher&) = delete; + HttpServerDispatcher(HttpServerDispatcher&&) = delete; + HttpServerDispatcher& operator=(HttpServerDispatcher&&) = delete; ~HttpServerDispatcher(); void start(); - void run_loop(boost::asio::yield_context yield); + void run_loop(const boost::asio::yield_context& yield); private: EchoPipe &echo_pipe_; diff --git a/appsec/tests/mock_helper/mock_helper_main.cc b/appsec/tests/mock_helper/mock_helper_main.cc index 6982260fde..49b92ffe53 100644 --- a/appsec/tests/mock_helper/mock_helper_main.cc +++ b/appsec/tests/mock_helper/mock_helper_main.cc @@ -17,11 +17,11 @@ #define RAPIDJSON_NO_SIZETYPEDEFINE namespace rapidjson { using SizeType = std::uint32_t; -} +} // namespace rapidjson #include #include -#include #include +#include #include @@ -39,13 +39,12 @@ using SizeType = std::uint32_t; #include #include -#include "mock_helper_main.hpp" #include "mock_dd_agent.hpp" +#include "mock_helper_main.hpp" #include namespace po = boost::program_options; namespace asio = boost::asio; -namespace ip = asio::ip; namespace local = asio::local; namespace posix = asio::posix; using boost::system::error_code; @@ -60,7 +59,7 @@ MsgpackToJson::MsgpackToJson(const char *buffer, size_t size) void MsgpackToJson::convert() { mpack_tree_parse(&tree_); - mpack_error_t err = mpack_tree_error(&tree_); + const mpack_error_t err = mpack_tree_error(&tree_); if (err != mpack_ok) { throw std::runtime_error{std::string{"Error parsing msgpack: "} + mpack_error_to_string(err)}; @@ -69,9 +68,11 @@ void MsgpackToJson::convert() mpack_node_t root = mpack_tree_root(&tree_); write(root); } + +// NOLINTNEXTLINE(misc-no-recursion) void MsgpackToJson::write(mpack_node_t &node) { - mpack_type_t type = mpack_node_type(node); + const mpack_type_t type = mpack_node_type(node); switch (type) { case mpack_type_nil: writer_.Null(); @@ -108,10 +109,10 @@ void MsgpackToJson::write(mpack_node_t &node) writer_.StartObject(); auto len = mpack_node_map_count(node); for (decltype(len) i = 0; i < len; i++) { - mpack_node_t key = mpack_node_map_key_at(node, i); + mpack_node_t const key = mpack_node_map_key_at(node, i); mpack_node_t value = mpack_node_map_value_at(node, i); - mpack_type_t key_type = mpack_node_type(key); + mpack_type_t const key_type = mpack_node_type(key); if (key_type != mpack_type_str) { throw std::runtime_error{ "saw nonstring map key in msgpack message"}; @@ -138,9 +139,9 @@ EchoPipe::EchoPipe() : stream_{try_fds()} } } void EchoPipe::write( - const asio::const_buffer data, asio::yield_context yield) + asio::const_buffer buff, asio::yield_context yield) { - std::array buffers{data, {"", 1}}; + std::array buffers{buff, {"", 1}}; SPDLOG_INFO("Writing to echo pipe {} + 1 bytes", buffers[0].size()); SPDLOG_DEBUG("Content: {}", std::string_view{ @@ -171,20 +172,20 @@ std::optional EchoPipe::try_single_fd(int fd) { struct ::stat statbuf = {0}; if (fstat(fd, &statbuf) == -1) { - error_code ec = {errno, boost::system::system_category()}; + error_code const ec = {errno, boost::system::system_category()}; SPDLOG_INFO("fstat() failed for fd {}: {}", fd, ec.message()); return std::nullopt; } - if (!(statbuf.st_mode & (S_IFIFO | S_IFCHR))) { + if ((statbuf.st_mode & (S_IFIFO | S_IFCHR)) == 0) { SPDLOG_INFO( "File descriptor {0} is not a FIFO or character device: {1:o}", fd, statbuf.st_mode & S_IFMT); return std::nullopt; } - int new_fd = ::dup(fd); + const int new_fd = ::dup(fd); // NOLINT(android-cloexec-dup) if (new_fd == -1) { - error_code ec = {errno, boost::system::system_category()}; + error_code const ec = {errno, boost::system::system_category()}; SPDLOG_INFO("Call to dup of fd {} failed: {}", fd, ec.message()); return std::nullopt; } @@ -200,11 +201,11 @@ struct OwningBuffer { OwningBuffer(const char *buf, std::size_t len) : buf_{buf}, len_{len} {} OwningBuffer(const OwningBuffer &) = delete; const OwningBuffer &operator=(const OwningBuffer &) = delete; - OwningBuffer(OwningBuffer &&oth) : OwningBuffer{} + OwningBuffer(OwningBuffer &&oth) noexcept : OwningBuffer{} { *this = std::move(oth); } - const OwningBuffer &operator=(OwningBuffer &&oth) + OwningBuffer &operator=(OwningBuffer &&oth) noexcept { std::free(const_cast(buf_)); buf_ = oth.buf_; @@ -231,6 +232,7 @@ class MpackWriter { std::free(data_); } + // NOLINTNEXTLINE(misc-no-recursion) MpackWriter &operator<<(const rapidjson::Value &val) { if (val.IsInt() || val.IsInt64()) { @@ -420,22 +422,22 @@ class Client { { JsonToMsgpack json_to_msgpack{doc}; json_to_msgpack.convert(); - if (json_to_msgpack.delay()) { + if (json_to_msgpack.delay() != 0U) { SPDLOG_INFO("Will wait {} seconds before sending response", json_to_msgpack.delay()); asio::steady_timer timer{iocontext}; timer.expires_after(std::chrono::seconds{json_to_msgpack.delay()}); timer.async_wait(yield); } - OwningBuffer buf = json_to_msgpack.move_buffer(); + OwningBuffer const buf = json_to_msgpack.move_buffer(); - Header h; + Header h{}; memcpy(&h.marker, "dds", 4); h.size = buf.len_; SPDLOG_INFO("Writing response; size {} (header) + {} (body)", sizeof(h), buf.len_); - std::array buffers = { + std::array const buffers = { asio::const_buffer(reinterpret_cast(&h), sizeof(h)), asio::const_buffer(buf.buf_, buf.len_), }; @@ -516,7 +518,7 @@ class Dispatcher { socklen_t len = sizeof(addr); if (::getsockname(fd, reinterpret_cast(&addr), &len) == -1) { - error_code ec = {errno, boost::system::system_category()}; + error_code const ec = {errno, boost::system::system_category()}; SPDLOG_INFO("Call to getsockname failed on socket {}: {}", fd, ec.message()); return std::nullopt; @@ -608,13 +610,19 @@ static void _fatal_signal_handler(int signum) { int main(int argc, char *argv[]) { - ::signal(SIGSEGV, _fatal_signal_handler); - ::signal(SIGABRT, _fatal_signal_handler); - auto console = spdlog::stderr_logger_mt("console"); spdlog::set_default_logger(console); spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l] %v at %s:%!"); + auto prev = ::signal(SIGSEGV, _fatal_signal_handler); + if (prev == SIG_ERR) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + SPDLOG_CRITICAL("Could not set signal handler for SIGSEGV"); + } + prev = ::signal(SIGABRT, _fatal_signal_handler); + if (prev == SIG_ERR) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + SPDLOG_CRITICAL("Could not set signal handler for SIGABRT"); + } + po::options_description opt_desc{"Allowed options"}; // clang-format off opt_desc.add_options() @@ -641,11 +649,11 @@ int main(int argc, char *argv[]) } po::notify(opt_vm); - if (opt_vm.count("help")) { + if (opt_vm.count("help") != 0U) { std::cerr << opt_desc << "\n"; return 1; } - if (!opt_vm.count("response")) { + if (opt_vm.count("response") == 0U) { std::cerr << "At least one response is required\n"; return 1; } @@ -673,15 +681,15 @@ int main(int argc, char *argv[]) }); std::optional signal_lock; - if (opt_vm.count("lock")) { + if (opt_vm.count("lock") != 0U) { signal_lock.emplace(opt_vm["lock"].as()); spawn(iocontext, [&signal_lock](auto yield) { signal_lock->lock(yield); }); } asio::signal_set signals{iocontext, SIGINT, SIGTERM}; - signals.async_wait([](const error_code &err, int signal) { - SPDLOG_INFO("Got signal {}; exiting", signal); + signals.async_wait([](const error_code & /*err*/, int signal) { + SPDLOG_INFO("Got signal {}; exiting", signal); // NOLINT iocontext.stop(); }); diff --git a/appsec/tests/mock_helper/mock_helper_main.hpp b/appsec/tests/mock_helper/mock_helper_main.hpp index 65d297c60f..1a4e41b196 100644 --- a/appsec/tests/mock_helper/mock_helper_main.hpp +++ b/appsec/tests/mock_helper/mock_helper_main.hpp @@ -5,13 +5,13 @@ // Copyright 2021 Datadog, Inc. #pragma once +#include #include #include -#include -#include -#include #include +#include +#include #include #include @@ -26,20 +26,21 @@ class EchoPipe { // lifetime: till the end of the program public: EchoPipe(); - void write(const asio::const_buffer buff, asio::yield_context yield); + void write(asio::const_buffer buff, asio::yield_context yield); template void add_close_cb(Callable &&cb) { - spawn(iocontext, [this, cb = std::move(cb)](auto yield) { + spawn( + iocontext, [this, cb = std::forward(cb)](auto yield) { #ifdef __linux__ - auto wait_type = posix::stream_descriptor::wait_error; + auto wait_type = posix::stream_descriptor::wait_error; #else auto wait_type = posix::stream_descriptor::wait_read; #endif - stream_.async_wait(wait_type, yield); - SPDLOG_INFO("The echo pipe was closed"); - cb(); - }); + stream_.async_wait(wait_type, yield); + SPDLOG_INFO("The echo pipe was closed"); // NOLINT + cb(); + }); } private: @@ -59,9 +60,13 @@ class MsgpackToJson { template , void>> MsgpackToJson(const T *buffer, size_t size) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) : MsgpackToJson{reinterpret_cast(buffer), size} {} MsgpackToJson(const MsgpackToJson &) = delete; + MsgpackToJson(MsgpackToJson &&) = delete; + MsgpackToJson &operator=(const MsgpackToJson &) = delete; + MsgpackToJson &operator=(MsgpackToJson &&) = delete; ~MsgpackToJson() { mpack_tree_destroy(&tree_); } @@ -73,14 +78,14 @@ class MsgpackToJson { asio::const_buffer asio_buffer() { const char *str = buffer_.GetString(); - size_t len = buffer_.GetLength(); + const size_t len = buffer_.GetLength(); return {str, len}; } private: void write(mpack_node_t &node); - mpack_tree_t tree_; + mpack_tree_t tree_{}; rapidjson::StringBuffer buffer_; writer_t writer_{buffer_}; }; diff --git a/appsec/third_party/CMakeLists.txt b/appsec/third_party/CMakeLists.txt index d06aa54ff3..d925ea6a5f 100644 --- a/appsec/third_party/CMakeLists.txt +++ b/appsec/third_party/CMakeLists.txt @@ -22,6 +22,9 @@ if(DD_APPSEC_BUILD_HELPER) file(GLOB_RECURSE CPPBASE64_C_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cpp-base64/base64.cpp) add_library(cpp-base64 STATIC ${CPPBASE64_C_SOURCES}) target_include_directories(cpp-base64 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/cpp-base64) + set_target_properties(cpp-base64 PROPERTIES + POSITION_INDEPENDENT_CODE 1 + C_VISIBILITY_PRESET hidden) endif() if(DD_APPSEC_BUILD_EXTENSION) @@ -44,6 +47,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG eb3220622e73a4889eee355ffa37972b3cac3df5) FetchContent_MakeAvailable(spdlog) +set_target_properties(spdlog PROPERTIES POSITION_INDEPENDENT_CODE 1) set(ZLIB_VERSION v1.3) FetchContent_Declare( diff --git a/benchmark/runall.sh b/benchmark/runall.sh index 6045fcedb6..7f0a5eeafb 100755 --- a/benchmark/runall.sh +++ b/benchmark/runall.sh @@ -22,13 +22,25 @@ elif [ "$SCENARIO" = "tracer" ]; then ## Non-OPCache Benchmarks make benchmarks - cp tests/Benchmarks/reports/tracer-bench-results.csv "$ARTIFACTS_DIR" + cp tests/Benchmarks/reports/tracer-bench-results.csv "$ARTIFACTS_DIR/tracer-bench-results.csv" ## OPCache Benchmarks make benchmarks_opcache - cp tests/Benchmarks/reports/tracer-bench-results-opcache.csv "$ARTIFACTS_DIR" + cp tests/Benchmarks/reports/tracer-bench-results-opcache.csv "$ARTIFACTS_DIR/tracer-bench-results-opcache.csv" ## Request Startup/Shutdown Benchmarks make benchmarks_tea - cp tea/benchmarks/reports/tea-bench-results.json "$ARTIFACTS_DIR" + cp tea/benchmarks/reports/tracer-tea-bench-results.json "$ARTIFACTS_DIR/tracer-tea-bench-results.json" +elif [ "$SCENARIO" = "appsec" ]; then + # Run Appsec Benchmarks + cd .. + make composer_tests_update + make benchmarks_run_dependencies + make install_appsec + + ## Non-OPCache Benchmarks + BENCHMARK_EXTRA="--group=frameworks" make call_benchmarks + cp tests/Benchmarks/reports/tracer-bench-results.csv "$ARTIFACTS_DIR/appsec-bench-results.csv" + + make delete_ini fi diff --git a/cbindgen.toml b/cbindgen.toml index 8a17207ed6..16bb97fa53 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -25,4 +25,16 @@ rename_variants = "ScreamingSnakeCase" [parse] parse_deps = true -include = ["ddcommon", "ddtelemetry", "ddcommon-ffi", "ddtelemetry-ffi", "datadog-sidecar", "datadog-ipc", "uuid"] +include = [ + "ddcommon", + "ddtelemetry", + "ddtelemetry-ffi", + "ddcommon-ffi", + "datadog-crashtracker-ffi", + "datadog-sidecar", + "datadog-ipc", + "datadog-live-debugger", + "datadog-live-debugger-ffi", + "datadog-remote-config", + "uuid" +] diff --git a/components-rs/Cargo.toml b/components-rs/Cargo.toml index ea315a94e9..824e09eeb5 100644 --- a/components-rs/Cargo.toml +++ b/components-rs/Cargo.toml @@ -12,11 +12,18 @@ ddcommon = { path = "../libdatadog/ddcommon" } ddcommon-ffi = { path = "../libdatadog/ddcommon-ffi", default-features = false } ddtelemetry = { path = "../libdatadog/ddtelemetry" } ddtelemetry-ffi = { path = "../libdatadog/ddtelemetry-ffi", default-features = false } +datadog-dynamic-configuration = { path = "../libdatadog/dynamic-configuration" } +datadog-live-debugger = { path = "../libdatadog/live-debugger" } +datadog-live-debugger-ffi = { path = "../libdatadog/live-debugger-ffi", default-features = false } +datadog-ipc = { path = "../libdatadog/ipc" } +datadog-remote-config = { path = "../libdatadog/remote-config" } datadog-sidecar = { path = "../libdatadog/sidecar" } datadog-sidecar-ffi = { path = "../libdatadog/sidecar-ffi" } +datadog-crashtracker-ffi = { path = "../libdatadog/crashtracker-ffi", default-features = false, features = ["collector"] } spawn_worker = { path = "../libdatadog/spawn_worker" } anyhow = { version = "1.0" } const-str = "0.5.6" +itertools = "0.11.0" serde = "1.0.196" simd-json = "0.13.8" serde_with = "3.6.0" @@ -33,6 +40,9 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "env-filter", ] } +serde_json = "1.0.113" +regex = "1.10.5" +regex-automata = "0.4.5" [build-dependencies] cbindgen = "0.26" diff --git a/components-rs/common.h b/components-rs/common.h index dd08695ac3..4d0bfaf234 100644 --- a/components-rs/common.h +++ b/components-rs/common.h @@ -73,7 +73,9 @@ typedef struct ddog_Error { typedef struct ddog_Slice_CChar { /** - * Must be non-null and suitably aligned for the underlying type. + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. */ const char *ptr; /** @@ -104,6 +106,118 @@ typedef struct ddog_Option_Error { typedef struct ddog_Option_Error ddog_MaybeError; +typedef struct ddog_ArrayQueue { + struct ddog_ArrayQueue *inner; + void (*item_delete_fn)(void*); +} ddog_ArrayQueue; + +typedef enum ddog_ArrayQueue_NewResult_Tag { + DDOG_ARRAY_QUEUE_NEW_RESULT_OK, + DDOG_ARRAY_QUEUE_NEW_RESULT_ERR, +} ddog_ArrayQueue_NewResult_Tag; + +typedef struct ddog_ArrayQueue_NewResult { + ddog_ArrayQueue_NewResult_Tag tag; + union { + struct { + struct ddog_ArrayQueue *ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_ArrayQueue_NewResult; + +/** + * Data structure for the result of the push() and force_push() functions. + * force_push() replaces the oldest element if the queue is full, while push() returns the given + * value if the queue is full. For push(), it's redundant to return the value since the caller + * already has it, but it's returned for consistency with crossbeam API and with force_push(). + */ +typedef enum ddog_ArrayQueue_PushResult_Tag { + DDOG_ARRAY_QUEUE_PUSH_RESULT_OK, + DDOG_ARRAY_QUEUE_PUSH_RESULT_FULL, + DDOG_ARRAY_QUEUE_PUSH_RESULT_ERR, +} ddog_ArrayQueue_PushResult_Tag; + +typedef struct ddog_ArrayQueue_PushResult { + ddog_ArrayQueue_PushResult_Tag tag; + union { + struct { + void *full; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_ArrayQueue_PushResult; + +typedef enum ddog_ArrayQueue_PopResult_Tag { + DDOG_ARRAY_QUEUE_POP_RESULT_OK, + DDOG_ARRAY_QUEUE_POP_RESULT_EMPTY, + DDOG_ARRAY_QUEUE_POP_RESULT_ERR, +} ddog_ArrayQueue_PopResult_Tag; + +typedef struct ddog_ArrayQueue_PopResult { + ddog_ArrayQueue_PopResult_Tag tag; + union { + struct { + void *ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_ArrayQueue_PopResult; + +typedef enum ddog_ArrayQueue_BoolResult_Tag { + DDOG_ARRAY_QUEUE_BOOL_RESULT_OK, + DDOG_ARRAY_QUEUE_BOOL_RESULT_ERR, +} ddog_ArrayQueue_BoolResult_Tag; + +typedef struct ddog_ArrayQueue_BoolResult { + ddog_ArrayQueue_BoolResult_Tag tag; + union { + struct { + bool ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_ArrayQueue_BoolResult; + +typedef enum ddog_ArrayQueue_UsizeResult_Tag { + DDOG_ARRAY_QUEUE_USIZE_RESULT_OK, + DDOG_ARRAY_QUEUE_USIZE_RESULT_ERR, +} ddog_ArrayQueue_UsizeResult_Tag; + +typedef struct ddog_ArrayQueue_UsizeResult { + ddog_ArrayQueue_UsizeResult_Tag tag; + union { + struct { + uintptr_t ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_ArrayQueue_UsizeResult; + +typedef enum ddog_Option_U32_Tag { + DDOG_OPTION_U32_SOME_U32, + DDOG_OPTION_U32_NONE_U32, +} ddog_Option_U32_Tag; + +typedef struct ddog_Option_U32 { + ddog_Option_U32_Tag tag; + union { + struct { + uint32_t some; + }; + }; +} ddog_Option_U32; + /** * A wrapper for returning owned strings from FFI */ @@ -145,6 +259,8 @@ typedef struct ddog_Vec_Tag_ParseResult { #define ddog_LOG_ONCE (1 << 3) +#define ddog_MultiTargetFetcher_DEFAULT_CLIENTS_LIMIT 100 + typedef enum ddog_ConfigurationOrigin { DDOG_CONFIGURATION_ORIGIN_ENV_VAR, DDOG_CONFIGURATION_ORIGIN_CODE, @@ -153,6 +269,17 @@ typedef enum ddog_ConfigurationOrigin { DDOG_CONFIGURATION_ORIGIN_DEFAULT, } ddog_ConfigurationOrigin; +typedef enum ddog_EvaluateAt { + DDOG_EVALUATE_AT_ENTRY, + DDOG_EVALUATE_AT_EXIT, +} ddog_EvaluateAt; + +typedef enum ddog_InBodyLocation { + DDOG_IN_BODY_LOCATION_NONE, + DDOG_IN_BODY_LOCATION_START, + DDOG_IN_BODY_LOCATION_END, +} ddog_InBodyLocation; + typedef enum ddog_Log { DDOG_LOG_ERROR = 1, DDOG_LOG_WARN = 2, @@ -167,6 +294,13 @@ typedef enum ddog_Log { DDOG_LOG_HOOK_TRACE = (5 | (4 << 4)), } ddog_Log; +typedef enum ddog_MetricKind { + DDOG_METRIC_KIND_COUNT, + DDOG_METRIC_KIND_GAUGE, + DDOG_METRIC_KIND_HISTOGRAM, + DDOG_METRIC_KIND_DISTRIBUTION, +} ddog_MetricKind; + typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_TRACERS, DDOG_METRIC_NAMESPACE_PROFILERS, @@ -181,11 +315,79 @@ typedef enum ddog_MetricNamespace { DDOG_METRIC_NAMESPACE_SIDECAR, } ddog_MetricNamespace; +typedef enum ddog_ProbeStatus { + DDOG_PROBE_STATUS_RECEIVED, + DDOG_PROBE_STATUS_INSTALLED, + DDOG_PROBE_STATUS_EMITTING, + DDOG_PROBE_STATUS_ERROR, + DDOG_PROBE_STATUS_BLOCKED, + DDOG_PROBE_STATUS_WARNING, +} ddog_ProbeStatus; + +typedef enum ddog_RemoteConfigCapabilities { + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_ACTIVATION = 1, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_IP_BLOCKING = 2, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_DD_RULES = 3, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_EXCLUSIONS = 4, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_REQUEST_BLOCKING = 5, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RESPONSE_BLOCKING = 6, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_USER_BLOCKING = 7, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_CUSTOM_RULES = 8, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_CUSTOM_BLOCKING_RESPONSE = 9, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_TRUSTED_IPS = 10, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_API_SECURITY_SAMPLE_RATE = 11, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_SAMPLE_RATE = 12, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_LOGS_INJECTION = 13, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_HTTP_HEADER_TAGS = 14, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_CUSTOM_TAGS = 15, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_PROCESSOR_OVERRIDES = 16, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_CUSTOM_DATA_SCANNERS = 17, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_EXCLUSION_DATA = 18, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_ENABLED = 19, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_DATA_STREAMS_ENABLED = 20, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_SQLI = 21, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_LFI = 22, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_SSRF = 23, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_SHI = 24, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_XXE = 25, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_RCE = 26, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_NOSQLI = 27, + DDOG_REMOTE_CONFIG_CAPABILITIES_ASM_RASP_XSS = 28, + DDOG_REMOTE_CONFIG_CAPABILITIES_APM_TRACING_SAMPLE_RULES = 29, + DDOG_REMOTE_CONFIG_CAPABILITIES_CSM_ACTIVATION = 30, +} ddog_RemoteConfigCapabilities; + +typedef enum ddog_RemoteConfigProduct { + DDOG_REMOTE_CONFIG_PRODUCT_APM_TRACING, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DATA, + DDOG_REMOTE_CONFIG_PRODUCT_ASM, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_DD, + DDOG_REMOTE_CONFIG_PRODUCT_ASM_FEATURES, + DDOG_REMOTE_CONFIG_PRODUCT_LIVE_DEBUGGER, +} ddog_RemoteConfigProduct; + +typedef enum ddog_SpanProbeTarget { + DDOG_SPAN_PROBE_TARGET_ACTIVE, + DDOG_SPAN_PROBE_TARGET_ROOT, +} ddog_SpanProbeTarget; + +typedef struct ddog_DebuggerPayload ddog_DebuggerPayload; + +typedef struct ddog_DslString ddog_DslString; + /** * `InstanceId` is a structure that holds session and runtime identifiers. */ typedef struct ddog_InstanceId ddog_InstanceId; +typedef struct ddog_MaybeShmLimiter ddog_MaybeShmLimiter; + +typedef struct ddog_ProbeCondition ddog_ProbeCondition; + +typedef struct ddog_ProbeValue ddog_ProbeValue; + +typedef struct ddog_RemoteConfigState ddog_RemoteConfigState; + typedef struct ddog_SidecarActionsBuffer ddog_SidecarActionsBuffer; /** @@ -199,6 +401,360 @@ typedef struct ddog_SidecarActionsBuffer ddog_SidecarActionsBuffer; */ typedef struct ddog_SidecarTransport ddog_SidecarTransport; +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_CChar { + const char *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_CChar; + +typedef enum ddog_IntermediateValue_Tag { + DDOG_INTERMEDIATE_VALUE_STRING, + DDOG_INTERMEDIATE_VALUE_NUMBER, + DDOG_INTERMEDIATE_VALUE_BOOL, + DDOG_INTERMEDIATE_VALUE_NULL, + DDOG_INTERMEDIATE_VALUE_REFERENCED, +} ddog_IntermediateValue_Tag; + +typedef struct ddog_IntermediateValue { + ddog_IntermediateValue_Tag tag; + union { + struct { + ddog_CharSlice string; + }; + struct { + double number; + }; + struct { + bool bool_; + }; + struct { + const void *referenced; + }; + }; +} ddog_IntermediateValue; + +typedef struct ddog_VoidCollection { + intptr_t count; + const void *elements; + void (*free)(struct ddog_VoidCollection); +} ddog_VoidCollection; + +typedef struct ddog_Evaluator { + bool (*equals)(void*, struct ddog_IntermediateValue, struct ddog_IntermediateValue); + bool (*greater_than)(void*, struct ddog_IntermediateValue, struct ddog_IntermediateValue); + bool (*greater_or_equals)(void*, struct ddog_IntermediateValue, struct ddog_IntermediateValue); + const void *(*fetch_identifier)(void*, const ddog_CharSlice*); + const void *(*fetch_index)(void*, const void*, struct ddog_IntermediateValue); + const void *(*fetch_nested)(void*, const void*, struct ddog_IntermediateValue); + uintptr_t (*length)(void*, const void*); + struct ddog_VoidCollection (*try_enumerate)(void*, const void*); + ddog_CharSlice (*stringify)(void*, const void*); + ddog_CharSlice (*get_string)(void*, const void*); + intptr_t (*convert_index)(void*, const void*); + bool (*instanceof)(void*, const void*, const ddog_CharSlice*); +} ddog_Evaluator; + +typedef struct ddog_CharSliceVec { + const ddog_CharSlice *strings; + uintptr_t string_count; +} ddog_CharSliceVec; + +typedef enum ddog_Option_CharSlice_Tag { + DDOG_OPTION_CHAR_SLICE_SOME_CHAR_SLICE, + DDOG_OPTION_CHAR_SLICE_NONE_CHAR_SLICE, +} ddog_Option_CharSlice_Tag; + +typedef struct ddog_Option_CharSlice { + ddog_Option_CharSlice_Tag tag; + union { + struct { + ddog_CharSlice some; + }; + }; +} ddog_Option_CharSlice; + +typedef struct ddog_ProbeTarget { + ddog_CharSlice type_name; + ddog_CharSlice method_name; + ddog_CharSlice source_file; + struct ddog_Option_CharSlice signature; + const uint32_t *lines; + uint32_t lines_count; + enum ddog_InBodyLocation in_body_location; +} ddog_ProbeTarget; + +typedef struct ddog_MetricProbe { + enum ddog_MetricKind kind; + ddog_CharSlice name; + const struct ddog_ProbeValue *value; +} ddog_MetricProbe; + +typedef struct ddog_CaptureConfiguration { + uint32_t max_reference_depth; + uint32_t max_collection_size; + uint32_t max_length; + uint32_t max_field_count; +} ddog_CaptureConfiguration; + +typedef struct ddog_LogProbe { + const struct ddog_DslString *segments; + const struct ddog_ProbeCondition *when; + const struct ddog_CaptureConfiguration *capture; + bool capture_snapshot; + uint32_t sampling_snapshots_per_second; +} ddog_LogProbe; + +typedef struct ddog_Tag { + ddog_CharSlice name; + const struct ddog_DslString *value; +} ddog_Tag; + +typedef struct ddog_SpanProbeTag { + struct ddog_Tag tag; + bool next_condition; +} ddog_SpanProbeTag; + +typedef struct ddog_SpanDecorationProbe { + enum ddog_SpanProbeTarget target; + const struct ddog_ProbeCondition *const *conditions; + const struct ddog_SpanProbeTag *span_tags; + uintptr_t span_tags_num; +} ddog_SpanDecorationProbe; + +typedef enum ddog_ProbeType_Tag { + DDOG_PROBE_TYPE_METRIC, + DDOG_PROBE_TYPE_LOG, + DDOG_PROBE_TYPE_SPAN, + DDOG_PROBE_TYPE_SPAN_DECORATION, +} ddog_ProbeType_Tag; + +typedef struct ddog_ProbeType { + ddog_ProbeType_Tag tag; + union { + struct { + struct ddog_MetricProbe metric; + }; + struct { + struct ddog_LogProbe log; + }; + struct { + struct ddog_SpanDecorationProbe span_decoration; + }; + }; +} ddog_ProbeType; + +typedef struct ddog_Probe { + ddog_CharSlice id; + uint64_t version; + ddog_CharSlice language; + struct ddog_CharSliceVec tags; + struct ddog_ProbeTarget target; + enum ddog_EvaluateAt evaluate_at; + struct ddog_ProbeType probe; + ddog_CharSlice diagnostic_msg; + enum ddog_ProbeStatus status; + ddog_CharSlice status_msg; + ddog_CharSlice status_exception; + ddog_CharSlice status_stacktrace; +} ddog_Probe; + +typedef struct ddog_LiveDebuggerCallbacks { + int64_t (*set_probe)(struct ddog_Probe probe, const struct ddog_MaybeShmLimiter *limiter); + void (*remove_probe)(int64_t id); +} ddog_LiveDebuggerCallbacks; + +typedef struct ddog_LiveDebuggerSetup { + const struct ddog_Evaluator *evaluator; + struct ddog_LiveDebuggerCallbacks callbacks; +} ddog_LiveDebuggerSetup; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_DebuggerPayload { + const struct ddog_DebuggerPayload *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_DebuggerPayload; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_RemoteConfigProduct { + const enum ddog_RemoteConfigProduct *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_RemoteConfigProduct; + +typedef struct ddog_Vec_RemoteConfigProduct ddog_VecRemoteConfigProduct; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_RemoteConfigCapabilities { + const enum ddog_RemoteConfigCapabilities *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_RemoteConfigCapabilities; + +typedef struct ddog_Vec_RemoteConfigCapabilities ddog_VecRemoteConfigCapabilities; + +typedef struct ddog_DebuggerCapture ddog_DebuggerCapture; +typedef struct ddog_DebuggerValue ddog_DebuggerValue; + + +#define ddog_EVALUATOR_RESULT_UNDEFINED (const void*)0 + +#define ddog_EVALUATOR_RESULT_INVALID (const void*)-1 + +#define ddog_EVALUATOR_RESULT_REDACTED (const void*)-2 + +typedef enum ddog_DebuggerType { + DDOG_DEBUGGER_TYPE_DIAGNOSTICS, + DDOG_DEBUGGER_TYPE_LOGS, +} ddog_DebuggerType; + +typedef enum ddog_FieldType { + DDOG_FIELD_TYPE_STATIC, + DDOG_FIELD_TYPE_ARG, + DDOG_FIELD_TYPE_LOCAL, +} ddog_FieldType; + +typedef struct ddog_Entry ddog_Entry; + +typedef struct ddog_HashMap_CowStr__Value ddog_HashMap_CowStr__Value; + +typedef struct ddog_InternalIntermediateValue ddog_InternalIntermediateValue; + +typedef struct ddog_SenderHandle ddog_SenderHandle; + +typedef struct ddog_SnapshotEvaluationError ddog_SnapshotEvaluationError; + +typedef struct ddog_String ddog_String; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_SnapshotEvaluationError { + const struct ddog_SnapshotEvaluationError *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_SnapshotEvaluationError; + +typedef enum ddog_ConditionEvaluationResult_Tag { + DDOG_CONDITION_EVALUATION_RESULT_SUCCESS, + DDOG_CONDITION_EVALUATION_RESULT_FAILURE, + DDOG_CONDITION_EVALUATION_RESULT_ERROR, +} ddog_ConditionEvaluationResult_Tag; + +typedef struct ddog_ConditionEvaluationResult { + ddog_ConditionEvaluationResult_Tag tag; + union { + struct { + struct ddog_Vec_SnapshotEvaluationError *error; + }; + }; +} ddog_ConditionEvaluationResult; + +typedef enum ddog_ValueEvaluationResult_Tag { + DDOG_VALUE_EVALUATION_RESULT_SUCCESS, + DDOG_VALUE_EVALUATION_RESULT_ERROR, +} ddog_ValueEvaluationResult_Tag; + +typedef struct ddog_ValueEvaluationResult { + ddog_ValueEvaluationResult_Tag tag; + union { + struct { + struct ddog_InternalIntermediateValue *success; + }; + struct { + struct ddog_Vec_SnapshotEvaluationError *error; + }; + }; +} ddog_ValueEvaluationResult; + +typedef struct ddog_FilterList { + struct ddog_CharSliceVec package_prefixes; + struct ddog_CharSliceVec classes; +} ddog_FilterList; + +typedef struct ddog_ServiceConfiguration { + ddog_CharSlice id; + struct ddog_FilterList allow; + struct ddog_FilterList deny; + uint32_t sampling_snapshots_per_second; +} ddog_ServiceConfiguration; + +typedef enum ddog_LiveDebuggingData_Tag { + DDOG_LIVE_DEBUGGING_DATA_NONE, + DDOG_LIVE_DEBUGGING_DATA_PROBE, + DDOG_LIVE_DEBUGGING_DATA_SERVICE_CONFIGURATION, +} ddog_LiveDebuggingData_Tag; + +typedef struct ddog_LiveDebuggingData { + ddog_LiveDebuggingData_Tag tag; + union { + struct { + struct ddog_Probe probe; + }; + struct { + struct ddog_ServiceConfiguration service_configuration; + }; + }; +} ddog_LiveDebuggingData; + +typedef struct ddog_LiveDebuggingParseResult { + struct ddog_LiveDebuggingData data; + struct ddog_LiveDebuggingData *opaque_data; +} ddog_LiveDebuggingParseResult; + +typedef struct ddog_HashMap_CowStr__Value ddog_Fields; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_DebuggerValue { + const ddog_DebuggerValue *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_DebuggerValue; + +/** + * Holds the raw parts of a Rust Vec; it should only be created from Rust, + * never from C. + */ +typedef struct ddog_Vec_Entry { + const struct ddog_Entry *ptr; + uintptr_t len; + uintptr_t capacity; +} ddog_Vec_Entry; + +typedef struct ddog_CaptureValue { + ddog_CharSlice type; + ddog_CharSlice value; + ddog_Fields *fields; + struct ddog_Vec_DebuggerValue elements; + struct ddog_Vec_Entry entries; + bool is_null; + bool truncated; + ddog_CharSlice not_captured_reason; + ddog_CharSlice size; +} ddog_CaptureValue; + +typedef struct ddog_OwnedCharSlice { + ddog_CharSlice slice; + void (*free)(ddog_CharSlice); +} ddog_OwnedCharSlice; + typedef enum ddog_LogLevel { DDOG_LOG_LEVEL_ERROR, DDOG_LOG_LEVEL_WARN, @@ -269,6 +825,13 @@ typedef struct ddog_AgentRemoteConfigReader ddog_AgentRemoteConfigReader; typedef struct ddog_AgentRemoteConfigWriter_ShmHandle ddog_AgentRemoteConfigWriter_ShmHandle; +typedef struct ddog_Arc_Target ddog_Arc_Target; + +/** + * Fundamental configuration of the RC client, which always must be set. + */ +typedef struct ddog_ConfigInvariants ddog_ConfigInvariants; + typedef struct ddog_MappedMem_ShmHandle ddog_MappedMem_ShmHandle; /** @@ -277,6 +840,8 @@ typedef struct ddog_MappedMem_ShmHandle ddog_MappedMem_ShmHandle; */ typedef struct ddog_PlatformHandle_File ddog_PlatformHandle_File; +typedef struct ddog_RemoteConfigReader ddog_RemoteConfigReader; + /** * `RuntimeMetadata` is a struct that represents the runtime metadata of a language. */ @@ -299,6 +864,309 @@ typedef struct ddog_TracerHeaderTags { bool client_computed_stats; } ddog_TracerHeaderTags; +typedef enum ddog_crasht_DemangleOptions { + DDOG_CRASHT_DEMANGLE_OPTIONS_COMPLETE, + DDOG_CRASHT_DEMANGLE_OPTIONS_NAME_ONLY, +} ddog_crasht_DemangleOptions; + +typedef enum ddog_crasht_NormalizedAddressTypes { + DDOG_CRASHT_NORMALIZED_ADDRESS_TYPES_NONE = 0, + DDOG_CRASHT_NORMALIZED_ADDRESS_TYPES_ELF, + DDOG_CRASHT_NORMALIZED_ADDRESS_TYPES_PDB, +} ddog_crasht_NormalizedAddressTypes; + +/** + * This enum represents operations a the tracked library might be engaged in. + * Currently only implemented for profiling. + * The idea is that if a crash consistently occurs while a particular operation + * is ongoing, its likely related. + * + * In the future, we might also track wall-clock time of operations + * (or some statistical sampling thereof) using the same enum. + * + * NOTE: This enum is known to be non-exhaustive. Feel free to add new types + * as needed. + */ +typedef enum ddog_crasht_OpTypes { + DDOG_CRASHT_OP_TYPES_PROFILER_INACTIVE = 0, + DDOG_CRASHT_OP_TYPES_PROFILER_COLLECTING_SAMPLE, + DDOG_CRASHT_OP_TYPES_PROFILER_UNWINDING, + DDOG_CRASHT_OP_TYPES_PROFILER_SERIALIZING, + /** + * Dummy value to allow easier iteration + */ + DDOG_CRASHT_OP_TYPES_SIZE, +} ddog_crasht_OpTypes; + +/** + * Stacktrace collection occurs in the context of a crashing process. + * If the stack is sufficiently corruputed, it is possible (but unlikely), + * for stack trace collection itself to crash. + * We recommend fully enabling stacktrace collection, but having an environment + * variable to allow downgrading the collector. + */ +typedef enum ddog_crasht_StacktraceCollection { + /** + * Stacktrace collection occurs in the + */ + DDOG_CRASHT_STACKTRACE_COLLECTION_DISABLED, + DDOG_CRASHT_STACKTRACE_COLLECTION_WITHOUT_SYMBOLS, + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, + DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_SYMBOLS_IN_RECEIVER, +} ddog_crasht_StacktraceCollection; + +/** + * A generic result type for when a crashtracking operation may fail, + * but there's nothing to return in the case of success. + */ +typedef enum ddog_crasht_Result_Tag { + DDOG_CRASHT_RESULT_OK, + DDOG_CRASHT_RESULT_ERR, +} ddog_crasht_Result_Tag; + +typedef struct ddog_crasht_Result { + ddog_crasht_Result_Tag tag; + union { + struct { + /** + * Do not use the value of Ok. This value only exists to overcome + * Rust -> C code generation. + */ + bool ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_crasht_Result; + +typedef struct ddog_crasht_Slice_CharSlice { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const ddog_CharSlice *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_crasht_Slice_CharSlice; + +typedef struct ddog_crasht_Config { + struct ddog_crasht_Slice_CharSlice additional_files; + bool create_alt_stack; + /** + * The endpoint to send the crash report to (can be a file://). + * If None, the crashtracker will infer the agent host from env variables. + */ + const struct ddog_Endpoint *endpoint; + enum ddog_crasht_StacktraceCollection resolve_frames; + uint64_t timeout_secs; + bool wait_for_receiver; +} ddog_crasht_Config; + +typedef struct ddog_crasht_EnvVar { + ddog_CharSlice key; + ddog_CharSlice val; +} ddog_crasht_EnvVar; + +typedef struct ddog_crasht_Slice_EnvVar { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct ddog_crasht_EnvVar *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_crasht_Slice_EnvVar; + +typedef struct ddog_crasht_ReceiverConfig { + struct ddog_crasht_Slice_CharSlice args; + struct ddog_crasht_Slice_EnvVar env; + ddog_CharSlice path_to_receiver_binary; + /** + * Optional filename to forward stderr to (useful for logging/debugging) + */ + ddog_CharSlice optional_stderr_filename; + /** + * Optional filename to forward stdout to (useful for logging/debugging) + */ + ddog_CharSlice optional_stdout_filename; +} ddog_crasht_ReceiverConfig; + +typedef struct ddog_crasht_Metadata { + ddog_CharSlice library_name; + ddog_CharSlice library_version; + ddog_CharSlice family; + /** + * Should include "service", "environment", etc + */ + const struct ddog_Vec_Tag *tags; +} ddog_crasht_Metadata; + +typedef enum ddog_crasht_UsizeResult_Tag { + DDOG_CRASHT_USIZE_RESULT_OK, + DDOG_CRASHT_USIZE_RESULT_ERR, +} ddog_crasht_UsizeResult_Tag; + +typedef struct ddog_crasht_UsizeResult { + ddog_crasht_UsizeResult_Tag tag; + union { + struct { + uintptr_t ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_crasht_UsizeResult; + +/** + * Represents a CrashInfo. Do not access its member for any reason, only use + * the C API functions on this struct. + */ +typedef struct ddog_crasht_CrashInfo { + struct ddog_crasht_CrashInfo *inner; +} ddog_crasht_CrashInfo; + +/** + * Returned by [ddog_prof_Profile_new]. + */ +typedef enum ddog_crasht_CrashInfoNewResult_Tag { + DDOG_CRASHT_CRASH_INFO_NEW_RESULT_OK, + DDOG_CRASHT_CRASH_INFO_NEW_RESULT_ERR, +} ddog_crasht_CrashInfoNewResult_Tag; + +typedef struct ddog_crasht_CrashInfoNewResult { + ddog_crasht_CrashInfoNewResult_Tag tag; + union { + struct { + struct ddog_crasht_CrashInfo ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_crasht_CrashInfoNewResult; + +typedef struct ddog_crasht_SigInfo { + uint64_t signum; + ddog_CharSlice signame; +} ddog_crasht_SigInfo; + +typedef struct ddog_crasht_StackFrameNames { + struct ddog_Option_U32 colno; + ddog_CharSlice filename; + struct ddog_Option_U32 lineno; + ddog_CharSlice name; +} ddog_crasht_StackFrameNames; + +typedef struct ddog_crasht_Slice_StackFrameNames { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct ddog_crasht_StackFrameNames *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_crasht_Slice_StackFrameNames; + +typedef struct ddog_Slice_U8 { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const uint8_t *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_Slice_U8; + +/** + * Use to represent bytes -- does not need to be valid UTF-8. + */ +typedef struct ddog_Slice_U8 ddog_ByteSlice; + +typedef struct ddog_crasht_NormalizedAddress { + uint64_t file_offset; + ddog_ByteSlice build_id; + uint64_t age; + ddog_CharSlice path; + enum ddog_crasht_NormalizedAddressTypes typ; +} ddog_crasht_NormalizedAddress; + +typedef struct ddog_crasht_StackFrame { + ddog_CharSlice build_id; + uintptr_t ip; + uintptr_t module_base_address; + struct ddog_crasht_Slice_StackFrameNames names; + struct ddog_crasht_NormalizedAddress normalized_ip; + uintptr_t sp; + uintptr_t symbol_address; +} ddog_crasht_StackFrame; + +typedef struct ddog_crasht_Slice_StackFrame { + /** + * Should be non-null and suitably aligned for the underlying type. It is + * allowed but not recommended for the pointer to be null when the len is + * zero. + */ + const struct ddog_crasht_StackFrame *ptr; + /** + * The number of elements (not bytes) that `.ptr` points to. Must be less + * than or equal to [isize::MAX]. + */ + uintptr_t len; +} ddog_crasht_Slice_StackFrame; + +/** + * Represents time since the Unix Epoch in seconds plus nanoseconds. + */ +typedef struct ddog_Timespec { + int64_t seconds; + uint32_t nanoseconds; +} ddog_Timespec; + +/** + * A wrapper for returning owned strings from FFI + */ +typedef struct ddog_crasht_StringWrapper { + /** + * This is a String stuffed into the vec. + */ + struct ddog_Vec_U8 message; +} ddog_crasht_StringWrapper; + +typedef enum ddog_crasht_StringWrapperResult_Tag { + DDOG_CRASHT_STRING_WRAPPER_RESULT_OK, + DDOG_CRASHT_STRING_WRAPPER_RESULT_ERR, +} ddog_crasht_StringWrapperResult_Tag; + +typedef struct ddog_crasht_StringWrapperResult { + ddog_crasht_StringWrapperResult_Tag tag; + union { + struct { + struct ddog_crasht_StringWrapper ok; + }; + struct { + struct ddog_Error err; + }; + }; +} ddog_crasht_StringWrapperResult; + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -319,8 +1187,80 @@ ddog_CharSlice ddog_Error_message(const struct ddog_Error *error); void ddog_MaybeError_drop(ddog_MaybeError); +/** + * Creates a new ArrayQueue with the given capacity and item_delete_fn. + * The item_delete_fn is called when an item is dropped from the queue. + */ +DDOG_CHECK_RETURN +struct ddog_ArrayQueue_NewResult ddog_ArrayQueue_new(uintptr_t capacity, + void (*item_delete_fn)(void*)); + +/** + * Drops the ArrayQueue. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +void ddog_ArrayQueue_drop(struct ddog_ArrayQueue *queue); + +/** + * Pushes an item into the ArrayQueue. It returns the given value if the queue is full. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. The value + * is null or points to a valid memory location that can be deallocated by the item_delete_fn. + */ +struct ddog_ArrayQueue_PushResult ddog_ArrayQueue_push(const struct ddog_ArrayQueue *queue_ptr, + void *value); + +/** + * Pushes an element into the queue, replacing the oldest element if necessary. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. The value + * is null or points to a valid memory location that can be deallocated by the item_delete_fn. + */ +DDOG_CHECK_RETURN +struct ddog_ArrayQueue_PushResult ddog_ArrayQueue_force_push(const struct ddog_ArrayQueue *queue_ptr, + void *value); + +/** + * Pops an item from the ArrayQueue. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +DDOG_CHECK_RETURN +struct ddog_ArrayQueue_PopResult ddog_ArrayQueue_pop(const struct ddog_ArrayQueue *queue_ptr); + +/** + * Checks if the ArrayQueue is empty. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +struct ddog_ArrayQueue_BoolResult ddog_ArrayQueue_is_empty(const struct ddog_ArrayQueue *queue_ptr); + +/** + * Returns the length of the ArrayQueue. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +struct ddog_ArrayQueue_UsizeResult ddog_ArrayQueue_len(const struct ddog_ArrayQueue *queue_ptr); + +/** + * Returns true if the underlying queue is full. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +struct ddog_ArrayQueue_BoolResult ddog_ArrayQueue_is_full(const struct ddog_ArrayQueue *queue_ptr); + +/** + * Returns the capacity of the ArrayQueue. + * # Safety + * The pointer is null or points to a valid memory location allocated by ArrayQueue_new. + */ +struct ddog_ArrayQueue_UsizeResult ddog_ArrayQueue_capacity(const struct ddog_ArrayQueue *queue_ptr); + DDOG_CHECK_RETURN struct ddog_Endpoint *ddog_endpoint_from_url(ddog_CharSlice url); +DDOG_CHECK_RETURN struct ddog_Endpoint *ddog_endpoint_from_filename(ddog_CharSlice filename); + DDOG_CHECK_RETURN struct ddog_Endpoint *ddog_endpoint_from_api_key(ddog_CharSlice api_key); DDOG_CHECK_RETURN @@ -330,8 +1270,14 @@ struct ddog_Error *ddog_endpoint_from_api_key_and_site(ddog_CharSlice api_key, void ddog_endpoint_set_timeout(struct ddog_Endpoint *endpoint, uint64_t millis); +void ddog_endpoint_set_test_token(struct ddog_Endpoint *endpoint, ddog_CharSlice token); + void ddog_endpoint_drop(struct ddog_Endpoint*); +struct ddog_Option_U32 ddog_Option_U32_some(uint32_t v); + +struct ddog_Option_U32 ddog_Option_U32_none(void); + /** * # Safety * Only pass null or a valid reference to a `ddog_StringWrapper`. diff --git a/components-rs/crashtracker.h b/components-rs/crashtracker.h new file mode 100644 index 0000000000..42c18798c3 --- /dev/null +++ b/components-rs/crashtracker.h @@ -0,0 +1,436 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + + +#ifndef DDOG_CRASHTRACKER_H +#define DDOG_CRASHTRACKER_H + +#pragma once + +#include +#include +#include +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Cleans up after the crash-tracker: + * Unregister the crash handler, restore the previous handler (if any), and + * shut down the receiver. Note that the use of this function is optional: + * the receiver will automatically shutdown when the pipe is closed on program + * exit. + * + * # Preconditions + * This function assumes that the crashtracker has previously been + * initialized. + * # Safety + * Crash-tracking functions are not reentrant. + * No other crash-handler functions should be called concurrently. + * # Atomicity + * This function is not atomic. A crash during its execution may lead to + * unexpected crash-handling behaviour. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_shutdown(void); + +/** + * Reinitialize the crash-tracking infrastructure after a fork. + * This should be one of the first things done after a fork, to minimize the + * chance that a crash occurs between the fork, and this call. + * In particular, reset the counters that track the profiler state machine, + * and start a new receiver to collect data from this fork. + * NOTE: An alternative design would be to have a 1:many sidecar listening on a + * socket instead of 1:1 receiver listening on a pipe, but the only real + * advantage would be to have fewer processes in `ps -a`. + * + * # Preconditions + * This function assumes that the crash-tracker has previously been + * initialized. + * # Safety + * Crash-tracking functions are not reentrant. + * No other crash-handler functions should be called concurrently. + * # Atomicity + * This function is not atomic. A crash during its execution may lead to + * unexpected crash-handling behaviour. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_update_on_fork(struct ddog_crasht_Config config, + struct ddog_crasht_ReceiverConfig receiver_config, + struct ddog_crasht_Metadata metadata); + +/** + * Initialize the crash-tracking infrastructure. + * + * # Preconditions + * None. + * # Safety + * Crash-tracking functions are not reentrant. + * No other crash-handler functions should be called concurrently. + * # Atomicity + * This function is not atomic. A crash during its execution may lead to + * unexpected crash-handling behaviour. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_init_with_receiver(struct ddog_crasht_Config config, + struct ddog_crasht_ReceiverConfig receiver_config, + struct ddog_crasht_Metadata metadata); + +/** + * Initialize the crash-tracking infrastructure, writing to an unix socket in case of crash. + * + * # Preconditions + * None. + * # Safety + * Crash-tracking functions are not reentrant. + * No other crash-handler functions should be called concurrently. + * # Atomicity + * This function is not atomic. A crash during its execution may lead to + * unexpected crash-handling behaviour. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_init_with_unix_socket(struct ddog_crasht_Config config, + ddog_CharSlice socket_path, + struct ddog_crasht_Metadata metadata); + +/** + * Resets all counters to 0. + * Expected to be used after a fork, to reset the counters on the child + * ATOMICITY: + * This is NOT ATOMIC. + * Should only be used when no conflicting updates can occur, + * e.g. after a fork but before profiling ops start on the child. + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_reset_counters(void); + +/** + * Atomically increments the count associated with `op`. + * Useful for tracking what operations were occuring when a crash occurred. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_begin_op(enum ddog_crasht_OpTypes op); + +/** + * Atomically decrements the count associated with `op`. + * Useful for tracking what operations were occuring when a crash occurred. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_end_op(enum ddog_crasht_OpTypes op); + +/** + * Resets all stored spans to 0. + * Expected to be used after a fork, to reset the spans on the child + * ATOMICITY: + * This is NOT ATOMIC. + * Should only be used when no conflicting updates can occur, + * e.g. after a fork but before profiling ops start on the child. + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_clear_span_ids(void); + +/** + * Resets all stored traces to 0. + * Expected to be used after a fork, to reset the traces on the child + * ATOMICITY: + * This is NOT ATOMIC. + * Should only be used when no conflicting updates can occur, + * e.g. after a fork but before profiling ops start on the child. + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_clear_trace_ids(void); + +/** + * Atomically registers an active traceId. + * Useful for tracking what operations were occurring when a crash occurred. + * 0 is reserved for "NoId" + * The set does not check for duplicates. Adding the same id twice is an error. + * + * Inputs: + * id: the 128 bit id, broken into 2 64 bit chunks (see note) + * + * Returns: + * Ok(handle) on success. The handle is needed to later remove the id; + * Err() on failure. The most likely cause of failure is that the underlying set is full. + * + * Note: 128 bit ints in FFI were not stabilized until Rust 1.77 + * https://blog.rust-lang.org/2024/03/30/i128-layout-update.html + * We're currently locked into 1.76.0, have to do an ugly workaround involving 2 64 bit ints + * until we can upgrade. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_UsizeResult ddog_crasht_insert_trace_id(uint64_t id_high, + uint64_t id_low); + +/** + * Atomically registers an active SpanId. + * Useful for tracking what operations were occurring when a crash occurred. + * 0 is reserved for "NoId". + * The set does not check for duplicates. Adding the same id twice is an error. + * + * Inputs: + * id: the 128 bit id, broken into 2 64 bit chunks (see note) + * + * Returns: + * Ok(handle) on success. The handle is needed to later remove the id; + * Err() on failure. The most likely cause of failure is that the underlying set is full. + * + * Note: 128 bit ints in FFI were not stabilized until Rust 1.77 + * https://blog.rust-lang.org/2024/03/30/i128-layout-update.html + * We're currently locked into 1.76.0, have to do an ugly workaround involving 2 64 bit ints + * until we can upgrade. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_UsizeResult ddog_crasht_insert_span_id(uint64_t id_high, + uint64_t id_low); + +/** + * Atomically removes a completed SpanId. + * Useful for tracking what operations were occurring when a crash occurred. + * 0 is reserved for "NoId" + * + * Inputs: + * id: the 128 bit id, broken into 2 64 bit chunks (see note) + * idx: The handle for the id, from a previous successful call to `insert_span_id`. + * Attempting to remove the same element twice is an error. + * Returns: + * `Ok` on success. + * `Err` on failure. If `id` is not found at `idx`, `Err` will be returned and the set will not + * be modified. + * + * Note: 128 bit ints in FFI were not stabilized until Rust 1.77 + * https://blog.rust-lang.org/2024/03/30/i128-layout-update.html + * We're currently locked into 1.76.0, have to do an ugly workaround involving 2 64 bit ints + * until we can upgrade. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_remove_span_id(uint64_t id_high, + uint64_t id_low, + uintptr_t idx); + +/** + * Atomically removes a completed TraceId. + * Useful for tracking what operations were occurring when a crash occurred. + * 0 is reserved for "NoId" + * + * Inputs: + * id: the 128 bit id, broken into 2 64 bit chunks (see note) + * idx: The handle for the id, from a previous successful call to `insert_span_id`. + * Attempting to remove the same element twice is an error. + * Returns: + * `Ok` on success. + * `Err` on failure. If `id` is not found at `idx`, `Err` will be returned and the set will not + * be modified. + * + * Note: 128 bit ints in FFI were not stabilized until Rust 1.77 + * https://blog.rust-lang.org/2024/03/30/i128-layout-update.html + * We're currently locked into 1.76.0, have to do an ugly workaround involving 2 64 bit ints + * until we can upgrade. + * + * # Safety + * No safety concerns. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_remove_trace_id(uint64_t id_high, + uint64_t id_low, + uintptr_t idx); + +/** + * Create a new crashinfo, and returns an opaque reference to it. + * # Safety + * No safety issues. + */ +DDOG_CHECK_RETURN struct ddog_crasht_CrashInfoNewResult ddog_crasht_CrashInfo_new(void); + +/** + * # Safety + * The `crash_info` can be null, but if non-null it must point to a CrashInfo + * made by this module, which has not previously been dropped. + */ +void ddog_crasht_CrashInfo_drop(struct ddog_crasht_CrashInfo *crashinfo); + +/** + * Best effort attempt to normalize all `ip` on the stacktrace. + * `pid` must be the pid of the currently active process where the ips came from. + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_normalize_ips(struct ddog_crasht_CrashInfo *crashinfo, + uint32_t pid); + +/** + * Adds a "counter" variable, with the given value. Useful for determining if + * "interesting" operations were occurring when the crash did. + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * `name` should be a valid reference to a utf8 encoded String. + * The string is copied into the crashinfo, so it does not need to outlive this + * call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_add_counter(struct ddog_crasht_CrashInfo *crashinfo, + ddog_CharSlice name, + int64_t val); + +/** + * Adds the contents of "file" to the crashinfo + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * `name` should be a valid reference to a utf8 encoded String. + * The string is copied into the crashinfo, so it does not need to outlive this + * call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_add_file(struct ddog_crasht_CrashInfo *crashinfo, + ddog_CharSlice filename); + +/** + * Adds the tag with given "key" and "value" to the crashinfo + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * `key` should be a valid reference to a utf8 encoded String. + * `value` should be a valid reference to a utf8 encoded String. + * The string is copied into the crashinfo, so it does not need to outlive this + * call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_add_tag(struct ddog_crasht_CrashInfo *crashinfo, + ddog_CharSlice key, + ddog_CharSlice value); + +/** + * Sets the crashinfo metadata + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * All references inside `metadata` must be valid. + * Strings are copied into the crashinfo, and do not need to outlive this call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_set_metadata(struct ddog_crasht_CrashInfo *crashinfo, + struct ddog_crasht_Metadata metadata); + +/** + * Sets the crashinfo siginfo + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * All references inside `metadata` must be valid. + * Strings are copied into the crashinfo, and do not need to outlive this call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_set_siginfo(struct ddog_crasht_CrashInfo *crashinfo, + struct ddog_crasht_SigInfo siginfo); + +/** + * If `thread_id` is empty, sets `stacktrace` as the default stacktrace. + * Otherwise, adds an additional stacktrace with id "thread_id". + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + * All references inside `stacktraces` must be valid. + * Strings are copied into the crashinfo, and do not need to outlive this call. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_set_stacktrace(struct ddog_crasht_CrashInfo *crashinfo, + ddog_CharSlice thread_id, + struct ddog_crasht_Slice_StackFrame stacktrace); + +/** + * Sets the timestamp to the given unix timestamp + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_set_timestamp(struct ddog_crasht_CrashInfo *crashinfo, + struct ddog_Timespec ts); + +/** + * Sets the timestamp to the current time + * + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_set_timestamp_to_now(struct ddog_crasht_CrashInfo *crashinfo); + +/** + * Exports `crashinfo` to the backend at `endpoint` + * Note that we support the "file://" endpoint for local file output. + * # Safety + * `crashinfo` must be a valid pointer to a `CrashInfo` object. + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_CrashInfo_upload_to_endpoint(struct ddog_crasht_CrashInfo *crashinfo, + const struct ddog_Endpoint *endpoint); + +/** + * Demangles the string "name". + * If demangling fails, returns an empty string "" + * + * # Safety + * `name` should be a valid reference to a utf8 encoded String. + * The string is copied into the result, and does not need to outlive this call + */ +DDOG_CHECK_RETURN +struct ddog_crasht_StringWrapperResult ddog_crasht_demangle(ddog_CharSlice name, + enum ddog_crasht_DemangleOptions options); + +/** + * Receives data from a crash collector via a pipe on `stdin`, formats it into + * `CrashInfo` json, and emits it to the endpoint/file defined in `config`. + * + * At a high-level, this exists because doing anything in a + * signal handler is dangerous, so we fork a sidecar to do the stuff we aren't + * allowed to do in the handler. + * + * See comments in [crashtracker/lib.rs] for a full architecture description. + * # Safety + * No safety concerns + */ +DDOG_CHECK_RETURN struct ddog_crasht_Result ddog_crasht_receiver_entry_point_stdin(void); + +/** + * Receives data from a crash collector via a pipe on `stdin`, formats it into + * `CrashInfo` json, and emits it to the endpoint/file defined in `config`. + * + * At a high-level, this exists because doing anything in a + * signal handler is dangerous, so we fork a sidecar to do the stuff we aren't + * allowed to do in the handler. + * + * See comments in [profiling/crashtracker/mod.rs] for a full architecture + * description. + * # Safety + * No safety concerns + */ +DDOG_CHECK_RETURN +struct ddog_crasht_Result ddog_crasht_receiver_entry_point_unix_socket(ddog_CharSlice socket_path); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* DDOG_CRASHTRACKER_H */ diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index a66edb1f3c..8bef6a8387 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -8,6 +8,10 @@ #include "telemetry.h" #include "sidecar.h" +typedef struct ddog_Vec_CChar *(*ddog_DynamicConfigUpdate)(ddog_CharSlice config, + ddog_CharSlice value, + bool return_old); + /** * `QueueId` is a struct that represents a unique identifier for a queue. * It contains a single field, `inner`, which is a 64-bit unsigned integer. @@ -125,8 +129,14 @@ extern ddog_Uuid ddtrace_runtime_id; extern void (*ddog_log_callback)(ddog_CharSlice); +extern ddog_VecRemoteConfigProduct DDTRACE_REMOTE_CONFIG_PRODUCTS; + +extern ddog_VecRemoteConfigCapabilities DDTRACE_REMOTE_CONFIG_CAPABILITIES; + extern const uint8_t *DDOG_PHP_FUNCTION; +extern struct ddog_SidecarTransport *ddtrace_sidecar; + /** * # Safety * Must be called from a single-threaded context, such as MINIT. @@ -155,11 +165,70 @@ void ddog_reset_logger(void); uint32_t ddog_get_logs_count(ddog_CharSlice level); +void ddog_init_remote_config(bool live_debugging_enabled, bool appsec_features, bool appsec_config); + +struct ddog_RemoteConfigState *ddog_init_remote_config_state(const struct ddog_Endpoint *endpoint); + +const char *ddog_remote_config_get_path(const struct ddog_RemoteConfigState *remote_config); + +void ddog_process_remote_configs(struct ddog_RemoteConfigState *remote_config); + +bool ddog_type_can_be_instrumented(const struct ddog_RemoteConfigState *remote_config, + ddog_CharSlice typename_); + +bool ddog_global_log_probe_limiter_inc(const struct ddog_RemoteConfigState *remote_config); + +struct ddog_Vec_CChar *ddog_CharSlice_to_owned(ddog_CharSlice str); + +bool ddog_remote_configs_service_env_change(struct ddog_RemoteConfigState *remote_config, + ddog_CharSlice service, + ddog_CharSlice env, + ddog_CharSlice version); + +bool ddog_remote_config_alter_dynamic_config(struct ddog_RemoteConfigState *remote_config, + ddog_CharSlice config, + ddog_CharSlice new_value); + +void ddog_setup_remote_config(ddog_DynamicConfigUpdate update_config, + const struct ddog_LiveDebuggerSetup *setup); + +void ddog_rinit_remote_config(struct ddog_RemoteConfigState *remote_config); + +void ddog_rshutdown_remote_config(struct ddog_RemoteConfigState *remote_config); + +void ddog_shutdown_remote_config(struct ddog_RemoteConfigState*); + +void ddog_log_debugger_data(const struct ddog_Vec_DebuggerPayload *payloads); + +void ddog_log_debugger_datum(const struct ddog_DebuggerPayload *payload); + +ddog_MaybeError ddog_send_debugger_diagnostics(const struct ddog_RemoteConfigState *remote_config_state, + struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_QueueId queue_id, + const struct ddog_Probe *probe, + uint64_t timestamp); + +ddog_MaybeError ddog_sidecar_enable_appsec(ddog_CharSlice shared_lib_path, + ddog_CharSlice socket_file_path, + ddog_CharSlice lock_file_path, + ddog_CharSlice log_file_path, + ddog_CharSlice log_level); + ddog_MaybeError ddog_sidecar_connect_php(struct ddog_SidecarTransport **connection, const char *error_path, ddog_CharSlice log_level, bool enable_telemetry); +void ddtrace_sidecar_reconnect(struct ddog_SidecarTransport **transport, + struct ddog_SidecarTransport *(*factory)(void)); + +bool ddog_shm_limiter_inc(const struct ddog_MaybeShmLimiter *limiter, uint32_t limit); + +bool ddog_exception_hash_limiter_inc(struct ddog_SidecarTransport *connection, + uint64_t hash, + uint32_t granularity_seconds); + bool ddtrace_detect_composer_installed_json(struct ddog_SidecarTransport **transport, const struct ddog_InstanceId *instance_id, const ddog_QueueId *queue_id, diff --git a/components-rs/lib.rs b/components-rs/lib.rs index 33a88b7cb3..49db14b504 100644 --- a/components-rs/lib.rs +++ b/components-rs/lib.rs @@ -1,8 +1,9 @@ +#![allow(internal_features)] #![feature(allow_internal_unstable)] -#![feature(local_key_cell_methods)] #![feature(linkage)] pub mod log; +pub mod remote_config; pub mod sidecar; pub mod telemetry; @@ -10,9 +11,9 @@ use std::borrow::Cow; use std::ffi::c_char; use std::ptr::null_mut; use ddcommon::entity_id::{get_container_id, set_cgroup_file}; -use ddcommon_ffi::CharSlice; use uuid::Uuid; +pub use datadog_crashtracker_ffi::*; pub use datadog_sidecar_ffi::*; use ddcommon_ffi::slice::AsBytes; pub use ddcommon_ffi::*; diff --git a/components-rs/live-debugger.h b/components-rs/live-debugger.h new file mode 100644 index 0000000000..bb3a67a892 --- /dev/null +++ b/components-rs/live-debugger.h @@ -0,0 +1,139 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021-Present Datadog, Inc. + +#ifndef DDOG_LIVE_DEBUGGER_H +#define DDOG_LIVE_DEBUGGER_H + +#include +#include +#include +#include +#include "common.h" + +void drop_span_decoration_probe(struct ddog_SpanDecorationProbe); + +struct ddog_CaptureConfiguration ddog_capture_defaults(void); + +void ddog_register_expr_evaluator(const struct ddog_Evaluator *eval); + +struct ddog_ConditionEvaluationResult ddog_evaluate_condition(const struct ddog_ProbeCondition *condition, + void *context); + +void ddog_drop_void_collection_string(struct ddog_VoidCollection void_); + +struct ddog_VoidCollection ddog_evaluate_unmanaged_string(const struct ddog_DslString *segments, + void *context, + struct ddog_Vec_SnapshotEvaluationError **errors); + +struct ddog_ValueEvaluationResult ddog_evaluate_value(const struct ddog_ProbeValue *value, + void *context); + +struct ddog_IntermediateValue ddog_evaluated_value_get(const struct ddog_InternalIntermediateValue *value); + +void ddog_evaluated_value_drop(struct ddog_InternalIntermediateValue*); + +struct ddog_VoidCollection ddog_evaluated_value_into_unmanaged_string(struct ddog_InternalIntermediateValue *value, + void *context); + +struct ddog_LiveDebuggingParseResult ddog_parse_live_debugger_json(ddog_CharSlice json); + +void ddog_drop_live_debugger_parse_result(struct ddog_LiveDebuggingParseResult); + +ddog_DebuggerCapture *ddog_create_exception_snapshot(struct ddog_Vec_DebuggerPayload *buffer, + ddog_CharSlice service, + ddog_CharSlice language, + ddog_CharSlice id, + ddog_CharSlice exception_id, + ddog_CharSlice exception_hash, + uint32_t frame_index, + ddog_CharSlice type_name, + ddog_CharSlice method_name, + uint64_t timestamp); + +struct ddog_DebuggerPayload *ddog_create_log_probe_snapshot(const struct ddog_Probe *probe, + const ddog_CharSlice *message, + ddog_CharSlice service, + ddog_CharSlice language, + uint64_t timestamp); + +void ddog_update_payload_message(struct ddog_DebuggerPayload *payload, ddog_CharSlice message); + +ddog_DebuggerCapture *ddog_snapshot_entry(struct ddog_DebuggerPayload *payload); + +ddog_DebuggerCapture *ddog_snapshot_lines(struct ddog_DebuggerPayload *payload, uint32_t line); + +ddog_DebuggerCapture *ddog_snapshot_exit(struct ddog_DebuggerPayload *payload); + +bool ddog_snapshot_redacted_name(ddog_CharSlice name); + +void ddog_snapshot_add_redacted_name(ddog_CharSlice name); + +bool ddog_snapshot_redacted_type(ddog_CharSlice name); + +void ddog_snapshot_add_redacted_type(ddog_CharSlice name); + +void ddog_snapshot_add_field(ddog_DebuggerCapture *capture, + enum ddog_FieldType type, + ddog_CharSlice name, + struct ddog_CaptureValue value); + +void ddog_capture_value_add_element(struct ddog_CaptureValue *value, + struct ddog_CaptureValue element); + +void ddog_capture_value_add_entry(struct ddog_CaptureValue *value, + struct ddog_CaptureValue key, + struct ddog_CaptureValue element); + +void ddog_capture_value_add_field(struct ddog_CaptureValue *value, + ddog_CharSlice key, + struct ddog_CaptureValue element); + +void ddog_snapshot_format_new_uuid(uint8_t (*buf)[36]); + +ddog_CharSlice ddog_evaluation_error_first_msg(const struct ddog_Vec_SnapshotEvaluationError *vec); + +void ddog_evaluation_error_drop(struct ddog_Vec_SnapshotEvaluationError*); + +struct ddog_DebuggerPayload *ddog_evaluation_error_snapshot(const struct ddog_Probe *probe, + ddog_CharSlice service, + ddog_CharSlice language, + struct ddog_Vec_SnapshotEvaluationError *errors, + uint64_t timestamp); + +void ddog_serialize_debugger_payload(const struct ddog_DebuggerPayload *payload, + void (*callback)(ddog_CharSlice)); + +void ddog_drop_debugger_payload(struct ddog_DebuggerPayload*); + +struct ddog_DebuggerPayload *ddog_debugger_diagnostics_create(const struct ddog_Probe *probe, + ddog_CharSlice service, + ddog_CharSlice runtime_id, + uint64_t timestamp); + +void ddog_debugger_diagnostics_set_parent_id(struct ddog_DebuggerPayload *payload, + ddog_CharSlice parent_id); + +struct ddog_String *ddog_live_debugger_build_tags(ddog_CharSlice debugger_version, + ddog_CharSlice env, + ddog_CharSlice version, + ddog_CharSlice runtime_id, + struct ddog_Vec_Tag global_tags); + +struct ddog_String *ddog_live_debugger_tags_from_raw(ddog_CharSlice tags); + +ddog_MaybeError ddog_live_debugger_spawn_sender(const ddog_Endpoint *endpoint, + struct ddog_String *tags, + struct ddog_SenderHandle **handle); + +bool ddog_live_debugger_send_raw_data(struct ddog_SenderHandle *handle, + enum ddog_DebuggerType debugger_type, + struct ddog_OwnedCharSlice data); + +bool ddog_live_debugger_send_payload(struct ddog_SenderHandle *handle, + const struct ddog_DebuggerPayload *data); + +void ddog_live_debugger_drop_sender(struct ddog_SenderHandle *sender); + +void ddog_live_debugger_join_sender(struct ddog_SenderHandle *sender); + +#endif /* DDOG_LIVE_DEBUGGER_H */ diff --git a/components-rs/remote_config.rs b/components-rs/remote_config.rs new file mode 100644 index 0000000000..7952c37353 --- /dev/null +++ b/components-rs/remote_config.rs @@ -0,0 +1,532 @@ +use crate::sidecar::MaybeShmLimiter; +use datadog_dynamic_configuration::{data::TracingSamplingRuleProvenance, Configs}; +use datadog_live_debugger::debugger_defs::{DebuggerData, DebuggerPayload}; +use datadog_live_debugger::{FilterList, LiveDebuggingData, ServiceConfiguration}; +use datadog_live_debugger_ffi::data::Probe; +use datadog_live_debugger_ffi::evaluator::{ddog_register_expr_evaluator, Evaluator}; +use datadog_live_debugger_ffi::send_data::{ + ddog_debugger_diagnostics_create_unboxed, ddog_snapshot_redacted_type, +}; +use datadog_remote_config::fetch::ConfigInvariants; +use datadog_remote_config::{ + RemoteConfigCapabilities, RemoteConfigData, RemoteConfigProduct, Target, +}; +use datadog_sidecar::service::blocking::SidecarTransport; +use datadog_sidecar::service::{InstanceId, QueueId}; +use datadog_sidecar::shm_remote_config::{RemoteConfigManager, RemoteConfigUpdate}; +use datadog_sidecar_ffi::ddog_sidecar_send_debugger_data; +use ddcommon::Endpoint; +use ddcommon_ffi::slice::AsBytes; +use ddcommon_ffi::{CharSlice, MaybeError}; +use itertools::Itertools; +use regex_automata::dfa::regex::Regex; +use serde::Serialize; +use std::borrow::Cow; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::ffi::c_char; +use std::mem; +use std::sync::Arc; +use tracing::debug; + +type DynamicConfigUpdate = for<'a> extern "C" fn( + config: CharSlice, + value: CharSlice, + return_old: bool, +) -> *mut Vec; + +static mut LIVE_DEBUGGER_CALLBACKS: Option = None; +static mut DYNAMIC_CONFIG_UPDATE: Option = None; + +type VecRemoteConfigProduct = ddcommon_ffi::Vec; +#[no_mangle] +pub static mut DDTRACE_REMOTE_CONFIG_PRODUCTS: VecRemoteConfigProduct = ddcommon_ffi::Vec::new(); + +type VecRemoteConfigCapabilities = ddcommon_ffi::Vec; +#[no_mangle] +pub static mut DDTRACE_REMOTE_CONFIG_CAPABILITIES: VecRemoteConfigCapabilities = + ddcommon_ffi::Vec::new(); + +#[derive(Default)] +struct DynamicConfig { + active_config_path: Option, + configs: Vec, + old_config_values: HashMap>, +} + +pub struct RemoteConfigState { + manager: RemoteConfigManager, + live_debugger: LiveDebuggerState, + dynamic_config: DynamicConfig, +} + +#[repr(C)] +pub struct LiveDebuggerSetup<'a> { + pub evaluator: &'a Evaluator, + pub callbacks: LiveDebuggerCallbacks, +} + +#[repr(C)] +#[derive(Clone)] +pub struct LiveDebuggerCallbacks { + pub set_probe: extern "C" fn(probe: Probe, limiter: &MaybeShmLimiter) -> i64, + pub remove_probe: extern "C" fn(id: i64), +} + +#[derive(Default)] +pub struct LiveDebuggerState { + pub spans_map: HashMap, + pub active: HashMap>, + pub config_id: String, + pub allow_dfa: Option, + pub deny_dfa: Option, +} + +#[no_mangle] +pub unsafe extern "C" fn ddog_init_remote_config( + live_debugging_enabled: bool, + appsec_features: bool, + appsec_config: bool, +) { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::ApmTracing); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingCustomTags); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingEnabled); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingHttpHeaderTags); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingLogsInjection); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingSampleRate); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::ApmTracingSampleRules); + + if live_debugging_enabled { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::LiveDebugger) + } + + if appsec_features { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmFeatures); + DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(RemoteConfigCapabilities::AsmActivation); + } + + if appsec_config { + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmData); + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::AsmDD); + DDTRACE_REMOTE_CONFIG_PRODUCTS.push(RemoteConfigProduct::Asm); + [ + RemoteConfigCapabilities::AsmIpBlocking, + RemoteConfigCapabilities::AsmDdRules, + RemoteConfigCapabilities::AsmExclusions, + RemoteConfigCapabilities::AsmRequestBlocking, + RemoteConfigCapabilities::AsmResponseBlocking, + RemoteConfigCapabilities::AsmUserBlocking, + RemoteConfigCapabilities::AsmCustomRules, + RemoteConfigCapabilities::AsmCustomBlockingResponse, + RemoteConfigCapabilities::AsmTrustedIps, + ] + .iter() + .for_each(|c| DDTRACE_REMOTE_CONFIG_CAPABILITIES.push(*c)); + } +} + +// Per-thread state +#[no_mangle] +pub unsafe extern "C" fn ddog_init_remote_config_state( + endpoint: &Endpoint, +) -> Box { + Box::new(RemoteConfigState { + manager: RemoteConfigManager::new(ConfigInvariants { + language: "php".to_string(), + tracer_version: include_str!("../VERSION").into(), + endpoint: endpoint.clone(), + products: DDTRACE_REMOTE_CONFIG_PRODUCTS.to_vec(), + capabilities: DDTRACE_REMOTE_CONFIG_CAPABILITIES.to_vec(), + }), + live_debugger: LiveDebuggerState::default(), + dynamic_config: Default::default(), + }) +} + +#[derive(Serialize)] +struct SampleRule<'a> { + #[serde(skip_serializing_if = "Option::is_none")] + name: Option<&'a str>, + service: &'a str, + resource: &'a str, + #[serde(skip_serializing_if = "HashMap::is_empty")] + tags: HashMap<&'a str, &'a str>, + #[serde(rename = "_provenance")] + provenance: TracingSamplingRuleProvenance, + sample_rate: f64, +} + +fn map_config(config: &Configs) -> (&'static str, String) { + match config { + Configs::TracingHeaderTags(tags) => ( + "datadog.trace.header_tags", + tags.iter().map(|(k, _)| k).join(","), + ), + Configs::TracingSampleRate(rate) => ("datadog.trace.sample_rate", rate.to_string()), + Configs::LogInjectionEnabled(enabled) => ( + "datadog.logs_injection", + (if *enabled { "1" } else { "0" }).to_string(), + ), + Configs::TracingTags(tags) => ("datadog.tags", tags.join(",")), + Configs::TracingEnabled(enabled) => ( + "datadog.trace.enabled", + (if *enabled { "1" } else { "0" }).to_string(), + ), + Configs::TracingSamplingRules(rules) => { + let map: Vec<_> = rules + .iter() + .map(|r| SampleRule { + name: r.name.as_deref(), + service: r.service.as_str(), + resource: r.resource.as_str(), + tags: r + .tags + .iter() + .map(|t| (t.key.as_str(), t.value_glob.as_str())) + .collect(), + provenance: r.provenance, + sample_rate: r.sample_rate, + }) + .collect(); + ( + "datadog.trace.sampling_rules", + serde_json::to_string(&map).unwrap(), + ) + } + } +} + +fn remove_old_configs(remote_config: &mut RemoteConfigState) { + for (name, val) in remote_config.dynamic_config.old_config_values.drain() { + unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.as_str().into(), (&val).into(), false); + } + remote_config.dynamic_config.old_config_values.clear(); + remote_config.dynamic_config.active_config_path = None; +} + +fn insert_new_configs( + old_config_values: &mut HashMap>, + old_configs: &mut Vec, + new_configs: Vec, +) { + let mut found_configs = HashSet::new(); + for config in new_configs.iter() { + let (name, val) = map_config(config); + let is_update = old_config_values.contains_key(name); + let original = + unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.into(), val.as_str().into(), !is_update); + if !original.is_null() { + old_config_values.insert(name.into(), *unsafe { Box::from_raw(original) }); + } + found_configs.insert(mem::discriminant(config)); + } + for config in old_configs.iter() { + if !found_configs.contains(&mem::discriminant(config)) { + let (name, _) = map_config(config); + if let Some(val) = old_config_values.remove(name) { + unsafe { DYNAMIC_CONFIG_UPDATE }.unwrap()(name.into(), (&val).into(), false); + } + } + } + *old_configs = new_configs; +} + +#[no_mangle] +pub extern "C" fn ddog_remote_config_get_path(remote_config: &RemoteConfigState) -> *const c_char { + remote_config + .manager + .active_reader + .as_ref() + .map(|r| r.get_path().as_ptr()) + .unwrap_or(std::ptr::null()) +} + +#[no_mangle] +pub extern "C" fn ddog_process_remote_configs(remote_config: &mut RemoteConfigState) { + loop { + match remote_config.manager.fetch_update() { + RemoteConfigUpdate::None => break, + RemoteConfigUpdate::Add { + value, + limiter_index, + } => match value.data { + RemoteConfigData::LiveDebugger(debugger) => { + let val = Box::new((debugger, MaybeShmLimiter::open(limiter_index))); + let rc_ref = unsafe { mem::transmute(remote_config as *mut _) }; // sigh, borrow checker + let entry = remote_config.live_debugger.active.entry(value.config_id); + let (debugger, limiter) = &mut **match entry { + Entry::Occupied(mut e) => { + e.insert(val); + e.into_mut() + } + Entry::Vacant(e) => e.insert(val), + }; + apply_config(rc_ref, debugger, limiter); + } + RemoteConfigData::DynamicConfig(config_data) => { + let configs: Vec = config_data.lib_config.into(); + if !configs.is_empty() { + insert_new_configs( + &mut remote_config.dynamic_config.old_config_values, + &mut remote_config.dynamic_config.configs, + configs, + ); + remote_config.dynamic_config.active_config_path = Some(value.config_id); + } + } + RemoteConfigData::Ignored(_) => (), + }, + RemoteConfigUpdate::Remove(path) => match path.product { + RemoteConfigProduct::LiveDebugger => { + if let Some(boxed) = remote_config.live_debugger.active.remove(&path.config_id) + { + remove_config(remote_config, &boxed.0); + } + } + RemoteConfigProduct::ApmTracing => { + if Some(path.config_id) == remote_config.dynamic_config.active_config_path { + remove_old_configs(remote_config); + } + } + _ => (), + }, + } + } +} + +fn apply_config( + remote_config: &mut RemoteConfigState, + debugger: &LiveDebuggingData, + limiter: &MaybeShmLimiter, +) { + if let Some(callbacks) = unsafe { &LIVE_DEBUGGER_CALLBACKS } { + match debugger { + LiveDebuggingData::Probe(probe) => { + debug!("Applying live debugger probe {probe:?}"); + let hook_id = (callbacks.set_probe)(probe.into(), limiter); + if hook_id >= 0 { + remote_config + .live_debugger + .spans_map + .insert(probe.id.clone(), hook_id); + } + } + LiveDebuggingData::ServiceConfiguration(config) => { + debug!("Applying live debugger service config {config:?}"); + fn build_regex(list: &FilterList) -> Option { + if list.classes.is_empty() && list.package_prefixes.is_empty() { + None + } else { + let mut regex = "".to_string(); + for s in list.classes.iter() { + if !regex.is_empty() { + regex.push('|'); + } + regex.push_str(®ex::escape(s.as_str())); + } + for s in list.package_prefixes.iter() { + if !regex.is_empty() { + regex.push('|'); + } + regex.push_str(®ex::escape(s.as_str())); + regex.push_str(".*"); + } + Some(Regex::new(regex.as_str()).unwrap()) + } + } + remote_config.live_debugger.config_id = config.id.clone(); + remote_config.live_debugger.allow_dfa = build_regex(&config.allow); + remote_config.live_debugger.deny_dfa = build_regex(&config.deny); + } + } + } +} + +fn remove_config(remote_config: &mut RemoteConfigState, debugger: &LiveDebuggingData) { + if let Some(callbacks) = unsafe { &LIVE_DEBUGGER_CALLBACKS } { + match debugger { + LiveDebuggingData::Probe(probe) => { + if let Some(id) = remote_config.live_debugger.spans_map.remove(&probe.id) { + debug!("Removing live debugger probe {}", probe.id); + (callbacks.remove_probe)(id); + } + } + LiveDebuggingData::ServiceConfiguration(ServiceConfiguration { id, .. }) => { + // There can only be one active service configuration, but I don't want to rely on the order of adding and removing service configurations + if id == &remote_config.live_debugger.config_id { + debug!("Resetting live-debugger service config"); + remote_config.live_debugger.allow_dfa = None; + remote_config.live_debugger.deny_dfa = None; + } + } + } + } +} + +#[no_mangle] +pub extern "C" fn ddog_type_can_be_instrumented( + remote_config: &RemoteConfigState, + typename: CharSlice, +) -> bool { + if ddog_snapshot_redacted_type(typename) { + return false; + } + + if let Some(regex) = &remote_config.live_debugger.allow_dfa { + if !regex.is_match(typename.as_bytes()) { + return false; + } + } + + if let Some(regex) = &remote_config.live_debugger.deny_dfa { + if regex.is_match(typename.as_bytes()) { + return false; + } + } + + true +} + +#[no_mangle] +pub extern "C" fn ddog_global_log_probe_limiter_inc(remote_config: &RemoteConfigState) -> bool { + if let Some(boxed) = remote_config + .live_debugger + .active + .get(&remote_config.live_debugger.config_id) + { + if let (LiveDebuggingData::ServiceConfiguration(config), limiter) = &**boxed { + limiter.inc(config.sampling_snapshots_per_second) + } else { + true + } + } else { + true + } +} + +#[no_mangle] +pub unsafe extern "C" fn ddog_CharSlice_to_owned(str: CharSlice) -> *mut Vec { + Box::into_raw(Box::new(str.as_slice().into())) +} + +#[no_mangle] +pub extern "C" fn ddog_remote_configs_service_env_change( + remote_config: &mut RemoteConfigState, + service: CharSlice, + env: CharSlice, + version: CharSlice, +) -> bool { + let new_target = Target { + service: service.to_utf8_lossy().to_string(), + env: env.to_utf8_lossy().to_string(), + app_version: version.to_utf8_lossy().to_string(), + }; + + if let Some(target) = remote_config.manager.get_target() { + if **target == new_target { + return false; + } + } + + remote_config.manager.track_target(&Arc::new(new_target)); + ddog_process_remote_configs(remote_config); + + true +} + +#[no_mangle] +pub unsafe extern "C" fn ddog_remote_config_alter_dynamic_config( + remote_config: &mut RemoteConfigState, + config: CharSlice, + new_value: CharSlice, +) -> bool { + if let Some(entry) = remote_config + .dynamic_config + .old_config_values + .get_mut(config.try_to_utf8().unwrap()) + { + *entry = new_value.as_slice().into(); + return false; + } + true +} + +#[no_mangle] +pub unsafe extern "C" fn ddog_setup_remote_config( + update_config: DynamicConfigUpdate, + setup: &LiveDebuggerSetup, +) { + ddog_register_expr_evaluator(setup.evaluator); + DYNAMIC_CONFIG_UPDATE = Some(update_config); + LIVE_DEBUGGER_CALLBACKS = Some(setup.callbacks.clone()); +} + +#[no_mangle] +pub extern "C" fn ddog_rinit_remote_config(remote_config: &mut RemoteConfigState) { + ddog_process_remote_configs(remote_config); +} + +#[no_mangle] +pub extern "C" fn ddog_rshutdown_remote_config(remote_config: &mut RemoteConfigState) { + remote_config.live_debugger.spans_map.clear(); + remote_config.dynamic_config.old_config_values.clear(); + remote_config.manager.unload_configs(&[ + RemoteConfigProduct::ApmTracing, + RemoteConfigProduct::LiveDebugger, + ]); +} + +#[no_mangle] +pub extern "C" fn ddog_shutdown_remote_config(_: Box) {} + +#[no_mangle] +pub extern "C" fn ddog_log_debugger_data(payloads: &Vec) { + if !payloads.is_empty() { + debug!( + "Submitting debugger data: {}", + serde_json::to_string(payloads).unwrap() + ); + } +} + +#[no_mangle] +pub extern "C" fn ddog_log_debugger_datum(payload: &DebuggerPayload) { + debug!( + "Submitting debugger data: {}", + serde_json::to_string(payload).unwrap() + ); +} + +#[no_mangle] +pub unsafe extern "C" fn ddog_send_debugger_diagnostics<'a>( + remote_config_state: &RemoteConfigState, + transport: &mut Box, + instance_id: &InstanceId, + queue_id: QueueId, + probe: &'a Probe, + timestamp: u64, +) -> MaybeError { + let service = Cow::Borrowed( + remote_config_state + .manager + .get_target() + .map_or("", |t| t.service.as_str()), + ); + let mut payload = ddog_debugger_diagnostics_create_unboxed( + probe, + service, + Cow::Borrowed(&instance_id.runtime_id), + timestamp, + ); + let DebuggerData::Diagnostics(ref mut diagnostics) = payload.debugger else { + unreachable!(); + }; + diagnostics.parent_id = Some(Cow::Borrowed( + remote_config_state.manager.current_runtime_id.as_str(), + )); + debug!( + "Submitting debugger diagnostics data: {:?}", + serde_json::to_string(&payload).unwrap() + ); + ddog_sidecar_send_debugger_data(transport, instance_id, queue_id, vec![payload]) +} diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 9b6696ca82..955cfeb8fe 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -60,6 +60,32 @@ void ddog_agent_remote_config_reader_drop(struct ddog_AgentRemoteConfigReader*); void ddog_agent_remote_config_writer_drop(struct ddog_AgentRemoteConfigWriter_ShmHandle*); +struct ddog_RemoteConfigReader *ddog_remote_config_reader_for_endpoint(const ddog_CharSlice *language, + const ddog_CharSlice *tracer_version, + const struct ddog_Endpoint *endpoint, + ddog_CharSlice service_name, + ddog_CharSlice env_name, + ddog_CharSlice app_version, + const enum ddog_RemoteConfigProduct *remote_config_products, + uintptr_t remote_config_products_count, + const enum ddog_RemoteConfigCapabilities *remote_config_capabilities, + uintptr_t remote_config_capabilities_count); + +/** + * # Safety + * Argument should point to a valid C string. + */ +struct ddog_RemoteConfigReader *ddog_remote_config_reader_for_path(const char *path); + +char *ddog_remote_config_path(const struct ddog_ConfigInvariants *id, + const struct ddog_Arc_Target *target); + +void ddog_remote_config_path_free(char *path); + +bool ddog_remote_config_read(struct ddog_RemoteConfigReader *reader, ddog_CharSlice *data); + +void ddog_remote_config_reader_drop(struct ddog_RemoteConfigReader*); + void ddog_sidecar_transport_drop(struct ddog_SidecarTransport*); /** @@ -127,7 +153,7 @@ ddog_MaybeError ddog_sidecar_telemetry_flushServiceData(struct ddog_SidecarTrans /** * Enqueues a list of actions to be performed. */ -ddog_MaybeError ddog_sidecar_telemetry_end(struct ddog_SidecarTransport **transport, +ddog_MaybeError ddog_sidecar_lifecycle_end(struct ddog_SidecarTransport **transport, const struct ddog_InstanceId *instance_id, const ddog_QueueId *queue_id); @@ -150,12 +176,20 @@ ddog_MaybeError ddog_sidecar_session_set_config(struct ddog_SidecarTransport **t ddog_CharSlice session_id, const struct ddog_Endpoint *agent_endpoint, const struct ddog_Endpoint *dogstatsd_endpoint, - uint64_t flush_interval_milliseconds, - uint64_t telemetry_heartbeat_interval_millis, + ddog_CharSlice language, + ddog_CharSlice tracer_version, + uint32_t flush_interval_milliseconds, + uint32_t remote_config_poll_interval_millis, + uint32_t telemetry_heartbeat_interval_millis, uintptr_t force_flush_size, uintptr_t force_drop_size, ddog_CharSlice log_level, - ddog_CharSlice log_path); + ddog_CharSlice log_path, + void *remote_config_notify_function, + const enum ddog_RemoteConfigProduct *remote_config_products, + uintptr_t remote_config_products_count, + const enum ddog_RemoteConfigCapabilities *remote_config_capabilities, + uintptr_t remote_config_capabilities_count); /** * Sends a trace to the sidecar via shared memory. @@ -174,6 +208,24 @@ ddog_MaybeError ddog_sidecar_send_trace_v04_bytes(struct ddog_SidecarTransport * ddog_CharSlice data, const struct ddog_TracerHeaderTags *tracer_header_tags); +ddog_MaybeError ddog_sidecar_send_debugger_data(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_QueueId queue_id, + struct ddog_Vec_DebuggerPayload payloads); + +ddog_MaybeError ddog_sidecar_send_debugger_datum(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + ddog_QueueId queue_id, + struct ddog_DebuggerPayload *payload); + +ddog_MaybeError ddog_sidecar_set_remote_config_data(struct ddog_SidecarTransport **transport, + const struct ddog_InstanceId *instance_id, + const ddog_QueueId *queue_id, + ddog_CharSlice service_name, + ddog_CharSlice env_name, + ddog_CharSlice app_version, + const struct ddog_Vec_Tag *global_tags); + /** * Dumps the current state of the sidecar. */ @@ -229,6 +281,13 @@ ddog_MaybeError ddog_sidecar_dogstatsd_set(struct ddog_SidecarTransport **transp int64_t value, const struct ddog_Vec_Tag *tags); +/** + * Sets x-datadog-test-session-token on all requests for the given session. + */ +ddog_MaybeError ddog_sidecar_set_test_session_token(struct ddog_SidecarTransport **transport, + ddog_CharSlice session_id, + ddog_CharSlice token); + /** * This function creates a new transport using the provided callback function when the current * transport is closed. @@ -241,4 +300,9 @@ ddog_MaybeError ddog_sidecar_dogstatsd_set(struct ddog_SidecarTransport **transp void ddog_sidecar_reconnect(struct ddog_SidecarTransport **transport, struct ddog_SidecarTransport *(*factory)(void)); +/** + * Return the path of the crashtracker unix domain socket. + */ +ddog_CharSlice ddog_sidecar_get_crashtracker_unix_socket_path(void); + #endif /* DDOG_SIDECAR_H */ diff --git a/components-rs/sidecar.rs b/components-rs/sidecar.rs index a6b747a8e6..b1759eb3a4 100644 --- a/components-rs/sidecar.rs +++ b/components-rs/sidecar.rs @@ -1,8 +1,19 @@ use std::ffi::{c_char, CStr, OsStr}; +use std::ops::DerefMut; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; -use datadog_sidecar::config::{self, LogMethod}; -use datadog_sidecar::service::blocking::SidecarTransport; +use lazy_static::{lazy_static, LazyStatic}; +use tracing::warn; +#[cfg(windows)] +use std::os::windows::ffi::OsStrExt; +use std::sync::Mutex; +use std::time::Duration; +use datadog_sidecar::config::{self, AppSecConfig, LogMethod}; +use datadog_sidecar::service::blocking::{acquire_exception_hash_rate_limiter, SidecarTransport}; +use ddcommon::rate_limiter::{Limiter, LocalLimiter}; +use datadog_ipc::rate_limiter::{AnyLimiter, ShmLimiterMemory}; +use datadog_sidecar::service::exception_hash_rate_limiter::ExceptionHashRateLimiter; +use datadog_sidecar::tracer::shm_limiter_path; use ddcommon_ffi::slice::AsBytes; use ddcommon_ffi::{CharSlice, self as ffi, MaybeError}; use ddtelemetry_ffi::try_c; @@ -46,6 +57,52 @@ fn run_sidecar(mut cfg: config::Config) -> anyhow::Result { datadog_sidecar::start_or_connect_to_sidecar(cfg) } +lazy_static! { + static ref APPSEC_CONFIG: Mutex> = Mutex::new(None); +} + +// must be called prior to ddog_sidecar_connect +#[no_mangle] +pub extern "C" fn ddog_sidecar_enable_appsec( + shared_lib_path: CharSlice, + socket_file_path: CharSlice, + lock_file_path: CharSlice, + log_file_path: CharSlice, + log_level: CharSlice, +) -> MaybeError { + let mut appsec_config_guard = APPSEC_CONFIG.lock().unwrap(); + let shared_lib_path_os: std::ffi::OsString; + let socket_file_path_os: std::ffi::OsString; + let lock_file_path_os: std::ffi::OsString; + let log_file_path_os: std::ffi::OsString; + + #[cfg(unix)] + { + shared_lib_path_os = OsStr::from_bytes(shared_lib_path.as_bytes()).to_owned(); + socket_file_path_os = OsStr::from_bytes(socket_file_path.as_bytes()).to_owned(); + lock_file_path_os = OsStr::from_bytes(lock_file_path.as_bytes()).to_owned(); + log_file_path_os = OsStr::from_bytes(log_file_path.as_bytes()).to_owned(); + } + + #[cfg(windows)] + { + shared_lib_path_os = OsStr::new(&*shared_lib_path.to_utf8_lossy()).to_owned(); + socket_file_path_os = OsStr::new(&*socket_file_path.to_utf8_lossy()).to_owned(); + lock_file_path_os = OsStr::new(&*lock_file_path.to_utf8_lossy()).to_owned(); + log_file_path_os = OsStr::new(&*log_file_path.to_utf8_lossy()).to_owned(); + } + + appsec_config_guard.deref_mut().replace(AppSecConfig { + shared_lib_path: shared_lib_path_os, + socket_file_path: socket_file_path_os, + lock_file_path: lock_file_path_os, + log_file_path: log_file_path_os, + log_level: log_level.to_utf8_lossy().to_string(), + }); + + MaybeError::None +} + #[no_mangle] pub extern "C" fn ddog_sidecar_connect_php( connection: &mut *mut SidecarTransport, @@ -55,6 +112,8 @@ pub extern "C" fn ddog_sidecar_connect_php( ) -> MaybeError { let mut cfg = config::FromEnv::config(); cfg.self_telemetry = enable_telemetry; + let appsec_cfg_guard = APPSEC_CONFIG.lock().unwrap(); + cfg.appsec_config = appsec_cfg_guard.clone(); unsafe { if *error_path != 0 { let error_path = CStr::from_ptr(error_path).to_bytes(); @@ -71,8 +130,81 @@ pub extern "C" fn ddog_sidecar_connect_php( let log_level = OsStr::from_bytes(log_level.as_bytes()).into(); cfg.child_env.insert(OsStr::new("DD_TRACE_LOG_LEVEL").into(), log_level); } - let stream = Box::new(try_c!(run_sidecar(cfg))); + let mut stream = Box::new(try_c!(run_sidecar(cfg))); + // Generally the Send buffer ought to be big enough for instantaneous transmission + _ = stream.set_write_timeout(Some(Duration::from_millis(100))); + _ = stream.set_read_timeout(Some(Duration::from_secs(1))); *connection = Box::into_raw(stream); MaybeError::None } + +#[no_mangle] +#[allow(non_upper_case_globals)] +pub static mut ddtrace_sidecar: *mut SidecarTransport = std::ptr::null_mut(); + +#[no_mangle] +pub extern "C" fn ddtrace_sidecar_reconnect( + transport: &mut Box, + factory: unsafe extern "C" fn() -> Option>, +) { + transport.reconnect(|| unsafe { + let sidecar = factory(); + if sidecar.is_some() { + LazyStatic::initialize(&SHM_LIMITER); + } + sidecar + }); +} + + +lazy_static! { + pub static ref SHM_LIMITER: Option = ShmLimiterMemory::open(&shm_limiter_path()).map_or_else(|e| { + warn!("Attempt to use the SHM_LIMITER failed: {e:?}"); + None + }, Some); + + pub static ref EXCEPTION_HASH_LIMITER: Option = ExceptionHashRateLimiter::open().map_or_else(|e| { + warn!("Attempt to use the EXCEPTION_HASH_LIMITER failed: {e:?}"); + None + }, Some); +} + +pub struct MaybeShmLimiter(Option); + +impl MaybeShmLimiter { + pub fn open(index: u32) -> Self { + MaybeShmLimiter(if index == 0 { + None + } else { + match &*SHM_LIMITER { + Some(limiter) => limiter.get(index).map(AnyLimiter::Shm), + None => Some(AnyLimiter::Local(LocalLimiter::default())), + } + }) + } + + pub fn inc(&self, limit: u32) -> bool { + if let Some(ref limiter) = self.0 { + limiter.inc(limit) + } else { + true + } + } +} + +#[no_mangle] +pub extern "C" fn ddog_shm_limiter_inc(limiter: &MaybeShmLimiter, limit: u32) -> bool { + limiter.inc(limit) +} + +#[no_mangle] +pub extern "C" fn ddog_exception_hash_limiter_inc(connection: &mut SidecarTransport, hash: u64, granularity_seconds: u32) -> bool { + if let Some(limiter) = &*EXCEPTION_HASH_LIMITER { + if let Some(limiter) = limiter.find(hash) { + return limiter.inc(); + } + } + let _ = acquire_exception_hash_rate_limiter(connection, hash, Duration::from_secs(granularity_seconds as u64)); + true +} diff --git a/config.m4 b/config.m4 index c29a3c9357..e8f1f1c652 100644 --- a/config.m4 +++ b/config.m4 @@ -65,7 +65,7 @@ if test "$PHP_DDTRACE" != "no"; then fi if test "$PHP_DDTRACE_SANITIZE" != "no"; then - EXTRA_LDFLAGS="-fsanitize=address" + EXTRA_LDFLAGS="-fsanitize=address -lasan" EXTRA_CFLAGS="-fsanitize=address -fno-omit-frame-pointer" fi @@ -141,6 +141,7 @@ if test "$PHP_DDTRACE" != "no"; then ext/arrays.c \ ext/auto_flush.c \ ext/autoload_php_files.c \ + ext/collect_backtrace.c \ ext/comms_php.c \ ext/compat_string.c \ ext/coms.c \ @@ -151,6 +152,7 @@ if test "$PHP_DDTRACE" != "no"; then ext/dogstatsd_client.c \ ext/engine_api.c \ ext/engine_hooks.c \ + ext/exception_serialize.c \ ext/excluded_modules.c \ ext/git.c \ ext/handlers_api.c \ @@ -160,6 +162,7 @@ if test "$PHP_DDTRACE" != "no"; then ext/integrations/exec_integration.c \ ext/integrations/integrations.c \ ext/ip_extraction.c \ + ext/live_debugger.c \ ext/logging.c \ ext/limiter/limiter.c \ ext/memory_limit.c \ @@ -167,12 +170,14 @@ if test "$PHP_DDTRACE" != "no"; then ext/priority_sampling/priority_sampling.c \ ext/profiling.c \ ext/random.c \ + ext/remote_config.c \ ext/serializer.c \ ext/sidecar.c \ ext/signals.c \ ext/span.c \ ext/startup_logging.c \ ext/telemetry.c \ + ext/threads.c \ ext/tracer_tag_propagation/tracer_tag_propagation.c \ ext/user_request.c \ ext/hook/uhook.c \ @@ -303,7 +308,7 @@ EOT pushdef([PHP_GEN_GLOBAL_MAKEFILE], [ popdef([PHP_GEN_GLOBAL_MAKEFILE]) PHP_GEN_GLOBAL_MAKEFILE - sed -i $({ sed --version 2>&1 || echo ''; } | grep GNU >/dev/null || echo "''") -e '/^distclean:/a\'$'\n\t''rm -rf target/' -e '/.*\.a /{s/| xargs rm -f/! -path ".\/target\/*" | xargs rm -f/'$'\n}' Makefile + [sed -i $({ sed --version 2>&1 || echo ''; } | grep GNU >/dev/null || echo "''") -e '/.*\.[ao] /{s/| xargs rm -f/! -path ".\/target*\/*" | xargs rm -f/'$'\n}' -e '/^distclean:/a\'$'\n\t''rm -rf target/ target_mockgen/' Makefile] DDTRACE_GEN_GLOBAL_MAKEFILE_WRAP ]) ]) @@ -324,7 +329,7 @@ EOT ddtrace_rust_lib="\$(builddir)/target/$ddtrace_cargo_profile/libddtrace_php.a" cat <> Makefile.fragments -$ddtrace_rust_lib: $( (find "$ext_srcdir/components-rs" -name "*.rs" -o -name "Cargo.toml"; find "$ext_srcdir/../../libdatadog" -name "*.rs" -not -path "*/target/*"; find "$ext_srcdir/libdatadog" -name "*.rs" -not -path "*/target/*") 2>/dev/null | xargs ) +$ddtrace_rust_lib: $( (find "$ext_srcdir/components-rs" -name "*.rs" -o -name "Cargo.toml"; find "$ext_srcdir/../../libdatadog" -name "*.rs" -not -path "*/target/*"; find "$ext_srcdir/libdatadog" -name "*.rs" -not -path "*/target/*") 2>/dev/null | tr '\n' ' ' ) (cd "$ext_srcdir"; CARGO_TARGET_DIR=\$(builddir)/target/ SHARED=$(test "$ext_shared" = "yes" && echo 1) PROFILE="$ddtrace_cargo_profile" host_os="$host_os" DDTRACE_CARGO=\$(DDTRACE_CARGO) $(if test "$PHP_DDTRACE_SANITIZE" != "no"; then echo COMPILE_ASAN=1; fi) sh ./compile_rust.sh \$(shell echo "\$(MAKEFLAGS)" | $EGREP -o "[[-]]j[[0-9]]+")) EOT fi @@ -336,7 +341,7 @@ EOT if test "$PHP_DDTRACE_SIDECAR_MOCKGEN" != "-"; then ddtrace_mockgen_invocation="HOST= TARGET= $PHP_DDTRACE_SIDECAR_MOCKGEN" else - ddtrace_mockgen_invocation="cd \"$ext_srcdir/components-rs/php_sidecar_mockgen\"; HOST= TARGET= CARGO_TARGET_DIR=\$(builddir)/target/ \$(DDTRACE_CARGO) run" + ddtrace_mockgen_invocation="cd \"$ext_srcdir/components-rs/php_sidecar_mockgen\"; HOST= TARGET= CARGO_TARGET_DIR=\$(builddir)/target_mockgen/ \$(DDTRACE_CARGO) run" fi cat <> Makefile.fragments diff --git a/config.w32 b/config.w32 index 7aa9fb102a..9f37c9ef8f 100644 --- a/config.w32 +++ b/config.w32 @@ -15,7 +15,40 @@ if (PHP_DDTRACE != 'no') { var version = PHP_VERSION * 100 + PHP_MINOR_VERSION; - var DDTRACE_EXT_SOURCES = "arrays.c auto_flush.c autoload_php_files.c compat_string.c configuration.c distributed_tracing_headers.c ddshared.c dogstatsd.c engine_api.c engine_hooks.c excluded_modules.c git.c handlers_api.c handlers_curl" + (version < 800 ? "_php7" : "") + ".c handlers_exception.c handlers_internal.c handlers_pcntl.c ip_extraction.c logging.c memory_limit.c otel_config.c profiling.c random.c serializer.c sidecar.c span.c startup_logging.c telemetry.c user_request.c"; + var DDTRACE_EXT_SOURCES = "arrays.c"; + DDTRACE_EXT_SOURCES += " auto_flush.c"; + DDTRACE_EXT_SOURCES += " autoload_php_files.c"; + DDTRACE_EXT_SOURCES += " collect_backtrace.c"; + DDTRACE_EXT_SOURCES += " compat_string.c"; + DDTRACE_EXT_SOURCES += " configuration.c"; + DDTRACE_EXT_SOURCES += " distributed_tracing_headers.c"; + DDTRACE_EXT_SOURCES += " ddshared.c"; + DDTRACE_EXT_SOURCES += " dogstatsd.c"; + DDTRACE_EXT_SOURCES += " engine_api.c"; + DDTRACE_EXT_SOURCES += " engine_hooks.c"; + DDTRACE_EXT_SOURCES += " exception_serialize.c"; + DDTRACE_EXT_SOURCES += " excluded_modules.c"; + DDTRACE_EXT_SOURCES += " git.c"; + DDTRACE_EXT_SOURCES += " handlers_api.c"; + DDTRACE_EXT_SOURCES += " handlers_curl" + (version < 800 ? "_php7" : "") + ".c"; + DDTRACE_EXT_SOURCES += " handlers_exception.c"; + DDTRACE_EXT_SOURCES += " handlers_internal.c"; + DDTRACE_EXT_SOURCES += " handlers_pcntl.c"; + DDTRACE_EXT_SOURCES += " ip_extraction.c"; + DDTRACE_EXT_SOURCES += " live_debugger.c"; + DDTRACE_EXT_SOURCES += " logging.c"; + DDTRACE_EXT_SOURCES += " memory_limit.c"; + DDTRACE_EXT_SOURCES += " otel_config.c"; + DDTRACE_EXT_SOURCES += " profiling.c"; + DDTRACE_EXT_SOURCES += " random.c"; + DDTRACE_EXT_SOURCES += " remote_config.c"; + DDTRACE_EXT_SOURCES += " serializer.c"; + DDTRACE_EXT_SOURCES += " sidecar.c"; + DDTRACE_EXT_SOURCES += " span.c"; + DDTRACE_EXT_SOURCES += " startup_logging.c"; + DDTRACE_EXT_SOURCES += " telemetry.c"; + DDTRACE_EXT_SOURCES += " threads.c"; + DDTRACE_EXT_SOURCES += " user_request.c"; if (version >= 800 && version < 802) { DDTRACE_EXT_SOURCES += " weakrefs.c"; } diff --git a/datadog-setup.php b/datadog-setup.php index ce066b5933..e1c89dda27 100644 --- a/datadog-setup.php +++ b/datadog-setup.php @@ -468,7 +468,7 @@ function install($options) $tmpArchiveRoot = $tmpDir . '/dd-library-php'; $tmpArchiveTraceRoot = $tmpDir . '/dd-library-php/trace'; $tmpArchiveAppsecRoot = $tmpDir . '/dd-library-php/appsec'; - $tmpArchiveAppsecBin = "{$tmpArchiveAppsecRoot}/bin"; + $tmpArchiveAppsecLib = "{$tmpArchiveAppsecRoot}/lib"; $tmpArchiveAppsecEtc = "{$tmpArchiveAppsecRoot}/etc"; $tmpArchiveProfilingRoot = $tmpDir . '/dd-library-php/profiling'; $tmpSrcDir = $tmpArchiveTraceRoot . '/src'; @@ -533,8 +533,8 @@ function install($options) // Appsec helper and rules if (file_exists($tmpArchiveAppsecRoot)) { execute_or_exit( - "Cannot copy files from '$tmpArchiveAppsecBin' to '$installDir'", - (IS_WINDOWS ? "xcopy /s /e /y /g /b /o /h " : "cp -rf ") . escapeshellarg("$tmpArchiveAppsecBin") . ' ' . escapeshellarg($installDir) + "Cannot copy files from '$tmpArchiveAppsecLib' to '$installDir'", + (IS_WINDOWS ? "xcopy /s /e /y /g /b /o /h " : "cp -rf ") . escapeshellarg("$tmpArchiveAppsecLib") . ' ' . escapeshellarg($installDir) ); execute_or_exit( "Cannot copy files from '$tmpArchiveAppsecEtc' to '$installDir'", @@ -617,7 +617,7 @@ function install($options) $appsecExtensionDestination = $extDir . '/' . EXTENSION_PREFIX . 'ddappsec.' . EXTENSION_SUFFIX; safe_copy_extension($appsecExtensionRealPath, $appsecExtensionDestination); } - $appSecHelperPath = $installDir . '/bin/ddappsec-helper'; + $appSecHelperPath = $installDir . '/lib/libddappsec-helper.so'; if (isset($options[OPT_PHP_INI])) { $iniFilePaths = $options[OPT_PHP_INI]; @@ -1101,8 +1101,13 @@ function check_library_prerequisite_or_exit($requiredLibrary) */ function check_php_ext_prerequisite_or_exit($binary, $extName) { - $extensions = shell_exec("$binary -m"); + $extensions = shell_exec(escapeshellarg($binary) . " -m"); + // See: https://github.com/DataDog/dd-trace-php/issues/2787 + if ($extensions === null || $extensions === false || strpos($extensions, '[PHP Modules]') === false) { + echo "WARNING: The output of '$binary -m' could not be reliably checked. Please make sure you have the PHP extension '$extName' installed.\n"; + return; + } if (!in_array($extName, array_map("trim", explode("\n", $extensions)))) { print_error_and_exit("Required PHP extension '$extName' not found.\n"); } @@ -1574,7 +1579,7 @@ function ini_values($binary) if ($found[EXTENSION_DIR] == "") { $found[EXTENSION_DIR] = dirname(PHP_BINARY); - } elseif ($found[EXTENSION_DIR][0] != "/" && (!IS_WINDOWS || !preg_match('~^([A-Z]:|\\\\)\\\\~i', $found[EXTENSION_DIR]))) { + } elseif ($found[EXTENSION_DIR][0] != "/" && (!IS_WINDOWS || !preg_match('~^([A-Z]:[\\\\/]|\\\\{2})~i', $found[EXTENSION_DIR]))) { $found[EXTENSION_DIR] = dirname(PHP_BINARY) . '/' . $found[EXTENSION_DIR]; } @@ -1789,7 +1794,7 @@ function add_missing_ini_settings($iniFilePath, $settings, $replacements) // right extension setting is available. $settingRegex = '(' . preg_quote($setting['name']) . '\s?=\s?'; if ($setting['name'] === 'extension' || $setting['name'] == 'zend_extension') { - $settingRegex .= preg_quote($setting['default']); + $settingRegex .= ".*".preg_quote($setting['default']); } $settingRegex .= ')'; @@ -2255,49 +2260,21 @@ function get_ini_settings($sourcesDir, $appsecHelperPath, $appsecRulesPath) 'arbitrary file name to which the messages will be appended', ], ], - [ - 'name' => 'datadog.appsec.helper_launch', - 'default' => 'On', - 'commented' => true, - 'description' => [ - 'The dd-appsec extension communicates with a helper process via UNIX sockets.', - 'This setting determines whether the extension should try to launch the daemon', - 'in case it cannot obtain a connection.', - 'If this is disabled, the helper should be launched through some other method.', - 'The extension expects the helper to run under the same user as the process', - 'where PHP is running, and will verify it.', - ], - ], [ 'name' => 'datadog.appsec.helper_path', 'default' => $appsecHelperPath, 'commented' => false, 'description' => [ - 'If ddappsec.helper_launch is enabled, this setting determines which binary', - 'the extension should try to execute.', - 'Only relevant if ddappsec.helper_launch is enabled.', + 'The path to the shared library that the appsec extension loads in the sidecar.', 'This ini setting is configured by the installer', ], ], - [ - 'name' => 'datadog.appsec.helper_extra_args', - 'default' => '', - 'commented' => true, - 'description' => [ - 'Additional arguments that should be used when attempting to launch the helper', - 'process. The extension always passes \'--lock_path - --socket_path fd:\'', - 'The arguments should be space separated. Both single and double quotes can', - 'be used should an argument contain spaces. The backslash (\) can be used to', - 'escape spaces, quotes, and the backslash itself.', - 'Only relevant if ddappsec.helper_launch is enabled', - ], - ], [ 'name' => 'datadog.appsec.rules', 'default' => $appsecRulesPath, 'commented' => true, 'description' => [ - 'The path to the rules json file. The helper process must be able to read the', + 'The path to the rules json file. The sidecar process must be able to read the', 'file. This ini setting is configured by the installer', ], ], @@ -2306,10 +2283,9 @@ function get_ini_settings($sourcesDir, $appsecHelperPath, $appsecRulesPath) 'default' => '/tmp/', 'commented' => true, 'description' => [ - 'The location to the UNIX socket that extension uses to communicate with the', - 'helper and the lock file that the extension processes will use to', - 'synchronize the launching of the helper.', - 'Only relevant if datadog.appsec.helper_launch is enabled', + 'The directory where to place the lock file and the UNIX socket that the', + 'extension uses communicate with the helper inside sidecar. Ultimately,', + 'the paths include the version of the extension and uid/gid.', ], ], [ @@ -2317,11 +2293,17 @@ function get_ini_settings($sourcesDir, $appsecHelperPath, $appsecRulesPath) 'default' => '/dev/null', 'commented' => true, 'description' => [ - 'The location of the log file of the helper. This default to /dev/null (the log', - 'messages will be discarded). This file is opened by the extension just before', - 'launching the daemon and the file descriptor is passed to the helper as its', - 'stderr, to which it will write its messages; this setting is therefore only', - 'relevant if ddappsec.helper_launch is enabled', + 'The location of the log file of the helper. This defaults to /dev/null', + '(the log messages will be discarded).', + ], + ], + [ + 'name' => 'datadog.appsec.helper_log_level', + 'default' => 'info', + 'commented' => true, + 'description' => [ + 'The verbosity of the logging of the appsec helper loaded in the sidecar. ', + 'Valid values are trace, debug, info, warn, err, critical and off', ], ], [ diff --git a/ddtrace.sym b/ddtrace.sym index 4777cd0f4f..d20803d5d3 100644 --- a/ddtrace.sym +++ b/ddtrace.sym @@ -5,5 +5,13 @@ ddtrace_set_priority_sampling_on_span_zobj ddtrace_runtime_id ddtrace_user_req_add_listeners ddtrace_ip_extraction_find +ddtrace_set_all_thread_vm_interrupt +ddtrace_remote_config_get_path +ddog_remote_config_reader_for_path +ddog_remote_config_read +ddog_remote_config_reader_drop get_module ddog_daemon_entry_point +ddog_set_rc_notify_fn +ddog_remote_config_path +ddog_remote_config_path_free diff --git a/docker-compose.yml b/docker-compose.yml index 7f7d7c72d5..9375dcfb9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,9 +13,10 @@ x-aliases: - .scenarios.lock:/home/circleci/app/.scenarios.lock - agent-socket:/var/run/datadog - .cargo-registry:/rust/cargo/registry - tmpfs: [ '/home/circleci/app/tmp:uid=3434,gid=3434,exec', '/home/circleci/app/tests/vendor:uid=3434,gid=3434,exec' ] + tmpfs: [ '/home/circleci/app/tmp:uid=3434,gid=3434,exec,size=12G' ] depends_on: - agent + #- staging_agent - ddagent_integration - request-replayer - test-agent @@ -83,6 +84,7 @@ services: '8.3-buster': { <<: *linux_php_service, image: 'datadog/dd-trace-ci:php-8.3_buster' } 'php-master-buster': { <<: *linux_php_service, image: 'datadog/dd-trace-ci:php-master_buster' } # --- Bookworm --- + '8.2-bookworm': { <<: *linux_php_service, image: 'datadog/dd-trace-ci:php-8.2_bookworm-3' } '8.3-bookworm': { <<: *linux_php_service, image: 'datadog/dd-trace-ci:php-8.3_bookworm-3' } # --- CentOS 6 --- '7.0-centos7': { <<: *linux_php_service, image: 'datadog/dd-trace-ci:php-7.0_centos-7' } @@ -115,6 +117,8 @@ services: command: --default-authentication-plugin=mysql_native_password --sql-mode="NO_ENGINE_SUBSTITUTION" ports: - "3306:3306" + volumes: + - ./dockerfiles/services/mysql:/docker-entrypoint-initdb.d environment: - MYSQL_ROOT_PASSWORD=test - MYSQL_PASSWORD=test @@ -197,9 +201,29 @@ services: - DD_APM_ENABLED=true - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=1 - DD_APM_RECEIVER_SOCKET=/var/run/datadog/apm.socket + - DD_REMOTE_CONFIGURATION_ENABLED=true ports: - "8126:8126" + staging_agent: + image: datadog/agent:latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /proc/:/host/proc/:ro + - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro + - agent-socket:/var/run/datadog + environment: + - DD_API_KEY=${DATADOG_STAGING_API_KEY} + - DD_SITE=datad0g.com + - DD_APM_ENABLED=true + - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=1 + - DD_APM_RECEIVER_SOCKET=/var/run/datadog/apm.socket + - DD_REMOTE_CONFIGURATION_ENABLED=true + - DD_REMOTE_CONFIGURATION_REFRESH_INTERVAL=5s + - DD_APM_RECEIVER_PORT=8125 + ports: + - "8125:8125" + packager: image: datadog/docker-library:ddtrace_php_fpm_packaging working_dir: /app @@ -222,7 +246,7 @@ services: - DD_DISABLE_ERROR_RESPONSES=true - SNAPSHOTS_DIR=/snapshots - SNAPSHOT_CI=0 - - SNAPSHOT_REMOVED_ATTRS=start,duration,metrics.php.compilation.total_time_ms,metrics.process_id + - SNAPSHOT_REMOVED_ATTRS=start,duration,metrics.php.compilation.total_time_ms,metrics.php.memory.peak_usage_bytes,metrics.php.memory.peak_real_usage_bytes,metrics.process_id - ENABLED_CHECKS=trace_stall,trace_peer_service,trace_dd_service diff --git a/dockerfiles/ci/alpine_compile_extension/base.Dockerfile b/dockerfiles/ci/alpine_compile_extension/base.Dockerfile index e70853e019..6fbaba7e5c 100644 --- a/dockerfiles/ci/alpine_compile_extension/base.Dockerfile +++ b/dockerfiles/ci/alpine_compile_extension/base.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.18 +FROM alpine:3.19 RUN mkdir -p /app WORKDIR /app @@ -13,6 +13,7 @@ RUN set -eux; \ g++ \ gcc \ make \ + cmake \ build-base \ curl-dev \ libedit-dev \ @@ -22,7 +23,8 @@ RUN set -eux; \ libxml2-dev \ gnu-libiconv-dev \ oniguruma-dev \ - tar + tar \ + cmake # Profiling deps # Minimum: libclang. Nice-to-have: full toolchain including linker to play @@ -30,4 +32,6 @@ RUN set -eux; \ # version. RUN apk add --no-cache llvm16-libs clang16-dev lld llvm16 rust-stdlib cargo clang git protoc unzip +RUN cargo install --force --locked bindgen-cli && mv /root/.cargo/bin/bindgen /usr/local/bin/ && rm -rf /root/.cargo + CMD ["bash"] diff --git a/dockerfiles/ci/alpine_compile_extension/env-init b/dockerfiles/ci/alpine_compile_extension/env-init index 09d0fe509a..b6b2bde7bd 100755 --- a/dockerfiles/ci/alpine_compile_extension/env-init +++ b/dockerfiles/ci/alpine_compile_extension/env-init @@ -1,16 +1,17 @@ #!/bin/sh switch_php() { - phpType=$1 + phpType=${1#*-} - if [ ${phpType} != 'zts' ] && [ ${phpType} != 'nts' ]; then - echo "Invalid PHP version. Valid versions are: 'nts' and 'zts-" + if [ "${phpType}" != 'zts' ] && [ "${phpType}" != 'nts' ] && [ "${phpType#*.}" = "${phpType}" ]; then + echo "Invalid PHP version. Valid versions are: 'nts' and 'zts'" return 1 fi - if [ ${phpType} = 'zts' ]; then + if [ "${phpType}" = 'zts' ]; then export PATH="$(echo "${PATH}" | sed 's/\(\/usr\/local\/php-[^:]*\)\(-zts\|\)\/bin/\1-zts\/bin/')" else - export PATH="$(echo "${PATH}" | sed 's/\(\/usr\/local\/php-[^:]*\)\(-zts\|\)\/bin/\1\/bin/')" + export PATH="$(echo "${PATH}" | sed 's/\(\/usr\/local\/php-[^:-]*\)\(-zts\|\)\/bin/\1\/bin/')" fi -} \ No newline at end of file +} +alias switch-php=switch_php diff --git a/dockerfiles/ci/bookworm/Dockerfile b/dockerfiles/ci/bookworm/Dockerfile index 3a5f6a91b8..2b02b0fc41 100644 --- a/dockerfiles/ci/bookworm/Dockerfile +++ b/dockerfiles/ci/bookworm/Dockerfile @@ -9,10 +9,6 @@ ENV ACCEPT_EULA=Y # with cross-language link-time optimization. Needs to match rustc -Vv's llvm # version. ENV DEVLIBS \ - catch2 \ - clang-16 \ - cmake \ - lcov \ libclang-16-dev \ llvm-16-dev \ lld-16 \ @@ -24,6 +20,7 @@ ENV DEVLIBS \ libjpeg-dev \ libmcrypt-dev \ libmemcached-dev \ + libodbc1 \ libonig-dev \ libpq-dev \ libpng-dev \ @@ -34,13 +31,12 @@ ENV DEVLIBS \ libxml2-dev \ libxslt1-dev \ libzip-dev \ - lsof \ + odbcinst1debian2 \ zlib1g-dev \ libasan6 \ gnupg \ unixodbc-dev \ - unixodbc \ - valgrind + unixodbc #netcat ENV RUNTIME_DEPS \ @@ -52,6 +48,7 @@ ENV RUNTIME_DEPS \ gdb \ git \ less \ + lsof \ netbase \ nginx \ strace \ @@ -65,10 +62,14 @@ ENV RUNTIME_DEPS \ ENV PHPIZE_DEPS \ autoconf \ bison \ + catch2 \ + clang-16 \ + cmake \ dpkg-dev \ g++ \ gcc \ file \ + lcov \ libc-dev \ make \ pkg-config \ diff --git a/dockerfiles/ci/buster/Dockerfile b/dockerfiles/ci/buster/Dockerfile index 9c70530fa4..ea2af3d9db 100644 --- a/dockerfiles/ci/buster/Dockerfile +++ b/dockerfiles/ci/buster/Dockerfile @@ -9,7 +9,6 @@ ENV ACCEPT_EULA=Y # with cross-language link-time optimization. Needs to match rustc -Vv's llvm # version. ENV DEVLIBS \ - clang-16 \ libclang-16-dev \ libclang-rt-16-dev \ llvm-16-dev \ @@ -23,6 +22,7 @@ ENV DEVLIBS \ libjpeg-dev \ libmcrypt-dev \ libmemcached-dev \ + libodbc1 \ libonig-dev \ libpq-dev \ libpng-dev \ @@ -33,24 +33,24 @@ ENV DEVLIBS \ libxml2-dev \ libxslt1-dev \ libzip-dev \ + odbcinst1debian2 \ zlib1g-dev \ libasan5 \ - gnupg \ - gpg \ unixodbc-dev \ unixodbc +#netcat ENV RUNTIME_DEPS \ apache2 \ apache2-dev \ ca-certificates \ - clang \ clang-format \ curl \ debian-goodies \ gdb \ git \ less \ + lsof \ netbase \ netcat \ nginx \ @@ -65,10 +65,16 @@ ENV RUNTIME_DEPS \ ENV PHPIZE_DEPS \ autoconf \ bison \ + clang-16 \ + cmake \ dpkg-dev \ file \ g++ \ gcc \ + gnupg \ + gpg \ + file \ + lcov \ libc-dev \ make \ pkg-config \ @@ -261,9 +267,9 @@ RUN set -eux; \ chown -R circleci:circleci /opt; # rust sha256sum generated locally after verifying it with sha256 -ARG RUST_VERSION="1.71.1" -ARG RUST_SHA256_ARM="c7cf230c740a62ea1ca6a4304d955c286aea44e3c6fc960b986a8c2eeea4ec3f" -ARG RUST_SHA256_X86="34778d1cda674990dfc0537bc600066046ae9cb5d65a07809f7e7da31d4689c4" +ARG RUST_VERSION="1.76.0" +ARG RUST_SHA256_ARM="2e8313421e8fb673efdf356cdfdd4bc16516f2610d4f6faa01327983104c05a0" +ARG RUST_SHA256_X86="9d589d2036b503cc45ecc94992d616fb3deec074deb36cacc2f5c212408f7399" # Mount a cache into /rust/cargo if you want to pre-fetch packages or something ENV CARGO_HOME=/rust/cargo ENV RUSTUP_HOME=/rust/rustup diff --git a/dockerfiles/ci/buster/build-extensions.sh b/dockerfiles/ci/buster/build-extensions.sh index 670705aa8b..3fe52c7961 100755 --- a/dockerfiles/ci/buster/build-extensions.sh +++ b/dockerfiles/ci/buster/build-extensions.sh @@ -5,6 +5,7 @@ set -eux SHARED_BUILD=$(if php -i | grep -q enable-pcntl=shared; then echo 1; else echo 0; fi) PHP_VERSION_ID=$(php -r 'echo PHP_MAJOR_VERSION . PHP_MINOR_VERSION;') PHP_ZTS=$(php -r 'echo PHP_ZTS;') +EXTENSION_DIR=$(php-config --extension-dir) XDEBUG_VERSIONS=(-3.1.2) if [[ $PHP_VERSION_ID -le 70 ]]; then @@ -81,14 +82,14 @@ if [[ $SHARED_BUILD -ne 0 ]]; then phpize ./configure make - mv ./modules/*.so $(php-config --extension-dir) + mv ./modules/*.so $EXTENSION_DIR make clean for curlVer in ${CURL_VERSIONS}; do PKG_CONFIG_PATH=/opt/curl/${curlVer}/lib/pkgconfig/ ./configure make - mv ./modules/curl.so $(php-config --extension-dir)/curl-${curlVer}.so + mv ./modules/curl.so $EXTENSION_DIR/curl-${curlVer}.so make clean done phpize --clean @@ -114,8 +115,6 @@ else yes 'no' | pecl install memcached; echo "extension=memcached.so" >> ${iniDir}/memcached.ini; yes '' | pecl install memcache$MEMCACHE_VERSION; echo "extension=memcache.so" >> ${iniDir}/memcache.ini; pecl install mongodb$MONGODB_VERSION; echo "extension=mongodb.so" >> ${iniDir}/mongodb.ini; - # Redis 6.0.0 dropped support for PHP 7.1 and below - pecl install redis$(if [[ $PHP_VERSION_ID -le 71 ]]; then echo -5.3.7; fi); echo "extension=redis.so" >> ${iniDir}/redis.ini; pecl install sqlsrv$SQLSRV_VERSION; echo "extension=sqlsrv.so" >> ${iniDir}/sqlsrv.ini; # Xdebug is disabled by default for VERSION in "${XDEBUG_VERSIONS[@]}"; do @@ -125,9 +124,38 @@ else done echo "zend_extension=opcache.so" >> ${iniDir}/../php-apache2handler.ini; - # ext-parallel needs PHP 8 + # ext-parallel needs PHP 8 ZTS if [[ $PHP_VERSION_ID -ge 80 && $PHP_ZTS -eq 1 ]]; then pecl install parallel; echo "extension=parallel" >> ${iniDir}/parallel.ini; fi + + # ext-swoole needs PHP 8 + if [[ $PHP_VERSION_ID -ge 80 ]]; then + pushd /tmp + pecl download swoole-5.1.2; # we don't install swoole here + tar xzf swoole-5.1.2.tgz + cd swoole-5.1.2 + phpize + ./configure --host=$HOST_ARCH-linux-gnu + make install + popd + fi + + # We don't install any redis.so to inis, but allow selection at runtime. + if [[ $PHP_VERSION_ID -lt 80 ]]; then + pecl install redis-3.1.6 + mv $EXTENSION_DIR/redis.so $EXTENSION_DIR/redis-3.1.6.so + pecl install redis-4.3.0 + mv $EXTENSION_DIR/redis.so $EXTENSION_DIR/redis-4.3.0.so + fi + pecl install redis-5.3.7 + + # Redis 6.0.0 dropped support for PHP 7.1 and below + if [[ $PHP_VERSION_ID -gt 71 ]]; then + mv $EXTENSION_DIR/redis.so $EXTENSION_DIR/redis-5.3.7.so + pecl install redis + else + ln -s $EXTENSION_DIR/redis.so $EXTENSION_DIR/redis-5.3.7.so + fi fi diff --git a/dockerfiles/ci/buster/php-8.0/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch b/dockerfiles/ci/buster/php-8.0/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch new file mode 100644 index 0000000000..13532cd62c --- /dev/null +++ b/dockerfiles/ci/buster/php-8.0/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch @@ -0,0 +1,27 @@ +From 79314c03bca9b9aff892b3cc1a099b6ae47776fe Mon Sep 17 00:00:00 2001 +From: Bob Weinand +Date: Wed, 7 Aug 2024 22:53:39 +0200 +Subject: [PATCH] Introduce DD_IGNORE_ARGINFO_ZPP_CHECK env var to skip these + assertions in testing + +See e.g. https://github.com/phpredis/phpredis/issues/1869, where phpredis 5 fails these assertions, and it likely will never be fixed there +This allows a simpler approach than having an extra nts-runner just for that test. +--- + Zend/zend_execute.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c +index d5e35898f9..e4df9fa149 100644 +--- a/Zend/zend_execute.c ++++ b/Zend/zend_execute.c +@@ -1142,6 +1142,7 @@ static zend_always_inline zend_bool zend_internal_call_should_throw(zend_functio + + static ZEND_COLD void zend_internal_call_arginfo_violation(zend_function *fbc) + { ++ if (getenv("DD_IGNORE_ARGINFO_ZPP_CHECK")) return; + zend_error(E_ERROR, "Arginfo / zpp mismatch during call of %s%s%s()", + fbc->common.scope ? ZSTR_VAL(fbc->common.scope->name) : "", + fbc->common.scope ? "::" : "", +-- +2.41.0 + diff --git a/dockerfiles/ci/buster/php-8.0/Dockerfile b/dockerfiles/ci/buster/php-8.0/Dockerfile index 736c4b8d9b..db8142a801 100644 --- a/dockerfiles/ci/buster/php-8.0/Dockerfile +++ b/dockerfiles/ci/buster/php-8.0/Dockerfile @@ -3,6 +3,7 @@ ARG phpTarGzUrl ARG phpSha256Hash COPY 0001-Better-support-for-cross-compilation.patch /home/circleci COPY 0001-Fix-GH-10611-fpm_env_init_main-leaks-environ.patch /home/circleci +COPY php-8.0/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch /home/circleci RUN set -eux; \ curl -fsSL -o /tmp/php.tar.gz "${phpTarGzUrl}"; \ (echo "${phpSha256Hash} /tmp/php.tar.gz" | sha256sum -c -); \ @@ -10,6 +11,7 @@ RUN set -eux; \ rm -f /tmp/php.tar.gz; \ cd ${PHP_SRC_DIR}; \ git apply /home/circleci/0001-Better-support-for-cross-compilation.patch; \ + git apply /home/circleci/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch; \ patch -p1 /home/circleci/0001-Fix-GH-10611-fpm_env_init_main-leaks-environ.patch; \ ./buildconf --force diff --git a/dockerfiles/ci/buster/php-8.1/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch b/dockerfiles/ci/buster/php-8.1/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch new file mode 100644 index 0000000000..00c13009f4 --- /dev/null +++ b/dockerfiles/ci/buster/php-8.1/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch @@ -0,0 +1,27 @@ +From 90936c193e6c95c1b7acf926434ab2e2adca06a4 Mon Sep 17 00:00:00 2001 +From: Bob Weinand +Date: Wed, 7 Aug 2024 22:53:39 +0200 +Subject: [PATCH] Introduce DD_IGNORE_ARGINFO_ZPP_CHECK env var to skip these + assertions in testing + +See e.g. https://github.com/phpredis/phpredis/issues/1869, where phpredis 5 fails these assertions, and it likely will never be fixed there +This allows a simpler approach than having an extra nts-runner just for that test. +--- + Zend/zend_execute.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c +index 5c9a59bb95..8e3c9d0ca7 100644 +--- a/Zend/zend_execute.c ++++ b/Zend/zend_execute.c +@@ -1196,6 +1196,7 @@ ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_d + + ZEND_API ZEND_COLD void zend_internal_call_arginfo_violation(zend_function *fbc) + { ++ if (getenv("DD_IGNORE_ARGINFO_ZPP_CHECK")) return; + zend_error(E_ERROR, "Arginfo / zpp mismatch during call of %s%s%s()", + fbc->common.scope ? ZSTR_VAL(fbc->common.scope->name) : "", + fbc->common.scope ? "::" : "", +-- +2.41.0 + diff --git a/dockerfiles/ci/buster/php-8.1/Dockerfile b/dockerfiles/ci/buster/php-8.1/Dockerfile index dc55cb9091..e2bab90455 100644 --- a/dockerfiles/ci/buster/php-8.1/Dockerfile +++ b/dockerfiles/ci/buster/php-8.1/Dockerfile @@ -6,12 +6,14 @@ ENV PHP_VERSION=${phpVersion} FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster as src ARG phpTarGzUrl ARG phpSha256Hash +COPY php-8.1/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch /home/circleci RUN set -eux; \ curl -fsSL -o /tmp/php.tar.gz "${phpTarGzUrl}"; \ (echo "${phpSha256Hash} /tmp/php.tar.gz" | sha256sum -c -); \ tar xf /tmp/php.tar.gz -C "${PHP_SRC_DIR}" --strip-components=1; \ rm -f /tmp/php.tar.gz; \ cd ${PHP_SRC_DIR}; \ + git apply /home/circleci/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch; \ ./buildconf --force; FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster AS build diff --git a/dockerfiles/ci/buster/php-8.2/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch b/dockerfiles/ci/buster/php-8.2/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch new file mode 100644 index 0000000000..fa60fbf5fa --- /dev/null +++ b/dockerfiles/ci/buster/php-8.2/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch @@ -0,0 +1,27 @@ +From 1931d38a5e9234bebae83fa73d8e99a187e05dfb Mon Sep 17 00:00:00 2001 +From: Bob Weinand +Date: Wed, 7 Aug 2024 22:53:39 +0200 +Subject: [PATCH] Introduce DD_IGNORE_ARGINFO_ZPP_CHECK env var to skip these + assertions in testing + +See e.g. https://github.com/phpredis/phpredis/issues/1869, where phpredis 5 fails these assertions, and it likely will never be fixed there +This allows a simpler approach than having an extra nts-runner just for that test. +--- + Zend/zend_execute.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c +index 3de48fb135..6e5a4062e1 100644 +--- a/Zend/zend_execute.c ++++ b/Zend/zend_execute.c +@@ -1237,6 +1237,7 @@ ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_d + + ZEND_API ZEND_COLD void zend_internal_call_arginfo_violation(zend_function *fbc) + { ++ if (getenv("DD_IGNORE_ARGINFO_ZPP_CHECK")) return; + zend_error(E_ERROR, "Arginfo / zpp mismatch during call of %s%s%s()", + fbc->common.scope ? ZSTR_VAL(fbc->common.scope->name) : "", + fbc->common.scope ? "::" : "", +-- +2.41.0 + diff --git a/dockerfiles/ci/buster/php-8.2/Dockerfile b/dockerfiles/ci/buster/php-8.2/Dockerfile index 1ade036ba8..7fbb88ba7c 100644 --- a/dockerfiles/ci/buster/php-8.2/Dockerfile +++ b/dockerfiles/ci/buster/php-8.2/Dockerfile @@ -7,6 +7,7 @@ FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster as src ARG phpTarGzUrl ARG phpSha256Hash COPY 0001-Delete-timers-on-fork.patch /home/circleci +COPY php-8.2/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch /home/circleci RUN set -eux; \ curl -fsSL -o /tmp/php.tar.gz "${phpTarGzUrl}"; \ (echo "${phpSha256Hash} /tmp/php.tar.gz" | sha256sum -c -); \ @@ -14,6 +15,7 @@ RUN set -eux; \ rm -f /tmp/php.tar.gz; \ cd ${PHP_SRC_DIR}; \ ./buildconf --force; \ + git apply /home/circleci/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch; \ patch Zend/zend_max_execution_timer.c /home/circleci/0001-Delete-timers-on-fork.patch; FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster AS build diff --git a/dockerfiles/ci/buster/php-8.3/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch b/dockerfiles/ci/buster/php-8.3/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch new file mode 100644 index 0000000000..d7d34ab937 --- /dev/null +++ b/dockerfiles/ci/buster/php-8.3/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch @@ -0,0 +1,27 @@ +From 16d2a319a36e90cf1bd3f0d3f3732c8035e81c02 Mon Sep 17 00:00:00 2001 +From: Bob Weinand +Date: Wed, 7 Aug 2024 22:53:39 +0200 +Subject: [PATCH] Introduce DD_IGNORE_ARGINFO_ZPP_CHECK env var to skip these + assertions in testing + +See e.g. https://github.com/phpredis/phpredis/issues/1869, where phpredis 5 fails these assertions, and it likely will never be fixed there +This allows a simpler approach than having an extra nts-runner just for that test. +--- + Zend/zend_execute.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c +index 8da2d37a25..869c547be0 100644 +--- a/Zend/zend_execute.c ++++ b/Zend/zend_execute.c +@@ -1297,6 +1297,7 @@ ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_d + + ZEND_API ZEND_COLD void zend_internal_call_arginfo_violation(zend_function *fbc) + { ++ if (getenv("DD_IGNORE_ARGINFO_ZPP_CHECK")) return; + zend_error(E_ERROR, "Arginfo / zpp mismatch during call of %s%s%s()", + fbc->common.scope ? ZSTR_VAL(fbc->common.scope->name) : "", + fbc->common.scope ? "::" : "", +-- +2.41.0 + diff --git a/dockerfiles/ci/buster/php-8.3/Dockerfile b/dockerfiles/ci/buster/php-8.3/Dockerfile index 5a14e63534..18e9f1fc77 100644 --- a/dockerfiles/ci/buster/php-8.3/Dockerfile +++ b/dockerfiles/ci/buster/php-8.3/Dockerfile @@ -9,12 +9,14 @@ COPY php-8.3/suppr.txt /home/circleci/suppr.txt FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster as src ARG phpTarGzUrl ARG phpSha256Hash +COPY php-8.3/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch /home/circleci RUN set -eux; \ curl -fsSL -o /tmp/php.tar.gz "${phpTarGzUrl}"; \ (echo "${phpSha256Hash} /tmp/php.tar.gz" | sha256sum -c -); \ tar xf /tmp/php.tar.gz -C "${PHP_SRC_DIR}" --strip-components=1; \ rm -f /tmp/php.tar.gz; \ cd ${PHP_SRC_DIR}; \ + git apply /home/circleci/0001-Introduce-DD_IGNORE_ARGINFO_ZPP_CHECK-env-var-to-ski.patch; \ ./buildconf --force; FROM --platform=$BUILDPLATFORM datadog/dd-trace-ci:buster AS build diff --git a/dockerfiles/ci/buster/php-8.3/suppr.txt b/dockerfiles/ci/buster/php-8.3/suppr.txt index cf16629467..94e010b06a 100644 --- a/dockerfiles/ci/buster/php-8.3/suppr.txt +++ b/dockerfiles/ci/buster/php-8.3/suppr.txt @@ -3,3 +3,4 @@ leak:timer_create leak:add_to_global leak:_dl_map_object_deps leak:__res_context_send +leak:_dl_make_tlsdesc_dynamic diff --git a/dockerfiles/ci/centos/7/base.Dockerfile b/dockerfiles/ci/centos/7/base.Dockerfile index 5463714668..80edfaa49b 100644 --- a/dockerfiles/ci/centos/7/base.Dockerfile +++ b/dockerfiles/ci/centos/7/base.Dockerfile @@ -160,9 +160,9 @@ RUN source scl_source enable devtoolset-7 \ && rm -fr "$FILENAME" "${FILENAME%.tar.gz}" "protobuf-${PROTOBUF_VERSION}" # rust sha256sum generated locally after verifying it with sha256 -ARG RUST_VERSION="1.71.1" -ARG RUST_SHA256_ARM="c7cf230c740a62ea1ca6a4304d955c286aea44e3c6fc960b986a8c2eeea4ec3f" -ARG RUST_SHA256_X86="34778d1cda674990dfc0537bc600066046ae9cb5d65a07809f7e7da31d4689c4" +ARG RUST_VERSION="1.76.0" +ARG RUST_SHA256_ARM="2e8313421e8fb673efdf356cdfdd4bc16516f2610d4f6faa01327983104c05a0" +ARG RUST_SHA256_X86="9d589d2036b503cc45ecc94992d616fb3deec074deb36cacc2f5c212408f7399" # Mount a cache into /rust/cargo if you want to pre-fetch packages or something ENV CARGO_HOME=/rust/cargo ENV RUSTUP_HOME=/rust/rustup @@ -183,7 +183,7 @@ RUN source scl_source enable devtoolset-7 \ # now install PHP specific dependencies RUN set -eux; \ - rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm; \ + yum install -y epel-release; \ yum update -y; \ yum install -y \ re2c \ diff --git a/dockerfiles/ci/windows/basetools.Dockerfile b/dockerfiles/ci/windows/basetools.Dockerfile index 028f5a3689..4765bcb4cb 100644 --- a/dockerfiles/ci/windows/basetools.Dockerfile +++ b/dockerfiles/ci/windows/basetools.Dockerfile @@ -1,6 +1,8 @@ ARG vsVersion FROM datadog/dd-trace-ci:windows-base-$vsVersion +RUN powershell.exe "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $Env:chocolateyVersion = '0.10.15'; $Env:chocolateyUseWindowsCompression = 'false'; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); ''" + # I really need some sane file editing utilities ADD https://ftp.nluug.nl/pub/vim/pc/vim90w32.zip /tmp/vim90w32.zip RUN powershell.exe Expand-Archive /tmp/vim90w32.zip /tmp @@ -18,6 +20,12 @@ RUN git config --global --add safe.directory C:/php-sdk ADD https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe /tmp/rustup-init.exe RUN /tmp/rustup-init.exe -y --default-toolchain=1.71.0 +RUN choco install -y cmake +RUN choco install -y nasm +RUN choco install -y llvm + +RUN powershell "[Environment]::SetEnvironmentVariable('PATH', $env:PATH + ';C:\Program Files\NASM;C:\Program Files\CMake\bin', 'Machine')" + # initial setup WORKDIR /php-sdk diff --git a/dockerfiles/ci/windows/vc14.Dockerfile b/dockerfiles/ci/windows/vc14.Dockerfile index 6570f45a95..1649c44441 100644 --- a/dockerfiles/ci/windows/vc14.Dockerfile +++ b/dockerfiles/ci/windows/vc14.Dockerfile @@ -1,4 +1,5 @@ FROM mcr.microsoft.com/windows/servercore:1809 -ADD https://aka.ms/vs/14/release/vs_buildtools.exe /tmp/vs_buildtools.exe +# https://aka.ms/vs/14/release/vs_buildtools.exe has been removed +ADD vs14_buildtools.exe /tmp/vs_buildtools.exe RUN /tmp/vs_buildtools.exe --quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.Net.Component.4.7.SDK --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.17763 diff --git a/dockerfiles/ci/windows/vs14_buildtools.exe b/dockerfiles/ci/windows/vs14_buildtools.exe new file mode 100644 index 0000000000..beda411485 Binary files /dev/null and b/dockerfiles/ci/windows/vs14_buildtools.exe differ diff --git a/dockerfiles/ci/xfail_tests/7.0.list b/dockerfiles/ci/xfail_tests/7.0.list index 82bcf86cc3..7947c6b2aa 100644 --- a/dockerfiles/ci/xfail_tests/7.0.list +++ b/dockerfiles/ci/xfail_tests/7.0.list @@ -159,7 +159,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_oo_002.phpt diff --git a/dockerfiles/ci/xfail_tests/7.1.list b/dockerfiles/ci/xfail_tests/7.1.list index f483e6af8f..ff77854962 100644 --- a/dockerfiles/ci/xfail_tests/7.1.list +++ b/dockerfiles/ci/xfail_tests/7.1.list @@ -171,7 +171,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_oo_002.phpt diff --git a/dockerfiles/ci/xfail_tests/7.2.list b/dockerfiles/ci/xfail_tests/7.2.list index a0f370f1e4..157761dd23 100644 --- a/dockerfiles/ci/xfail_tests/7.2.list +++ b/dockerfiles/ci/xfail_tests/7.2.list @@ -104,6 +104,7 @@ ext/date/tests/test-parse-from-format.phpt ext/fileinfo/tests/finfo_open_error.phpt ext/ftp/tests ext/gd/tests/bug70976.phpt +ext/intl/tests/bug60192-sort.phpt ext/json/tests/bug45791.phpt ext/json/tests/bug77843.phpt ext/json/tests/pass001.phpt @@ -156,7 +157,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_oo_002.phpt diff --git a/dockerfiles/ci/xfail_tests/7.3.list b/dockerfiles/ci/xfail_tests/7.3.list index 2bd57ab419..d5bb3e8e3d 100644 --- a/dockerfiles/ci/xfail_tests/7.3.list +++ b/dockerfiles/ci/xfail_tests/7.3.list @@ -52,6 +52,7 @@ Zend/tests/exception_during_property_assign_op.phpt Zend/tests/gc_037.phpt Zend/tests/gc_041.phpt Zend/tests/gc_042.phpt +Zend/tests/generators/exception_during_shutdown.phpt Zend/tests/generators/gc_with_yield_from.phpt Zend/tests/generators/generator_closure_with_this.phpt Zend/tests/generators/generator_with_nonscalar_keys.phpt @@ -111,6 +112,7 @@ ext/date/tests/test-parse-from-format.phpt ext/fileinfo/tests/finfo_open_error.phpt ext/ftp/tests ext/gd/tests/bug70976.phpt +ext/intl/tests/bug60192-sort.phpt ext/json/tests/bug45791.phpt ext/json/tests/bug77843.phpt ext/json/tests/json_decode_exceptions.phpt @@ -167,7 +169,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_oo_002.phpt diff --git a/dockerfiles/ci/xfail_tests/7.4.list b/dockerfiles/ci/xfail_tests/7.4.list index 8a77be4dee..436febec59 100644 --- a/dockerfiles/ci/xfail_tests/7.4.list +++ b/dockerfiles/ci/xfail_tests/7.4.list @@ -67,6 +67,7 @@ Zend/tests/gc_031.phpt Zend/tests/gc_037.phpt Zend/tests/gc_041.phpt Zend/tests/gc_042.phpt +Zend/tests/generators/exception_during_shutdown.phpt Zend/tests/generators/gc_with_yield_from.phpt Zend/tests/generators/generator_closure_with_this.phpt Zend/tests/generators/generator_with_nonscalar_keys.phpt @@ -160,9 +161,11 @@ ext/date/tests/date_diff1.phpt ext/date/tests/date_time_fractions_serialize.phpt ext/date/tests/date_timestamp_set_nullparam2.phpt ext/date/tests/test-parse-from-format.phpt +ext/dom/tests/dom003.phpt ext/fileinfo/tests/finfo_open_error.phpt ext/ftp/tests ext/gd/tests/bug70976.phpt +ext/intl/tests/bug60192-sort.phpt ext/json/tests/bug45791.phpt ext/json/tests/bug77843.phpt ext/json/tests/json_decode_exceptions.phpt @@ -224,7 +227,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_oo_002.phpt diff --git a/dockerfiles/ci/xfail_tests/8.0.list b/dockerfiles/ci/xfail_tests/8.0.list index 2b8f11168a..5f1266cd62 100644 --- a/dockerfiles/ci/xfail_tests/8.0.list +++ b/dockerfiles/ci/xfail_tests/8.0.list @@ -82,6 +82,7 @@ Zend/tests/gc_037.phpt Zend/tests/gc_041.phpt Zend/tests/gc_042.phpt Zend/tests/gc_043.phpt +Zend/tests/generators/exception_during_shutdown.phpt Zend/tests/generators/gc_with_yield_from.phpt Zend/tests/generators/generator_closure_with_this.phpt Zend/tests/generators/generator_with_nonscalar_keys.phpt @@ -200,6 +201,8 @@ ext/date/tests/date_time_fractions_serialize.phpt ext/date/tests/date_timestamp_set_nullparam2.phpt ext/date/tests/test-parse-from-format.phpt ext/dom/tests/bug55700.phpt +ext/dom/tests/dom003.phpt +ext/dom/tests/dom_set_attr_node.phpt ext/fileinfo/tests/finfo_open_error.phpt ext/ftp/tests ext/json/tests/bug45791.phpt @@ -267,7 +270,9 @@ ext/phar/tests/031.phpt ext/phar/tests/032.phpt ext/phar/tests/bug69720.phpt ext/phar/tests/bug70433.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_metadata_write2.phpt @@ -308,6 +313,7 @@ ext/simplexml/tests/bug72971.phpt ext/simplexml/tests/bug72971_2.phpt ext/simplexml/tests/bug75245.phpt ext/simplexml/tests/simplexml_load_file.phpt +ext/soap/tests/bug77088.phpt ext/sockets/tests/socket_addrinfo_bind.phpt ext/sockets/tests/socket_addrinfo_connect.phpt ext/sockets/tests/socket_create_pair.phpt diff --git a/dockerfiles/ci/xfail_tests/8.1.list b/dockerfiles/ci/xfail_tests/8.1.list index 6ee1ddc595..2015e626a7 100644 --- a/dockerfiles/ci/xfail_tests/8.1.list +++ b/dockerfiles/ci/xfail_tests/8.1.list @@ -43,7 +43,11 @@ ext/curl/tests/bug79033.phpt ext/curl/tests/curl_int_cast.phpt ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt ext/date/tests/bug52113.phpt +ext/dom/tests/dom003.phpt +ext/dom/tests/dom_set_attr_node.phpt ext/fileinfo/tests/finfo_open_error.phpt +ext/json/tests/json_decode_exceptions.phpt +ext/json/tests/json_encode_exceptions.phpt ext/mbstring/tests/zend_multibyte-01.phpt ext/mbstring/tests/zend_multibyte-02.phpt ext/mbstring/tests/zend_multibyte-06.phpt @@ -98,7 +102,9 @@ ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt ext/pdo_sqlite/tests/pdo_sqlite_lastinsertid.phpt ext/phar/tests/031.phpt ext/phar/tests/032.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_metadata_write4.phpt @@ -108,6 +114,7 @@ ext/phar/tests/phar_oo_007.phpt ext/readline/tests/libedit_callback_handler_install_001.phpt ext/readline/tests/libedit_callback_handler_remove_001.phpt ext/simplexml/tests/bug51615.phpt +ext/soap/tests/bug77088.phpt ext/spl/tests/SplFixedArray_setSize_param_float.phpt ext/spl/tests/bug40091.phpt ext/spl/tests/bug44144.phpt diff --git a/dockerfiles/ci/xfail_tests/8.2.list b/dockerfiles/ci/xfail_tests/8.2.list index 4630cafb3a..f63e984ba5 100644 --- a/dockerfiles/ci/xfail_tests/8.2.list +++ b/dockerfiles/ci/xfail_tests/8.2.list @@ -43,8 +43,12 @@ ext/curl/tests/curl_int_cast.phpt ext/curl/tests/curl_postfields_array.phpt ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt ext/date/tests/bug52113.phpt +ext/dom/tests/dom003.phpt +ext/dom/tests/dom_set_attr_node.phpt ext/ffi/tests/gh12905.phpt ext/fileinfo/tests/finfo_open_error.phpt +ext/json/tests/json_decode_exceptions.phpt +ext/json/tests/json_encode_exceptions.phpt ext/mbstring/tests/zend_multibyte-01.phpt ext/mbstring/tests/zend_multibyte-02.phpt ext/mbstring/tests/zend_multibyte-06.phpt @@ -99,7 +103,9 @@ ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt ext/pdo_sqlite/tests/pdo_sqlite_lastinsertid.phpt ext/phar/tests/031.phpt ext/phar/tests/032.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_metadata_write4.phpt @@ -109,6 +115,7 @@ ext/phar/tests/phar_oo_007.phpt ext/readline/tests/libedit_callback_handler_install_001.phpt ext/readline/tests/libedit_callback_handler_remove_001.phpt ext/simplexml/tests/bug51615.phpt +ext/soap/tests/bug77088.phpt ext/spl/tests/bug40091.phpt ext/spl/tests/bug44144.phpt ext/spl/tests/bug48493.phpt @@ -117,6 +124,7 @@ ext/spl/tests/bug65006.phpt ext/spl/tests/bug71236.phpt ext/spl/tests/bug75049.phpt ext/spl/tests/fixedarray_003.phpt +ext/spl/tests/gh8318.phpt ext/spl/tests/gh10011.phpt ext/spl/tests/spl_autoload_002.phpt ext/spl/tests/spl_autoload_004.phpt diff --git a/dockerfiles/ci/xfail_tests/8.3.list b/dockerfiles/ci/xfail_tests/8.3.list index e4b0d92081..cf52679333 100644 --- a/dockerfiles/ci/xfail_tests/8.3.list +++ b/dockerfiles/ci/xfail_tests/8.3.list @@ -46,7 +46,11 @@ ext/curl/tests/curl_int_cast.phpt ext/curl/tests/curl_postfields_array.phpt ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt ext/date/tests/bug52113.phpt +ext/dom/tests/dom_set_attr_node.phpt +ext/dom/tests/dom003.phpt ext/fileinfo/tests/finfo_open_error.phpt +ext/json/tests/json_decode_exceptions.phpt +ext/json/tests/json_encode_exceptions.phpt ext/mbstring/tests/zend_multibyte-01.phpt ext/mbstring/tests/zend_multibyte-02.phpt ext/mbstring/tests/zend_multibyte-06.phpt @@ -101,7 +105,9 @@ ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt ext/pdo_sqlite/tests/pdo_sqlite_lastinsertid.phpt ext/phar/tests/031.phpt ext/phar/tests/032.phpt +ext/phar/tests/cache_list/frontcontroller29.phpt ext/phar/tests/fatal_error_webphar.phpt +ext/phar/tests/frontcontroller29.phpt ext/phar/tests/phar_buildfromiterator10.phpt ext/phar/tests/phar_buildfromiterator8.phpt ext/phar/tests/phar_metadata_write4.phpt @@ -111,6 +117,7 @@ ext/phar/tests/phar_oo_007.phpt ext/readline/tests/libedit_callback_handler_install_001.phpt ext/readline/tests/libedit_callback_handler_remove_001.phpt ext/simplexml/tests/bug51615.phpt +ext/soap/tests/bug77088.phpt ext/spl/tests/bug40091.phpt ext/spl/tests/bug44144.phpt ext/spl/tests/bug48493.phpt @@ -119,6 +126,7 @@ ext/spl/tests/bug65006.phpt ext/spl/tests/bug71236.phpt ext/spl/tests/bug75049.phpt ext/spl/tests/fixedarray_003.phpt +ext/spl/tests/gh8318.phpt ext/spl/tests/gh10011.phpt ext/spl/tests/spl_autoload_002.phpt ext/spl/tests/spl_autoload_004.phpt diff --git a/dockerfiles/ci/xfail_tests/README.md b/dockerfiles/ci/xfail_tests/README.md index 826bdb2fe6..af3a122cef 100644 --- a/dockerfiles/ci/xfail_tests/README.md +++ b/dockerfiles/ci/xfail_tests/README.md @@ -53,6 +53,17 @@ The following tests assert the output of `var_dump($obj)` and fail because we ad - `ext/spl/tests/gh10011.phpt` - `Zend/tests/gc_045.phpt` +## Tests related to exceptions + +- `Zend/tests/generators/exception_during_shutdown.phpt` +- `ext/dom/tests/dom003.phpt` +- `ext/dom/tests/dom_set_attr_node.phpt` +- `ext/intl/tests/bug60192-sort.phpt` +- `ext/phar/tests/frontcontroller29.phpt` +- `ext/phar/tests/cache_list/frontcontroller29.phpt` +- `ext/soap/tests/bug77088.phpt` +- `ext/spl/tests/gh8318.phpt` + --- # Specific tests @@ -85,6 +96,10 @@ Test does http request to shut down server. Distributed tracing headers are injected +## `ext/intl/tests/bug60192-sort.phpt` + +Has a refcounting bug on PHP 7.4 (which gets triggered by the tracer, but isn't caused by it). + ## `ext/pcntl/tests/pcntl_unshare_01.phpt` Disabled on versions: `7.4` (it wasn't there on [7.3-](https://github.com/php/php-src/tree/PHP-7.3/ext/pcntl/tests)). diff --git a/dockerfiles/frameworks/contrib/entrypoint.sh b/dockerfiles/frameworks/contrib/entrypoint.sh index 091a51b0a7..9cb39364fe 100755 --- a/dockerfiles/frameworks/contrib/entrypoint.sh +++ b/dockerfiles/frameworks/contrib/entrypoint.sh @@ -2,7 +2,5 @@ if [[ -z "$NO_DDTRACE" ]]; then curl -o /tmp/ddtrace.deb http://nginx_file_server/ddtrace.deb dpkg -i /tmp/ddtrace.deb - - export DD_TRACE_CLI_ENABLED=true fi exec "$@" diff --git a/dockerfiles/frameworks/contrib/phpredis/3.1.6/run.sh b/dockerfiles/frameworks/contrib/phpredis/3.1.6/run.sh index c66a6ee525..57d5283fe0 100755 --- a/dockerfiles/frameworks/contrib/phpredis/3.1.6/run.sh +++ b/dockerfiles/frameworks/contrib/phpredis/3.1.6/run.sh @@ -1,5 +1,5 @@ #!/bin/bash -xe switch_php 7.3 -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray +php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis +php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray diff --git a/dockerfiles/frameworks/contrib/phpredis/4.3.0/run.sh b/dockerfiles/frameworks/contrib/phpredis/4.3.0/run.sh index c66a6ee525..57d5283fe0 100755 --- a/dockerfiles/frameworks/contrib/phpredis/4.3.0/run.sh +++ b/dockerfiles/frameworks/contrib/phpredis/4.3.0/run.sh @@ -1,5 +1,5 @@ #!/bin/bash -xe switch_php 7.3 -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray +php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis +php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray diff --git a/dockerfiles/frameworks/contrib/phpredis/5.3.1/run.sh b/dockerfiles/frameworks/contrib/phpredis/5.3.1/run.sh index c66a6ee525..57d5283fe0 100755 --- a/dockerfiles/frameworks/contrib/phpredis/5.3.1/run.sh +++ b/dockerfiles/frameworks/contrib/phpredis/5.3.1/run.sh @@ -1,5 +1,5 @@ #!/bin/bash -xe switch_php 7.3 -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis -DD_TRACE_CLI_ENABLED=true php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray +php ./tests/TestRedis.php --host ${REDIS_HOST} --class Redis +php ./tests/TestRedis.php --host ${REDIS_HOST} --class RedisArray diff --git a/dockerfiles/release-candidates/docker-compose.yml b/dockerfiles/release-candidates/docker-compose.yml index 3244155a42..bfad8b453b 100644 --- a/dockerfiles/release-candidates/docker-compose.yml +++ b/dockerfiles/release-candidates/docker-compose.yml @@ -11,13 +11,11 @@ x-base-php-service: tty: true environment: - DD_AGENT_HOST=ddagent - - DD_TRACE_CLI_ENABLED=1 - DD_TRACE_DEBUG=1 - DD_TRACE_STARTUP_LOGS=0 - DD_ENV=testing # Should take priority over DD_TAGS=env:localhost - DD_TAGS=env:localhost,foo.tag:custom - DD_SERVICE=rc-${PHP_FPM_CONTAINER:-testing-service} # Will emmit deprecated diagnostic - #- DD_TRACE_AUTO_FLUSH_ENABLED=1 #- DD_TRACE_GENERATE_ROOT_SPAN=0 cap_add: - SYS_PTRACE diff --git a/dockerfiles/services/Makefile b/dockerfiles/services/Makefile index 4161c622ad..281af0a546 100644 --- a/dockerfiles/services/Makefile +++ b/dockerfiles/services/Makefile @@ -1,5 +1,6 @@ Q := @ REDIS_IMAGE := datadog/dd-trace-ci:php-redis-5.0 +MYSQL_IMAGE := datadog/dd-trace-ci:php-mysql-dev-5.6 redis_build: $(Q) docker build -t $(REDIS_IMAGE) redis @@ -7,6 +8,9 @@ redis_build: redis_publish: redis_build $(Q) docker push $(REDIS_IMAGE) +mysql_publish: redis_build + docker buildx build --platform=linux/amd64 -t $(MYSQL_IMAGE) mysql --push + # It requires buildx to be able to build cross-architecture images request-replayer_linux_push: docker buildx build --platform=linux/arm64,linux/amd64 -t datadog/dd-trace-ci:php-request-replayer-2.0 ./request-replayer -f request-replayer/linux.Dockerfile --push diff --git a/dockerfiles/services/mysql/Dockerfile b/dockerfiles/services/mysql/Dockerfile new file mode 100644 index 0000000000..d8b4bdb522 --- /dev/null +++ b/dockerfiles/services/mysql/Dockerfile @@ -0,0 +1,3 @@ +FROM mysql:5.6 + +COPY init.sql /docker-entrypoint-initdb.d/init.sql diff --git a/dockerfiles/services/mysql/init.sql b/dockerfiles/services/mysql/init.sql new file mode 100644 index 0000000000..05e4a16192 --- /dev/null +++ b/dockerfiles/services/mysql/init.sql @@ -0,0 +1,13 @@ +CREATE DATABASE IF NOT EXISTS test; +create table test.users ( + id integer not null primary key auto_increment, + email varchar(100) not null unique, + name varchar(100), + password varchar(100), + remember_token varchar(100), + updated_at timestamp, + created_at timestamp +); + +GRANT ALL PRIVILEGES ON *.* TO 'test'@'%' WITH GRANT OPTION; +FLUSH PRIVILEGES; diff --git a/dockerfiles/services/request-replayer/linux.Dockerfile b/dockerfiles/services/request-replayer/linux.Dockerfile index 9928511205..040be82310 100644 --- a/dockerfiles/services/request-replayer/linux.Dockerfile +++ b/dockerfiles/services/request-replayer/linux.Dockerfile @@ -9,6 +9,7 @@ WORKDIR /var/www COPY src /var/www EXPOSE 80 +EXPOSE 80/udp RUN composer install diff --git a/dockerfiles/services/request-replayer/src/index.php b/dockerfiles/services/request-replayer/src/index.php index 092ecb4455..5afba6fb99 100644 --- a/dockerfiles/services/request-replayer/src/index.php +++ b/dockerfiles/services/request-replayer/src/index.php @@ -12,26 +12,95 @@ exit; } -define('REQUEST_LATEST_DUMP_FILE', getenv('REQUEST_LATEST_DUMP_FILE') ?: (sys_get_temp_dir() . '/dump.json')); -define('REQUEST_NEXT_RESPONSE_FILE', getenv('REQUEST_NEXT_RESPONSE_FILE') ?: (sys_get_temp_dir() . '/response.json')); -define('REQUEST_LOG_FILE', getenv('REQUEST_LOG_FILE') ?: (sys_get_temp_dir() . '/requests-log.txt')); +function decodeDogStatsDMetrics($metrics) +{ + $metrics = array_filter($metrics); + + // Format of DogStatsD metrics: metric_name:value|type|#tag1:value1,tag2:value2 + // Parts: |-> 0 |-> 1|-> 2 + $decodedMetrics = []; + foreach ($metrics as $metric) { + $parts = explode('|', $metric); + + $nameAndValue = explode(':', $parts[0]); + $metricName = $nameAndValue[0]; + $value = $nameAndValue[1]; + + $type = $parts[1]; + + $tags = []; + if (count($parts) > 2) { + $parts[2] = substr($parts[2], 1); // Remove leading # + $tags = explode(',', $parts[2]); + $tags = array_map(function ($tag) { + return explode(':', $tag); + }, $tags); + $tags = array_combine(array_column($tags, 0), array_column($tags, 1)); + } + $decodedMetrics[] = [ + 'name' => $metricName, + 'value' => $value, + 'type' => $type, + 'tags' => $tags, + ]; + } + return $decodedMetrics; +} + +$uri = explode("?", $_SERVER['REQUEST_URI'])[0]; + +$temp_location = sys_get_temp_dir(); + +$metricsServerPid = "$temp_location/metrics-server.pid"; +if (!file_exists($metricsServerPid)) { + shell_exec("nohup bash -c 'php metricsserver.php & pid=$!; echo \$pid > $metricsServerPid; wait \$pid; rm $metricsServerPid' > /dev/null 2>&1 &"); +} + +$token = $_SERVER["HTTP_X_DATADOG_TEST_SESSION_TOKEN"] ?? ""; + +if ($uri === "/metrics") { + $decodedMetrics = decodeDogStatsDMetrics(explode("\n", trim($_GET["metrics"], "\n"))); + if (isset($decodedMetrics[0]["tags"]["x-datadog-test-session-token"])) { + $token = $decodedMetrics[0]["tags"]["x-datadog-test-session-token"]; + unset($decodedMetrics[0]["tags"]["x-datadog-test-session-token"]); + } +} + +if ($token != "") { + $token = str_replace("/", "-", $token); + $temp_location .= "/token-$token"; + @mkdir($temp_location); +} + +define('REQUEST_LATEST_DUMP_FILE', getenv('REQUEST_LATEST_DUMP_FILE') ?: ("$temp_location/dump.json")); +define('REQUEST_NEXT_RESPONSE_FILE', getenv('REQUEST_NEXT_RESPONSE_FILE') ?: ("$temp_location/response.json")); +define('REQUEST_LOG_FILE', getenv('REQUEST_LOG_FILE') ?: ("$temp_location/requests-log.txt")); +define('REQUEST_RC_CONFIGS_FILE', getenv('REQUEST_RC_CONFIGS_FILE') ?: ("$temp_location/rc_configs.json")); +define('REQUEST_METRICS_FILE', getenv('REQUEST_METRICS_FILE') ?: ("$temp_location/metrics.json")); +define('REQUEST_METRICS_LOG_FILE', getenv('REQUEST_METRICS_LOG_FILE') ?: ("$temp_location/metrics-log.txt")); function logRequest($message, $data = '') { + global $token; if (!empty($data)) { $message .= ":\n" . $data; } error_log( - sprintf('[%s | %s] %s', $_SERVER['REQUEST_URI'], REQUEST_LATEST_DUMP_FILE, $message) + sprintf('[%s | %s%s] %s', $_SERVER['REQUEST_URI'], REQUEST_LATEST_DUMP_FILE, $token == "" ? "" : " | $token", $message) ); } set_error_handler(function ($number, $message, $errfile, $errline) { - logRequest("Triggered error $number $message in $errfile on line $errline"); + if (error_reporting() == 0) { + return; + } + logRequest("Triggered error $number $message in $errfile on line $errline: " . (new \Exception)->getTraceAsString()); trigger_error($message, $number); }); -switch ($_SERVER['REQUEST_URI']) { +$rc_configs = file_exists(REQUEST_RC_CONFIGS_FILE) ? json_decode(file_get_contents(REQUEST_RC_CONFIGS_FILE), true) : []; + +switch ($uri) { case '/replay': if (!file_exists(REQUEST_LATEST_DUMP_FILE)) { logRequest('Cannot replay last request; request log does not exist'); @@ -43,22 +112,108 @@ function logRequest($message, $data = '') unlink(REQUEST_LOG_FILE); logRequest('Returned last request and deleted request log', $request); break; + case '/replay-metrics': + if (!file_exists(REQUEST_METRICS_FILE)) { + logRequest('Cannot replay last request; metrics log does not exist'); + break; + } + $request = file_get_contents(REQUEST_METRICS_FILE); + echo $request; + unlink(REQUEST_METRICS_FILE); + unlink(REQUEST_METRICS_LOG_FILE); + logRequest('Returned last metrics and deleted metrics log', $request); + break; case '/clear-dumped-data': - if (!file_exists(REQUEST_LATEST_DUMP_FILE)) { + if (!file_exists(REQUEST_LATEST_DUMP_FILE) && !file_exists(REQUEST_METRICS_FILE) && !file_exists(REQUEST_RC_CONFIGS_FILE)) { logRequest('Cannot delete request log; request log does not exist'); break; } - unlink(REQUEST_LATEST_DUMP_FILE); - unlink(REQUEST_LOG_FILE); - if (file_exists(REQUEST_NEXT_RESPONSE_FILE)) { - unlink(REQUEST_NEXT_RESPONSE_FILE); - } + if (file_exists(REQUEST_RC_CONFIGS_FILE)) { + unlink(REQUEST_RC_CONFIGS_FILE); + } + if (file_exists(REQUEST_LATEST_DUMP_FILE)) { + unlink(REQUEST_LATEST_DUMP_FILE); + unlink(REQUEST_LOG_FILE); + } + if (file_exists(REQUEST_METRICS_FILE)) { + unlink(REQUEST_METRICS_FILE); + unlink(REQUEST_METRICS_LOG_FILE); + } + if (file_exists(REQUEST_NEXT_RESPONSE_FILE)) { + unlink(REQUEST_NEXT_RESPONSE_FILE); + } logRequest('Deleted request log'); break; case '/next-response': $raw = file_get_contents('php://input'); file_put_contents(REQUEST_NEXT_RESPONSE_FILE, $raw); break; + case '/add-rc-config-file': + $rc_configs[$_GET["path"]] = ["service" => $_GET["service"], "data" => file_get_contents('php://input')]; + file_put_contents(REQUEST_RC_CONFIGS_FILE, json_encode($rc_configs, JSON_UNESCAPED_SLASHES)); + break; + case '/del-rc-config-file': + unset($rc_configs[$_GET["path"]]); + file_put_contents(REQUEST_RC_CONFIGS_FILE, json_encode($rc_configs, JSON_UNESCAPED_SLASHES)); + break; + case '/v0.7/config': + $request = file_get_contents('php://input'); + logRequest("Requested remote config", $request); + $request = json_decode($request, true); + $recentUpdate = @filemtime(REQUEST_RC_CONFIGS_FILE) > time() - 2; + $response = [ + "roots" => [], + "targets" => [ + "signatures" => [], + "signed" => [ + "_type" => "targets", + "custom" => [ + "opaque_backend_state" => "foobarbaz", + "agent_refresh_interval" => ($recentUpdate ? 10 : 10000) * 1000000, // in ns + ], + "expires" => "9999-12-31T23:59:59Z", + "spec_version" => "1.0.0", + "targets" => new \StdClass, + "version" => 1, + ], + ], + "target_files" => [], + "client_configs" => [], + ]; + foreach ($rc_configs as $path => $config) { + if ($config["service"] == $request["client"]["client_tracer"]["service"]) { + $content = $config["data"]; + $response["targets"]["signed"]["targets"]->$path = [ + "custom" => ["v" => strlen($path)], + "hashes" => ["sha256" => hash("sha256", $content)], + "length" => strlen($content), + ]; + $response["target_files"][] = [ + "path" => $path, + "raw" => base64_encode($content), + ]; + $response["client_configs"][] = $path; + } + } + logRequest("Returned remote config", json_encode($response, JSON_UNESCAPED_SLASHES)); + $response["targets"] = base64_encode(json_encode($response["targets"], JSON_UNESCAPED_SLASHES)); + echo json_encode($response, JSON_UNESCAPED_SLASHES); + break; + case "/metrics": + $_SERVER['REQUEST_URI'] = $uri; + logRequest('Logged new metrics', json_encode($decodedMetrics)); + foreach ($decodedMetrics as $metric) { + file_put_contents(REQUEST_METRICS_LOG_FILE, json_encode($metric) . "\n", FILE_APPEND); + + if (file_exists(REQUEST_METRICS_FILE)) { + $allMetrics = json_decode(file_get_contents(REQUEST_METRICS_FILE), true); + } else { + $allMetrics = []; + } + $allMetrics[] = $metric; + file_put_contents(REQUEST_METRICS_FILE, json_encode($allMetrics)); + } + break; default: $headers = getallheaders(); if (isset($headers['X-Datadog-Diagnostic-Check']) || isset($headers['x-datadog-diagnostic-check'])) { @@ -66,52 +221,67 @@ function logRequest($message, $data = '') break; } - $raw = file_get_contents('php://input'); - if ((isset($headers['Content-Type']) && $headers['Content-Type'] === 'application/msgpack') - || (isset($headers['content-type']) && $headers['content-type'] === 'application/msgpack')) { - // We unpack in two phases: - // 1) using UnpackOptions::BIGINT_AS_GMP and only asserting that trace_id, span_id and parent_id are either - // integers (when <= PHP_INT_MAX) or GMP (when > PHP_INT_MAX); - // 2) using UnpackOptions::BIGINT_AS_STR and storing the actual result. - // We cannot use the first unpacked payload as when, later, we json_encode() the payload, GMPs larger than - // PHP_INT_MAX would be serialized to PHP_INT_MAX - $gmpUnpacker = new BufferUnpacker($raw, UnpackOptions::BIGINT_AS_GMP); - $gmpTraces = $gmpUnpacker->unpack(); - foreach (isset($gmpTraces["chunks"]) ? $gmpTraces["chunks"] : $gmpTraces as $trace) { - foreach (isset($trace["spans"]) ? $trace["spans"] : $trace as $span) { - foreach (['trace_id', 'span_id', 'parent_id'] as $field) { - if (!isset($span[$field])) { - continue; - } + $newIncomingRequest = [ + 'uri' => $_SERVER['REQUEST_URI'], + 'headers' => $headers, + ]; + + if (!empty($_FILES)) { + $newIncomingRequest["files"] = $_FILES; + foreach ($newIncomingRequest["files"] as &$file) { + $file["contents"] = file_get_contents($file["tmp_name"]); + unset($file["tmp_name"]); + } + } else { + $raw = file_get_contents('php://input'); + if ((isset($headers['Content-Type']) && $headers['Content-Type'] === 'application/msgpack') + || (isset($headers['content-type']) && $headers['content-type'] === 'application/msgpack')) { + // We unpack in two phases: + // 1) using UnpackOptions::BIGINT_AS_GMP and only asserting that trace_id, span_id and parent_id are either + // integers (when <= PHP_INT_MAX) or GMP (when > PHP_INT_MAX); + // 2) using UnpackOptions::BIGINT_AS_STR and storing the actual result. + // We cannot use the first unpacked payload as when, later, we json_encode() the payload, GMPs larger than + // PHP_INT_MAX would be serialized to PHP_INT_MAX + $gmpUnpacker = new BufferUnpacker($raw, UnpackOptions::BIGINT_AS_GMP); + $gmpTraces = $gmpUnpacker->unpack(); + foreach (isset($gmpTraces["chunks"]) ? $gmpTraces["chunks"] : $gmpTraces as $trace) { + foreach (isset($trace["spans"]) ? $trace["spans"] : $trace as $span) { + foreach (['trace_id', 'span_id', 'parent_id'] as $field) { + if (!isset($span[$field])) { + continue; + } - $value = $span[$field]; - if (!is_int($value) && !is_a($value, 'GMP')) { - logRequest("Wrong type for $field: " . var_export($value, 1)); - exit(); + $value = $span[$field]; + if (!is_int($value) && !is_a($value, 'GMP')) { + logRequest("Wrong type for $field: " . var_export($value, 1)); + exit(); + } } } } - } - $strUnpacker = new BufferUnpacker($raw, UnpackOptions::BIGINT_AS_STR); - $strTraces = $strUnpacker->unpack(); - $traces = isset($strTraces["chunks"]) ? [&$strTraces["chunks"]] : [&$strTraces]; - foreach ($traces[0] as &$trace) { - $spans = isset($trace["spans"]) ? [&$trace["spans"]] : [&$trace]; - foreach ($spans[0] as &$span) { - foreach (['trace_id', 'span_id', 'parent_id'] as $field) { - if (!isset($span[$field])) { - continue; - } + $strUnpacker = new BufferUnpacker($raw, UnpackOptions::BIGINT_AS_STR); + $strTraces = $strUnpacker->unpack(); + $traces = isset($strTraces["chunks"]) ? [&$strTraces["chunks"]] : [&$strTraces]; + foreach ($traces[0] as &$trace) { + $spans = isset($trace["spans"]) ? [&$trace["spans"]] : [&$trace]; + foreach ($spans[0] as &$span) { + foreach (['trace_id', 'span_id', 'parent_id'] as $field) { + if (!isset($span[$field])) { + continue; + } - $span[$field] = (string)$span[$field]; + $span[$field] = (string)$span[$field]; + } } } + + $body = json_encode($strTraces); + } else { + $body = $raw; } - $body = json_encode($strTraces); - } else { - $body = $raw; + $newIncomingRequest["body"] = $body; } if (file_exists(REQUEST_LATEST_DUMP_FILE)) { $tracesStack = json_decode(file_get_contents(REQUEST_LATEST_DUMP_FILE), true); @@ -119,12 +289,6 @@ function logRequest($message, $data = '') $tracesStack = []; } - $newIncomingRequest = [ - 'uri' => $_SERVER['REQUEST_URI'], - 'headers' => $headers, - 'body' => $body, - ]; - $tracesStack[] = $newIncomingRequest; $newIncomingRequestJson = json_encode($newIncomingRequest); diff --git a/dockerfiles/services/request-replayer/src/metricsserver.php b/dockerfiles/services/request-replayer/src/metricsserver.php new file mode 100644 index 0000000000..dda9bde3bc --- /dev/null +++ b/dockerfiles/services/request-replayer/src/metricsserver.php @@ -0,0 +1,8 @@ += 70300 + zend_fetch_function(ZSTR_KNOWN(ZEND_STR_MAGIC_AUTOLOAD)) +#else + zend_hash_str_find_ptr(EG(function_table), ZEND_AUTOLOAD_FUNC_NAME, sizeof(ZEND_AUTOLOAD_FUNC_NAME) - 1) +#endif + ; + if (func) { + zval ret; + zend_fcall_info fcall_info; + zend_fcall_info_cache fcall_cache; + + fcall_info.size = sizeof(fcall_info); + ZVAL_STR(&fcall_info.function_name, func->common.function_name); + fcall_info.retval = &ret; + fcall_info.param_count = 1; + fcall_info.params = EX_VAR_NUM(0); + fcall_info.object = NULL; + fcall_info.no_separation = 1; +#if PHP_VERSION_ID < 70100 + fcall_info.symbol_table = NULL; +#endif + +#if PHP_VERSION_ID < 70300 + fcall_cache.initialized = 1; +#endif + fcall_cache.function_handler = func; + fcall_cache.calling_scope = NULL; + fcall_cache.called_scope = NULL; + fcall_cache.object = NULL; + + zend_call_function(&fcall_info, &fcall_cache); + zval_ptr_dtor(&ret); + } + + // skip original implementation if there's no spl autoloader registered + return true; + } + + return false; } static ZEND_NAMED_FUNCTION(dd_wrap_autoload_register_fn) { diff --git a/ext/collect_backtrace.c b/ext/collect_backtrace.c new file mode 100644 index 0000000000..a0f7c671fc --- /dev/null +++ b/ext/collect_backtrace.c @@ -0,0 +1,568 @@ +#include "collect_backtrace.h" +#include "ddtrace.h" +#include +#include +#if PHP_VERSION_ID >= 80000 +#include +#endif +#include +#include "compatibility.h" + +// skip_args to not capture args if anyway captured via !DEBUG_BACKTRACE_IGNORE_ARGS +void ddtrace_call_get_locals(zend_execute_data *call, zval *locals_array, bool skip_args) { + zend_op_array *op_array = &call->func->op_array; + +#if PHP_VERSION_ID >= 70100 + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_SYMBOL_TABLE) +#else + if (call->symbol_table) +#endif + { + // array_dup also duplicates indirects away + ZVAL_ARR(locals_array, zend_array_dup(call->symbol_table)); + if (!skip_args) { + // But we want just locals, no args + for (uint32_t i = 0; i < op_array->num_args; ++i) { + zend_hash_del(Z_ARR_P(locals_array), op_array->vars[i]); + } + } + return; + } + + zend_array *locals = zend_new_array(op_array->last_var - op_array->num_args); + for (int i = skip_args ? (int)op_array->num_args : 0; i < op_array->last_var; ++i) { + zval *var = ZEND_CALL_VAR_NUM(call, i); + Z_TRY_ADDREF_P(var); + zend_hash_add_new(locals, op_array->vars[i], var); + } + ZVAL_ARR(locals_array, locals); +} + +/* Copy from zend_builin_functions.c */ +static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) { + uint32_t num_args = ZEND_CALL_NUM_ARGS(call); + + if (num_args) { + uint32_t i = 0; + zval *p = ZEND_CALL_ARG(call, 1); + + array_init_size(arg_array, num_args); + zend_hash_real_init_packed(Z_ARRVAL_P(arg_array)); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(arg_array)) { + if (call->func->type == ZEND_USER_FUNCTION) { + uint32_t first_extra_arg = MIN(num_args, call->func->op_array.num_args); + +#if PHP_VERSION_ID >= 70100 + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_SYMBOL_TABLE)) +#else + if (call->symbol_table) +#endif + { + /* In case of attached symbol_table, values on stack may be invalid + * and we have to access them through symbol_table + * See: https://bugs.php.net/bug.php?id=73156 + */ + while (i < first_extra_arg) { + zend_string *arg_name = call->func->op_array.vars[i]; + zval original_arg = {0}; + zval *arg = zend_hash_find_ex_ind(call->symbol_table, arg_name, 1); + + if (arg) { + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + +#if PHP_VERSION_ID >= 80200 + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + i + ); + + bool is_sensitive = attribute != NULL; + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); + } else +#endif + { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + + ZEND_HASH_FILL_NEXT(); + i++; + } + } else { + while (i < first_extra_arg) { + zval original_arg = {0}; + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { + zval *arg = p; + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + +#if PHP_VERSION_ID >= 80200 + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + i + ); + bool is_sensitive = attribute != NULL; + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); + } else +#endif + { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } + p = ZEND_CALL_VAR_NUM(call, call->func->op_array.last_var + call->func->op_array.T); + } + + while (i < num_args) { + zval original_arg = {0}; + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { + zval *arg = p; + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + +#if PHP_VERSION_ID >= 80200 + bool is_sensitive = 0; + + if (i < call->func->common.num_args || call->func->common.fn_flags & ZEND_ACC_VARIADIC) { + zend_attribute *attribute = zend_get_parameter_attribute_str( + call->func->common.attributes, + "sensitiveparameter", + sizeof("sensitiveparameter") - 1, + MIN(i, call->func->common.num_args) + ); + is_sensitive = attribute != NULL; + } + + if (is_sensitive) { + zval redacted_arg; + object_init_ex(&redacted_arg, zend_ce_sensitive_parameter_value); + zend_call_method_with_1_params(Z_OBJ_P(&redacted_arg), zend_ce_sensitive_parameter_value, &zend_ce_sensitive_parameter_value->constructor, "__construct", NULL, &original_arg); + ZEND_HASH_FILL_SET(&redacted_arg); + } else +#endif + { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } ZEND_HASH_FILL_END(); + Z_ARRVAL_P(arg_array)->nNumOfElements = num_args; + } else { + ZVAL_EMPTY_ARRAY(arg_array); + } + +#if PHP_VERSION_ID >= 80000 + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_string *name; + zval *arg; + SEPARATE_ARRAY(arg_array); + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(call->extra_named_params, name, arg) { + ZVAL_DEREF(arg); + Z_TRY_ADDREF_P(arg); + zend_hash_add_new(Z_ARRVAL_P(arg_array), name, arg); + } ZEND_HASH_FOREACH_END(); + } +#endif +} + +#if PHP_VERSION_ID < 80100 +static inline zend_bool skip_internal_handler(zend_execute_data *skip) /* {{{ */ +{ + return !(skip->func && ZEND_USER_CODE(skip->func->common.type)) + && skip->prev_execute_data + && skip->prev_execute_data->func + && ZEND_USER_CODE(skip->prev_execute_data->func->common.type) + && skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL + && skip->prev_execute_data->opline->opcode != ZEND_DO_ICALL + && skip->prev_execute_data->opline->opcode != ZEND_DO_UCALL + && skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL_BY_NAME + && skip->prev_execute_data->opline->opcode != ZEND_INCLUDE_OR_EVAL; +} +#endif + +/* Copy of zend_fetch_debug_backtrace with ability to gather local variables */ +void ddtrace_fetch_debug_backtrace(zval *return_value, int skip_last, int options, int limit) +{ + zend_execute_data *call; + zend_object *object; + bool fake_frame = 0; + int lineno, frameno = 0; + zend_function *func; + zend_string *filename; + zend_string *include_filename = NULL; + zval tmp; + HashTable *stack_frame, *prev_stack_frame = NULL; + zend_string *key_locals = NULL; + + array_init(return_value); + + call = EG(current_execute_data); + if (!call) { + return; + } + + if (options & DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS) { + key_locals = zend_string_init(ZEND_STRL("locals"), 0); + } + +#if PHP_VERSION_ID >= 80300 + if (EG(filename_override)) { + // Add the current execution point to the frame so we don't lose it + zend_string *filename_override = EG(filename_override); + zend_long lineno_override = EG(lineno_override); + EG(filename_override) = NULL; + EG(lineno_override) = -1; + + zend_string *filename = zend_get_executed_filename_ex(); + zend_long lineno = zend_get_executed_lineno(); + if (filename && (!zend_string_equals(filename, filename_override) || lineno != lineno_override)) { + stack_frame = zend_new_array(8); + zend_hash_real_init_mixed(stack_frame); + ZVAL_STR_COPY(&tmp, filename); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1); + ZVAL_LONG(&tmp, lineno); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_LINE), &tmp, 1); + ZVAL_STR_COPY(&tmp, ZSTR_KNOWN(ZEND_STR_CONST_EXPR_PLACEHOLDER)); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1); + ZVAL_ARR(&tmp, stack_frame); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + + EG(filename_override) = filename_override; + EG(lineno_override) = lineno_override; + } +#endif + + if (skip_last) { + /* skip debug_backtrace() */ + call = call->prev_execute_data; + } + + while (call && (limit == 0 || frameno < limit)) { + if (UNEXPECTED(!call->func)) { + /* This is the fake frame inserted for nested generators. Normally, + * this frame is preceded by the actual generator frame and then + * replaced by zend_generator_check_placeholder_frame() below. + * However, the frame is popped before cleaning the stack frame, + * which is observable by destructors. */ + call = zend_generator_check_placeholder_frame(call); + } + + zend_execute_data *prev = call->prev_execute_data; + + if (!prev) { + /* add frame for a handler call without {main} code */ + if (EXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_TOP_FUNCTION) == 0)) { + break; + } +#if PHP_VERSION_ID < 70100 + } else if (call->func && (call->func->op_array.fn_flags & ZEND_ACC_GENERATOR)) { +#else + } else if (UNEXPECTED((ZEND_CALL_INFO(call) & ZEND_CALL_GENERATOR) != 0)) { +#endif + prev = zend_generator_check_placeholder_frame(prev); + } + +#if PHP_VERSION_ID < 80100 + /* skip internal handler */ + if (prev && skip_internal_handler(prev)) { + prev = prev->prev_execute_data; + } +#endif + +#if PHP_VERSION_ID >= 80400 + /* For frameless calls we add an additional frame for the call itself. */ + if (ZEND_USER_CODE(call->func->type)) { + const zend_op *opline = call->opline; + if (!ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) { + goto not_frameless_call; + } + int num_args = ZEND_FLF_NUM_ARGS(opline->opcode); + /* Check if any args were already freed. Skip the frame in that case. */ + if (num_args >= 1) { + zval *arg = zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, call); + if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call; + } + if (num_args >= 2) { + zval *arg = zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, call); + if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call; + } + if (num_args >= 3) { + const zend_op *op_data = opline + 1; + zval *arg = zend_get_zval_ptr(op_data, op_data->op1_type, &op_data->op1, call); + if (Z_TYPE_P(arg) == IS_UNDEF) goto not_frameless_call; + } + stack_frame = zend_new_array(8); + zend_hash_real_init_mixed(stack_frame); + zend_function *func = ZEND_FLF_FUNC(opline); + zend_string *name = func->common.function_name; + ZVAL_STRINGL(&tmp, ZSTR_VAL(name), ZSTR_LEN(name)); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1); + /* Steal file and line from the previous frame. */ + if (call->func && ZEND_USER_CODE(call->func->common.type)) { + filename = call->func->op_array.filename; + if (call->opline->opcode == ZEND_HANDLE_EXCEPTION) { + if (EG(opline_before_exception)) { + lineno = EG(opline_before_exception)->lineno; + } else { + lineno = call->func->op_array.line_end; + } + } else { + lineno = call->opline->lineno; + } + ZVAL_STR_COPY(&tmp, filename); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1); + ZVAL_LONG(&tmp, lineno); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_LINE), &tmp, 1); + if ((options & DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS)) { + ddtrace_call_get_locals(call, &tmp, (options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0); + _zend_hash_append_ex(stack_frame, key_locals, &tmp, 0); + } + if (prev_stack_frame) { + zend_hash_del(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_FILE)); + zend_hash_del(prev_stack_frame, ZSTR_KNOWN(ZEND_STR_LINE)); + zend_hash_del(prev_stack_frame, key_locals); + } + } + if ((options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0) { + HashTable *args = zend_new_array(8); + zend_hash_real_init_mixed(args); + if (num_args >= 1) { + zval *arg = zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, call); + Z_TRY_ADDREF_P(arg); + zend_hash_next_index_insert_new(args, arg); + } + if (num_args >= 2) { + zval *arg = zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, call); + Z_TRY_ADDREF_P(arg); + zend_hash_next_index_insert_new(args, arg); + } + if (num_args >= 3) { + const zend_op *op_data = opline + 1; + zval *arg = zend_get_zval_ptr(op_data, op_data->op1_type, &op_data->op1, call); + Z_TRY_ADDREF_P(arg); + zend_hash_next_index_insert_new(args, arg); + } + ZVAL_ARR(&tmp, args); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_ARGS), &tmp, 1); + } + ZVAL_ARR(&tmp, stack_frame); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + } + not_frameless_call: +#else + UNUSED(prev_stack_frame); +#endif + + /* We use _zend_hash_append*() and the array must be preallocated */ + stack_frame = zend_new_array(8); + zend_hash_real_init_mixed(stack_frame); + + if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) { + filename = prev->func->op_array.filename; + if (prev->opline->opcode == ZEND_HANDLE_EXCEPTION) { + if (EG(opline_before_exception)) { + lineno = EG(opline_before_exception)->lineno; + } else { + lineno = prev->func->op_array.line_end; + } + } else { + lineno = prev->opline->lineno; + } + ZVAL_STR_COPY(&tmp, filename); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1); + ZVAL_LONG(&tmp, lineno); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_LINE), &tmp, 1); + if ((options & DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS)) { + ddtrace_call_get_locals(prev, &tmp, (options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0); + _zend_hash_append_ex(stack_frame, key_locals, &tmp, 0); + } + + /* try to fetch args only if an FCALL was just made - elsewise we're in the middle of a function + * and debug_backtrace() might have been called by the error_handler. in this case we don't + * want to pop anything of the argument-stack */ + } else { + zend_execute_data *prev_call = prev; + + while (prev_call) { + zend_execute_data *prev; + + if (prev_call && + prev_call->func && + !ZEND_USER_CODE(prev_call->func->common.type) && + !(prev_call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + break; + } + + prev = prev_call->prev_execute_data; + if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type)) { + ZVAL_STR_COPY(&tmp, prev->func->op_array.filename); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FILE), &tmp, 1); + ZVAL_LONG(&tmp, prev->opline->lineno); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_LINE), &tmp, 1); + if ((options & DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS)) { + ddtrace_call_get_locals(prev, &tmp, (options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0); + _zend_hash_append_ex(stack_frame, key_locals, &tmp, 0); + } + break; + } + prev_call = prev; + } + filename = NULL; + } + + func = call->func; + if (!fake_frame && func && func->common.function_name) { + ZVAL_STR_COPY(&tmp, func->common.function_name); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1); + + if (Z_TYPE(call->This) == IS_OBJECT +#if PHP_VERSION_ID < 80000 + && Z_OBJ(call->This) +#endif + ) { + object = Z_OBJ(call->This); + /* $this may be passed into regular internal functions */ + if (func->common.scope) { + ZVAL_STR_COPY(&tmp, func->common.scope->name); +#if PHP_VERSION_ID >= 70300 + } else if (object->handlers->get_class_name == zend_std_get_class_name) { + ZVAL_STR_COPY(&tmp, object->ce->name); +#endif + } else { + ZVAL_STR(&tmp, object->handlers->get_class_name(object)); + } + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_CLASS), &tmp, 1); + if ((options & DEBUG_BACKTRACE_PROVIDE_OBJECT) != 0) { + ZVAL_OBJ_COPY(&tmp, object); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_OBJECT), &tmp, 1); + } + + ZVAL_INTERNED_STR(&tmp, ZSTR_KNOWN(ZEND_STR_OBJECT_OPERATOR)); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_TYPE), &tmp, 1); + } else if (func->common.scope) { + ZVAL_STR_COPY(&tmp, func->common.scope->name); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_CLASS), &tmp, 1); + ZVAL_INTERNED_STR(&tmp, ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM)); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_TYPE), &tmp, 1); + } + + if ((options & DEBUG_BACKTRACE_IGNORE_ARGS) == 0 && + func->type != ZEND_EVAL_CODE) { + + debug_backtrace_get_args(call, &tmp); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_ARGS), &tmp, 1); + } + } else { + /* i know this is kinda ugly, but i'm trying to avoid extra cycles in the main execution loop */ + bool build_filename_arg = 1; + zend_string *pseudo_function_name; + uint32_t include_kind = 0; + if (prev && prev->func && ZEND_USER_CODE(prev->func->common.type) && prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) { + include_kind = prev->opline->extended_value; + } + + switch (include_kind) { + case ZEND_EVAL: + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_EVAL); + build_filename_arg = 0; + break; + case ZEND_INCLUDE: + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_INCLUDE); + break; + case ZEND_REQUIRE: + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_REQUIRE); + break; + case ZEND_INCLUDE_ONCE: + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_INCLUDE_ONCE); + break; + case ZEND_REQUIRE_ONCE: + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_REQUIRE_ONCE); + break; + default: + /* Skip dummy frame unless it is needed to preserve filename/lineno info. */ + if (!filename) { + zend_array_destroy(stack_frame); + goto skip_frame; + } + + pseudo_function_name = ZSTR_KNOWN(ZEND_STR_UNKNOWN); + build_filename_arg = 0; + break; + } + + if (build_filename_arg && include_filename) { + zval arg_array; + + array_init(&arg_array); + + /* include_filename always points to the last filename of the last last called-function. + if we have called include in the frame above - this is the file we have included. + */ + + ZVAL_STR_COPY(&tmp, include_filename); + zend_hash_next_index_insert_new(Z_ARRVAL(arg_array), &tmp); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_ARGS), &arg_array, 1); + } + + ZVAL_INTERNED_STR(&tmp, pseudo_function_name); + _zend_hash_append_ex(stack_frame, ZSTR_KNOWN(ZEND_STR_FUNCTION), &tmp, 1); + } + + ZVAL_ARR(&tmp, stack_frame); + zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &tmp); + frameno++; + prev_stack_frame = stack_frame; + + skip_frame: + if (UNEXPECTED(ZEND_CALL_KIND(call) == ZEND_CALL_TOP_FUNCTION) + && !fake_frame + && prev + && prev->func + && ZEND_USER_CODE(prev->func->common.type) + && prev->opline->opcode == ZEND_INCLUDE_OR_EVAL) { + fake_frame = 1; + } else { + fake_frame = 0; + include_filename = filename; + call = prev; + } + } + + if (key_locals) { + zend_string_release(key_locals); + } +} diff --git a/ext/collect_backtrace.h b/ext/collect_backtrace.h new file mode 100644 index 0000000000..098f233e2f --- /dev/null +++ b/ext/collect_backtrace.h @@ -0,0 +1,13 @@ +#ifndef DD_COLLECT_BACKTRACE_H +#define DD_COLLECT_BACKTRACE_H + +#include +#include + +#define DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS (1 << 30) + +void ddtrace_fetch_debug_backtrace(zval *return_value, int skip_last, int options, int limit); +void ddtrace_call_get_locals(zend_execute_data *call, zval *locals_array, bool skip_args); + + +#endif // DD_COLLECT_BACKTRACE_H diff --git a/ext/compat_string.c b/ext/compat_string.c index c29452cac5..1b19c8a2ee 100644 --- a/ext/compat_string.c +++ b/ext/compat_string.c @@ -27,20 +27,32 @@ void ddtrace_downcase_zval(zval *src) { zend_string_release(str); } -zend_string *ddtrace_convert_to_str(zval *op) { +zend_string *ddtrace_convert_to_str(const zval *op) { try_again: switch (Z_TYPE_P(op)) { case IS_UNDEF: return zend_string_init("undef", sizeof("undef") - 1, 0); case IS_NULL: +#if PHP_VERSION_ID < 80000 return zend_string_init("null", sizeof("null") - 1, 0); +#else + return ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE); +#endif case IS_FALSE: +#if PHP_VERSION_ID < 80000 return zend_string_init("false", sizeof("false") - 1, 0); +#else + return ZSTR_KNOWN(ZEND_STR_FALSE); +#endif case IS_TRUE: +#if PHP_VERSION_ID < 80200 return zend_string_init("true", sizeof("true") - 1, 0); +#else + return ZSTR_KNOWN(ZEND_STR_TRUE); +#endif case IS_RESOURCE: return strpprintf(0, "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op)); diff --git a/ext/compat_string.h b/ext/compat_string.h index 6cdb75b8ec..25de9a02a3 100644 --- a/ext/compat_string.h +++ b/ext/compat_string.h @@ -20,6 +20,6 @@ size_t ddtrace_spprintf(char **message, size_t max_len, char *format, ...); * overflows due to recursion stemming from our own code as the root. **/ void ddtrace_convert_to_string(zval *dst, zval *src); -zend_string *ddtrace_convert_to_str(zval *op); +zend_string *ddtrace_convert_to_str(const zval *op); #endif // COMPAT_STRING_H diff --git a/ext/compatibility.h b/ext/compatibility.h index b284d2f718..6ed5229a53 100644 --- a/ext/compatibility.h +++ b/ext/compatibility.h @@ -95,7 +95,76 @@ static inline zend_long zval_get_long(zval *op) { #define PHP_DOUBLE_MAX_LENGTH 1080 #endif +enum { + ZEND_STR_TRACE, + ZEND_STR_LINE, + ZEND_STR_FILE, + ZEND_STR_MESSAGE, + ZEND_STR_CODE, + ZEND_STR_TYPE, + ZEND_STR_FUNCTION, + ZEND_STR_OBJECT, + ZEND_STR_CLASS, + ZEND_STR_OBJECT_OPERATOR, + ZEND_STR_PAAMAYIM_NEKUDOTAYIM, + ZEND_STR_ARGS, + ZEND_STR_UNKNOWN, + ZEND_STR_EVAL, + ZEND_STR_INCLUDE, + ZEND_STR_REQUIRE, + ZEND_STR_INCLUDE_ONCE, + ZEND_STR_REQUIRE_ONCE, + ZEND_STR_PREVIOUS, + ZEND_STR__LAST +}; +extern zend_string *ddtrace_known_strings[ZEND_STR__LAST]; +#define ZSTR_KNOWN(idx) ddtrace_known_strings[idx] + #define zend_declare_class_constant_ex(ce, name, value, access_type, doc_comment) zend_declare_class_constant(ce, ZSTR_VAL(name), ZSTR_LEN(name), value) + +// copied from PHP-7.0 source, but converting zend_long * to uint32_t * - assuming little endian +static inline void zend_property_guard_dtor(zval *el) { + efree_size(Z_PTR_P(el), sizeof(zend_ulong)); +} +static inline uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member) { + HashTable *guards; + zend_long stub, *guard; + + ZEND_ASSERT(GC_FLAGS(zobj) & IS_OBJ_USE_GUARDS); + if (GC_FLAGS(zobj) & IS_OBJ_HAS_GUARDS) { + guards = Z_PTR(zobj->properties_table[zobj->ce->default_properties_count]); + ZEND_ASSERT(guards != NULL); + if ((guard = zend_hash_find_ptr(guards, member)) != NULL) { + return (uint32_t *)(guard + 1) - 1; + } + } else { + ALLOC_HASHTABLE(guards); + zend_hash_init(guards, 8, NULL, zend_property_guard_dtor, 0); + Z_PTR(zobj->properties_table[zobj->ce->default_properties_count]) = guards; + GC_FLAGS(zobj) |= IS_OBJ_HAS_GUARDS; + } + + stub = 0; + guard = zend_hash_add_mem(guards, member, &stub, sizeof(zend_ulong)); + return (uint32_t *)(guard + 1) - 1; +} + +static inline zval *zend_read_property_ex(zend_class_entry *scope, zval *object, zend_string *name, zend_bool silent, zval *rv) { + zval property, *value; + zend_class_entry *old_scope = EG(scope); + + EG(scope) = scope; + + if (!Z_OBJ_HT_P(object)->read_property) { + zend_error_noreturn(E_CORE_ERROR, "Property %s of class %s cannot be read", ZSTR_VAL(name), ZSTR_VAL(Z_OBJCE_P(object)->name)); + } + + ZVAL_STR(&property, name); + value = Z_OBJ_HT_P(object)->read_property(object, &property, silent?BP_VAR_IS:BP_VAR_R, NULL, rv); + + EG(scope) = old_scope; + return value; +} #endif #if PHP_VERSION_ID < 70200 @@ -135,6 +204,15 @@ static inline zend_string *php_base64_encode_str(const zend_string *str) { } #define DD_PARAM_PROLOGUE(deref, separate) Z_PARAM_PROLOGUE(deref) + +#define ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(ht, _key, _val) \ + ZEND_HASH_REVERSE_FOREACH(ht, 0); \ + _key = _p->key; \ + _val = _z; + +#if PHP_VERSION_ID >= 70100 +#define ZSTR_KNOWN(idx) CG(known_strings)[idx] +#endif #else #define DD_PARAM_PROLOGUE Z_PARAM_PROLOGUE #endif @@ -156,12 +234,7 @@ static inline HashTable *zend_new_array(uint32_t nSize) { return ht; } -#define DD_ZVAL_EMPTY_ARRAY(z) do { \ - zval *__z = (z); \ - Z_ARR_P(__z) = zend_new_array(0); \ - Z_TYPE_INFO_P(__z) = IS_ARRAY; \ - } while (0) -#define ZVAL_EMPTY_ARRAY DD_ZVAL_EMPTY_ARRAY +#define ZVAL_EMPTY_ARRAY(z) ZVAL_ARR(z, zend_new_array(0)) #define GC_IS_RECURSIVE(gc) ((gc)->u.v.nApplyCount > 0) #define GC_PROTECT_RECURSION(gc) (++(gc)->u.v.nApplyCount) @@ -174,6 +247,8 @@ static inline HashTable *zend_new_array(uint32_t nSize) { #define ZEND_CLOSURE_OBJECT(op_array) \ ((zend_object*)((char*)(op_array) - sizeof(zend_object))) +#define zend_std_read_dimension std_object_handlers.read_dimension + // make ZEND_STRL work #undef zend_hash_str_update #define zend_hash_str_update(...) _zend_hash_str_update(__VA_ARGS__ ZEND_FILE_LINE_CC) @@ -184,6 +259,12 @@ static inline HashTable *zend_new_array(uint32_t nSize) { #undef zend_hash_str_add_new #define zend_hash_str_add_new(...) _zend_hash_str_add_new(__VA_ARGS__ ZEND_FILE_LINE_CC) +#define zend_hash_real_init_packed(ht) zend_hash_real_init(ht, 1) +#define zend_hash_real_init_mixed(ht) zend_hash_real_init(ht, 0) +#define _zend_hash_append_ex(ht, key, zv, known) _zend_hash_append(ht, key, zv) +#define zend_hash_find_ex(ht, key, known) zend_hash_find(ht, key) +#define zend_hash_find_ex_ind(ht, key, known) zend_hash_find_ind(ht, key) + #define smart_str_free_ex(str, persistent) smart_str_free(str) static inline zend_bool zend_ini_parse_bool(zend_string *str) { @@ -195,12 +276,42 @@ static inline zend_bool zend_ini_parse_bool(zend_string *str) { return atoi(ZSTR_VAL(str)) != 0; } } + +static inline zend_string *zend_ini_get_value(zend_string *name) { + zend_ini_entry *ini_entry; + + ini_entry = zend_hash_find_ptr(EG(ini_directives), name); + if (ini_entry) { + return ini_entry->value ? ini_entry->value : ZSTR_EMPTY_ALLOC(); + } else { + return NULL; + } +} + +#define ZVAL_DEINDIRECT(z) do { \ + if (Z_TYPE_P(z) == IS_INDIRECT) { \ + (z) = Z_INDIRECT_P(z); \ + } \ + } while (0) + #endif #if PHP_VERSION_ID < 70400 #define ZEND_THIS (&EX(This)) #define Z_PROP_FLAG_P(z) Z_EXTRA_P(z) +#define ZVAL_COPY_VALUE_PROP ZVAL_COPY_VALUE + +#define ZEND_HASH_FILL_SET(_val) do { \ + ZVAL_COPY_VALUE(&__fill_bkt->val, _val); \ + __fill_bkt->h = (__fill_idx); \ + __fill_bkt->key = NULL; \ + } while (0) + +#define ZEND_HASH_FILL_NEXT() do { \ + __fill_bkt++; \ + __fill_idx++; \ + } while (0) #define DD_PARAM_ERROR_CODE error_code #else @@ -229,6 +340,8 @@ static inline const zend_function *dd_zend_get_closure_method_def(zend_object *o } #define zend_get_closure_method_def dd_zend_get_closure_method_def +#define instanceof_function_slow instanceof_function + #define ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, classname, allow_null, default_value) ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) #define ZEND_ARG_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask, default_value) ZEND_ARG_INFO(pass_by_ref, name) #define zend_declare_typed_property(ce, name, default, visibility, doc_comment, type) zend_declare_property_ex(ce, name, default, visibility, doc_comment); (void)type @@ -325,8 +438,28 @@ static zend_always_inline void zend_array_release(zend_array *array) } } +#define ZEND_UNREACHABLE() ZEND_ASSUME(0) + #define ZEND_ARG_SEND_MODE(arg_info) (arg_info)->pass_by_reference #define zend_value_error zend_type_error + +#define zend_update_property_ex(scope, object, name, value) do { zval _zv; ZVAL_OBJ(&_zv, object); zend_update_property_ex(scope, &_zv, name, value); } while (0) + +#define is_numeric_string_ex(str, length, lval, dval, allow_errors, oflow_info, trailing_data) is_numeric_string_ex(str, length, lval, dval, allow_errors, oflow_info) + +static zend_always_inline int zend_compare(zval *op1, zval *op2) { + zval result; + if (compare_function(&result, op1, op2) == FAILURE) { + return 1; + } + return Z_LVAL(result); +} + +// const cast +#undef Z_OBJPROP_P +#define Z_OBJPROP_P(zv) Z_OBJPROP(*(zval *)(zv)) +#undef Z_OBJDEBUG_P +#define Z_OBJDEBUG_P(zv, is_temp) Z_OBJDEBUG(*(zval *)(zv), is_temp) #endif #if PHP_VERSION_ID < 80100 @@ -358,6 +491,32 @@ static inline void smart_str_append_double(smart_str *str, double num, int preci } } +#define zend_hash_find_known_hash zend_hash_find + +static zend_always_inline bool zend_array_is_list(zend_array *array) { + zend_long expected_idx = 0; + zend_long num_idx; + zend_string* str_idx; + /* Empty arrays are lists */ + if (zend_hash_num_elements(array) == 0) { + return 1; + } + +#if PHP_VERSION_ID >= 70100 + if (HT_IS_PACKED(array) && HT_IS_WITHOUT_HOLES(array)) { + return 1; + } +#endif + + /* Check if the list could theoretically be repacked */ + ZEND_HASH_FOREACH_KEY(array, num_idx, str_idx) { + if (str_idx != NULL || num_idx != expected_idx++) { + return 0; + } + } ZEND_HASH_FOREACH_END(); + + return 1; +} #endif #if PHP_VERSION_ID < 80200 @@ -405,6 +564,8 @@ static inline zend_string *ddtrace_strpprintf(size_t max_len, const char *format #define zend_strpprintf ddtrace_strpprintf #define ZEND_HASH_ELEMENT(ht, idx) (&ht->arData[idx].val) +#define ZEND_HASH_MAP_FOREACH_PTR ZEND_HASH_FOREACH_PTR +#define ZEND_HASH_MAP_FOREACH_STR_KEY_VAL ZEND_HASH_FOREACH_STR_KEY_VAL #if PHP_VERSION_ID >= 80000 #define zend_weakrefs_hash_add zend_weakrefs_hash_add_fallback @@ -442,12 +603,24 @@ static zend_always_inline zend_result zend_call_function_with_return_value(zend_ #define Z_PARAM_ZVAL_OR_NULL(dest) Z_PARAM_ZVAL_EX(dest, 1, 0) +#define ZEND_GUARD_PROPERTY_MASK 0xf + +// strip const +#if PHP_VERSION_ID < 70300 +#undef zval_get_double +#define zval_get_double(zv) _zval_get_double((zval *)(zv)) +#else +#define zval_get_double(zv) zval_get_double((zval *)(zv)) +#endif + #endif #if PHP_VERSION_ID < 80400 #define zend_parse_arg_func(arg, dest_fci, dest_fcc, check_null, error, free_trampoline) zend_parse_arg_func(arg, dest_fci, dest_fcc, check_null, error) #undef ZEND_RAW_FENTRY #define ZEND_RAW_FENTRY(zend_name, name, arg_info, flags, ...) { zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags }, + +#define hasThis() (Z_TYPE_P(ZEND_THIS) == IS_OBJECT) #endif #endif // DD_COMPATIBILITY_H diff --git a/ext/coms.c b/ext/coms.c index 15ace13601..f5baf81077 100644 --- a/ext/coms.c +++ b/ext/coms.c @@ -805,11 +805,15 @@ static void _dd_curl_set_headers(struct _writer_loop_data_t *writer, size_t trac headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); headers = curl_slist_append(headers, "Content-Type: application/msgpack"); - char buffer[64]; + char buffer[300]; int bytes_written = snprintf(buffer, sizeof buffer, DD_TRACE_COUNT_HEADER "%zu", trace_count); if (bytes_written > ((int)sizeof(DD_TRACE_COUNT_HEADER)) - 1 && bytes_written < ((int)sizeof buffer)) { headers = curl_slist_append(headers, buffer); } + if (*ddtrace_coms_globals.test_session_token) { + sprintf(buffer, "x-datadog-test-session-token: %s", ddtrace_coms_globals.test_session_token); + headers = curl_slist_append(headers, buffer); + } _dd_curl_reset_headers(writer); @@ -863,6 +867,10 @@ static void _dd_curl_send_stack(struct _writer_loop_data_t *writer, ddtrace_coms // We can ignore that for now as we don't do TLS traffic to the agent currently curl_easy_setopt(writer->curl, CURLOPT_NOSIGNAL, 1); + if (response.s) { + smart_str_free_ex(&response, true); + } + continue; } else { if (get_global_DD_TRACE_DEBUG_CURL_OUTPUT()) { @@ -1064,6 +1072,11 @@ static void *_dd_writer_loop(void *_) { ddtrace_curl_set_timeout(writer->curl); ddtrace_curl_set_connect_timeout(writer->curl); struct curl_slist *headers = curl_slist_append(NULL, "Content-Type: application/json"); + if (*ddtrace_coms_globals.test_session_token) { + char buffer[300]; + sprintf(buffer, "x-datadog-test-session-token: %s", ddtrace_coms_globals.test_session_token); + headers = curl_slist_append(headers, buffer); + } curl_easy_setopt(writer->curl, CURLOPT_HTTPHEADER, headers); ddtrace_curl_set_telemetry_url(writer->curl); curl_easy_perform(writer->curl); @@ -1385,6 +1398,16 @@ bool ddtrace_in_writer_thread(void) { return (pthread_self() == writer->thread->self); } +void ddtrace_coms_set_test_session_token(const char *token, size_t token_len) { + if (token_len > 255) { + token_len = 255; + } + // We don't care too much about incorrectness caused by race-conditions here: it's just testing code + // And it won't ever crash as the 255th byte is always 0. + memcpy(ddtrace_coms_globals.test_session_token, token, token_len); + ddtrace_coms_globals.test_session_token[token_len] = 0; +} + /* for testing {{{ */ #define DDTRACE_NUMBER_OF_DATA_TO_WRITE 2000 #define DDTRACE_DATA_TO_WRITE "0123456789" diff --git a/ext/coms.h b/ext/coms.h index d1a521b616..cfb7e1c0aa 100644 --- a/ext/coms.h +++ b/ext/coms.h @@ -45,6 +45,8 @@ typedef struct ddtrace_coms_state_t { /* Whether to send fallback telemetry. */ bool bgs_fallback_telemetry; char initial_service_name[100]; + + char test_session_token[255]; } ddtrace_coms_state_t; inline bool ddtrace_coms_is_stack_unused(ddtrace_coms_stack_t *stack) { return atomic_load(&stack->refcount) == 0; } @@ -61,6 +63,7 @@ void ddtrace_coms_mshutdown(void); void ddtrace_coms_curl_shutdown(void); void ddtrace_coms_rshutdown(void); uint32_t ddtrace_coms_next_group_id(void); +void ddtrace_coms_set_test_session_token(const char *token, size_t token_len); bool ddtrace_coms_init_and_start_writer(void); bool ddtrace_coms_trigger_writer_flush(void); diff --git a/ext/configuration.c b/ext/configuration.c index 347dc9f907..ab51b810e6 100644 --- a/ext/configuration.c +++ b/ext/configuration.c @@ -5,8 +5,12 @@ #include "ip_extraction.h" #include "logging.h" #include "json/json.h" +#include "sidecar.h" #include #include +#include "sidecar.h" + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); #define DD_TO_DATADOG_INC 5 /* "DD" expanded to "datadog" */ @@ -42,9 +46,10 @@ DD_CONFIGURATION #undef CALIAS #define CONFIG(...) #define ELEMENT(arg) 1, -#define CALIASES(...) APPLY_N(ELEMENT, ##__VA_ARGS__) +#define CALIASES(...) (APPLY_N(ELEMENT, ##__VA_ARGS__)) +#define ELEMENTS(...) __VA_ARGS__ #define CALIAS(type, name, default, aliases, ...) \ - _Static_assert(sizeof((uint8_t[]){aliases}) < ZAI_CONFIG_NAMES_COUNT_MAX, \ + _Static_assert(sizeof((uint8_t[]){ELEMENTS aliases}) < ZAI_CONFIG_NAMES_COUNT_MAX, \ #name " has more than the allowed ZAI_CONFIG_NAMES_COUNT_MAX alias names"); DD_CONFIGURATION #undef CALIAS @@ -89,22 +94,37 @@ static bool dd_parse_sampling_rules_format(zai_str value, zval *decoded_value, b return true; } +#define INI_CHANGE_DYNAMIC_CONFIG(name, config) \ + static bool ddtrace_alter_##name(zval *old_value, zval *new_value, zend_string *new_str) { \ + UNUSED(old_value, new_value); \ + if (!DDTRACE_G(remote_config_state)) { \ + return true; \ + } \ + return ddog_remote_config_alter_dynamic_config(DDTRACE_G(remote_config_state), DDOG_CHARSLICE_C(config), dd_zend_string_to_CharSlice(new_str)); \ + } + +INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_HEADER_TAGS, "datadog.trace.header_tags") +INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_SAMPLE_RATE, "datadog.trace.sample_rate") +INI_CHANGE_DYNAMIC_CONFIG(DD_TRACE_LOGS_ENABLED, "datadog.logs_injection") + #define CALIAS_EXPAND(name) {.ptr = name, .len = sizeof(name) - 1}, +#define EXPAND_FIRST(arg, ...) arg +#define EXPAND_CALL(macro, args) macro args // I hate the "traditional" MSVC preprocessor +#define EXPAND_IDENTITY(...) __VA_ARGS__ #ifndef _WIN32 // Allow for partially defined struct initialization here #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #else #define CONFIG(...) -#define CALIASES(...) {APPLY_N(CALIAS_EXPAND, ##__VA_ARGS__)} -#define CALIAS(type, name, default, aliases, ...) const zai_str dd_config_aliases_##name[] = aliases; +#define CALIASES(...) ({APPLY_N(CALIAS_EXPAND, ##__VA_ARGS__)}) +#define CALIAS(type, name, default, aliases, ...) const zai_str dd_config_aliases_##name[] = EXPAND_CALL(EXPAND_IDENTITY, EXPAND_FIRST(aliases)); DD_CONFIGURATION #undef CALIAS #undef CONFIG #endif #define CUSTOM(...) CUSTOM -#define EXPAND_CALL(macro, args) macro args // I hate the "traditional" MSVC preprocessor #define CONFIG(type, name, ...) EXPAND_CALL(ZAI_CONFIG_ENTRY, (DDTRACE_CONFIG_##name, name, type, __VA_ARGS__)), #ifndef _WIN32 #define CALIASES(...) ((zai_str[]){APPLY_N(CALIAS_EXPAND, ##__VA_ARGS__)}) @@ -144,6 +164,8 @@ static void dd_ini_env_to_ini_name(const zai_str env_name, zai_config_name *ini_ ini_name->ptr[sizeof("datadog.trace") - 1] = '.'; } else if (env_name.ptr == strstr(env_name.ptr, "DD_APPSEC_")) { ini_name->ptr[sizeof("datadog.appsec") - 1] = '.'; + } else if (env_name.ptr == strstr(env_name.ptr, "DD_DYNAMIC_INSTRUMENTATION_")) { + ini_name->ptr[sizeof("datadog.dynamic_instrumentation") - 1] = '.'; } } else { ini_name->len = 0; @@ -154,6 +176,10 @@ static void dd_ini_env_to_ini_name(const zai_str env_name, zai_config_name *ini_ } bool ddtrace_config_minit(int module_number) { + if (ddtrace_active_sapi == DATADOG_PHP_SAPI_CLI) { + config_entries[DDTRACE_CONFIG_DD_TRACE_AUTO_FLUSH_ENABLED].default_encoded_value = (zai_str) ZAI_STR_FROM_CSTR("true"); + } + if (!zai_config_minit(config_entries, (sizeof config_entries / sizeof *config_entries), dd_ini_env_to_ini_name, module_number)) { ddtrace_log_ginit(); @@ -166,6 +192,7 @@ bool ddtrace_config_minit(int module_number) { // arduous way of accessing the decoded_value directly from zai_config_memoized_entries. zai_config_first_time_rinit(false); + ddtrace_alter_dd_trace_debug(NULL, &zai_config_memoized_entries[DDTRACE_CONFIG_DD_TRACE_DEBUG].decoded_value, NULL); ddtrace_log_ginit(); return true; } diff --git a/ext/configuration.h b/ext/configuration.h index 8328f15ade..57dd53cc7e 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -53,7 +53,7 @@ enum ddtrace_sampling_rules_format { #define DD_INTEGRATION_ANALYTICS_ENABLED_DEFAULT false #define DD_INTEGRATION_ANALYTICS_SAMPLE_RATE_DEFAULT 1 -#if PHP_VERSION_ID >= 80400 || defined(_WIN32) +#if PHP_VERSION_ID >= 80300 || defined(_WIN32) #define DD_SIDECAR_TRACE_SENDER_DEFAULT true #else #define DD_SIDECAR_TRACE_SENDER_DEFAULT false @@ -67,18 +67,18 @@ enum ddtrace_sampling_rules_format { #define DD_CFG_STR(str) #str #define DD_CFG_EXPSTR(str) DD_CFG_STR(str) -#define INTEGRATION_ALIAS(id, _, initial, alias) \ - CALIAS(BOOL, DD_TRACE_##id##_ENABLED, initial, CALIASES(DD_CFG_STR(alias))) +#define INTEGRATION_ALIAS(id, _, initial, ...) \ + CALIAS(BOOL, id, initial, __VA_ARGS__) #define INTEGRATION_WITH_DEFAULT(id, _, initial) \ - CONFIG(BOOL, DD_TRACE_##id##_ENABLED, initial) + CONFIG(BOOL, id, initial) #define INTEGRATION_NORMAL(id, _) \ - CONFIG(BOOL, DD_TRACE_##id##_ENABLED, "true") -#define GET_INTEGRATION_CONFIG_MACRO(_1, _2, DEFAULT, NAME, ...) NAME + CONFIG(BOOL, id, "true") +#define GET_INTEGRATION_CONFIG_MACRO(_1, _2, _3, DEFAULT, NAME, ...) NAME #if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL #define GET_INTEGRATION_CONFIG_MACRO_EXPAND(...) __VA_ARGS__ -#define INTEGRATION_CONFIG_ACTIVE(id, ...) GET_INTEGRATION_CONFIG_MACRO_EXPAND(GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_WITH_DEFAULT, INTEGRATION_NORMAL))GET_INTEGRATION_CONFIG_MACRO_EXPAND((id, __VA_ARGS__)) +#define INTEGRATION_CONFIG_ACTIVE(id, ...) GET_INTEGRATION_CONFIG_MACRO_EXPAND(GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_ALIAS, INTEGRATION_WITH_DEFAULT, INTEGRATION_NORMAL))GET_INTEGRATION_CONFIG_MACRO_EXPAND((DD_TRACE_##id##_ENABLED, __VA_ARGS__)) #else -#define INTEGRATION_CONFIG_ACTIVE(id, ...) GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_WITH_DEFAULT, INTEGRATION_NORMAL)(id, __VA_ARGS__) +#define INTEGRATION_CONFIG_ACTIVE(id, ...) GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_ALIAS, INTEGRATION_WITH_DEFAULT, INTEGRATION_NORMAL)(DD_TRACE_##id##_ENABLED, __VA_ARGS__) #endif #define INTEGRATION(id, ...) \ INTEGRATION_CONFIG_ACTIVE(id, __VA_ARGS__) \ @@ -90,9 +90,15 @@ enum ddtrace_sampling_rules_format { #define DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT \ "(?i)(?:(?:\"|%22)?)(?:(?:old[-_]?|new[-_]?)?p(?:ass)?w(?:or)?d(?:1|2)?|pass(?:[-_]?phrase)?|secret|(?:api[-_]?|private[-_]?|public[-_]?|access[-_]?|secret[-_]?|app(?:lication)?[-_]?)key(?:[-_]?id)?|token|consumer[-_]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:\"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:\"|%22)(?:%2[^2]|%[^2]|[^\"%])+(?:\"|%22))|(?:bearer(?:\\s|%20)+[a-z0-9._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+/=-]|%3D|%2F|%2B)+)?|-{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY-{5}[^\\-]+-{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY(?:-{5})?(?:\\n|%0A)?|(?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)(?:\\s|%20|%09)+(?:[a-z0-9/.+]|%2F|%5C|%2B){100,}(?:=|%3D)*(?:(?:\\s|%20|%09)+[a-z0-9._-]+)?)" +#ifdef __SANITIZE_ADDRESS__ +#define DD_CRASHTRACKING_ENABLED_DEFAULT "false" +#else +#define DD_CRASHTRACKING_ENABLED_DEFAULT "true" +#endif + #define DD_CONFIGURATION_ALL \ CONFIG(STRING, DD_TRACE_SOURCES_PATH, DD_DEFAULT_SOURCES_PATH, .ini_change = zai_config_system_ini_change) \ - CONFIG(STRING, DD_AUTOLOAD_NO_COMPILE, "0", .ini_change = zai_config_system_ini_change) \ + CONFIG(BOOL, DD_AUTOLOAD_NO_COMPILE, "false", .ini_change = zai_config_system_ini_change) \ CONFIG(STRING, DD_TRACE_AGENT_URL, "", .ini_change = zai_config_system_ini_change) \ CONFIG(STRING, DD_AGENT_HOST, "", .ini_change = zai_config_system_ini_change) \ CONFIG(STRING, DD_DOGSTATSD_URL, "") \ @@ -112,9 +118,10 @@ enum ddtrace_sampling_rules_format { CONFIG(INT, DD_TRACE_AGENT_PORT, "0", .ini_change = zai_config_system_ini_change) \ CONFIG(BOOL, DD_TRACE_ANALYTICS_ENABLED, "false") \ CONFIG(BOOL, DD_TRACE_APPEND_TRACE_IDS_TO_LOGS, "false") \ - CONFIG(BOOL, DD_TRACE_AUTO_FLUSH_ENABLED, "false") \ - CONFIG(BOOL, DD_TRACE_CLI_ENABLED, "false") \ + CONFIG(BOOL, DD_TRACE_AUTO_FLUSH_ENABLED, "false") /* true in CLI */ \ + CONFIG(BOOL, DD_TRACE_CLI_ENABLED, "true") \ CONFIG(BOOL, DD_TRACE_MEASURE_COMPILE_TIME, "true") \ + CONFIG(BOOL, DD_TRACE_MEASURE_PEAK_MEMORY_USAGE, "true") \ CONFIG(BOOL, DD_TRACE_DEBUG, "false", .ini_change = ddtrace_alter_dd_trace_debug) \ CONFIG(BOOL, DD_TRACE_ENABLED, "true", .ini_change = ddtrace_alter_dd_trace_disabled_config, \ .env_config_fallback = ddtrace_conf_otel_traces_exporter) \ @@ -124,11 +131,17 @@ enum ddtrace_sampling_rules_format { CONFIG(BOOL, DD_TRACE_DB_CLIENT_SPLIT_BY_INSTANCE, "false") \ CONFIG(BOOL, DD_TRACE_HTTP_CLIENT_SPLIT_BY_DOMAIN, "false") \ CONFIG(BOOL, DD_TRACE_REDIS_CLIENT_SPLIT_BY_HOST, "false") \ + CONFIG(BOOL, DD_EXCEPTION_REPLAY_ENABLED, "false") \ + CONFIG(INT, DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES, "-1") \ + CONFIG(INT, DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS, "3600") \ CONFIG(STRING, DD_TRACE_MEMORY_LIMIT, "") \ CONFIG(BOOL, DD_TRACE_REPORT_HOSTNAME, "false") \ CONFIG(BOOL, DD_TRACE_FLUSH_COLLECT_CYCLES, "false") \ CONFIG(BOOL, DD_TRACE_LARAVEL_QUEUE_DISTRIBUTED_TRACING, "true") \ + CONFIG(BOOL, DD_TRACE_SYMFONY_MESSENGER_DISTRIBUTED_TRACING, "true") \ + CONFIG(BOOL, DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES, "false") \ CONFIG(BOOL, DD_TRACE_REMOVE_ROOT_SPAN_LARAVEL_QUEUE, "true") \ + CONFIG(BOOL, DD_TRACE_REMOVE_ROOT_SPAN_SYMFONY_MESSENGER, "true") \ CONFIG(BOOL, DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS, "false") \ CONFIG(SET, DD_TRACE_RESOURCE_URI_FRAGMENT_REGEX, "") \ CONFIG(SET, DD_TRACE_RESOURCE_URI_MAPPING_INCOMING, "") \ @@ -137,13 +150,13 @@ enum ddtrace_sampling_rules_format { CONFIG(SET, DD_TRACE_HTTP_URL_QUERY_PARAM_ALLOWED, "*") \ CONFIG(SET, DD_TRACE_HTTP_POST_DATA_PARAM_ALLOWED, "") \ CONFIG(INT, DD_TRACE_RATE_LIMIT, "0", .ini_change = zai_config_system_ini_change) \ - CONFIG(DOUBLE, DD_TRACE_SAMPLE_RATE, "-1", \ + CONFIG(DOUBLE, DD_TRACE_SAMPLE_RATE, "-1", .ini_change = ddtrace_alter_DD_TRACE_SAMPLE_RATE, \ .env_config_fallback = ddtrace_conf_otel_sample_rate) \ CONFIG(JSON, DD_TRACE_SAMPLING_RULES, "[]") \ CONFIG(CUSTOM(INT), DD_TRACE_SAMPLING_RULES_FORMAT, "glob", .parser = dd_parse_sampling_rules_format) \ CONFIG(JSON, DD_SPAN_SAMPLING_RULES, "[]") \ CONFIG(STRING, DD_SPAN_SAMPLING_RULES_FILE, "", .ini_change = ddtrace_alter_sampling_rules_file_config) \ - CONFIG(SET_LOWERCASE, DD_TRACE_HEADER_TAGS, "") \ + CONFIG(SET_LOWERCASE, DD_TRACE_HEADER_TAGS, "", .ini_change = ddtrace_alter_DD_TRACE_HEADER_TAGS) \ CONFIG(INT, DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH, "512") \ CONFIG(MAP, DD_TRACE_PEER_SERVICE_MAPPING, "") \ CONFIG(BOOL, DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED, "false") \ @@ -160,6 +173,7 @@ enum ddtrace_sampling_rules_format { .ini_change = zai_config_system_ini_change) \ CONFIG(INT, DD_TRACE_DEBUG_PRNG_SEED, "-1", .ini_change = ddtrace_reseed_seed_change) \ CONFIG(BOOL, DD_LOG_BACKTRACE, "false") \ + CONFIG(BOOL, DD_CRASHTRACKING_ENABLED, DD_CRASHTRACKING_ENABLED_DEFAULT) \ CONFIG(BOOL, DD_TRACE_GENERATE_ROOT_SPAN, "true", .ini_change = ddtrace_span_alter_root_span_config) \ CONFIG(INT, DD_TRACE_SPANS_LIMIT, "1000") \ CONFIG(BOOL, DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED, "true") \ @@ -194,6 +208,7 @@ enum ddtrace_sampling_rules_format { CONFIG(INT, DD_TRACE_AGENT_MAX_PAYLOAD_SIZE, "52428800", .ini_change = zai_config_system_ini_change) \ CONFIG(INT, DD_TRACE_AGENT_STACK_INITIAL_SIZE, "131072", .ini_change = zai_config_system_ini_change) \ CONFIG(INT, DD_TRACE_AGENT_STACK_BACKLOG, "12", .ini_change = zai_config_system_ini_change) \ + CONFIG(STRING, DD_TRACE_AGENT_TEST_SESSION_TOKEN, "", .ini_change = ddtrace_alter_test_session_token) \ CONFIG(BOOL, DD_TRACE_PROPAGATE_USER_ID_DEFAULT, "false") \ CONFIG(CUSTOM(INT), DD_DBM_PROPAGATION_MODE, "disabled", .parser = dd_parse_dbm_mode) \ CONFIG(SET, DD_TRACE_WORDPRESS_ADDITIONAL_ACTIONS, "") \ @@ -205,6 +220,7 @@ enum ddtrace_sampling_rules_format { CONFIG(STRING, DD_TRACE_LOG_LEVEL, "error", .ini_change = ddtrace_alter_dd_trace_log_level, \ .env_config_fallback = ddtrace_conf_otel_log_level) \ CONFIG(BOOL, DD_APPSEC_SCA_ENABLED, "false", .ini_change = zai_config_system_ini_change) \ + CONFIG(BOOL, DD_APPSEC_TESTING, "false") \ CONFIG(BOOL, DD_TRACE_GIT_METADATA_ENABLED, "true") \ CONFIG(STRING, DD_GIT_COMMIT_SHA, "") \ CONFIG(STRING, DD_GIT_REPOSITORY_URL, "") \ @@ -214,6 +230,12 @@ enum ddtrace_sampling_rules_format { CONFIG(INT, DD_OPENAI_SPAN_CHAR_LIMIT, "128") \ CONFIG(DOUBLE, DD_OPENAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE, "1.0") \ CONFIG(DOUBLE, DD_OPENAI_LOG_PROMPT_COMPLETION_SAMPLE_RATE, "0.1") \ + CONFIG(BOOL, DD_INJECT_FORCE, "false", .ini_change = zai_config_system_ini_change) \ + CONFIG(DOUBLE, DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS, "5", .ini_change = zai_config_system_ini_change) \ + CONFIG(BOOL, DD_REMOTE_CONFIG_ENABLED, "true", .ini_change = zai_config_system_ini_change) \ + CONFIG(BOOL, DD_DYNAMIC_INSTRUMENTATION_ENABLED, "false", .ini_change = zai_config_system_ini_change) \ + CONFIG(SET, DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS, "", .ini_change = zai_config_system_ini_change) \ + CONFIG(SET, DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES, "", .ini_change = zai_config_system_ini_change) \ DD_INTEGRATIONS #ifndef _WIN32 diff --git a/ext/ddtrace.c b/ext/ddtrace.c index b51bbaaaa0..9578861dbb 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1,4 +1,3 @@ - #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -64,6 +63,7 @@ #include "priority_sampling/priority_sampling.h" #include "random.h" #include "autoload_php_files.h" +#include "remote_config.h" #include "serializer.h" #include "sidecar.h" #ifndef _WIN32 @@ -72,6 +72,7 @@ #include "span.h" #include "startup_logging.h" #include "telemetry.h" +#include "threads.h" #include "tracer_tag_propagation/tracer_tag_propagation.h" #include "user_request.h" #include "zend_hrtime.h" @@ -85,24 +86,25 @@ // On PHP 7 we cannot declare arrays as internal values. Assign null and handle in create_object where necessary. #if PHP_VERSION_ID < 80000 +#pragma push_macro("ZVAL_EMPTY_ARRAY") #undef ZVAL_EMPTY_ARRAY #define ZVAL_EMPTY_ARRAY ZVAL_NULL #endif // CG(empty_string) is not accessible during MINIT (in ZTS at least) #if PHP_VERSION_ID < 70200 +#pragma push_macro("ZVAL_EMPTY_STRING") #undef ZVAL_EMPTY_STRING #define ZVAL_EMPTY_STRING(z) ZVAL_NEW_STR(z, zend_string_init("", 0, 1)) #endif #include "ddtrace_arginfo.h" #include "distributed_tracing_headers.h" +#include "live_debugger.h" #if PHP_VERSION_ID < 70200 -#undef ZVAL_EMPTY_STRING -#define ZVAL_EMPTY_STRING(z) ZVAL_INTERNED_STR(z, ZSTR_EMPTY_ALLOC()) +#pragma pop_macro("ZVAL_EMPTY_STRING") #endif #if PHP_VERSION_ID < 80000 -#undef ZVAL_EMPTY_ARRAY -#define ZVAL_EMPTY_ARRAY DD_ZVAL_EMPTY_ARRAY +#pragma pop_macro("ZVAL_EMPTY_ARRAY") #endif // For manual ZPP @@ -359,8 +361,9 @@ bool dd_save_sampling_rules_file_config(zend_string *path, int modify_type, int return altered; } -bool ddtrace_alter_sampling_rules_file_config(zval *old_value, zval *new_value) { +bool ddtrace_alter_sampling_rules_file_config(zval *old_value, zval *new_value, zend_string *new_str) { (void) old_value; + (void) new_str; if (Z_STRLEN_P(new_value) == 0) { return true; } @@ -368,8 +371,8 @@ bool ddtrace_alter_sampling_rules_file_config(zval *old_value, zval *new_value) return dd_save_sampling_rules_file_config(Z_STR_P(new_value), PHP_INI_USER, PHP_INI_STAGE_RUNTIME); } -static inline bool dd_alter_prop(size_t prop_offset, zval *old_value, zval *new_value) { - UNUSED(old_value); +static inline bool dd_alter_prop(size_t prop_offset, zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(old_value, new_str); ddtrace_span_properties *pspan = ddtrace_active_span_props(); while (pspan) { @@ -382,16 +385,18 @@ static inline bool dd_alter_prop(size_t prop_offset, zval *old_value, zval *new_ return true; } -bool ddtrace_alter_dd_service(zval *old_value, zval *new_value) { - return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_service), old_value, new_value); +bool ddtrace_alter_dd_service(zval *old_value, zval *new_value, zend_string *new_str) { + return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_service), old_value, new_value, new_str); } -bool ddtrace_alter_dd_env(zval *old_value, zval *new_value) { - return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_env), old_value, new_value); +bool ddtrace_alter_dd_env(zval *old_value, zval *new_value, zend_string *new_str) { + return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_env), old_value, new_value, new_str); } -bool ddtrace_alter_dd_version(zval *old_value, zval *new_value) { - return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_version), old_value, new_value); +bool ddtrace_alter_dd_version(zval *old_value, zval *new_value, zend_string *new_str) { + return dd_alter_prop(XtOffsetOf(ddtrace_span_properties, property_version), old_value, new_value, new_str); } +static zend_module_entry *dd_appsec_module() { return zend_hash_str_find_ptr(&module_registry, "ddappsec", sizeof("ddappsec") - 1); } + static void dd_activate_once(void) { ddtrace_config_first_rinit(); ddtrace_generate_runtime_id(); @@ -413,13 +418,14 @@ static void dd_activate_once(void) { bgs_service = ddtrace_default_service_name(); } } - if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) + if (get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED() || get_global_DD_TRACE_SIDECAR_TRACE_SENDER() || + (dd_appsec_module() != NULL && !get_global_DD_APPSEC_TESTING())) #endif { - bool modules_activated = PG(modules_activated); - PG(modules_activated) = false; + bool request_startup = PG(during_request_startup); + PG(during_request_startup) = false; ddtrace_sidecar_setup(); - PG(modules_activated) = modules_activated; + PG(during_request_startup) = request_startup; } #ifndef _WIN32 if (!get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) { @@ -435,6 +441,10 @@ static void dd_activate_once(void) { get_global_DD_TRACE_AGENT_MAX_PAYLOAD_SIZE(), get_global_DD_TRACE_AGENT_STACK_BACKLOG(), bgs_fallback ? ZSTR_VAL(bgs_service) : NULL); + zend_string *testing_token = get_global_DD_TRACE_AGENT_TEST_SESSION_TOKEN(); + if (ZSTR_LEN(testing_token)) { + ddtrace_coms_set_test_session_token(ZSTR_VAL(testing_token), ZSTR_LEN(testing_token)); + } if (bgs_fallback) { zend_string_release(bgs_service); } @@ -445,6 +455,13 @@ static void dd_activate_once(void) { static pthread_once_t dd_activate_once_control = PTHREAD_ONCE_INIT; +static bool dd_is_cli_autodisabled(const char *arg) { + const char *slashend = strrchr(arg, '/'); + const char *backslashend = strrchr(arg, '\\'); + arg = MAX(MAX(slashend, backslashend) + 1, arg); + return strcmp(arg, "composer") == 0 || strcmp(arg, "composer.phar") == 0; +} + static void ddtrace_activate(void) { ddog_reset_logger(); @@ -467,13 +484,23 @@ static void ddtrace_activate(void) { ddtrace_sidecar_ensure_active(); } + ddtrace_sidecar_rinit(); + zend_string *sampling_rules_file = get_DD_SPAN_SAMPLING_RULES_FILE(); if (ZSTR_LEN(sampling_rules_file) > 0 && !zend_string_equals(get_global_DD_SPAN_SAMPLING_RULES_FILE(), sampling_rules_file)) { dd_save_sampling_rules_file_config(sampling_rules_file, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); } - if (!ddtrace_disable && strcmp(sapi_module.name, "cli") == 0 && !get_DD_TRACE_CLI_ENABLED()) { - ddtrace_disable = 2; + if (!ddtrace_disable && strcmp(sapi_module.name, "cli") == 0) { + if (zai_config_memoized_entries[DDTRACE_CONFIG_DD_TRACE_CLI_ENABLED].name_index < 0 && SG(request_info).argv && dd_is_cli_autodisabled(SG(request_info).argv[0])) { + zend_string *zero = zend_string_init("0", 1, 0); + zend_alter_ini_entry(zai_config_memoized_entries[DDTRACE_CONFIG_DD_TRACE_CLI_ENABLED].ini_entries[0]->name, zero, + ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME); + zend_string_release(zero); + } + if (!get_DD_TRACE_CLI_ENABLED()) { + ddtrace_disable = 2; + } } if (ddtrace_disable) { @@ -530,6 +557,12 @@ static PHP_GINIT_FUNCTION(ddtrace) { ZEND_TSRMLS_CACHE_UPDATE(); #endif php_ddtrace_init_globals(ddtrace_globals); +#if PHP_VERSION_ID < 70100 + zai_vm_interrupt = &ddtrace_globals->zai_vm_interrupt; +#endif +#if ZTS + ddtrace_thread_ginit(); +#endif zai_hook_ginit(); zend_hash_init(&ddtrace_globals->git_metadata, 8, unused, (dtor_func_t)ddtrace_git_metadata_dtor, 1); } @@ -603,8 +636,14 @@ static void dd_clean_main_thread_locals() { #endif static PHP_GSHUTDOWN_FUNCTION(ddtrace) { - if (ddtrace_globals->remote_config_reader) { - ddog_agent_remote_config_reader_drop(ddtrace_globals->remote_config_reader); +#if ZTS + ddtrace_thread_gshutdown(); +#endif + if (ddtrace_globals->agent_config_reader) { + ddog_agent_remote_config_reader_drop(ddtrace_globals->agent_config_reader); + } + if (ddtrace_globals->remote_config_state) { + ddog_shutdown_remote_config(ddtrace_globals->remote_config_state); } zai_hook_gshutdown(); if (ddtrace_globals->telemetry_buffer) { @@ -621,6 +660,138 @@ static PHP_GSHUTDOWN_FUNCTION(ddtrace) { #endif } +static void dd_span_event_construct(ddtrace_span_event *event, zend_string *name, zend_long timestamp, zval *attributes) +{ + zval garbage_name, garbage_timestamp, garbage_attributes; + + // Copy current values to temporary zval variables + ZVAL_COPY_VALUE(&garbage_name, &event->property_name); + ZVAL_COPY_VALUE(&garbage_timestamp, &event->property_timestamp); + ZVAL_COPY_VALUE(&garbage_attributes, &event->property_attributes); + + ZVAL_STR_COPY(&event->property_name, name); + + // Use the provided timestamp or the current time in nanoseconds + if (timestamp == 0) { + struct timespec ts; + timespec_get(&ts, TIME_UTC); + timestamp = ts.tv_sec * ZEND_NANO_IN_SEC + ts.tv_nsec; + } + ZVAL_LONG(&event->property_timestamp, timestamp); + + // Initialize attributes + if (attributes) { + ZVAL_COPY(&event->property_attributes, attributes); + } else { + array_init(&event->property_attributes); + } + + // Free the copied values after replacement + zval_ptr_dtor(&garbage_name); + zval_ptr_dtor(&garbage_timestamp); + zval_ptr_dtor(&garbage_attributes); +} + +/* DDTrace\SpanEvent */ +zend_class_entry *ddtrace_ce_span_event; + +PHP_METHOD(DDTrace_SpanEvent, jsonSerialize) { + ddtrace_span_event *event = (ddtrace_span_event*)Z_OBJ_P(ZEND_THIS); + + zval array; + array_init(&array); + + Z_TRY_ADDREF(event->property_name); + add_assoc_zval_ex(&array, ZEND_STRL("name"), &event->property_name); + Z_TRY_ADDREF(event->property_timestamp); + add_assoc_zval_ex(&array, ZEND_STRL("time_unix_nano"), &event->property_timestamp); + + // Handle attributes dynamically + zval *attributes = &event->property_attributes; + zval combined_attributes; + array_init(&combined_attributes); + + if (instanceof_function(event->std.ce, ddtrace_ce_exception_span_event)) { + // Handle exception attributes dynamically if an exception property exists + ddtrace_exception_span_event *exception_event = (ddtrace_exception_span_event *) event; + zval *exception = &exception_event->property_exception; + if (Z_TYPE_P(exception) == IS_OBJECT && instanceof_function(Z_OBJCE_P(exception), zend_ce_throwable)) { + // Get exception message, type, and stack trace directly + zend_string *message = zai_exception_message(Z_OBJ_P(exception)); + if (ZSTR_LEN(message)) { + add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.message"), zend_string_copy(message)); + } + add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.type"), zend_string_copy(Z_OBJCE_P(exception)->name)); + + // Get the exception stack trace using zai_get_trace_without_args_from_exception + zend_string *stacktrace = zai_get_trace_without_args_from_exception(Z_OBJ_P(exception)); + add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.stacktrace"), stacktrace); + } + } + + if (Z_TYPE_P(attributes) == IS_ARRAY) { + zend_hash_copy(Z_ARRVAL(combined_attributes), Z_ARRVAL_P(attributes), (copy_ctor_func_t)zval_add_ref); + } + + if (zend_hash_num_elements(Z_ARRVAL(combined_attributes)) > 0) { + add_assoc_zval_ex(&array, ZEND_STRL("attributes"), &combined_attributes); + } else { + zval_ptr_dtor(&combined_attributes); // Clean up if no elements + } + + RETURN_ARR(Z_ARR(array)); // Return the array +} + +PHP_METHOD(DDTrace_SpanEvent, __construct) +{ + UNUSED(return_value); + + zend_string *name; + zval *attributes = NULL; + zend_long timestamp = 0; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(name) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_EX(attributes, 1, 0) + Z_PARAM_LONG(timestamp) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_span_event *event = (ddtrace_span_event*)Z_OBJ_P(ZEND_THIS); + + // Use the static function to set properties and handle cleanup + dd_span_event_construct(event, name, timestamp, attributes); +} + +/* DDTrace\ExceptionSpanEvent */ +zend_class_entry *ddtrace_ce_exception_span_event; + +PHP_METHOD(DDTrace_ExceptionSpanEvent, __construct) +{ + UNUSED(return_value); + + zval *exception; + zval *attributes = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_EX(attributes, 1, 0) + ZEND_PARSE_PARAMETERS_END(); + + ddtrace_exception_span_event *event = (ddtrace_exception_span_event*)Z_OBJ_P(ZEND_THIS); + + // Use the static function to set properties and handle cleanup + zend_string *name = zend_string_init(ZEND_STRL("exception"), 0); + dd_span_event_construct(&event->span_event, name, 0, attributes); + zend_string_release(name); + + zval garbage; + ZVAL_COPY_VALUE(&garbage, &event->property_exception); + ZVAL_COPY(&event->property_exception, exception); + zval_ptr_dtor(&garbage); +} + /* DDTrace\SpanLink */ zend_class_entry *ddtrace_ce_span_link; @@ -714,6 +885,7 @@ static zend_object *dd_init_span_data_object(zend_class_entry *class_type, ddtra array_init(&span->property_metrics); array_init(&span->property_meta_struct); array_init(&span->property_links); + array_init(&span->property_events); array_init(&span->property_peer_service_sources); #endif // Explicitly assign property-mapped NULLs @@ -889,6 +1061,7 @@ static zval *ddtrace_root_span_data_write(zend_object *object, zend_string *memb #endif ddtrace_root_span_data *span = ROOTSPANDATA(obj); zval zv; + bool root_span_data_changed = false; if (zend_string_equals_literal(prop_name, "parentId")) { if (Z_TYPE_P(value) == IS_LONG && Z_LVAL_P(value)) { span->parent_id = (uint64_t) Z_LVAL_P(value); @@ -910,14 +1083,32 @@ static zval *ddtrace_root_span_data_write(zend_object *object, zend_string *memb }; value = &span->property_id; } + } else if (zend_string_equals_literal(prop_name, "service")) { + if (ddtrace_span_is_entrypoint_root(&span->span) && !zend_is_identical(&span->property_service, value)) { + root_span_data_changed = true; + } + } else if (zend_string_equals_literal(prop_name, "env")) { + if (ddtrace_span_is_entrypoint_root(&span->span) && !zend_is_identical(&span->property_env, value)) { + root_span_data_changed = true; + } + } else if (zend_string_equals_literal(prop_name, "version")) { + if (ddtrace_span_is_entrypoint_root(&span->span) && !zend_is_identical(&span->property_version, value)) { + root_span_data_changed = true; + } } else if (zend_string_equals_literal(prop_name, "samplingPriority")) { span->explicit_sampling_priority = zval_get_long(value) != DDTRACE_PRIORITY_SAMPLING_UNKNOWN; } #if PHP_VERSION_ID >= 70400 - return ddtrace_span_data_readonly(object, member, value, cache_slot); + zval *ret = ddtrace_span_data_readonly(object, member, value, cache_slot); #else ddtrace_span_data_readonly(object, member, value, cache_slot); +#endif + if (root_span_data_changed) { + ddtrace_sidecar_submit_root_span_data(); + } +#if PHP_VERSION_ID >= 70400 + return ret; #endif } @@ -1066,6 +1257,31 @@ static void dd_disable_if_incompatible_sapi_detected(void) { } } +#if PHP_VERSION_ID < 70100 +zend_string *ddtrace_known_strings[ZEND_STR__LAST]; +void ddtrace_init_known_strings(void) { + ddtrace_known_strings[ZEND_STR_TRACE] = zend_string_init_interned(ZEND_STRL("trace"), 1); + ddtrace_known_strings[ZEND_STR_LINE] = zend_string_init_interned(ZEND_STRL("line"), 1); + ddtrace_known_strings[ZEND_STR_FILE] = zend_string_init_interned(ZEND_STRL("file"), 1); + ddtrace_known_strings[ZEND_STR_MESSAGE] = zend_string_init_interned(ZEND_STRL("message"), 1); + ddtrace_known_strings[ZEND_STR_CODE] = zend_string_init_interned(ZEND_STRL("code"), 1); + ddtrace_known_strings[ZEND_STR_TYPE] = zend_string_init_interned(ZEND_STRL("type"), 1); + ddtrace_known_strings[ZEND_STR_FUNCTION] = zend_string_init_interned(ZEND_STRL("function"), 1); + ddtrace_known_strings[ZEND_STR_OBJECT] = zend_string_init_interned(ZEND_STRL("object"), 1); + ddtrace_known_strings[ZEND_STR_CLASS] = zend_string_init_interned(ZEND_STRL("class"), 1); + ddtrace_known_strings[ZEND_STR_OBJECT_OPERATOR] = zend_string_init_interned(ZEND_STRL("->"), 1); + ddtrace_known_strings[ZEND_STR_PAAMAYIM_NEKUDOTAYIM] = zend_string_init_interned(ZEND_STRL("::"), 1); + ddtrace_known_strings[ZEND_STR_ARGS] = zend_string_init_interned(ZEND_STRL("args"), 1); + ddtrace_known_strings[ZEND_STR_UNKNOWN] = zend_string_init_interned(ZEND_STRL("unknown"), 1); + ddtrace_known_strings[ZEND_STR_EVAL] = zend_string_init_interned(ZEND_STRL("eval"), 1); + ddtrace_known_strings[ZEND_STR_INCLUDE] = zend_string_init_interned(ZEND_STRL("include"), 1); + ddtrace_known_strings[ZEND_STR_REQUIRE] = zend_string_init_interned(ZEND_STRL("require"), 1); + ddtrace_known_strings[ZEND_STR_INCLUDE_ONCE] = zend_string_init_interned(ZEND_STRL("include_once"), 1); + ddtrace_known_strings[ZEND_STR_REQUIRE_ONCE] = zend_string_init_interned(ZEND_STRL("require_once"), 1); + ddtrace_known_strings[ZEND_STR_PREVIOUS] = zend_string_init_interned(ZEND_STRL("previous"), 1); +} +#endif + static PHP_MINIT_FUNCTION(ddtrace) { UNUSED(type); @@ -1096,6 +1312,10 @@ static PHP_MINIT_FUNCTION(ddtrace) { ddtrace_startup_hrtime(); #endif +#if PHP_VERSION_ID < 70100 + ddtrace_init_known_strings(); +#endif + register_ddtrace_symbols(module_number); REGISTER_INI_ENTRIES(); @@ -1165,6 +1385,8 @@ static PHP_MINIT_FUNCTION(ddtrace) { dd_register_fatal_error_ce(); ddtrace_ce_integration = register_class_DDTrace_Integration(); ddtrace_ce_span_link = register_class_DDTrace_SpanLink(php_json_serializable_ce); + ddtrace_ce_span_event = register_class_DDTrace_SpanEvent(php_json_serializable_ce); + ddtrace_ce_exception_span_event = register_class_DDTrace_ExceptionSpanEvent(ddtrace_ce_span_event); ddtrace_ce_git_metadata = register_class_DDTrace_GitMetadata(); ddtrace_ce_git_metadata->create_object = ddtrace_git_metadata_create; memcpy(&ddtrace_git_metadata_handlers, &std_object_handlers, sizeof(zend_object_handlers)); @@ -1177,6 +1399,9 @@ static PHP_MINIT_FUNCTION(ddtrace) { dd_ip_extraction_startup(); ddtrace_serializer_startup(); + ddtrace_live_debugger_minit(); + ddtrace_minit_remote_config(); + return SUCCESS; } @@ -1194,6 +1419,8 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { return SUCCESS; } + ddtrace_mshutdown_remote_config(); + if (DDTRACE_G(agent_rate_by_service)) { zai_json_release_persistent_array(DDTRACE_G(agent_rate_by_service)); DDTRACE_G(agent_rate_by_service) = NULL; @@ -1260,6 +1487,7 @@ static void dd_rinit_once(void) { static pthread_once_t dd_rinit_once_control = PTHREAD_ONCE_INIT; static void dd_initialize_request(void) { + DDTRACE_G(request_initialized) = true; DDTRACE_G(distributed_trace_id) = (ddtrace_trace_id){0}; DDTRACE_G(distributed_parent_trace_id) = 0; DDTRACE_G(additional_global_tags) = zend_new_array(0); @@ -1272,18 +1500,26 @@ static void dd_initialize_request(void) { // Things that should only run on the first RINIT after each minit. pthread_once(&dd_rinit_once_control, dd_rinit_once); - if (!DDTRACE_G(remote_config_reader)) { + if (!DDTRACE_G(agent_config_reader)) { if (get_global_DD_TRACE_SIDECAR_TRACE_SENDER()) { if (ddtrace_endpoint) { - DDTRACE_G(remote_config_reader) = ddog_agent_remote_config_reader_for_endpoint(ddtrace_endpoint); + DDTRACE_G(agent_config_reader) = ddog_agent_remote_config_reader_for_endpoint(ddtrace_endpoint); } #ifndef _WIN32 } else if (ddtrace_coms_agent_config_handle) { - ddog_agent_remote_config_reader_for_anon_shm(ddtrace_coms_agent_config_handle, &DDTRACE_G(remote_config_reader)); + ddog_agent_remote_config_reader_for_anon_shm(ddtrace_coms_agent_config_handle, &DDTRACE_G(agent_config_reader)); #endif } } + if (!DDTRACE_G(remote_config_state) && ddtrace_endpoint) { + DDTRACE_G(remote_config_state) = ddog_init_remote_config_state(ddtrace_endpoint); + } + + if (DDTRACE_G(remote_config_state)) { + ddtrace_rinit_remote_config(); + } + ddtrace_internal_handlers_rinit(); ddtrace_log_rinit(PG(error_log)); @@ -1306,10 +1542,6 @@ static void dd_initialize_request(void) { ddtrace_distributed_tracing_result distributed_result = ddtrace_read_distributed_tracing_ids(ddtrace_read_zai_header, NULL); ddtrace_apply_distributed_tracing_result(&distributed_result, NULL); - if (!DDTRACE_G(telemetry_queue_id)) { - DDTRACE_G(telemetry_queue_id) = ddog_sidecar_queueId_generate(); - } - if (get_DD_TRACE_GENERATE_ROOT_SPAN()) { ddtrace_push_root_span(); } @@ -1423,10 +1655,13 @@ void dd_force_shutdown_tracing(void) { DDTRACE_G(in_shutdown) = false; } -static void dd_finalize_telemetry(void) { - if (DDTRACE_G(telemetry_queue_id)) { +static void dd_finalize_sidecar_lifecycle(void) { + if (DDTRACE_G(request_initialized)) { ddtrace_telemetry_finalize(); - DDTRACE_G(telemetry_queue_id) = 0; + if (ddtrace_sidecar) { + ddtrace_ffi_try("Failed signaling lifecycle end", + ddog_sidecar_lifecycle_end(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id))); + } } } @@ -1445,6 +1680,10 @@ static PHP_RSHUTDOWN_FUNCTION(ddtrace) { dd_shutdown_hooks_and_observer(); } + if (DDTRACE_G(remote_config_state)) { + ddtrace_rshutdown_remote_config(); + } + if (!ddtrace_disable) { ddtrace_autoload_rshutdown(); @@ -1452,8 +1691,9 @@ static PHP_RSHUTDOWN_FUNCTION(ddtrace) { DDTRACE_G(active_stack) = NULL; } - dd_finalize_telemetry(); + dd_finalize_sidecar_lifecycle(); ddtrace_telemetry_rshutdown(); + ddtrace_sidecar_rshutdown(); if (DDTRACE_G(last_flushed_root_service_name)) { zend_string_release(DDTRACE_G(last_flushed_root_service_name)); @@ -1482,6 +1722,8 @@ zend_result ddtrace_post_deactivate(void) { // zai config may be accessed indirectly via other modules RSHUTDOWN, so delay this until the last possible time zai_config_rshutdown(); + + DDTRACE_G(request_initialized) = false; return SUCCESS; } @@ -1493,7 +1735,9 @@ void ddtrace_disable_tracing_in_current_request(void) { zend_string_release(zero); } -bool ddtrace_alter_dd_trace_disabled_config(zval *old_value, zval *new_value) { +bool ddtrace_alter_dd_trace_disabled_config(zval *old_value, zval *new_value, zend_string *new_str) { + (void)new_str; + if (Z_TYPE_P(old_value) == Z_TYPE_P(new_value)) { return true; } @@ -1975,9 +2219,13 @@ void dd_internal_handle_fork(void) { ddtrace_coms_clean_background_sender_after_fork(); } #endif - if (DDTRACE_G(remote_config_reader)) { - ddog_agent_remote_config_reader_drop(DDTRACE_G(remote_config_reader)); - DDTRACE_G(remote_config_reader) = NULL; + if (DDTRACE_G(agent_config_reader)) { + ddog_agent_remote_config_reader_drop(DDTRACE_G(agent_config_reader)); + DDTRACE_G(agent_config_reader) = NULL; + } + if (DDTRACE_G(remote_config_state)) { + ddog_shutdown_remote_config(DDTRACE_G(remote_config_state)); + DDTRACE_G(remote_config_state) = NULL; } ddtrace_seed_prng(); ddtrace_generate_runtime_id(); @@ -2005,7 +2253,7 @@ void dd_internal_handle_fork(void) { ddtrace_coms_init_and_start_writer(); if (ddtrace_coms_agent_config_handle) { - ddog_agent_remote_config_reader_for_anon_shm(ddtrace_coms_agent_config_handle, &DDTRACE_G(remote_config_reader)); + ddog_agent_remote_config_reader_for_anon_shm(ddtrace_coms_agent_config_handle, &DDTRACE_G(agent_config_reader)); } } #endif @@ -2181,11 +2429,11 @@ PHP_FUNCTION(dd_trace_internal_fn) { RETVAL_FALSE; if (ZSTR_LEN(function_val) > 0) { if (FUNCTION_NAME_MATCHES("finalize_telemetry")) { - dd_finalize_telemetry(); + dd_finalize_sidecar_lifecycle(); RETVAL_TRUE; } else if (params_count == 1 && FUNCTION_NAME_MATCHES("detect_composer_installed_json")) { ddog_CharSlice path = dd_zend_string_to_CharSlice(Z_STR_P(ZVAL_VARARG_PARAM(params, 0))); - ddtrace_detect_composer_installed_json(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id), path); + ddtrace_detect_composer_installed_json(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), path); RETVAL_TRUE; } else if (FUNCTION_NAME_MATCHES("dump_sidecar")) { if (!ddtrace_sidecar) { diff --git a/ext/ddtrace.h b/ext/ddtrace.h index d6b8fb1d98..8f1c14b2f2 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -23,6 +23,8 @@ extern zend_class_entry *ddtrace_ce_root_span_data; extern zend_class_entry *ddtrace_ce_span_stack; extern zend_class_entry *ddtrace_ce_fatal_error; extern zend_class_entry *ddtrace_ce_span_link; +extern zend_class_entry *ddtrace_ce_span_event; +extern zend_class_entry *ddtrace_ce_exception_span_event; extern zend_class_entry *ddtrace_ce_integration; extern zend_class_entry *ddtrace_ce_git_metadata; @@ -31,6 +33,8 @@ typedef struct ddtrace_span_data ddtrace_span_data; typedef struct ddtrace_root_span_data ddtrace_root_span_data; typedef struct ddtrace_span_stack ddtrace_span_stack; typedef struct ddtrace_span_link ddtrace_span_link; +typedef struct ddtrace_span_event ddtrace_span_event; +typedef struct ddtrace_exception_span_event ddtrace_exception_span_event; typedef struct ddtrace_git_metadata ddtrace_git_metadata; extern datadog_php_sapi ddtrace_active_sapi; @@ -55,12 +59,12 @@ bool ddtrace_tracer_is_limited(void); // prepare the tracer state to start handling a new trace void dd_prepare_for_new_trace(void); void ddtrace_disable_tracing_in_current_request(void); -bool ddtrace_alter_dd_trace_disabled_config(zval *old_value, zval *new_value); -bool ddtrace_alter_sampling_rules_file_config(zval *old_value, zval *new_value); -bool ddtrace_alter_default_propagation_style(zval *old_value, zval *new_value); -bool ddtrace_alter_dd_service(zval *old_value, zval *new_value); -bool ddtrace_alter_dd_env(zval *old_value, zval *new_value); -bool ddtrace_alter_dd_version(zval *old_value, zval *new_value); +bool ddtrace_alter_dd_trace_disabled_config(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_sampling_rules_file_config(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_default_propagation_style(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_dd_service(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_dd_env(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_dd_version(zval *old_value, zval *new_value, zend_string *new_str); void dd_force_shutdown_tracing(void); void dd_internal_handle_fork(void); #ifdef CXA_THREAD_ATEXIT_WRAPPER @@ -105,6 +109,12 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace) #endif zend_bool in_shutdown; +#if PHP_VERSION_ID < 70100 + bool zai_vm_interrupt; +#endif + bool reread_remote_configuration; + bool root_span_data_submitted; + zend_long default_priority_sampling; zend_long propagated_priority_sampling; ddtrace_span_stack *active_stack; // never NULL except tracer is disabled @@ -120,15 +130,27 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace) zend_reference *curl_multi_injecting_spans; char *cgroup_file; - ddog_QueueId telemetry_queue_id; - ddog_AgentRemoteConfigReader *remote_config_reader; + ddog_QueueId sidecar_queue_id; + ddog_AgentRemoteConfigReader *agent_config_reader; + ddog_RemoteConfigState *remote_config_state; + zend_arena *debugger_capture_arena; + ddog_Vec_DebuggerPayload exception_debugger_buffer; + HashTable active_rc_hooks; HashTable *agent_rate_by_service; zend_string *last_flushed_root_service_name; zend_string *last_flushed_root_env_name; + ddog_Vec_Tag active_global_tags; + bool request_initialized; HashTable telemetry_spans_created_per_integration; ddog_SidecarActionsBuffer *telemetry_buffer; +#if PHP_VERSION_ID >= 80000 + HashTable curl_headers; + // Multi-handle API: curl_multi_*() + HashTable curl_multi_handles; +#endif + HashTable uhook_active_hooks; HashTable uhook_closure_hooks; @@ -144,7 +166,7 @@ ZEND_END_MODULE_GLOBALS(ddtrace) # define ATTR_TLS_GLOBAL_DYNAMIC # endif extern TSRM_TLS void *ATTR_TLS_GLOBAL_DYNAMIC TSRMLS_CACHE; -# define DDTRACE_G(v) TSRMG(ddtrace_globals_id, zend_ddtrace_globals *, v) +# define DDTRACE_G(v) ZEND_TSRMG(ddtrace_globals_id, zend_ddtrace_globals *, v) #else # define DDTRACE_G(v) (ddtrace_globals.v) #endif diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index c2b6a4fd8a..f10d16dd39 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -23,6 +23,52 @@ */ const DBM_PROPAGATION_FULL = UNKNOWN; + class SpanEvent implements \JsonSerializable { + /** + * SpanEvent constructor. + * + * @param string $name The event name. + * @param int|null $timestamp The event start time in nanoseconds, if not provided set the current Unix timestamp. + * @param array $attributes Optional attributes for the event. + */ + public function __construct(string $name, array $attributes = [], ?int $timestamp = null) {} + + /** + * @var string The event name + */ + public string $name; + + /** + * @var string[] $attributes + */ + public array $attributes; + + /** + * @var int The event start time in nanoseconds, if not provided set the current Unix timestamp + */ + public int $timestamp; + + /** + * @return mixed + */ + public function jsonSerialize(): mixed {} + } + + class ExceptionSpanEvent extends SpanEvent { + /** + * ExceptionSpanEvent constructor. + * + * @param \Throwable $exception exception to record. + * @param array $attributes Optional attributes for the event. + */ + public function __construct(\Throwable $exception, array $attributes = []) {} + + /** + * @var \Throwable + */ + public \Throwable $exception; + } + class SpanLink implements \JsonSerializable { /** * @var string $traceId A 32-character, lower-case hexadecimal encoded string of the linked trace ID. This field @@ -144,6 +190,11 @@ class SpanData { */ public array $links = []; + /** + * @var SpanEvent[] $spanEvents An array of span events + */ + public array $events = []; + /** * @var string[] $peerServiceSources A sorted list of tag names used to set the `peer.service` tag. If a tag * name is added to this field and the tag exists on the span at serialization time, then the value of the tag diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index d0d048e706..6ef23c7e21 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b7ca444d39b9a8489e4e93042e0f7e7eb9aa8b05 */ + * Stub hash: fa4bda312fa3b405b09e09c6bc81a05d2a8e3372 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_trace_method, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, className, IS_STRING, 0) @@ -271,9 +271,22 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_dd_trace_synchronous_flush, 0, 0 ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "100") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_SpanLink_jsonSerialize, 0, 0, IS_MIXED, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DDTrace_SpanEvent___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timestamp, IS_LONG, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_SpanEvent_jsonSerialize, 0, 0, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DDTrace_ExceptionSpanEvent___construct, 0, 0, 1) + ZEND_ARG_OBJ_INFO(0, exception, Throwable, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() + +#define arginfo_class_DDTrace_SpanLink_jsonSerialize arginfo_class_DDTrace_SpanEvent_jsonSerialize + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DDTrace_SpanLink_fromHeaders, 0, 1, DDTrace\\SpanLink, 0) ZEND_ARG_TYPE_MASK(0, headersOrCallback, MAY_BE_ARRAY|MAY_BE_CALLABLE, NULL) ZEND_END_ARG_INFO() @@ -361,6 +374,9 @@ ZEND_FUNCTION(DDTrace_trace_function); ZEND_FUNCTION(DDTrace_trace_method); ZEND_FUNCTION(dd_untrace); ZEND_FUNCTION(dd_trace_synchronous_flush); +ZEND_METHOD(DDTrace_SpanEvent, __construct); +ZEND_METHOD(DDTrace_SpanEvent, jsonSerialize); +ZEND_METHOD(DDTrace_ExceptionSpanEvent, __construct); ZEND_METHOD(DDTrace_SpanLink, jsonSerialize); ZEND_METHOD(DDTrace_SpanLink, fromHeaders); ZEND_METHOD(DDTrace_SpanData, getDuration); @@ -444,6 +460,17 @@ static const zend_function_entry ext_functions[] = { ZEND_FE_END }; +static const zend_function_entry class_DDTrace_SpanEvent_methods[] = { + ZEND_ME(DDTrace_SpanEvent, __construct, arginfo_class_DDTrace_SpanEvent___construct, ZEND_ACC_PUBLIC) + ZEND_ME(DDTrace_SpanEvent, jsonSerialize, arginfo_class_DDTrace_SpanEvent_jsonSerialize, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static const zend_function_entry class_DDTrace_ExceptionSpanEvent_methods[] = { + ZEND_ME(DDTrace_ExceptionSpanEvent, __construct, arginfo_class_DDTrace_ExceptionSpanEvent___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_DDTrace_SpanLink_methods[] = { ZEND_ME(DDTrace_SpanLink, jsonSerialize, arginfo_class_DDTrace_SpanLink_jsonSerialize, ZEND_ACC_PUBLIC) ZEND_ME(DDTrace_SpanLink, fromHeaders, arginfo_class_DDTrace_SpanLink_fromHeaders, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) @@ -491,6 +518,52 @@ static void register_ddtrace_symbols(int module_number) REGISTER_LONG_CONSTANT("DD_TRACE_PRIORITY_SAMPLING_UNSET", DDTRACE_PRIORITY_SAMPLING_UNSET, CONST_PERSISTENT); } +static zend_class_entry *register_class_DDTrace_SpanEvent(zend_class_entry *class_entry_JsonSerializable) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "DDTrace", "SpanEvent", class_DDTrace_SpanEvent_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + zend_class_implements(class_entry, 1, class_entry_JsonSerializable); + + zval property_name_default_value; + ZVAL_UNDEF(&property_name_default_value); + zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_name_name); + + zval property_attributes_default_value; + ZVAL_UNDEF(&property_attributes_default_value); + zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1); + zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_attributes_name); + + zval property_timestamp_default_value; + ZVAL_UNDEF(&property_timestamp_default_value); + zend_string *property_timestamp_name = zend_string_init("timestamp", sizeof("timestamp") - 1, 1); + zend_declare_typed_property(class_entry, property_timestamp_name, &property_timestamp_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_timestamp_name); + + return class_entry; +} + +static zend_class_entry *register_class_DDTrace_ExceptionSpanEvent(zend_class_entry *class_entry_DDTrace_SpanEvent) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "DDTrace", "ExceptionSpanEvent", class_DDTrace_ExceptionSpanEvent_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_DDTrace_SpanEvent); + + zval property_exception_default_value; + ZVAL_UNDEF(&property_exception_default_value); + zend_string *property_exception_name = zend_string_init("exception", sizeof("exception") - 1, 1); + zend_string *property_exception_class_Throwable = zend_string_init("Throwable", sizeof("Throwable")-1, 1); + zend_declare_typed_property(class_entry, property_exception_name, &property_exception_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_exception_class_Throwable, 0, 0)); + zend_string_release(property_exception_name); + + return class_entry; +} + static zend_class_entry *register_class_DDTrace_SpanLink(zend_class_entry *class_entry_JsonSerializable) { zend_class_entry ce, *class_entry; @@ -634,6 +707,12 @@ static zend_class_entry *register_class_DDTrace_SpanData(void) zend_declare_typed_property(class_entry, property_links_name, &property_links_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); zend_string_release(property_links_name); + zval property_events_default_value; + ZVAL_EMPTY_ARRAY(&property_events_default_value); + zend_string *property_events_name = zend_string_init("events", sizeof("events") - 1, 1); + zend_declare_typed_property(class_entry, property_events_name, &property_events_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_events_name); + zval property_peerServiceSources_default_value; ZVAL_EMPTY_ARRAY(&property_peerServiceSources_default_value); zend_string *property_peerServiceSources_name = zend_string_init("peerServiceSources", sizeof("peerServiceSources") - 1, 1); diff --git a/ext/distributed_tracing_headers.c b/ext/distributed_tracing_headers.c index cc0a41bd9e..f54a4d06e4 100644 --- a/ext/distributed_tracing_headers.c +++ b/ext/distributed_tracing_headers.c @@ -263,11 +263,13 @@ static ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids_t size_t valuelen = valueend - valuestart; if (keylen == 1 && keystart[0] == 'p') { - zval zv; - ZVAL_STRINGL(&zv, valuestart, valuelen); - zend_hash_update(&result.meta_tags, span_parent_key, &zv); - zend_string_release(span_parent_key); - span_parent_key = NULL; + if (span_parent_key) { + zval zv; + ZVAL_STRINGL(&zv, valuestart, valuelen); + zend_hash_update(&result.meta_tags, span_parent_key, &zv); + zend_string_release(span_parent_key); + span_parent_key = NULL; + } } else if (keylen == 1 && keystart[0] == 's') { int extraced_priority = strtol(valuestart, NULL, 10); if ((result.priority_sampling > 0) == (extraced_priority > 0)) { diff --git a/ext/exception_serialize.c b/ext/exception_serialize.c new file mode 100644 index 0000000000..3492185672 --- /dev/null +++ b/ext/exception_serialize.c @@ -0,0 +1,518 @@ +#include +#include +#include "ddtrace.h" +#include "configuration.h" +#include "exception_serialize.h" +#include "compat_string.h" +#include "SAPI.h" +#include "components/log/log.h" +#include "sidecar.h" +#include "live_debugger.h" +#include "ext/hash/php_hash.h" +#include +#include + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); + +static zend_result dd_exception_to_error_msg(zend_object *exception, void *context, add_tag_fn_t add_tag, enum dd_exception exception_state) { + zend_string *msg = zai_exception_message(exception); + zend_long line = zval_get_long(zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_LINE))); + zend_string *file = ddtrace_convert_to_str(zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_FILE))); + + char *error_text, *status_line = NULL; + + if (SG(sapi_headers).http_response_code >= 500) { + if (SG(sapi_headers).http_status_line) { + UNUSED(asprintf(&status_line, " (%s)", SG(sapi_headers).http_status_line)); + } else { + UNUSED(asprintf(&status_line, " (%d)", SG(sapi_headers).http_response_code)); + } + } + + const char *exception_type; + switch (exception_state) { + case DD_EXCEPTION_CAUGHT: exception_type = "Caught"; break; + case DD_EXCEPTION_UNCAUGHT: exception_type = "Uncaught"; break; + default: exception_type = "Thrown"; break; + } + + int error_len = asprintf(&error_text, "%s %s%s%s%s in %s:" ZEND_LONG_FMT, exception_type, + ZSTR_VAL(exception->ce->name), status_line ? status_line : "", ZSTR_LEN(msg) > 0 ? ": " : "", + ZSTR_VAL(msg), ZSTR_VAL(file), line); + + free(status_line); + + ddtrace_string key = DDTRACE_STRING_LITERAL("error.message"); + ddtrace_string value = {error_text, error_len}; + zend_result result = add_tag(context, key, value); + + zend_string_release(file); + free(error_text); + return result; +} + +static zend_result dd_exception_to_error_type(zend_object *exception, void *context, add_tag_fn_t add_tag) { + ddtrace_string value, key = DDTRACE_STRING_LITERAL("error.type"); + + if (instanceof_function(exception->ce, ddtrace_ce_fatal_error)) { + zval *code = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_CODE)); + const char *error_type_string = "{unknown error}"; + + if (Z_TYPE_P(code) == IS_LONG) { + switch (Z_LVAL_P(code)) { + case E_ERROR: + error_type_string = "E_ERROR"; + break; + case E_CORE_ERROR: + error_type_string = "E_CORE_ERROR"; + break; + case E_COMPILE_ERROR: + error_type_string = "E_COMPILE_ERROR"; + break; + case E_USER_ERROR: + error_type_string = "E_USER_ERROR"; + break; + default: + LOG_UNREACHABLE("Unhandled error type in DDTrace\\FatalError; is a fatal error case missing?"); + } + + } else { + LOG_UNREACHABLE("Exception was a DDTrace\\FatalError but failed to get an exception code"); + } + + value = ddtrace_string_cstring_ctor((char *)error_type_string); + } else { + zend_string *type_name = exception->ce->name; + value.ptr = ZSTR_VAL(type_name); + value.len = ZSTR_LEN(type_name); + } + + return add_tag(context, key, value); +} + +static zend_result dd_exception_trace_to_error_stack(zend_string *trace, void *context, add_tag_fn_t add_tag) { + ddtrace_string key = DDTRACE_STRING_LITERAL("error.stack"); + ddtrace_string value = {ZSTR_VAL(trace), ZSTR_LEN(trace)}; + zend_result result = add_tag(context, key, value); + zend_string_release(trace); + return result; +} + +static void ddtrace_capture_string_value(zend_string *str, struct ddog_CaptureValue *value, const ddog_CaptureConfiguration *config) { + value->type = DDOG_CHARSLICE_C("string"); + if (!value->not_captured_reason.len) { + value->value = (ddog_CharSlice) {.ptr = ZSTR_VAL(str), .len = ZSTR_LEN(str)}; + if (value->value.len > config->max_length) { + char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20); + int len = sprintf(integer, "%" PRIuPTR, value->value.len); + value->size = (ddog_CharSlice) {.ptr = integer, .len = len}; + value->value.len = config->max_length; + value->truncated = true; + } + } +} + +static void ddtrace_capture_long_value(zend_long num, struct ddog_CaptureValue *value) { + value->type = DDOG_CHARSLICE_C("int"); + if (!value->not_captured_reason.len) { + char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20); + int len = sprintf(integer, ZEND_LONG_FMT, num); + value->value = (ddog_CharSlice) {.ptr = integer, .len = len}; + } +} + +void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, const ddog_CaptureConfiguration *config, int remaining_nesting) { + ZVAL_DEREF(zv); + switch (Z_TYPE_P(zv)) { + case IS_FALSE: + value->type = DDOG_CHARSLICE_C("bool"); + if (!value->not_captured_reason.len) { + value->value = DDOG_CHARSLICE_C("false"); + } + break; + + case IS_TRUE: + value->type = DDOG_CHARSLICE_C("bool"); + if (!value->not_captured_reason.len) { + value->value = DDOG_CHARSLICE_C("true"); + } + break; + + case IS_LONG: + ddtrace_capture_long_value(Z_LVAL_P(zv), value); + break; + + case IS_DOUBLE: { + value->type = DDOG_CHARSLICE_C("float"); + if (!value->not_captured_reason.len) { + char *num = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 20); + php_gcvt(Z_DVAL_P(zv), (int) EG(precision), '.', 'E', num); + value->value = (ddog_CharSlice) {.ptr = num, .len = strlen(num)}; + } + break; + } + + case IS_STRING: + ddtrace_capture_string_value(Z_STR_P(zv), value, config); + break; + + case IS_ARRAY: { + value->type = DDOG_CHARSLICE_C("array"); + if (value->not_captured_reason.len) { + break; + } + if (remaining_nesting == 0) { + value->not_captured_reason = DDOG_CHARSLICE_C("depth"); + break; + } + zval *val; + if (zend_array_is_list(Z_ARR_P(zv))) { + int remaining_fields = config->max_collection_size; + ZEND_HASH_FOREACH_VAL(Z_ARR_P(zv), val) { + if (remaining_fields-- == 0) { + value->not_captured_reason = DDOG_CHARSLICE_C("collectionSize"); + break; + } + + struct ddog_CaptureValue value_capture = {0}; + ddtrace_create_capture_value(val, &value_capture, config, remaining_nesting - 1); + ddog_capture_value_add_element(value, value_capture); + } ZEND_HASH_FOREACH_END(); + } else { + zend_long idx; + zend_string *key; + int remaining_fields = config->max_collection_size; + ZEND_HASH_FOREACH_KEY_VAL(Z_ARR_P(zv), idx, key, val) { + if (remaining_fields-- == 0) { + value->not_captured_reason = DDOG_CHARSLICE_C("collectionSize"); + break; + } + + struct ddog_CaptureValue key_capture = {0}, value_capture = {0}; + if (key) { + ddtrace_capture_string_value(key, &key_capture, config); + ddtrace_snapshot_redacted_name(&value_capture, dd_zend_string_to_CharSlice(key)); + } else { + ddtrace_capture_long_value(idx, &key_capture); + } + ddtrace_create_capture_value(val, &value_capture, config, remaining_nesting - 1); + ddog_capture_value_add_entry(value, key_capture, value_capture); + } ZEND_HASH_FOREACH_END(); + } + break; + } + + case IS_OBJECT: { + zend_class_entry *ce = Z_OBJCE_P(zv); + value->type = (ddog_CharSlice){ .ptr = ZSTR_VAL(ce->name), .len = ZSTR_LEN(ce->name) }; + if (value->not_captured_reason.len) { + break; + } + if (remaining_nesting == 0) { + value->not_captured_reason = DDOG_CHARSLICE_C("depth"); + break; + } + if (ddog_snapshot_redacted_type(dd_zend_string_to_CharSlice(ce->name))) { + value->not_captured_reason = DDOG_CHARSLICE_C("redactedType"); + break; + } + zval *val; + zend_string *key; + int remaining_fields = config->max_field_count; +#if PHP_VERSION_ID < 70400 + int is_temp = 0; +#endif + // reverse to prefer child class properties first + HashTable *ht = ce->type == ZEND_INTERNAL_CLASS ? + #if PHP_VERSION_ID < 70400 + Z_OBJDEBUG_P(zv, is_temp) + #else + zend_get_properties_for(zv, ZEND_PROP_PURPOSE_DEBUG) + #endif + : Z_OBJPROP_P(zv); + ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(ht, key, val) { + if (!key) { + continue; + } + + if (remaining_fields-- == 0) { + value->not_captured_reason = DDOG_CHARSLICE_C("fieldCount"); + break; + } + + struct ddog_CaptureValue value_capture = {0}; + ddog_CharSlice fieldname; + if (ZSTR_LEN(key) < 3 || ZSTR_VAL(key)[0]) { + fieldname = (ddog_CharSlice) {.ptr = ZSTR_VAL(key), .len = ZSTR_LEN(key)}; + } else if (ZSTR_VAL(key)[1] == '*') { // skip \0*\0 + fieldname = (ddog_CharSlice) {.ptr = ZSTR_VAL(key) + 3, .len = ZSTR_LEN(key) - 3}; + } else { + char *name = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), ZSTR_LEN(key)); + int classname_len = strlen(ZSTR_VAL(key) + 1); + memcpy(name, ZSTR_VAL(key) + 1, classname_len); + name[classname_len++] = ':'; + name[classname_len++] = ':'; + memcpy(name + classname_len, ZSTR_VAL(key) + classname_len, ZSTR_LEN(key) - classname_len); + fieldname = (ddog_CharSlice) {.ptr = name, .len = ZSTR_LEN(key)}; + } + ddtrace_snapshot_redacted_name(&value_capture, fieldname); + ZVAL_DEINDIRECT(val); + ddtrace_create_capture_value(val, &value_capture, config, remaining_nesting - 1); + ddog_capture_value_add_field(value, fieldname, value_capture); + } ZEND_HASH_FOREACH_END(); + if (ce->type == ZEND_INTERNAL_CLASS) { +#if PHP_VERSION_ID < 70400 + if (is_temp) { + zend_array_release(ht); + } +#else + zend_release_properties(ht); +#endif + } + break; + } + + case IS_RESOURCE: { + const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(zv)); + ddtrace_capture_long_value(Z_RES_P(zv)->handle, value); + value->type = (ddog_CharSlice){ .ptr = type_name, .len = strlen(type_name) }; + break; + } + + default: + value->type = DDOG_CHARSLICE_C("null"); + value->is_null = true; + } +} + +#define uuid_len 36 +#define hash_len 16 + +static ddog_DebuggerCapture *dd_create_frame_and_collect_locals(char *exception_id, char *exception_hash, int frame_num, ddog_CharSlice class_slice, ddog_CharSlice func_slice, zval *locals, zend_string *service_name, const ddog_CaptureConfiguration *capture_config, uint64_t time, void *context, add_tag_fn_t add_meta) { + char *snapshot_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), uuid_len); + ddog_snapshot_format_new_uuid((uint8_t(*)[uuid_len])snapshot_id); + + char msg[40]; + int len = sprintf(msg, "_dd.debug.error.%d.snapshot_id", frame_num); + add_meta(context, (ddtrace_string){msg, len}, (ddtrace_string){snapshot_id, 36}); + + ddog_DebuggerCapture *capture = ddog_create_exception_snapshot(&DDTRACE_G(exception_debugger_buffer), + (ddog_CharSlice){ .ptr = ZSTR_VAL(service_name), .len = ZSTR_LEN(service_name) }, + DDOG_CHARSLICE_C("php"), + (ddog_CharSlice){ .ptr = snapshot_id, .len = uuid_len }, + (ddog_CharSlice){ .ptr = exception_id, .len = uuid_len }, + (ddog_CharSlice){ .ptr = exception_hash, .len = hash_len }, + frame_num, + class_slice, + func_slice, + time); + + if (locals && Z_TYPE_P(locals) == IS_ARRAY) { + zend_string *key; + zval *val; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARR_P(locals), key, val) { + if (!zend_string_equals_literal(key, "GLOBALS")) { + struct ddog_CaptureValue capture_value = {0}; + ddtrace_create_capture_value(val, &capture_value, capture_config, capture_config->max_reference_depth); + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_LOCAL, (ddog_CharSlice) {.ptr = ZSTR_VAL(key), .len = ZSTR_LEN(key)}, capture_value); + } + } ZEND_HASH_FOREACH_END(); + } + + return capture; +} + +static inline void dd_extend_hash(zend_ulong *hash, zend_string *str) { + *hash = *hash * 33 * ZSTR_LEN(str) + ZSTR_HASH(str); +} + +static zend_ulong ddtrace_compute_exception_hash(zend_object *exception) { + zend_ulong hash = 0; + + zval ex, *previous = &ex; + ZVAL_OBJ(&ex, exception); + + do { + exception = Z_OBJ_P(previous); + dd_extend_hash(&hash, exception->ce->name); + + zval *frame; + zval *trace = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_TRACE)); + if (Z_TYPE_P(trace) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARR_P(trace), frame) { + if (Z_TYPE_P(frame) != IS_ARRAY) { + continue; + } + + zval *class_name = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_CLASS)); + if (class_name && Z_TYPE_P(class_name) == IS_STRING) { + dd_extend_hash(&hash, Z_STR_P(class_name)); + } + zval *func_name = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_FUNCTION)); + if (func_name && Z_TYPE_P(func_name) == IS_STRING) { + dd_extend_hash(&hash, Z_STR_P(func_name)); + } + } ZEND_HASH_FOREACH_END(); + } + Z_PROTECT_RECURSION_P(previous); + previous = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + } while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && + instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)); + + return hash; +} + +static void ddtrace_collect_exception_debug_data(zend_object *exception, zend_string *service_name, uint64_t time, void *context, add_tag_fn_t add_meta) { + if (!ddtrace_exception_debugging_is_active()) { + return; + } + + zval *trace = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_TRACE)); + if (Z_TYPE_P(trace) != IS_ARRAY) { + return; + } + + zend_string *key_locals = zend_string_init(ZEND_STRL("locals"), 0); + zval *locals = zai_exception_read_property(exception, key_locals); + + if (!DDTRACE_G(debugger_capture_arena)) { + DDTRACE_G(debugger_capture_arena) = zend_arena_create(65536); + } + + const ddog_CaptureConfiguration capture_config = ddog_capture_defaults(); + + char *exception_hash = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), hash_len); + zend_ulong exception_long_hash = ddtrace_compute_exception_hash(exception); + php_hash_bin2hex(exception_hash, (unsigned char *)&exception_long_hash, sizeof(exception_long_hash)); + + add_meta(context, DDTRACE_STRING_LITERAL("error.debug_info_captured"), DDTRACE_STRING_LITERAL("true")); + add_meta(context, DDTRACE_STRING_LITERAL("_dd.debug.error.exception_hash"), (ddtrace_string){exception_hash, hash_len}); + + if (!ddog_exception_hash_limiter_inc(ddtrace_sidecar, (uint64_t)exception_long_hash, get_DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS())) { + LOG(TRACE, "Skipping exception replay capture due to hash %.*s already recently hit", hash_len, exception_hash); + return; + } + + char *exception_id = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), uuid_len); + ddog_snapshot_format_new_uuid((uint8_t(*)[uuid_len])exception_id); + + add_meta(context, DDTRACE_STRING_LITERAL("_dd.debug.error.exception_capture_id"), (ddtrace_string){exception_id, uuid_len}); + + zval *frame; + int frame_num = 0; + ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARR_P(trace), frame_num, frame) { + if (get_DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES() >= 0 && get_DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES() < frame_num) { + break; + } + + if (Z_TYPE_P(frame) != IS_ARRAY) { + continue; + } + + zend_class_entry *ce = NULL; + zend_function *func = NULL; + ddog_CharSlice func_slice = DDOG_CHARSLICE_C(""); + ddog_CharSlice class_slice = DDOG_CHARSLICE_C(""); + zval *class_name = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_CLASS)); + if (class_name && Z_TYPE_P(class_name) == IS_STRING) { + ce = zai_symbol_lookup_class_global(zai_str_from_zstr(Z_STR_P(class_name))); + class_slice = dd_zend_string_to_CharSlice(Z_STR_P(class_name)); + } + zval *func_name = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_FUNCTION)); + if (func_name && Z_TYPE_P(func_name) == IS_STRING) { + zai_str wtf = zai_str_from_zstr(Z_STR_P(func_name)); + func = zai_symbol_lookup_function(ce ? ZAI_SYMBOL_SCOPE_CLASS : ZAI_SYMBOL_SCOPE_GLOBAL, ce, &wtf); + func_slice = dd_zend_string_to_CharSlice(Z_STR_P(func_name)); + } + + ddog_DebuggerCapture *capture = dd_create_frame_and_collect_locals(exception_id, exception_hash, frame_num, class_slice, func_slice, locals, service_name, &capture_config, time, context, add_meta); + locals = zend_hash_find(Z_ARR_P(frame), key_locals); + + zend_string *key; + zval *val; + zval *args = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_ARGS)); + if (args && Z_TYPE_P(args) == IS_ARRAY) { + zend_long idx; + ZEND_HASH_FOREACH_KEY_VAL(Z_ARR_P(args), idx, key, val) { + struct ddog_CaptureValue capture_value = {0}; + ddtrace_create_capture_value(val, &capture_value, &capture_config, capture_config.max_reference_depth); + ddog_CharSlice arg_name; + if (key) { + arg_name = (ddog_CharSlice) {.ptr = ZSTR_VAL(key), .len = ZSTR_LEN(key)}; + } else if (func && idx < func->common.num_args) { + if (ZEND_USER_CODE(func->type)) { + zend_string *name = func->op_array.arg_info[idx].name; + arg_name = (ddog_CharSlice) {.ptr = ZSTR_VAL(name), .len = ZSTR_LEN(name)}; + } else { + const char *name = func->internal_function.arg_info[idx].name; + arg_name = (ddog_CharSlice) {.ptr = name, .len = strlen(name)}; + } + } else { + char *integer = zend_arena_alloc(&DDTRACE_G(debugger_capture_arena), 23); + int len = sprintf(integer, "arg" ZEND_LONG_FMT, idx); + arg_name = (ddog_CharSlice){ .ptr = integer, .len = len }; + } + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, arg_name, capture_value); + } ZEND_HASH_FOREACH_END(); + } + + zval *obj = zend_hash_find(Z_ARR_P(frame), ZSTR_KNOWN(ZEND_STR_OBJECT)); + if (obj) { + struct ddog_CaptureValue capture_value = {0}; + ddtrace_create_capture_value(obj, &capture_value, &capture_config, capture_config.max_reference_depth); + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, DDOG_CHARSLICE_C("this"), capture_value); + } + } ZEND_HASH_FOREACH_END(); + + if (get_DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES() < 0 || get_DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES() > frame_num) { + if (locals && Z_TYPE_P(locals) == IS_ARRAY) { + dd_create_frame_and_collect_locals(exception_id, exception_hash, frame_num + 1, DDOG_CHARSLICE_C(""), DDOG_CHARSLICE_C(""), locals, service_name, &capture_config, time, context, add_meta); + } + } + + zend_string_release(key_locals); +} + +// Guarantees that add_tag will only be called once per tag, will stop trying to add tags if one fails. +zend_result ddtrace_exception_to_meta(zend_object *exception, zend_string *service_name, uint64_t time, void *context, add_tag_fn_t add_meta, enum dd_exception exception_state) { + zend_object *exception_root = exception; + zend_string *full_trace = zai_get_trace_without_args_from_exception(exception); + + zval *previous = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && + instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)) { + zend_string *trace_string = zai_get_trace_without_args_from_exception(Z_OBJ_P(previous)); + + zend_string *msg = zai_exception_message(exception); + zend_long line = zval_get_long(zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_LINE))); + zend_string *file = ddtrace_convert_to_str(zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_FILE))); + + zend_string *complete_trace = + zend_strpprintf(0, "%s\n\nNext %s%s%s in %s:" ZEND_LONG_FMT "\nStack trace:\n%s", ZSTR_VAL(trace_string), + ZSTR_VAL(exception->ce->name), ZSTR_LEN(msg) ? ": " : "", ZSTR_VAL(msg), ZSTR_VAL(file), + line, ZSTR_VAL(full_trace)); + zend_string_release(trace_string); + zend_string_release(full_trace); + zend_string_release(file); + full_trace = complete_trace; + + Z_PROTECT_RECURSION_P(previous); + exception = Z_OBJ_P(previous); + previous = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + } + + // exception is now the innermost exception, i.e. what we need + ddtrace_collect_exception_debug_data(exception, service_name, time / 1000000, context, add_meta); + + previous = zai_exception_read_property(exception_root, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && + instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)) { + Z_UNPROTECT_RECURSION_P(previous); + previous = zai_exception_read_property(Z_OBJ_P(previous), ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + } + + bool success = dd_exception_to_error_msg(exception, context, add_meta, exception_state) == SUCCESS && + dd_exception_to_error_type(exception, context, add_meta) == SUCCESS && + dd_exception_trace_to_error_stack(full_trace, context, add_meta) == SUCCESS; + return success ? SUCCESS : FAILURE; +} diff --git a/ext/exception_serialize.h b/ext/exception_serialize.h new file mode 100644 index 0000000000..e545eadd58 --- /dev/null +++ b/ext/exception_serialize.h @@ -0,0 +1,15 @@ +#ifndef DD_EXCEPTION_REPLAY_H +#define DD_EXCEPTION_REPLAY_H +#include "serializer.h" +#include + +enum dd_exception { + DD_EXCEPTION_THROWN, + DD_EXCEPTION_CAUGHT, + DD_EXCEPTION_UNCAUGHT, +}; + +zend_result ddtrace_exception_to_meta(zend_object *exception, zend_string *service_name, uint64_t time, void *context, add_tag_fn_t add_meta, enum dd_exception exception_state); +void ddtrace_create_capture_value(zval *zv, struct ddog_CaptureValue *value, const ddog_CaptureConfiguration *config, int remaining_nesting); + +#endif // DD_EXCEPTION_REPLAY_H diff --git a/ext/excluded_modules.c b/ext/excluded_modules.c index 0caaa3d227..4bad02a804 100644 --- a/ext/excluded_modules.c +++ b/ext/excluded_modules.c @@ -6,8 +6,17 @@ #include #include +#include "configuration.h" bool ddtrace_is_excluded_module(zend_module_entry *module, char *error) { +#if PHP_VERSION_ID >= 80000 + if (strcmp("ionCube Loader", module->name) == 0) { + snprintf(error, DDTRACE_EXCLUDED_MODULES_ERROR_MAX_LEN, + "Found incompatible ionCube Loader extension"); + return true; + } +#endif + if (strcmp("xdebug", module->name) == 0) { /* PHP 7.0 was only supported from Xdebug 2.4 through 2.7 @@ -15,7 +24,7 @@ bool ddtrace_is_excluded_module(zend_module_entry *module, char *error) { */ #if PHP_VERSION_ID < 70100 snprintf(error, DDTRACE_EXCLUDED_MODULES_ERROR_MAX_LEN, - "Found incompatible Xdebug version %s; disabling conflicting functionality", module->version); + "Found incompatible Xdebug version %s", module->version); return true; #endif /* @@ -37,8 +46,7 @@ bool ddtrace_is_excluded_module(zend_module_entry *module, char *error) { int compare = php_version_compare(module->version, "2.9.5"); if (compare == -1) { snprintf(error, DDTRACE_EXCLUDED_MODULES_ERROR_MAX_LEN, - "Found incompatible Xdebug version %s; ddtrace requires Xdebug 2.9.5 or greater; disabling " - "conflicting functionality", + "Found incompatible Xdebug version %s; ddtrace requires Xdebug 2.9.5 or greater", module->version); return true; } @@ -50,18 +58,27 @@ void ddtrace_excluded_modules_startup() { zend_module_entry *module; ddtrace_has_excluded_module = false; + bool inject_force = get_global_DD_INJECT_FORCE(); ZEND_HASH_FOREACH_PTR(&module_registry, module) { char error[DDTRACE_EXCLUDED_MODULES_ERROR_MAX_LEN + 1]; if (module && module->name && module->version && ddtrace_is_excluded_module(module, error)) { ddtrace_has_excluded_module = true; - if (strcmp("xdebug", module->name) == 0) { - LOG(ERROR, error); - } else { + if (inject_force) { LOG(WARN, error); + } else { + LOG(ERROR, error); } - return; } } ZEND_HASH_FOREACH_END(); + + if (ddtrace_has_excluded_module) { + if (inject_force) { + LOG(WARN, "Found incompatible extension(s); ignoring since 'datadog.inject_force' is enabled"); + ddtrace_has_excluded_module = false; + } else { + LOG(ERROR, "Found incompatible extension(s); disabling conflicting functionality"); + } + } } diff --git a/ext/git.c b/ext/git.c index 230b1b83a7..cd90158116 100644 --- a/ext/git.c +++ b/ext/git.c @@ -45,7 +45,7 @@ static char *find_last_dir_separator(const char *path) { #endif } -zend_string *read_git_file(const char *path, bool persistent) { +zend_string *read_git_file(const char *path) { FILE *file = fopen(path, "r"); if (!file) { return NULL; @@ -61,14 +61,14 @@ zend_string *read_git_file(const char *path, bool persistent) { buffer[len] = '\0'; len = remove_trailing_newline(buffer); - return zend_string_init(buffer, len, persistent); + return zend_string_init(buffer, len, true); } zend_string *get_commit_sha(const char *git_dir) { char head_path[PATH_MAX]; snprintf(head_path, sizeof(head_path), "%s/HEAD", git_dir); - zend_string *head_content = read_git_file(head_path, true); + zend_string *head_content = read_git_file(head_path); if (!head_content) { return NULL; } @@ -78,7 +78,7 @@ zend_string *get_commit_sha(const char *git_dir) { char ref_path[PATH_MAX]; snprintf(ref_path, sizeof(ref_path), "%s/%s", git_dir, ZSTR_VAL(head_content) + strlen(ref_prefix)); zend_string_release(head_content); - return read_git_file(ref_path, true); + return read_git_file(ref_path); } return head_content; @@ -257,12 +257,6 @@ bool process_git_info(zend_string *git_dir, zend_string *cwd) { return success; } -void use_cached_metadata(git_metadata_t *git_metadata) { - zend_string_addref(git_metadata->property_commit); - zend_string_addref(git_metadata->property_repository); - add_git_info(git_metadata->property_commit, git_metadata->property_repository); -} - void replace_git_metadata(git_metadata_t *git_metadata, zend_string *commit_sha, zend_string *repository_url) { if (git_metadata->property_commit) { zend_string_release(git_metadata->property_commit); @@ -330,7 +324,7 @@ bool inject_from_git_dir() { git_metadata_t *git_metadata = zend_hash_find_ptr(&DDTRACE_G(git_metadata), cwd); if (git_metadata) { - use_cached_metadata(git_metadata); + add_git_info(git_metadata->property_commit, git_metadata->property_repository); zend_string_release(cwd); return true; } diff --git a/ext/handlers_curl.c b/ext/handlers_curl.c index baa5679356..366011f905 100644 --- a/ext/handlers_curl.c +++ b/ext/handlers_curl.c @@ -31,10 +31,6 @@ extern zend_class_entry *curl_multi_ce; bool dd_ext_curl_loaded = false; zend_long dd_const_curlopt_httpheader = 0; -ZEND_TLS HashTable dd_headers; - -// Multi-handle API: curl_multi_*() -ZEND_TLS HashTable dd_multi_handles; static zif_handler dd_curl_close_handler = NULL; static zif_handler dd_curl_exec_handler = NULL; @@ -67,15 +63,15 @@ static void dd_ch_store_headers(zend_object *ch, HashTable *headers) { zend_hash_init(new_headers, zend_hash_num_elements(headers), NULL, ZVAL_PTR_DTOR, 0); zend_hash_copy(new_headers, headers, (copy_ctor_func_t)zval_add_ref); - if (!zend_weakrefs_hash_add_ptr(&dd_headers, ch, new_headers)) { - zend_hash_index_update_ptr(&dd_headers, zend_object_to_weakref_key(ch), new_headers); + if (!zend_weakrefs_hash_add_ptr(&DDTRACE_G(curl_headers), ch, new_headers)) { + zend_hash_index_update_ptr(&DDTRACE_G(curl_headers), zend_object_to_weakref_key(ch), new_headers); } } -static void dd_ch_delete_headers(zend_object *ch) { zend_weakrefs_hash_del(&dd_headers, ch); } +static void dd_ch_delete_headers(zend_object *ch) { zend_weakrefs_hash_del(&DDTRACE_G(curl_headers), ch); } static void dd_ch_duplicate_headers(zend_object *ch_orig, zend_object *ch_new) { - HashTable *headers = zend_hash_index_find_ptr(&dd_headers, zend_object_to_weakref_key(ch_orig)); + HashTable *headers = zend_hash_index_find_ptr(&DDTRACE_G(curl_headers), zend_object_to_weakref_key(ch_orig)); if (headers) { dd_ch_store_headers(ch_new, headers); } @@ -84,7 +80,7 @@ static void dd_ch_duplicate_headers(zend_object *ch_orig, zend_object *ch_new) { static void dd_inject_distributed_tracing_headers(zend_object *ch) { zval headers; zend_array *dd_header_array; - if ((dd_header_array = zend_hash_index_find_ptr(&dd_headers, zend_object_to_weakref_key(ch)))) { + if ((dd_header_array = zend_hash_index_find_ptr(&DDTRACE_G(curl_headers), zend_object_to_weakref_key(ch)))) { ZVAL_ARR(&headers, zend_array_dup(dd_header_array)); } else { array_init(&headers); @@ -112,11 +108,11 @@ static void dd_inject_distributed_tracing_headers(zend_object *ch) { * headers on the first call to curl_multi_exec(). */ static void dd_multi_add_handle(zend_object *mh, zend_object *ch) { - HashTable *handles = zend_hash_index_find_ptr(&dd_multi_handles, zend_object_to_weakref_key(mh)); + HashTable *handles = zend_hash_index_find_ptr(&DDTRACE_G(curl_multi_handles), zend_object_to_weakref_key(mh)); if (!handles) { ALLOC_HASHTABLE(handles); zend_hash_init(handles, 8, NULL, ZVAL_PTR_DTOR, 0); - zend_weakrefs_hash_add_ptr(&dd_multi_handles, mh, handles); + zend_weakrefs_hash_add_ptr(&DDTRACE_G(curl_multi_handles), mh, handles); } zval tmp; @@ -127,14 +123,14 @@ static void dd_multi_add_handle(zend_object *mh, zend_object *ch) { /* Remove a curl handle from the multi-handle map when curl_multi_remove_handle() is called. */ static void dd_multi_remove_handle(zend_object *mh, zend_object *ch) { - HashTable *handles = zend_hash_index_find_ptr(&dd_multi_handles, zend_object_to_weakref_key(mh)); + HashTable *handles = zend_hash_index_find_ptr(&DDTRACE_G(curl_multi_handles), zend_object_to_weakref_key(mh)); if (handles) { zend_hash_index_del(handles, (zend_ulong)ch->handle); } } static void dd_multi_inject_headers(zend_object *mh) { - HashTable *handles = zend_hash_index_find_ptr(&dd_multi_handles, zend_object_to_weakref_key(mh)); + HashTable *handles = zend_hash_index_find_ptr(&DDTRACE_G(curl_multi_handles), zend_object_to_weakref_key(mh)); if (handles && zend_hash_num_elements(handles) > 0) { zend_object *ch; @@ -165,7 +161,7 @@ static void dd_multi_inject_headers(zend_object *mh) { DDTRACE_G(curl_multi_injecting_spans) = NULL; } - zend_weakrefs_hash_del(&dd_multi_handles, mh); + zend_weakrefs_hash_del(&DDTRACE_G(curl_multi_handles), mh); } } @@ -173,7 +169,7 @@ static void dd_multi_inject_headers(zend_object *mh) { static HashTable *ddtrace_curl_multi_get_gc(zend_object *object, zval **table, int *n) { HashTable *ret = dd_curl_multi_get_gc(object, table, n); - HashTable *handles = zend_hash_index_find_ptr(&dd_multi_handles, zend_object_to_weakref_key(object)); + HashTable *handles = zend_hash_index_find_ptr(&DDTRACE_G(curl_multi_handles), zend_object_to_weakref_key(object)); if (handles) { // extend the buffer created by curl_multi_get_gc zend_get_gc_buffer *gc_buffer = &EG(get_gc_buffer); @@ -246,7 +242,7 @@ ZEND_FUNCTION(ddtrace_curl_multi_close) { if (dd_load_curl_integration() && zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "O", &z_mh, curl_multi_ce) == SUCCESS) { - zend_weakrefs_hash_del(&dd_multi_handles, Z_OBJ_P(z_mh)); + zend_weakrefs_hash_del(&DDTRACE_G(curl_multi_handles), Z_OBJ_P(z_mh)); } dd_curl_multi_close_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); @@ -426,8 +422,8 @@ void ddtrace_curl_handlers_startup(void) { } void ddtrace_curl_handlers_rinit(void) { - zend_hash_init(&dd_headers, 8, NULL, (dtor_func_t)dd_ht_dtor, 0); - zend_hash_init(&dd_multi_handles, 8, NULL, (dtor_func_t)dd_ht_dtor, 0); + zend_hash_init(&DDTRACE_G(curl_headers), 8, NULL, (dtor_func_t)dd_ht_dtor, 0); + zend_hash_init(&DDTRACE_G(curl_multi_handles), 8, NULL, (dtor_func_t)dd_ht_dtor, 0); } static void dd_curl_destroy_weakref_ht(HashTable *ht) { @@ -440,6 +436,6 @@ static void dd_curl_destroy_weakref_ht(HashTable *ht) { } void ddtrace_curl_handlers_rshutdown(void) { - dd_curl_destroy_weakref_ht(&dd_headers); - dd_curl_destroy_weakref_ht(&dd_multi_handles); + dd_curl_destroy_weakref_ht(&DDTRACE_G(curl_headers)); + dd_curl_destroy_weakref_ht(&DDTRACE_G(curl_multi_handles)); } diff --git a/ext/handlers_exception.c b/ext/handlers_exception.c index 37ecf3f4a1..17a4e0ec5e 100644 --- a/ext/handlers_exception.c +++ b/ext/handlers_exception.c @@ -2,6 +2,7 @@ #include #include +#include "collect_backtrace.h" #include "configuration.h" #include "engine_hooks.h" // For 'ddtrace_resource' #include "handlers_exception.h" @@ -205,10 +206,6 @@ ZEND_ARG_INFO(0, error_filename) ZEND_ARG_INFO(0, error_lineno) ZEND_END_ARG_INFO() -#if PHP_VERSION_ID < 70100 -#define ZEND_STR_PREVIOUS "previous" -#endif - #if PHP_VERSION_ID < 70200 && !defined(__clang__) // zpp API is not safe by itself, but our code is safe here. #pragma GCC diagnostic ignored "-Wclobbered" @@ -326,11 +323,11 @@ static PHP_METHOD(DDTrace_ExceptionOrErrorHandler, execute) { // If this ever turns out to be problematic, we have to store it somewhere in DDTRACE_G() // and delay attaching until serialization. if (root_span && Z_TYPE_P((zval *)&old_exception) > IS_FALSE) { - zval *previous = ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_PREVIOUS); + zval *previous = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)) { Z_PROTECT_RECURSION_P(previous); - previous = ZAI_EXCEPTION_PROPERTY(Z_OBJ_P(previous), ZEND_STR_PREVIOUS); + previous = zai_exception_read_property(Z_OBJ_P(previous), ZSTR_KNOWN(ZEND_STR_PREVIOUS)); } if (Z_TYPE_P(previous) > IS_FALSE) { @@ -341,10 +338,10 @@ static PHP_METHOD(DDTrace_ExceptionOrErrorHandler, execute) { ZVAL_COPY_VALUE(previous, (zval *)&old_exception); } - previous = ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_PREVIOUS); + previous = zai_exception_read_property(exception, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); while (Z_TYPE_P(previous) == IS_OBJECT && Z_IS_RECURSIVE_P(previous)) { Z_UNPROTECT_RECURSION_P(previous); - previous = ZAI_EXCEPTION_PROPERTY(Z_OBJ_P(previous), ZEND_STR_PREVIOUS); + previous = zai_exception_read_property(Z_OBJ_P(previous), ZSTR_KNOWN(ZEND_STR_PREVIOUS)); } } } @@ -388,6 +385,87 @@ void dd_exception_handler_freed(zend_object *object) { } #endif +static zend_object *ddtrace_exception_new(zend_class_entry *class_type, zend_object *(*prev)(zend_class_entry *class_type)) { + zend_execute_data *ex = EG(current_execute_data); + EG(current_execute_data) = NULL; + zend_object *object = prev(class_type); + EG(current_execute_data) = ex; + + zend_class_entry *base_ce = zai_get_exception_base(object); + + bool ignore_args = zend_string_equals_literal(class_type->name, "SodiumException"); +#if PHP_VERSION_ID >= 70400 + ignore_args = ignore_args || EG(exception_ignore_args); +#endif + + bool exception_replay = get_DD_EXCEPTION_REPLAY_ENABLED(); + + zval trace; + ddtrace_fetch_debug_backtrace(&trace, 0, (ignore_args ? DEBUG_BACKTRACE_IGNORE_ARGS : 0) | (exception_replay ? DDTRACE_DEBUG_BACKTRACE_CAPTURE_LOCALS | DEBUG_BACKTRACE_PROVIDE_OBJECT : 0), 0); + Z_SET_REFCOUNT(trace, 0); + + zval filezv, linezv; + zend_string *filename; + if ((class_type != zend_ce_parse_error +#if PHP_VERSION_ID >= 70300 + && class_type != zend_ce_compile_error +#endif + ) || !(filename = zend_get_compiled_filename())) { + ZVAL_STRING(&filezv, zend_get_executed_filename()); + ZVAL_LONG(&linezv, zend_get_executed_lineno()); + } else { + ZVAL_STR_COPY(&filezv, filename); + ZVAL_LONG(&linezv, zend_get_compiled_lineno()); + } + + EG(current_execute_data) = NULL; // zend_std_write_property will have side effects when EX(opline) points to ZEND_ASSIGN_OBJ... + zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_TRACE), &trace); + zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &filezv); + zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &linezv); + zval_ptr_dtor(&filezv); + EG(current_execute_data) = ex; + + if (ex && ex->func && ZEND_USER_CODE(ex->func->type) && exception_replay) { + zval locals; + ddtrace_call_get_locals(ex, &locals, !ignore_args); + zend_string *key_locals = zend_string_init(ZEND_STRL("locals"), 0); + Z_SET_REFCOUNT(locals, 0); + zend_update_property_ex(base_ce, object, key_locals, &locals); + zend_string_release(key_locals); + } + + return object; +} + +// fast path +zend_object *(*prev_exception_default_create_object)(zend_class_entry *class_type); +static zend_object *ddtrace_default_exception_new(zend_class_entry *class_type) { + return ddtrace_exception_new(class_type, prev_exception_default_create_object); +} + +// support custom exception create handlers +HashTable ddtrace_exception_custom_create_object; +static zend_object *ddtrace_custom_exception_new(zend_class_entry *class_type) { + zend_class_entry *ce = class_type; + zend_object *(*prev)(zend_class_entry *class_type); + while (!(prev = zend_hash_index_find_ptr(&ddtrace_exception_custom_create_object, (zend_ulong)(uintptr_t)ce))) { + ce = ce->parent; + } + return ddtrace_exception_new(class_type, prev); +} + +static zend_property_info *dd_add_exception_locals_property(zend_class_entry *ce) { + zend_string *key = zend_string_init(ZEND_STRL("locals"), 1); +#if PHP_VERSION_ID >= 80000 + zend_property_info *prop = zend_declare_typed_property(ce, key, &EG(uninitialized_zval), ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); +#else + zend_declare_property_ex(ce, key, &EG(uninitialized_zval), ZEND_ACC_PRIVATE, NULL); + zend_property_info *prop = zend_hash_find_ptr(&ce->properties_info, key); +#endif + zend_string_release(key); + return prop; +} + void ddtrace_exception_handlers_startup(void) { ddtrace_exception_or_error_handler = (zend_internal_function){ .type = ZEND_INTERNAL_FUNCTION, @@ -422,9 +500,79 @@ void ddtrace_exception_handlers_startup(void) { for (size_t i = 0; i < handlers_len; ++i) { datadog_php_install_handler(handlers[i]); } + + zend_property_info *exception_prop = dd_add_exception_locals_property(zend_ce_exception); + zend_property_info *error_prop = dd_add_exception_locals_property(zend_ce_error); + + prev_exception_default_create_object = zend_ce_exception->create_object; + zend_hash_init(&ddtrace_exception_custom_create_object, 8, NULL, NULL, 1); + zend_class_entry *ce; + zend_string *locals_key = zend_string_init_interned(ZEND_STRL("locals"), 1); + ZEND_HASH_FOREACH_PTR(CG(class_table), ce) { + if ((ce->ce_flags & ZEND_ACC_INTERFACE) == 0 && instanceof_function_slow(ce, zend_ce_throwable)) { + if (ce->create_object) { + if (ce->create_object == prev_exception_default_create_object) { + ce->create_object = ddtrace_default_exception_new; + } else { + zend_hash_index_add_ptr(&ddtrace_exception_custom_create_object, (zend_long)(uintptr_t)ce, ce->create_object); + ce->create_object = ddtrace_custom_exception_new; + } + + // add locals property to all existing throwables + zend_class_entry *base_ce = NULL; + zend_property_info *parent_info; + if (ce != zend_ce_exception && instanceof_function_slow(ce, zend_ce_exception)) { + base_ce = zend_ce_exception; + parent_info = exception_prop; + } else if (ce != zend_ce_error && instanceof_function_slow(ce, zend_ce_error)) { + base_ce = zend_ce_error; + parent_info = error_prop; + } + if (base_ce) { + zval *child = zend_hash_find_known_hash(&ce->properties_info, locals_key); + if (child) { + ((zend_property_info *)Z_PTR_P(child))->flags |= ZEND_ACC_CHANGED; + } else { +#if PHP_VERSION_ID < 80100 + if (ce->type == ZEND_INTERNAL_CLASS) { + zend_property_info *property_info = parent_info; + parent_info = pemalloc(sizeof(zend_property_info), 1); + memcpy(parent_info, property_info, sizeof(zend_property_info)); + zend_string_addref(parent_info->name); + } +#endif + zend_hash_add_new_ptr(&ce->properties_info, locals_key, parent_info); + } + + zend_property_info *property_info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property_info) { + if (property_info->offset >= parent_info->offset && property_info->ce != base_ce && (property_info->flags & ZEND_ACC_STATIC) == 0) { + property_info->offset += sizeof(zval); + } + } ZEND_HASH_FOREACH_END(); + + int insert_at = zend_hash_num_elements(&base_ce->properties_info) - 1; + ce->default_properties_count++; + + ce->default_properties_table = perealloc(ce->default_properties_table, sizeof(zval) * ce->default_properties_count, 1); + memmove(ce->default_properties_table + insert_at + 1, ce->default_properties_table + insert_at, sizeof(zval) * (ce->default_properties_count - insert_at - 1)); + ZVAL_COPY_VALUE_PROP(ce->default_properties_table + insert_at, base_ce->default_properties_table + insert_at); + +#if PHP_VERSION_ID >= 70400 + ce->properties_info_table = perealloc(ce->properties_info_table, sizeof(zend_property_info *) * ce->default_properties_count, 1); + memmove(ce->properties_info_table + insert_at + 1, ce->properties_info_table + insert_at, sizeof(zend_property_info *) * (ce->default_properties_count - insert_at - 1)); + ce->properties_info_table[insert_at] = parent_info; +#endif + } + } + } + } ZEND_HASH_FOREACH_END(); } -void ddtrace_exception_handlers_shutdown(void) { ddtrace_free_unregistered_class(&dd_exception_or_error_handler_ce); } +void ddtrace_exception_handlers_shutdown(void) { + ddtrace_free_unregistered_class(&dd_exception_or_error_handler_ce); + zend_hash_destroy(&ddtrace_exception_custom_create_object); +} void ddtrace_exception_handlers_rinit(void) { if (Z_TYPE(EG(user_exception_handler)) != IS_OBJECT || diff --git a/ext/hook/uhook.c b/ext/hook/uhook.c index 9ea4071392..039e60f02c 100644 --- a/ext/hook/uhook.c +++ b/ext/hook/uhook.c @@ -99,7 +99,7 @@ HashTable *dd_uhook_collect_args(zend_execute_data *execute_data) { ht->nTableSize = num_args; #endif - zend_hash_real_init(ht, 1); + zend_hash_real_init_packed(ht); ZEND_HASH_FILL_PACKED(ht) { if (EX(func)->type == ZEND_USER_FUNCTION) { uint32_t first_extra_arg = MIN(num_args, func->op_array.num_args); @@ -196,7 +196,7 @@ static bool dd_uhook_call_hook(zend_execute_data *execute_data, zend_object *clo return Z_TYPE(rv) != IS_FALSE; } -static bool dd_uhook_match_filepath(zend_string *file, zend_string *source) { +bool ddtrace_uhook_match_filepath(zend_string *file, zend_string *source) { if (ZSTR_LEN(source) == 0) { return true; // empty path is wildcard } @@ -255,7 +255,7 @@ static bool dd_uhook_begin(zend_ulong invocation, zend_execute_data *execute_dat dd_uhook_def *def = auxiliary; dd_uhook_dynamic *dyn = dynamic; - if (def->file && (!execute_data->func->op_array.filename || !dd_uhook_match_filepath(execute_data->func->op_array.filename, def->file))) { + if (def->file && (!execute_data->func->op_array.filename || !ddtrace_uhook_match_filepath(execute_data->func->op_array.filename, def->file))) { dyn->hook_data = NULL; return true; } @@ -721,10 +721,6 @@ PHP_FUNCTION(DDTrace_remove_hook) { zai_str scope = zai_str_from_zstr(def->scope); zai_str function = zai_str_from_zstr(def->function); if (location && ZSTR_LEN(location)) { - zend_string *lower = zend_string_tolower(location); - zai_hook_exclude_class(scope, function, id, lower); - zend_string_release(lower); - LOG(HOOK_TRACE, "Excluding class %s from hook %d at %s:%d on %s %s%s%s", ZSTR_VAL(location), id, @@ -733,9 +729,11 @@ PHP_FUNCTION(DDTrace_remove_hook) { def->scope ? ZSTR_VAL(def->scope) : "", def->scope ? "::" : "", def->file ? ZSTR_VAL(def->file) : ZSTR_VAL(def->function)); - } else { - zai_hook_remove(scope, function, id); + zend_string *lower = zend_string_tolower(location); + zai_hook_exclude_class(scope, function, id, lower); + zend_string_release(lower); + } else { LOG(HOOK_TRACE, "Removing hook %d at %s:%d on %s %s%s%s", id, zend_get_executed_filename(), zend_get_executed_lineno(), @@ -743,6 +741,8 @@ PHP_FUNCTION(DDTrace_remove_hook) { def->scope ? ZSTR_VAL(def->scope) : "", def->scope ? "::" : "", def->file ? ZSTR_VAL(def->file) : ZSTR_VAL(def->function)); + + zai_hook_remove(scope, function, id); } } else { if (location && ZSTR_LEN(location)) { @@ -750,15 +750,22 @@ PHP_FUNCTION(DDTrace_remove_hook) { zai_hook_exclude_class_resolved(def->install_address, id, lower); zend_string_release(lower); } else { - zai_hook_remove_resolved(def->install_address, id); + if (def->closure) { + const zend_function *closure = zend_get_closure_method_def(def->closure); + LOG(HOOK_TRACE, "Removing hook %d at %s:%d on %s %s%s%s", + id, + zend_get_executed_filename(), zend_get_executed_lineno(), + closure->common.scope ? "method" : "function", + closure->common.scope ? ZSTR_VAL(closure->common.scope->name) : "", + closure->common.scope ? "::" : "", + ZSTR_VAL(closure->common.function_name)); + } else { + LOG(HOOK_TRACE, "Removing hook %d at %s:%d", + id, + zend_get_executed_filename(), zend_get_executed_lineno()); + } - LOG(HOOK_TRACE, "Removing hook %d at %s:%d on %s %s%s%s", - id, - zend_get_executed_filename(), zend_get_executed_lineno(), - def->file ? "file" : (def->scope ? "method" : "function"), - def->scope ? ZSTR_VAL(def->scope) : "", - def->scope ? "::" : "", - def->file ? ZSTR_VAL(def->file) : ZSTR_VAL(def->function)); + zai_hook_remove_resolved(def->install_address, id); } } } diff --git a/ext/hook/uhook.h b/ext/hook/uhook.h index 64366c6ca6..6510bebbfc 100644 --- a/ext/hook/uhook.h +++ b/ext/hook/uhook.h @@ -7,6 +7,7 @@ HashTable *dd_uhook_collect_args(zend_execute_data *execute_data); void dd_uhook_report_sandbox_error(zend_execute_data *execute_data, zend_object *closure); void dd_uhook_log_invocation(void (*log)(const char *, ...), zend_execute_data *execute_data, const char *type, zend_object *closure); +bool ddtrace_uhook_match_filepath(zend_string *file, zend_string *source); void zai_uhook_rinit(); void zai_uhook_rshutdown(); diff --git a/ext/integrations/integrations.c b/ext/integrations/integrations.c index 259d5910b8..ed53f75b94 100644 --- a/ext/integrations/integrations.c +++ b/ext/integrations/integrations.c @@ -124,10 +124,10 @@ static void dd_invoke_integration_loader_and_unhook_posthook(zend_ulong invocati LOG(DEBUG, "Loaded integration %s", ZSTR_VAL(aux->classname)); break; case DD_TRACE_INTEGRATION_NOT_LOADED: - LOG(DEBUG, "Integration %s not available. New attempts WILL NOT be performed.", ZSTR_VAL(aux->classname)); + LOG(DEBUG, "Integration %s not loaded, possibly unsupported version. New attempts WILL NOT be performed.", ZSTR_VAL(aux->classname)); break; case DD_TRACE_INTEGRATION_NOT_AVAILABLE: - LOG(DEBUG, "Integration {name} not loaded. New attempts might be performed.", ZSTR_VAL(aux->classname)); + LOG(DEBUG, "Integration {name} not available. New attempts might be performed.", ZSTR_VAL(aux->classname)); unload_hooks = false; break; default: @@ -420,6 +420,11 @@ void ddtrace_integrations_minit(void) { DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_SYMFONY, "Drupal\\Core\\DrupalKernel", "__construct", "DDTrace\\Integrations\\Symfony\\SymfonyIntegration"); + DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_SYMFONYMESSENGER, "Symfony\\Component\\Messenger\\Worker", "__construct", + "DDTrace\\Integrations\\SymfonyMessenger\\SymfonyMessengerIntegration"); + DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_SYMFONYMESSENGER, "Symfony\\Component\\Messenger\\MessageBusInterface", "dispatch", + "DDTrace\\Integrations\\SymfonyMessenger\\SymfonyMessengerIntegration"); + DD_SET_UP_DEFERRED_LOADING_BY_FUNCTION(DDTRACE_INTEGRATION_SQLSRV, "sqlsrv_connect", "DDTrace\\Integrations\\SQLSRV\\SQLSRVIntegration"); diff --git a/ext/integrations/integrations.h b/ext/integrations/integrations.h index 9ca0c4cae7..cc03df4294 100644 --- a/ext/integrations/integrations.h +++ b/ext/integrations/integrations.h @@ -9,45 +9,46 @@ #define DD_TRACE_INTEGRATION_LOADED 1 #define DD_TRACE_INTEGRATION_NOT_AVAILABLE 2 -#define DDTRACE_LONGEST_INTEGRATION_NAME_LEN 13 // "zendframework" FTW! +#define DDTRACE_LONGEST_INTEGRATION_NAME_LEN 16 // "symfonymessenger" FTW! -#define DD_INTEGRATIONS \ - INTEGRATION(AMQP, "amqp") \ - INTEGRATION(CAKEPHP, "cakephp") \ - INTEGRATION(CODEIGNITER, "codeigniter") \ - INTEGRATION(EXEC, "exec") \ - INTEGRATION(CURL, "curl") \ - INTEGRATION(DRUPAL, "drupal") \ - INTEGRATION(ELASTICSEARCH, "elasticsearch") \ - INTEGRATION(ELOQUENT, "eloquent") \ - INTEGRATION(FRANKENPHP, "frankenphp") \ - INTEGRATION(GUZZLE, "guzzle") \ - INTEGRATION(LAMINAS, "laminas") \ - INTEGRATION(LARAVEL, "laravel") \ - INTEGRATION(LARAVELQUEUE, "laravelqueue") \ - INTEGRATION(LOGS, "logs", "false", DD_LOGS_INJECTION) \ - INTEGRATION(LUMEN, "lumen") \ - INTEGRATION(MAGENTO, "magento") \ - INTEGRATION(MEMCACHE, "memcache") \ - INTEGRATION(MEMCACHED, "memcached") \ - INTEGRATION(MONGO, "mongo") \ - INTEGRATION(MONGODB, "mongodb") \ - INTEGRATION(MYSQLI, "mysqli") \ - INTEGRATION(NETTE, "nette") \ - INTEGRATION(OPENAI, "openai") \ - INTEGRATION(PCNTL, "pcntl") \ - INTEGRATION(PDO, "pdo") \ - INTEGRATION(PHPREDIS, "phpredis") \ - INTEGRATION(PREDIS, "predis") \ - INTEGRATION(PSR18, "psr18") \ - INTEGRATION(ROADRUNNER, "roadrunner") \ - INTEGRATION(SQLSRV, "sqlsrv") \ - INTEGRATION(SLIM, "slim") \ - INTEGRATION(SWOOLE, "swoole") \ - INTEGRATION(SYMFONY, "symfony") \ - INTEGRATION(WEB, "web") \ - INTEGRATION(WORDPRESS, "wordpress") \ - INTEGRATION(YII, "yii") \ +#define DD_INTEGRATIONS \ + INTEGRATION(AMQP, "amqp") \ + INTEGRATION(CAKEPHP, "cakephp") \ + INTEGRATION(CODEIGNITER, "codeigniter") \ + INTEGRATION(EXEC, "exec") \ + INTEGRATION(CURL, "curl") \ + INTEGRATION(DRUPAL, "drupal") \ + INTEGRATION(ELASTICSEARCH, "elasticsearch") \ + INTEGRATION(ELOQUENT, "eloquent") \ + INTEGRATION(FRANKENPHP, "frankenphp") \ + INTEGRATION(GUZZLE, "guzzle") \ + INTEGRATION(LAMINAS, "laminas") \ + INTEGRATION(LARAVEL, "laravel") \ + INTEGRATION(LARAVELQUEUE, "laravelqueue") \ + INTEGRATION(LOGS, "logs", "false", CALIASES("DD_LOGS_INJECTION"), .ini_change = ddtrace_alter_DD_TRACE_LOGS_ENABLED) \ + INTEGRATION(LUMEN, "lumen") \ + INTEGRATION(MAGENTO, "magento") \ + INTEGRATION(MEMCACHE, "memcache") \ + INTEGRATION(MEMCACHED, "memcached") \ + INTEGRATION(MONGO, "mongo") \ + INTEGRATION(MONGODB, "mongodb") \ + INTEGRATION(MYSQLI, "mysqli") \ + INTEGRATION(NETTE, "nette") \ + INTEGRATION(OPENAI, "openai") \ + INTEGRATION(PCNTL, "pcntl") \ + INTEGRATION(PDO, "pdo") \ + INTEGRATION(PHPREDIS, "phpredis") \ + INTEGRATION(PREDIS, "predis") \ + INTEGRATION(PSR18, "psr18") \ + INTEGRATION(ROADRUNNER, "roadrunner") \ + INTEGRATION(SQLSRV, "sqlsrv") \ + INTEGRATION(SLIM, "slim") \ + INTEGRATION(SWOOLE, "swoole") \ + INTEGRATION(SYMFONY, "symfony") \ + INTEGRATION(SYMFONYMESSENGER, "symfonymessenger") \ + INTEGRATION(WEB, "web") \ + INTEGRATION(WORDPRESS, "wordpress") \ + INTEGRATION(YII, "yii") \ INTEGRATION(ZENDFRAMEWORK, "zendframework") #define INTEGRATION(id, ...) DDTRACE_INTEGRATION_##id, diff --git a/ext/ip_extraction.c b/ext/ip_extraction.c index 5aacaa9f0b..9e04f302dd 100644 --- a/ext/ip_extraction.c +++ b/ext/ip_extraction.c @@ -235,6 +235,9 @@ static struct extract_res dd_parse_multiple_maybe_port(zend_string *zvalue, ipad do { for (; value < end && *value == ' '; value++) { } + if (end - value < 0) { + ZEND_UNREACHABLE(); + } const char *comma = memchr(value, ',', end - value); const char *end_cur = comma ? comma : end; ipaddr cur; diff --git a/ext/live_debugger.c b/ext/live_debugger.c new file mode 100644 index 0000000000..211be6513c --- /dev/null +++ b/ext/live_debugger.c @@ -0,0 +1,1305 @@ +#include "live_debugger.h" +#include "ddtrace.h" +#include "exception_serialize.h" +#include "zai_string/string.h" +#include "span.h" +#include "hook/uhook.h" +#include "sidecar.h" +#include "hook/hook.h" +#include "serializer.h" +#include "configuration.h" +#include "compat_string.h" +#include "zend_interfaces.h" +#include "zend_hrtime.h" +#include "components-rs/common.h" + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); + +struct eval_ctx { + zend_execute_data *frame; + zend_arena *arena; + zval *retval; + const ddog_CaptureConfiguration *config; +}; + +static void clean_ctx(struct eval_ctx *ctx) { + if (ctx->arena) { + zend_arena *arena = ctx->arena; + do { + zend_arena *prev = arena->prev; + for (zval *cur = (zval *)((char *)arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena))); cur < (zval *)arena->ptr; ++cur) { + zval_ptr_dtor(cur); + } + arena = prev; + } while (arena); + zend_arena_destroy(ctx->arena); + } +} + +static ddog_ConditionEvaluationResult dd_eval_condition(const ddog_ProbeCondition *condition, zval *retval) { + ddog_CaptureConfiguration config = ddog_capture_defaults(); + struct eval_ctx ctx = { + .frame = EG(current_execute_data), + .arena = NULL, + .retval = retval, + .config = &config, + }; + ddog_ConditionEvaluationResult result = ddog_evaluate_condition(condition, &ctx); + clean_ctx(&ctx); + return result; +} + +static ddog_ValueEvaluationResult dd_eval_value(const ddog_ProbeValue *value, zval *retval) { + ddog_CaptureConfiguration config = ddog_capture_defaults(); + struct eval_ctx ctx = { + .frame = EG(current_execute_data), + .arena = NULL, + .retval = retval, + .config = &config, + }; + ddog_ValueEvaluationResult result = ddog_evaluate_value(value, &ctx); + clean_ctx(&ctx); + return result; +} + +static zend_string *dd_eval_string(const ddog_DslString *string, const ddog_CaptureConfiguration *config, zval *retval, ddog_Vec_SnapshotEvaluationError **error) { + struct eval_ctx ctx = { + .frame = EG(current_execute_data), + .arena = NULL, + .retval = retval, + .config = config, + }; + ddog_VoidCollection bytes = ddog_evaluate_unmanaged_string(string, &ctx, error); + zend_string *str = zend_string_init(bytes.elements, bytes.count, 0); + bytes.free(bytes); + clean_ctx(&ctx); + return str; +} + +static zval *dd_persist_eval_arena(struct eval_ctx *eval_ctx, zval *zv) { + if (!eval_ctx->arena) { + eval_ctx->arena = zend_arena_create(4096); + } + zval *zvp = zend_arena_alloc(&eval_ctx->arena, sizeof(zval)); + ZVAL_COPY_VALUE(zvp, zv); + return zvp; +} + +static ddog_CharSlice dd_persist_str_eval_arena(struct eval_ctx *eval_ctx, zend_string *str) { + zval zv; + ZVAL_STR(&zv, str); + if (Z_REFCOUNTED(zv)) { + if (Z_REFCOUNT(zv) == 1) { + dd_persist_eval_arena(eval_ctx, &zv); + } else { + Z_DELREF(zv); + } + } + return dd_zend_string_to_CharSlice(Z_STR(zv)); +} + +typedef struct { + ddtrace_span_data *span; +} dd_span_probe_dynamic; + +typedef struct { + ddog_Probe probe; + zend_string *function; + zend_string *scope; + zend_string *file; + zend_string *probe_id; +} dd_probe_def; + +static bool dd_probe_file_mismatch(dd_probe_def *def, zend_execute_data *execute_data) { + return def->file && (!execute_data->func->op_array.filename || !ddtrace_uhook_match_filepath(execute_data->func->op_array.filename, def->file)); +} + +static void dd_probe_dtor(void *data) { + dd_probe_def *def = data; + if (def->probe.probe.tag == DDOG_PROBE_TYPE_SPAN_DECORATION) { + drop_span_decoration_probe(def->probe.probe.span_decoration); + } + if (def->file) { + zend_string_release(def->file); + } + if (def->scope) { + zend_string_release(def->scope); + } + if (def->function) { + zend_string_release(def->function); + } + zend_string_release(def->probe_id); + efree(def); +} + +static void dd_probe_resolved(void *data, bool found) { + dd_probe_def *def = data; + if (found) { + def->probe.status = DDOG_PROBE_STATUS_INSTALLED; + } else { + def->probe.status = DDOG_PROBE_STATUS_ERROR; + def->probe.status_msg = DDOG_CHARSLICE_C("Method does not exist on the given class"); + def->probe.status_exception = DDOG_CHARSLICE_C("METHOD_NOT_FOUND"); + } + ddog_send_debugger_diagnostics(DDTRACE_G(remote_config_state), &ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), &def->probe, ddtrace_nanoseconds_realtime() / 1000000); +} + +static int64_t dd_init_live_debugger_probe(const ddog_Probe *probe, dd_probe_def *def, zai_hook_begin begin, zai_hook_end end, void (*def_dtor)(void *), size_t dynamic) { + def->probe = *probe; + def->probe_id = dd_CharSlice_to_zend_string(probe->id); + def->file = NULL; + def->function = NULL; + def->scope = NULL; + + const ddog_ProbeTarget *target = &probe->target; + if (target->type_name.len) { + if (!ddog_type_can_be_instrumented(DDTRACE_G(remote_config_state), target->type_name)) { + def->probe.status = DDOG_PROBE_STATUS_BLOCKED; + goto error; + } + + def->scope = dd_CharSlice_to_zend_string(target->type_name); + } + if (target->method_name.len) { + def->function = dd_CharSlice_to_zend_string(target->method_name); + } else if (target->source_file.len) { + def->file = dd_CharSlice_to_zend_string(target->source_file); + } else { + def->probe.status = DDOG_PROBE_STATUS_ERROR; + def->probe.status_msg = DDOG_CHARSLICE_C("Target is not supported"); + def->probe.status_exception = DDOG_CHARSLICE_C("UNSUPPORTED_TARGET"); + goto error; + } + + zai_str scope = ZAI_STR_EMPTY, function = ZAI_STR_EMPTY; + if (def->scope) { + scope = (zai_str) ZAI_STR_FROM_ZSTR(def->scope); + } + if (def->function) { + function = (zai_str) ZAI_STR_FROM_ZSTR(def->function); + } + zend_long id = zai_hook_install( + scope, + function, + begin, + end, + ZAI_HOOK_AUX_RESOLVED(def, def_dtor, dd_probe_resolved), + dynamic); + + if (id < 0) { + def->probe.status = DDOG_PROBE_STATUS_ERROR; + def->probe.status_msg = DDOG_CHARSLICE_C("Method does not exist on the given class"); + def->probe.status_exception = DDOG_CHARSLICE_C("METHOD_NOT_FOUND"); +error: + ddog_send_debugger_diagnostics(DDTRACE_G(remote_config_state), &ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), &def->probe, ddtrace_nanoseconds_realtime() / 1000000); + def_dtor(def); + return -1; + } + + if (def->probe.status != DDOG_PROBE_STATUS_INSTALLED) { + def->probe.status = DDOG_PROBE_STATUS_RECEIVED; + ddog_send_debugger_diagnostics(DDTRACE_G(remote_config_state), &ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), &def->probe, ddtrace_nanoseconds_realtime() / 1000000); + } + + zend_hash_index_add_new_ptr(&DDTRACE_G(active_rc_hooks), id, def); + return id; +} + +static void dd_probe_mark_active(dd_probe_def *def) { + if (def->probe.status != DDOG_PROBE_STATUS_EMITTING) { + def->probe.status = DDOG_PROBE_STATUS_EMITTING; + ddog_send_debugger_diagnostics(DDTRACE_G(remote_config_state), &ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), &def->probe, ddtrace_nanoseconds_realtime() / 1000000); + } +} + +static bool dd_span_probe_begin(zend_ulong invocation, zend_execute_data *execute_data, void *auxiliary, void *dynamic) { + dd_probe_def *def = auxiliary; + dd_span_probe_dynamic *dyn = dynamic; + + if (dd_probe_file_mismatch(def, execute_data)) { + dyn->span = NULL; + return true; + } + + dd_probe_mark_active(def); + + dyn->span = ddtrace_alloc_execute_data_span(invocation, execute_data); + + zval garbage; + ZVAL_COPY_VALUE(&garbage, &dyn->span->property_resource); + ZVAL_COPY_VALUE(&dyn->span->property_resource, &dyn->span->property_name); + ZVAL_STRING(&dyn->span->property_name, "dd.dynamic.span"); + zval_ptr_dtor(&garbage); + + zval probe_id; + ZVAL_STR_COPY(&probe_id, def->probe_id); + zend_hash_str_update(ddtrace_property_array(&dyn->span->property_meta), ZEND_STRL("debugger.probeid"), &probe_id); + + return true; +} + +static void dd_span_probe_end(zend_ulong invocation, zend_execute_data *execute_data, zval *retval, void *auxiliary, void *dynamic) { + dd_span_probe_dynamic *dyn = dynamic; + + UNUSED(execute_data, retval, auxiliary); + + if (dyn->span) { + ddtrace_clear_execute_data_span(invocation, true); + } +} + +static int64_t dd_set_span_probe(const ddog_Probe *probe) { + dd_probe_def *def = emalloc(sizeof(*def)); + return dd_init_live_debugger_probe(probe, def, dd_span_probe_begin, dd_span_probe_end, dd_probe_dtor, sizeof(dd_span_probe_dynamic)); +} + +static void dd_submit_probe_eval_error_snapshot(const ddog_Probe *probe, ddog_Vec_SnapshotEvaluationError *error) { + zend_string *service_name = ddtrace_active_service_name(); + ddog_DebuggerPayload *snapshot = ddog_evaluation_error_snapshot(probe, + (ddog_CharSlice){ .ptr = ZSTR_VAL(service_name), .len = ZSTR_LEN(service_name) }, + DDOG_CHARSLICE_C("php"), + error, + ddtrace_nanoseconds_realtime() / 1000000); + ddtrace_sidecar_send_debugger_datum(snapshot); + zend_string_release(service_name); +} + +static void dd_span_decoration_end(zend_ulong invocation, zend_execute_data *execute_data, zval *retval, void *auxiliary, void *dynamic) { + dd_probe_def *def = auxiliary; + ddtrace_span_data *span = ddtrace_active_span(); + if (!span) { + return; + } + UNUSED(invocation, dynamic); + if (dd_probe_file_mismatch(def, execute_data)) { + return; + } + + dd_probe_mark_active(def); + + if (def->probe.probe.span_decoration.target == DDOG_SPAN_PROBE_TARGET_ROOT) { + span = &span->stack->root_span->span; + } + zend_array *meta = ddtrace_property_array(&span->property_meta); + + bool condition_result = true; + const ddog_ProbeCondition *const *condition = def->probe.probe.span_decoration.conditions; + for (uintptr_t i = 0; i < def->probe.probe.span_decoration.span_tags_num; ++i) { + const ddog_SpanProbeTag *spanTag = def->probe.probe.span_decoration.span_tags + i; + if (spanTag->next_condition) { + ddog_ConditionEvaluationResult result = dd_eval_condition(*(condition++), retval); + if (result.tag == DDOG_CONDITION_EVALUATION_RESULT_ERROR) { + dd_submit_probe_eval_error_snapshot(&def->probe, result.error); + condition_result = false; + } else { + condition_result = result.tag == DDOG_CONDITION_EVALUATION_RESULT_SUCCESS; + } + } + if (condition_result) { + zval zv; + ddog_Vec_SnapshotEvaluationError *error; + ddog_CaptureConfiguration config_defaults = ddog_capture_defaults(); + ZVAL_STR(&zv, dd_eval_string(spanTag->tag.value, &config_defaults, retval, &error)); + zend_hash_str_update(meta, spanTag->tag.name.ptr, spanTag->tag.name.len, &zv); + + zend_string *tag = zend_strpprintf(0, "_dd.di.%.*s.probe_id", (int)spanTag->tag.name.len, spanTag->tag.name.ptr); + ZVAL_STR_COPY(&zv, def->probe_id); + zend_hash_update(meta, tag, &zv); + zend_string_release(tag); + + if (error) { + ddog_CharSlice msg = ddog_evaluation_error_first_msg(error); + + tag = zend_strpprintf(0, "_dd.di.%.*s.evaluation_error", (int)spanTag->tag.name.len, spanTag->tag.name.ptr); + ZVAL_STR(&zv, dd_CharSlice_to_zend_string(msg)); + zend_hash_update(meta, tag, &zv); + zend_string_release(tag); + + ddog_evaluation_error_drop(error); + } + } + } +} + +static bool dd_span_decoration_begin(zend_ulong invocation, zend_execute_data *execute_data, void *auxiliary, void *dynamic) { + zval retval; + ZVAL_NULL(&retval); + dd_span_decoration_end(invocation, execute_data, &retval, auxiliary, dynamic); + return true; +} + +static int64_t dd_set_span_decoration(const ddog_Probe *probe) { + dd_probe_def *def = emalloc(sizeof(*def)); + + zai_hook_begin begin = NULL; + zai_hook_end end = NULL; + if (probe->target.in_body_location == DDOG_IN_BODY_LOCATION_START) { + begin = dd_span_decoration_begin; + } else { + end = dd_span_decoration_end; + } + return dd_init_live_debugger_probe(probe, def, begin, end, dd_probe_dtor, 0); +} + +typedef struct { + dd_probe_def parent; + const ddog_MaybeShmLimiter *limiter; +} dd_log_probe_def; + +typedef struct { + bool rejected; + ddog_DebuggerPayload *payload; + zend_string *service; + zend_arena *capture_arena; +} dd_log_probe_dyn; + +static bool dd_log_probe_eval_condition(dd_log_probe_def *def, zend_execute_data *execute_data, zval *retval) { + if (dd_probe_file_mismatch(&def->parent, execute_data)) { + return false; + } + + ddog_ConditionEvaluationResult condition = dd_eval_condition(def->parent.probe.probe.log.when, retval); + switch (condition.tag) { + case DDOG_CONDITION_EVALUATION_RESULT_SUCCESS: + return ddog_shm_limiter_inc(def->limiter, def->parent.probe.probe.log.sampling_snapshots_per_second) && ddog_global_log_probe_limiter_inc(DDTRACE_G(remote_config_state)); + case DDOG_CONDITION_EVALUATION_RESULT_ERROR: + dd_submit_probe_eval_error_snapshot(&def->parent.probe, condition.error); + break; + case DDOG_CONDITION_EVALUATION_RESULT_FAILURE: + break; + } + return false; +} + +static void dd_log_probe_ensure_payload(dd_log_probe_dyn *dyn, dd_log_probe_def *def, ddog_CharSlice *msg) { + if (dyn->payload) { + ddog_update_payload_message(dyn->payload, *msg); + } else { + dyn->service = ddtrace_active_service_name(); + dyn->payload = ddog_create_log_probe_snapshot(&def->parent.probe, msg, dd_zend_string_to_CharSlice(dyn->service), DDOG_CHARSLICE_C("php"), ddtrace_nanoseconds_realtime() / 1000000); + } +} + +static void dd_log_probe_capture_snapshot(ddog_DebuggerCapture *capture, dd_log_probe_def *def, zend_execute_data *execute_data) { + const ddog_CaptureConfiguration *capture_config = def->parent.probe.probe.log.capture; + if (ZEND_USER_CODE(EX(func)->type)) { + zend_array *symbol_table = zend_rebuild_symbol_table(); + zend_string *symbol; + zval *variable; + ZEND_HASH_FOREACH_STR_KEY_VAL_IND(symbol_table, symbol, variable) { + if (symbol) { + struct ddog_CaptureValue capture_value = {0}; + ddog_CharSlice name_slice = dd_zend_string_to_CharSlice(symbol); + ddtrace_snapshot_redacted_name(&capture_value, name_slice); + ddtrace_create_capture_value(variable, &capture_value, capture_config, capture_config->max_reference_depth); + ddog_FieldType type = EX_VAR_NUM(0) <= variable && variable < EX_VAR_NUM(EX(func)->op_array.num_args) ? DDOG_FIELD_TYPE_ARG : DDOG_FIELD_TYPE_LOCAL; + ddog_snapshot_add_field(capture, type, name_slice, capture_value); + } + } ZEND_HASH_FOREACH_END(); + } else if (EX(func)->internal_function.arg_info) { + uint32_t num_args = EX(func)->internal_function.num_args; + for (uintptr_t i = 0; i < num_args; ++i) { + const char *name = EX(func)->internal_function.arg_info[i].name; + ddog_CharSlice name_slice = { .len = strlen(name), .ptr = name }; + struct ddog_CaptureValue capture_value = {0}; + ddtrace_snapshot_redacted_name(&capture_value, name_slice); + ddtrace_create_capture_value(EX_VAR_NUM(i), &capture_value, capture_config, capture_config->max_reference_depth); + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, name_slice, capture_value); + } + } + if (hasThis()) { + struct ddog_CaptureValue capture_value = {0}; + ddtrace_create_capture_value(&EX(This), &capture_value, capture_config, capture_config->max_reference_depth); + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, DDOG_CHARSLICE_C("this"), capture_value); + } +} + +static void dd_log_probe_end(zend_ulong invocation, zend_execute_data *execute_data, zval *retval, void *auxiliary, void *dynamic) { + dd_log_probe_dyn *dyn = dynamic; + dd_log_probe_def *def = auxiliary; + UNUSED(invocation); + + if (dyn->rejected) { + return; + } + + if (def->parent.probe.evaluate_at == DDOG_EVALUATE_AT_EXIT && !dd_log_probe_eval_condition(def, execute_data, retval)) { + return; + } + + ddog_Vec_SnapshotEvaluationError *errors; + zend_string *result = dd_eval_string(def->parent.probe.probe.log.segments, def->parent.probe.probe.log.capture, retval, &errors); + if (errors) { + dd_submit_probe_eval_error_snapshot(&def->parent.probe, errors); + } + ddog_CharSlice result_msg = dd_zend_string_to_CharSlice(result); + dd_log_probe_ensure_payload(dyn, def, &result_msg); + + if (def->parent.probe.probe.log.capture_snapshot) { + DDTRACE_G(debugger_capture_arena) = dyn->capture_arena ? dyn->capture_arena : zend_arena_create(65536); + ddog_DebuggerCapture *capture = ddog_snapshot_exit(dyn->payload); + dd_log_probe_capture_snapshot(capture, def, execute_data); + const ddog_CaptureConfiguration *capture_config = def->parent.probe.probe.log.capture; + struct ddog_CaptureValue capture_value = {0}; + ddtrace_create_capture_value(&EX(This), &capture_value, capture_config, capture_config->max_reference_depth); + ddog_snapshot_add_field(capture, DDOG_FIELD_TYPE_ARG, DDOG_CHARSLICE_C("@return"), capture_value); + } + ddtrace_sidecar_send_debugger_datum(dyn->payload); + if (DDTRACE_G(debugger_capture_arena)) { + zend_arena_destroy(DDTRACE_G(debugger_capture_arena)); + DDTRACE_G(debugger_capture_arena) = NULL; + } + zend_string_release(result); + zend_string_release(dyn->service); +} + +static bool dd_log_probe_begin(zend_ulong invocation, zend_execute_data *execute_data, void *auxiliary, void *dynamic) { + dd_log_probe_dyn *dyn = dynamic; + dd_log_probe_def *def = auxiliary; + UNUSED(invocation); + + dd_probe_mark_active(&def->parent); + + zval retval; + ZVAL_NULL(&retval); + + dyn->payload = NULL; + dyn->rejected = def->parent.probe.evaluate_at == DDOG_EVALUATE_AT_ENTRY && !dd_log_probe_eval_condition(def, execute_data, &retval); + dyn->capture_arena = NULL; + + if (!dyn->rejected && def->parent.probe.evaluate_at == DDOG_EVALUATE_AT_ENTRY) { + dd_log_probe_ensure_payload(dyn, def, NULL); + if (def->parent.probe.probe.log.capture_snapshot) { + ddog_DebuggerCapture *capture = ddog_snapshot_entry(dyn->payload); + DDTRACE_G(debugger_capture_arena) = zend_arena_create(65536); + dd_log_probe_capture_snapshot(capture, def, execute_data); + dyn->capture_arena = DDTRACE_G(debugger_capture_arena); + DDTRACE_G(debugger_capture_arena) = NULL; + } + } + + return true; +} + +static int64_t dd_set_log_probe(const ddog_Probe *probe, const ddog_MaybeShmLimiter *limiter) { + dd_log_probe_def *def = emalloc(sizeof(*def)); + def->limiter = limiter; + return dd_init_live_debugger_probe(probe, &def->parent, dd_log_probe_begin, dd_log_probe_end, dd_probe_dtor, sizeof(dd_log_probe_dyn)); +} + +static void dd_metric_probe_end(zend_ulong invocation, zend_execute_data *execute_data, zval *retval, void *auxiliary, void *dynamic) { + dd_probe_def *def = auxiliary; + UNUSED(invocation, dynamic); + if (dd_probe_file_mismatch(def, execute_data)) { + return; + } + + dd_probe_mark_active(def); + + ddog_CharSlice *name = &def->probe.probe.metric.name; + zend_string *metric_name = zend_strpprintf(0, "dynamic.instrumentation.metric.probe.%.*s", (int)name->len, name->ptr); + + ddog_ValueEvaluationResult result = dd_eval_value(def->probe.probe.metric.value, retval); + if (result.tag == DDOG_VALUE_EVALUATION_RESULT_ERROR) { + dd_submit_probe_eval_error_snapshot(&def->probe, result.error); + return; + } + + ddog_IntermediateValue value = ddog_evaluated_value_get(result.success); + double metric_value = 0; + switch (value.tag) { + case DDOG_INTERMEDIATE_VALUE_NULL: + break; + case DDOG_INTERMEDIATE_VALUE_STRING: ; + zend_string *str = dd_CharSlice_to_zend_string(value.string); + metric_value = zend_strtod(ZSTR_VAL(str), NULL); + zend_string_release(str); + break; + case DDOG_INTERMEDIATE_VALUE_NUMBER: + metric_value = value.number; + break; + case DDOG_INTERMEDIATE_VALUE_BOOL: + metric_value = value.bool_; + break; + case DDOG_INTERMEDIATE_VALUE_REFERENCED: + metric_value = zval_get_double(value.referenced); + break; + } + + switch (def->probe.probe.metric.kind) { + case DDOG_METRIC_KIND_COUNT: + ddtrace_sidecar_dogstatsd_count(metric_name, (zend_long)metric_value, NULL); + break; + case DDOG_METRIC_KIND_GAUGE: + ddtrace_sidecar_dogstatsd_gauge(metric_name, metric_value, NULL); + break; + case DDOG_METRIC_KIND_HISTOGRAM: + ddtrace_sidecar_dogstatsd_histogram(metric_name, metric_value, NULL); + break; + case DDOG_METRIC_KIND_DISTRIBUTION: + ddtrace_sidecar_dogstatsd_distribution(metric_name, metric_value, NULL); + break; + } + + ddog_evaluated_value_drop(result.success); + zend_string_release(metric_name); +} + +static bool dd_metric_probe_begin(zend_ulong invocation, zend_execute_data *execute_data, void *auxiliary, void *dynamic) { + zval retval; + ZVAL_NULL(&retval); + dd_metric_probe_end(invocation, execute_data, &retval, auxiliary, dynamic); + return true; +} + +static int64_t dd_set_metric_probe(const ddog_Probe *probe) { + dd_probe_def *def = emalloc(sizeof(*def)); + + zai_hook_begin begin = NULL; + zai_hook_end end = NULL; + if (probe->target.in_body_location == DDOG_IN_BODY_LOCATION_START) { + begin = dd_metric_probe_begin; + } else { + end = dd_metric_probe_end; + } + return dd_init_live_debugger_probe(probe, def, begin, end, dd_probe_dtor, 0); +} + +static int64_t dd_set_probe(const ddog_Probe probe, const ddog_MaybeShmLimiter *limiter) { + switch (probe.probe.tag) { + case DDOG_PROBE_TYPE_METRIC: + return dd_set_metric_probe(&probe); + case DDOG_PROBE_TYPE_LOG: + return dd_set_log_probe(&probe, limiter); + case DDOG_PROBE_TYPE_SPAN: + return dd_set_span_probe(&probe); + case DDOG_PROBE_TYPE_SPAN_DECORATION: + return dd_set_span_decoration(&probe); + } + return -1; +} + +static void dd_remove_live_debugger_probe(int64_t id) { + dd_probe_def *def; + if ((def = zend_hash_index_find_ptr(&DDTRACE_G(active_rc_hooks), (zend_ulong)id))) { + zend_string *scope = def->scope ? zend_string_copy(def->scope) : NULL; + zend_string *func = def->function ? zend_string_copy(def->function) : NULL; + zai_hook_remove( + def->scope ? (zai_str)ZAI_STR_FROM_ZSTR(def->scope) : (zai_str)ZAI_STR_EMPTY, + def->function ? (zai_str)ZAI_STR_FROM_ZSTR(def->function) : (zai_str)ZAI_STR_EMPTY, + id); + if (scope) { + zend_string_release(scope); + } + if (func) { + zend_string_release(func); + } + } +} + +static void dd_free_void_collection_none(struct ddog_VoidCollection collection) { + UNUSED(collection); +} + +static ddog_VoidCollection dd_empty_collection = { + .free = dd_free_void_collection_none, + .count = 0, + .elements = NULL, +}; + +static void dd_free_void_collection(struct ddog_VoidCollection collection) { + efree((void *)collection.elements); +} + +static ddog_VoidCollection dd_alloc_void_collection(uint32_t elements) { + return (ddog_VoidCollection){ + .free = dd_free_void_collection, + .count = elements, + .elements = emalloc(sizeof(void *)), + }; +} + +static void dd_intermediate_to_zval(struct ddog_IntermediateValue val, zval *zv) { + switch (val.tag) { + case DDOG_INTERMEDIATE_VALUE_STRING: + ZVAL_STRINGL(zv, val.string.ptr, val.string.len); + break; + case DDOG_INTERMEDIATE_VALUE_NUMBER: + ZVAL_DOUBLE(zv, val.number); + break; + case DDOG_INTERMEDIATE_VALUE_BOOL: + ZVAL_BOOL(zv, val.bool_); + break; + case DDOG_INTERMEDIATE_VALUE_NULL: + ZVAL_NULL(zv); + break; + case DDOG_INTERMEDIATE_VALUE_REFERENCED: + ZVAL_COPY(zv, val.referenced); + break; + } +} + +static zend_long dd_zval_convert_index(zval *zvp, bool *success) { + zval *dim = (zval *) zvp; + + ZVAL_DEREF(dim); + switch (Z_TYPE_P(dim)) { + case IS_LONG: + *success = true; + return Z_LVAL_P(dim); + case IS_STRING: ; + zend_long off; + *success = IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &off, NULL, true, NULL, NULL); + return off; + default: + *success = false; + return 0; + } +} + +static inline int dd_eval_cmp(struct ddog_IntermediateValue a, struct ddog_IntermediateValue b) { + zval zva, zvb; + dd_intermediate_to_zval(a, &zva); + dd_intermediate_to_zval(b, &zvb); + + bool objectA = Z_TYPE(zva) == IS_OBJECT || (Z_TYPE(zva) == IS_REFERENCE && Z_TYPE_P(Z_REFVAL(zva)) == IS_OBJECT); + bool objectB = Z_TYPE(zvb) == IS_OBJECT || (Z_TYPE(zvb) == IS_REFERENCE && Z_TYPE_P(Z_REFVAL(zvb)) == IS_OBJECT); + + int ret; + if (objectA != objectB) { + // Avoid object casting, which may lead to notices + ret = objectA - objectB; + } else { + ret = zend_compare(&zva, &zvb); + } + + zval_ptr_dtor(&zva); + zval_ptr_dtor(&zvb); + + return ret; +} + +static bool dd_eval_equals(void *ctx, struct ddog_IntermediateValue a, struct ddog_IntermediateValue b) { + UNUSED(ctx); +#define TAGCASE(a, b) MIN(a, b) + (MAX(a, b) << 4) + switch (TAGCASE(a.tag, b.tag)) { + case TAGCASE(DDOG_INTERMEDIATE_VALUE_STRING, DDOG_INTERMEDIATE_VALUE_STRING): + return a.string.len == b.string.len && memcmp(a.string.ptr, b.string.ptr, a.string.len) == 0; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_NUMBER, DDOG_INTERMEDIATE_VALUE_NUMBER): + return a.number == b.number; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_BOOL, DDOG_INTERMEDIATE_VALUE_BOOL): + return a.bool_ == b.bool_; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_BOOL, DDOG_INTERMEDIATE_VALUE_NUMBER): + return a.tag == DDOG_INTERMEDIATE_VALUE_BOOL ? a.number == b.bool_ : a.bool_ == b.number; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_NULL, DDOG_INTERMEDIATE_VALUE_NULL): + return true; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_NULL, DDOG_INTERMEDIATE_VALUE_BOOL): + return (b.tag == DDOG_INTERMEDIATE_VALUE_NULL ? a.bool_ : b.bool_) == false; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_NUMBER, DDOG_INTERMEDIATE_VALUE_NULL): + return (b.tag == DDOG_INTERMEDIATE_VALUE_NULL ? a.number : b.number) == 0; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_STRING, DDOG_INTERMEDIATE_VALUE_NULL): + return (b.tag == DDOG_INTERMEDIATE_VALUE_NULL ? a.string : b.string).len == 0; + case TAGCASE(DDOG_INTERMEDIATE_VALUE_STRING, DDOG_INTERMEDIATE_VALUE_BOOL): + return ((b.tag == DDOG_INTERMEDIATE_VALUE_BOOL ? a.string : b.string).len == 0) != (b.tag != DDOG_INTERMEDIATE_VALUE_BOOL ? a.bool_ : b.bool_); + case TAGCASE(DDOG_INTERMEDIATE_VALUE_STRING, DDOG_INTERMEDIATE_VALUE_REFERENCED): ; + // avoid copies for ref == str + const zval *zv = a.tag == DDOG_INTERMEDIATE_VALUE_REFERENCED ? a.referenced : b.referenced; + if (Z_TYPE_P(zv) == IS_STRING) { + ddog_CharSlice *str = a.tag == DDOG_INTERMEDIATE_VALUE_REFERENCED ? &b.string : &a.string; + return zend_string_equals_cstr(Z_STR_P(zv), str->ptr, str->len); + } + } + + return dd_eval_cmp(a, b) == 0; +} + +static bool dd_eval_greater_than(void *ctx, struct ddog_IntermediateValue a, struct ddog_IntermediateValue b) { + UNUSED(ctx); + return dd_eval_cmp(a, b) > 0; +} + +static bool dd_eval_greater_or_equals(void *ctx, struct ddog_IntermediateValue a, struct ddog_IntermediateValue b) { + UNUSED(ctx); + return dd_eval_cmp(a, b) >= 0; +} + +static const void *dd_eval_fetch_identifier(void *ctx, const ddog_CharSlice *name) { + struct eval_ctx *eval_ctx = ctx; + zend_execute_data *execute_data = eval_ctx->frame; + + if (EX(func)) { + if (ZEND_USER_CODE(EX(func)->type)) { + zend_execute_data *current_execute_data = EG(current_execute_data); + EG(current_execute_data) = execute_data; + zend_array *symtable = zend_rebuild_symbol_table(); + if (!symtable) { + return NULL; + } + zval *zvp = zend_hash_str_find_ind(symtable, name->ptr, name->len); + EG(current_execute_data) = current_execute_data; + if (zvp) { + return zvp; + } + } else { + int call_args = MIN(EX_NUM_ARGS(), EX(func)->common.num_args); + for (int i = 0; i < call_args; ++i) { + const char *argname = EX(func)->internal_function.arg_info[i].name; + if (zend_binary_strcmp(argname, strlen(argname), name->ptr, name->len) == 0) { + return EX_VAR_NUM(i); + } + } + } + } + + if (name->len == 4 && memcmp(name->ptr, ZEND_STRL("this")) == 0) { + if (hasThis()) { + return &EX(This); + } + return NULL; + } + + if (name->len == sizeof("duration") && memcmp(name->ptr, ZEND_STRL("@duration")) == 0) { + ddtrace_span_data *span = ddtrace_active_span(); + if (span) { + zval zv; // milliseconds + ZVAL_DOUBLE(&zv, (zend_hrtime() - span->duration_start) / 1000000.); + return dd_persist_eval_arena(eval_ctx, &zv); + } else { + return NULL; + } + } + + if (name->len == sizeof("return") && memcmp(name->ptr, ZEND_STRL("@return")) == 0) { + return eval_ctx->retval; + } + + if (name->len == sizeof("exception") && memcmp(name->ptr, ZEND_STRL("@exception")) == 0) { + if (EG(exception)) { + zval zv; + ZVAL_OBJ_COPY(&zv, EG(exception)); + return dd_persist_eval_arena(eval_ctx, &zv); + } + return NULL; + } + + return NULL; +} + +static const void *dd_eval_fetch_nested(void *ctx, const void *container_ptr, struct ddog_IntermediateValue index) { + zval *container = (zval *)container_ptr, *dim = (zval *)index.referenced; + ZVAL_DEREF(container); + switch (Z_TYPE_P(container)) { + case IS_OBJECT: ; + if (ddog_snapshot_redacted_type(dd_zend_string_to_CharSlice(Z_OBJCE_P(container)->name))) { + return ddog_EVALUATOR_RESULT_REDACTED; + } + zend_property_info *prop; + zend_string *prop_name; + if (index.tag == DDOG_INTERMEDIATE_VALUE_STRING) { + prop = zend_hash_str_find_ptr(&Z_OBJCE_P(container)->properties_info, index.string.ptr, index.string.len); + if (!prop) { + prop_name = zend_string_init(index.string.ptr, index.string.len, 0); + } else { + prop_name = zend_string_copy(prop->name); + } + } else if (index.tag == DDOG_INTERMEDIATE_VALUE_REFERENCED) { + ZVAL_DEREF(dim); + if (Z_TYPE_P(dim) != IS_STRING) { + return ddog_EVALUATOR_RESULT_INVALID; + } + prop = zend_hash_find_ptr(&Z_OBJCE_P(container)->properties_info, Z_STR_P(dim)); + prop_name = zend_string_copy(Z_STR_P(dim)); + } else { + return ddog_EVALUATOR_RESULT_INVALID; + } + zval rv; + uint32_t *guard, orig_guard; + if (Z_OBJCE_P(container)->ce_flags & ZEND_ACC_USE_GUARDS) { + guard = zend_get_property_guard(Z_OBJ_P(container), prop_name); + orig_guard = *guard; + *guard |= ZEND_GUARD_PROPERTY_MASK; // bypass __magicMethods + } else { + guard = NULL; + } +#if PHP_VERSION_ID < 80000 + zval *ret = zend_read_property_ex(prop ? prop->ce : Z_OBJCE_P(container), container, prop_name, 1, &rv); +#else + zval *ret = zend_read_property_ex(prop ? prop->ce : Z_OBJCE_P(container), Z_OBJ_P(container), prop_name, 1, &rv); +#endif + if (guard) { + *guard = orig_guard; + } + zend_string_release(prop_name); + if (ret == &EG(uninitialized_zval)) { + return NULL; + } + if (ret == &rv) { + ret = dd_persist_eval_arena(ctx, ret); + } + return ret; + case IS_ARRAY: + switch (index.tag) { + case DDOG_INTERMEDIATE_VALUE_STRING: + return zend_symtable_str_find(Z_ARR_P(container), index.string.ptr, index.string.len); + case DDOG_INTERMEDIATE_VALUE_NUMBER: + return zend_hash_index_find(Z_ARR_P(container), (zend_ulong)index.number); + case DDOG_INTERMEDIATE_VALUE_REFERENCED: + ZVAL_DEREF(dim); + switch (Z_TYPE_P(dim)) { + case IS_STRING: + return zend_symtable_find(Z_ARR_P(container), Z_STR_P(dim)); + case IS_LONG: + return zend_hash_index_find(Z_ARR_P(container), (zend_ulong) Z_LVAL_P(dim)); + case IS_DOUBLE: + return zend_hash_index_find(Z_ARR_P(container), (zend_ulong) Z_DVAL_P(dim)); + } + return ddog_EVALUATOR_RESULT_INVALID; + default: + return ddog_EVALUATOR_RESULT_INVALID; + } + case IS_STRING: ; + zend_long off; + switch (index.tag) { + case DDOG_INTERMEDIATE_VALUE_STRING: ; + char *end = (char *)index.string.ptr + index.string.len; + off = strtoll(index.string.ptr, &end, 10); + break; + case DDOG_INTERMEDIATE_VALUE_NUMBER: + off = (zend_long)index.number; + break; + case DDOG_INTERMEDIATE_VALUE_REFERENCED: ; + bool success; + off = dd_zval_convert_index((zval *)index.referenced, &success); + if (!success) { + return ddog_EVALUATOR_RESULT_INVALID; + } + break; + default: + return ddog_EVALUATOR_RESULT_INVALID; + } + zval zv; + if (off < 0 || off >= (zend_long)Z_STRLEN_P(container)) { + ZVAL_EMPTY_STRING(&zv); + } else { + char chr = Z_STRVAL_P(container)[off]; +#if PHP_VERSION_ID < 70200 + ZVAL_STRINGL(&zv, &chr, 1); +#else + ZVAL_STR_COPY(&zv, zend_one_char_string[(unsigned char) chr]); +#endif + } + return dd_persist_eval_arena(ctx, &zv); + default: + return ddog_EVALUATOR_RESULT_INVALID; + } +} + +static void dd_sandboxed_read_dimension(zval *container, zval *dim, zval **ret, zval *rv) { + zai_sandbox sandbox; + zai_sandbox_open(&sandbox); + + // We expect read access to have no real side effects, but it still might throw for invalid offsets etc. + zend_try { +#if PHP_VERSION_ID < 80000 + if (Z_OBJ_HANDLER_P(container, has_dimension)(container, dim, 0)) { + *ret = Z_OBJ_HANDLER_P(container, read_dimension)(container, dim, BP_VAR_IS, rv); +#else + if (Z_OBJ_HANDLER_P(container, has_dimension)(Z_OBJ_P(container), dim, 0)) { + *ret = Z_OBJ_HANDLER_P(container, read_dimension)(Z_OBJ_P(container), dim, BP_VAR_IS, rv); +#endif + } else { + *ret = NULL; + } + } zend_catch { + zai_sandbox_bailout(&sandbox); + } zend_end_try(); + + zai_sandbox_close(&sandbox); +} + +static const void *dd_eval_fetch_index(void *ctx, const void *container_ptr, struct ddog_IntermediateValue index) { + zval *container = (zval *)container_ptr; + ZVAL_DEREF(container); + if (Z_TYPE_P(container) == IS_OBJECT) { + if (Z_OBJ_HANDLER_P(container, read_dimension) != zend_std_read_dimension) { // of internal classes like weakmap + zval dim, rv, *ret = (zval *)ddog_EVALUATOR_RESULT_INVALID; + dd_intermediate_to_zval(index, &dim); + dd_sandboxed_read_dimension(container, &dim, &ret, &rv); + + zval_ptr_dtor(&dim); + if (ret == &rv) { + return dd_persist_eval_arena(ctx, &rv); + } + return ret; + } + } + return dd_eval_fetch_nested(ctx, container_ptr, index); +} + +static uintptr_t dd_eval_length(void *ctx, const void *zvp) { + UNUSED(ctx); + const zval *zv = zvp; + retry: + switch (Z_TYPE_P(zv)) { + case IS_REFERENCE: + ZVAL_DEREF(zv); + goto retry; + + case IS_ARRAY: + return zend_array_count(Z_ARRVAL_P(zv)); + + case IS_OBJECT: + // Internal handler + if (Z_OBJ_HANDLER_P(zv, count_elements)) { + zend_long num; + zend_object *ex = EG(exception); +#if PHP_VERSION_ID < 80000 + if (SUCCESS == Z_OBJ_HANDLER_P(zv, count_elements)((zval *)zv, &num)) { +#else + if (SUCCESS == Z_OBJ_HANDLER_P(zv, count_elements)(Z_OBJ_P(zv), &num)) { +#endif + EG(exception) = ex; + return (uint64_t)num; + } + if (EG(exception)) { + zend_clear_exception(); + } + EG(exception) = ex; + } + + return zend_array_count(Z_OBJPROP_P(zv)); + + case IS_STRING: + return Z_STRLEN_P(zv); + + case IS_DOUBLE: + case IS_LONG: ; + zend_string *str = ddtrace_convert_to_str(zv); + uint64_t len = ZSTR_LEN(str); + zend_string_release(str); + return len; + + default: + return 0; + } +} + +static ddog_VoidCollection dd_eval_try_enumerate(void *ctx, const void *zvp) { + UNUSED(ctx); + const zval *zv = zvp; + HashTable *values; + retry: + switch (Z_TYPE_P(zv)) { + case IS_REFERENCE: + ZVAL_DEREF(zv); + goto retry; + + case IS_ARRAY: + values = Z_ARR_P(zv); + break; + + case IS_OBJECT: + if (ddog_snapshot_redacted_type(dd_zend_string_to_CharSlice(Z_OBJCE_P(zv)->name))) { + ddog_VoidCollection collection = dd_empty_collection; + collection.count = (intptr_t)ddog_EVALUATOR_RESULT_REDACTED; + return collection; + } + values = Z_OBJPROP_P(zv); + break; + + default: ; + ddog_VoidCollection collection = dd_empty_collection; + collection.count = (intptr_t)ddog_EVALUATOR_RESULT_INVALID; + return collection; + } + + zval *val; + int idx = 0; + ddog_VoidCollection collection = dd_alloc_void_collection(zend_hash_num_elements(values)); + ZEND_HASH_FOREACH_VAL_IND(values, val) { + ((zval **)collection.elements)[idx++] = val; + } ZEND_HASH_FOREACH_END(); + collection.count = idx; + return collection; +} + +static void dd_stringify_limited_str(zend_string *string, smart_str *str, const ddog_CaptureConfiguration *config) { + if (ZSTR_LEN(string) <= config->max_length) { + smart_str_append(str, string); + } else { + smart_str_appendl(str, ZSTR_VAL(string), config->max_length); + smart_str_appends(str, "..."); + } +} + +static void dd_stringify_zval(const zval *zv, smart_str *str, const ddog_CaptureConfiguration *config, int remaining_nesting) { + ZVAL_DEREF(zv); + switch (Z_TYPE_P(zv)) { + case IS_FALSE: + smart_str_appends(str, "false"); + break; + + case IS_TRUE: + smart_str_appends(str, "true"); + break; + + case IS_LONG: + smart_str_append_long(str, Z_LVAL_P(zv)); + break; + + case IS_DOUBLE: + smart_str_append_double(str, Z_DVAL_P(zv), EG(precision), 0); + break; + + case IS_STRING: + dd_stringify_limited_str(Z_STR_P(zv), str, config); + break; + + case IS_ARRAY: { + if (remaining_nesting == 0) { + smart_str_appends(str, "[...]"); + break; + } + zval *val; + bool first = true; + smart_str_appendc(str, '['); + if (zend_array_is_list(Z_ARR_P(zv))) { + int remaining_fields = config->max_collection_size; + ZEND_HASH_FOREACH_VAL(Z_ARR_P(zv), val) { + if (!first) { + smart_str_appends(str, ", "); + } + first = false; + if (remaining_fields-- == 0) { + smart_str_appends(str, "...]"); + break; + } + + dd_stringify_zval(val, str, config, remaining_nesting - 1); + } ZEND_HASH_FOREACH_END(); + } else { + zend_long idx; + zend_string *key; + int remaining_fields = config->max_collection_size; + ZEND_HASH_FOREACH_KEY_VAL(Z_ARR_P(zv), idx, key, val) { + if (!first) { + smart_str_appends(str, ", "); + } + first = false; + if (remaining_fields-- == 0) { + smart_str_appends(str, "...]"); + break; + } + + if (key) { + dd_stringify_limited_str(key, str, config); + if (ddog_snapshot_redacted_name(dd_zend_string_to_CharSlice(key))) { + smart_str_appends(str, " => {redacted}"); + continue; + } + } else { + smart_str_append_long(str, idx); + } + smart_str_appends(str, " => "); + dd_stringify_zval(val, str, config, remaining_nesting - 1); + } ZEND_HASH_FOREACH_END(); + } + smart_str_appendc(str, ']'); + break; + } + + case IS_OBJECT: { + zend_class_entry *ce = Z_OBJCE_P(zv); + smart_str_appendc(str, '('); + smart_str_append(str, ce->name); + smart_str_appendc(str, ')'); + smart_str_appendc(str, '{'); + if (ddog_snapshot_redacted_type(dd_zend_string_to_CharSlice(ce->name))) { + smart_str_appends(str, "redacted}"); + break; + } + if (remaining_nesting == 0) { + smart_str_appends(str, "...}"); + break; + } + zval *val; + zend_string *key; + int remaining_fields = config->max_field_count; +#if PHP_VERSION_ID < 70400 + int is_temp = 0; +#endif + // reverse to prefer child class properties first + HashTable *ht = ce->type == ZEND_INTERNAL_CLASS ? +#if PHP_VERSION_ID < 70400 + Z_OBJDEBUG_P(zv, is_temp) +#else + zend_get_properties_for((zval *)zv, ZEND_PROP_PURPOSE_DEBUG) +#endif + : Z_OBJPROP_P(zv); + bool first = true; + ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(ht, key, val) { + if (!key) { + continue; + } + + if (!first) { + smart_str_appends(str, ", "); + } + first = false; + if (remaining_fields-- == 0) { + smart_str_appends(str, "...}"); + break; + } + + ddog_CharSlice name; + if (ZSTR_LEN(key) < 3 || ZSTR_VAL(key)[0]) { + smart_str_append(str, key); + name = dd_zend_string_to_CharSlice(key); + } else if (ZSTR_VAL(key)[1] == '*') { // skip \0*\0 + name = (ddog_CharSlice){ .len = ZSTR_LEN(key) - 3, .ptr = ZSTR_VAL(key) + 3 }; + smart_str_appendl(str, name.ptr, name.len); + } else { + int classname_len = (int)strlen(ZSTR_VAL(key) + 1); + smart_str_appendl(str, ZSTR_VAL(key) + 1, classname_len); + smart_str_appends(str, "::"); + name = (ddog_CharSlice){ .len = ZSTR_LEN(key) - classname_len - 2, .ptr = ZSTR_VAL(key) + classname_len + 2 }; + smart_str_appendl(str, name.ptr, name.len); + } + smart_str_appends(str, ": "); + if (ddog_snapshot_redacted_name(name)) { + smart_str_appends(str, "{redacted}"); + } else { + ZVAL_DEINDIRECT(val); + dd_stringify_zval(val, str, config, remaining_nesting - 1); + } + } ZEND_HASH_FOREACH_END(); + if (ce->type == ZEND_INTERNAL_CLASS) { +#if PHP_VERSION_ID < 70400 + if (is_temp) { + zend_array_release(ht); + } +#else + zend_release_properties(ht); +#endif + } + smart_str_appendc(str, '}'); + break; + } + + case IS_RESOURCE: { + smart_str_appends(str, zend_rsrc_list_get_rsrc_type(Z_RES_P(zv))); + smart_str_appendc(str, '#'); + smart_str_append_long(str, Z_RES_P(zv)->handle); + break; + } + + default: + smart_str_appends(str, "null"); + } +} + + +static ddog_CharSlice dd_eval_get_string(void *ctx, const void *zvp) { + struct eval_ctx *eval_ctx = ctx; + const zval *zv = zvp; + + switch (Z_TYPE_P(zv)) { + case IS_STRING: + return dd_zend_string_to_CharSlice(Z_STR_P(zv)); + case IS_TRUE: + return DDOG_CHARSLICE_C("true"); + case IS_FALSE: + return DDOG_CHARSLICE_C("false"); + case IS_NULL: + return DDOG_CHARSLICE_C("null"); + } + + smart_str str = {0}; + dd_stringify_zval(zv, &str, eval_ctx->config, eval_ctx->config->max_reference_depth); + if (!str.s) { + return DDOG_CHARSLICE_C(""); + } + smart_str_0(&str); + return dd_persist_str_eval_arena(ctx, str.s); +} + +static ddog_CharSlice dd_eval_stringify(void *ctx, const void *zvp) { + UNUSED(ctx); + const zval *zv = zvp; + zend_string *str = ddtrace_convert_to_str(zv); + return dd_persist_str_eval_arena(ctx, str); +} + +static intptr_t dd_eval_convert_index(void *ctx, const void *zvp) { + UNUSED(ctx); + bool success; + intptr_t index = dd_zval_convert_index((zval *)zvp, &success); + if (success) { + return index; + } + return (intptr_t)ddog_EVALUATOR_RESULT_INVALID; +} + +static bool dd_eval_instanceof(void *ctx, const void *zvp, const ddog_CharSlice *class) { + UNUSED(ctx); + const zval *zv = zvp; + ZVAL_DEREF(zv); + if (Z_TYPE_P(zv) == IS_OBJECT) { + if (zend_binary_strcasecmp(ZEND_STRL("object"), class->ptr, class->len) == 0) { + return true; + } + zend_string *class_str = dd_CharSlice_to_zend_string(*class); +#if PHP_VERSION_ID < 70400 + zend_class_entry *ce = zend_lookup_class_ex(class_str, NULL, 0); +#else + zend_class_entry *ce = zend_lookup_class_ex(class_str, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); +#endif + zend_string_release(class_str); + return ce && instanceof_function(Z_OBJCE_P(zv), ce); + } + const char *name; +#if PHP_VERSION_ID < 70300 + if (Z_TYPE_P(zv) == _IS_BOOL || Z_TYPE_P(zv) == IS_FALSE || Z_TYPE_P(zv) == IS_TRUE) + { + name = "bool"; + } + else +#endif + { + name = zend_zval_type_name(zv); + } + return zend_binary_strcasecmp(name, strlen(name), class->ptr, class->len) == 0; +} + +const ddog_Evaluator dd_evaluator = { + .equals = dd_eval_equals, + .greater_than = dd_eval_greater_than, + .greater_or_equals = dd_eval_greater_or_equals, + .fetch_identifier = dd_eval_fetch_identifier, + .fetch_index = dd_eval_fetch_index, + .fetch_nested = dd_eval_fetch_nested, + .length = dd_eval_length, + .try_enumerate = dd_eval_try_enumerate, + .stringify = dd_eval_stringify, + .get_string = dd_eval_get_string, + .convert_index = dd_eval_convert_index, + .instanceof = dd_eval_instanceof, +}; + +ddog_LiveDebuggerSetup ddtrace_live_debugger_setup = { + .callbacks = { + .set_probe = dd_set_probe, + .remove_probe = dd_remove_live_debugger_probe, + }, + .evaluator = &dd_evaluator, +}; + +void ddtrace_live_debugger_minit(void) { + zend_string *value; + ZEND_HASH_FOREACH_STR_KEY(get_global_DD_DYNAMIC_INSTRUMENTATION_REDACTED_IDENTIFIERS(), value) { + ddog_snapshot_add_redacted_name(dd_zend_string_to_CharSlice(value)); + } ZEND_HASH_FOREACH_END(); + ZEND_HASH_FOREACH_STR_KEY(get_global_DD_DYNAMIC_INSTRUMENTATION_REDACTED_TYPES(), value) { + ddog_snapshot_add_redacted_type(dd_zend_string_to_CharSlice(value)); + } ZEND_HASH_FOREACH_END(); +} diff --git a/ext/live_debugger.h b/ext/live_debugger.h new file mode 100644 index 0000000000..8759466b2c --- /dev/null +++ b/ext/live_debugger.h @@ -0,0 +1,16 @@ +#ifndef DD_LIVE_DEBUGGER_H +#define DD_LIVE_DEBUGGER_H + +#include + +extern ddog_LiveDebuggerSetup ddtrace_live_debugger_setup; + +void ddtrace_live_debugger_minit(void); + +static inline void ddtrace_snapshot_redacted_name(ddog_CaptureValue *capture_value, ddog_CharSlice name) { + if (ddog_snapshot_redacted_name(name)) { + capture_value->not_captured_reason = DDOG_CHARSLICE_C("redactedIdent"); + } +} + +#endif // DD_LIVE_DEBUGGER_H diff --git a/ext/logging.c b/ext/logging.c index 3188e83df9..163bbe4215 100644 --- a/ext/logging.c +++ b/ext/logging.c @@ -205,16 +205,16 @@ void ddtrace_log_init(void) { ddog_log_callback = ddtrace_log_callback; } -bool ddtrace_alter_dd_trace_debug(zval *old_value, zval *new_value) { - UNUSED(old_value); +bool ddtrace_alter_dd_trace_debug(zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(old_value, new_str); dd_log_set_level(Z_TYPE_P(new_value) == IS_TRUE); return true; } -bool ddtrace_alter_dd_trace_log_level(zval *old_value, zval *new_value) { - UNUSED(old_value); +bool ddtrace_alter_dd_trace_log_level(zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(old_value, new_str); if (runtime_config_first_init ? get_DD_TRACE_DEBUG() : get_global_DD_TRACE_DEBUG()) { return true; } diff --git a/ext/logging.h b/ext/logging.h index 611c7f7064..14b7260f7f 100644 --- a/ext/logging.h +++ b/ext/logging.h @@ -26,7 +26,7 @@ int ddtrace_bgs_logf(const char *fmt, ...); /* }}} */ void ddtrace_log_init(void); -bool ddtrace_alter_dd_trace_debug(zval *old_value, zval *new_value); -bool ddtrace_alter_dd_trace_log_level(zval *old_value, zval *new_value); +bool ddtrace_alter_dd_trace_debug(zval *old_value, zval *new_value, zend_string *new_str); +bool ddtrace_alter_dd_trace_log_level(zval *old_value, zval *new_value, zend_string *new_str); #endif // DD_LOGGING_H diff --git a/ext/priority_sampling/priority_sampling.c b/ext/priority_sampling/priority_sampling.c index 23d34a4eff..b1cb69d6ca 100644 --- a/ext/priority_sampling/priority_sampling.c +++ b/ext/priority_sampling/priority_sampling.c @@ -18,7 +18,7 @@ ZEND_EXTERN_MODULE_GLOBALS(ddtrace); void ddtrace_try_read_agent_rate(void) { ddog_CharSlice data; - if (DDTRACE_G(remote_config_reader) && ddog_agent_remote_config_read(DDTRACE_G(remote_config_reader), &data)) { + if (DDTRACE_G(agent_config_reader) && ddog_agent_remote_config_read(DDTRACE_G(agent_config_reader), &data)) { zval json; if ((int)data.len > 0 && zai_json_decode_assoc_safe(&json, data.ptr, (int)data.len, 3, true) == SUCCESS) { if (Z_TYPE(json) == IS_ARRAY) { @@ -106,19 +106,20 @@ static ddtrace_rule_result dd_match_rules(ddtrace_span_data *span, bool eval_roo int index = -3; if (++index >= skip_at) { - return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX }; + return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX, .mechanism = DD_MECHANISM_RULE }; } zend_array *meta = ddtrace_property_array(&span->property_meta); if (zend_hash_str_exists(meta, ZEND_STRL("manual.keep"))) { - return (ddtrace_rule_result){ .sampling_rate = 1, .rule = -2 }; + // manual.keep and manual.drop count as manual + return (ddtrace_rule_result){ .sampling_rate = 1, .rule = -2, .mechanism = DD_MECHANISM_MANUAL }; } if (++index >= skip_at) { - return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX }; + return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX, .mechanism = DD_MECHANISM_RULE }; } if (zend_hash_str_exists(meta, ZEND_STRL("manual.drop"))) { - return (ddtrace_rule_result){ .sampling_rate = 0, .rule = -1 }; + return (ddtrace_rule_result){ .sampling_rate = 0, .rule = -1, .mechanism = DD_MECHANISM_MANUAL }; } zval *rule; @@ -140,11 +141,20 @@ static ddtrace_rule_result dd_match_rules(ddtrace_span_data *span, bool eval_roo if (dd_check_sampling_rule(Z_ARR_P(rule), span)) { zval *sample_rate_zv = zend_hash_str_find(Z_ARR_P(rule), ZEND_STRL("sample_rate")); - return (ddtrace_rule_result){ .sampling_rate = sample_rate_zv ? zval_get_double(sample_rate_zv) : 1, .rule = index }; + zval *provenance_zv = zend_hash_str_find(Z_ARR_P(rule), ZEND_STRL("_provenance")); + enum dd_sampling_mechanism mechanism = DD_MECHANISM_RULE; + if (provenance_zv && Z_TYPE_P(provenance_zv) == IS_STRING) { + if (zend_string_equals_literal(Z_STR_P(provenance_zv), "customer")) { + mechanism = DD_MECHANISM_REMOTE_USER_RULE; + } else if (zend_string_equals_literal(Z_STR_P(provenance_zv), "dynamic")) { + mechanism = DD_MECHANISM_REMOTE_DYNAMIC_RULE; + } + } + return (ddtrace_rule_result){ .sampling_rate = sample_rate_zv ? zval_get_double(sample_rate_zv) : 1, .rule = index, .mechanism = mechanism }; } } ZEND_HASH_FOREACH_END(); - return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX }; + return (ddtrace_rule_result){ .sampling_rate = 0, .rule = INT32_MAX, .mechanism = DD_MECHANISM_RULE }; } void ddtrace_decide_on_closed_span_sampling(ddtrace_span_data *span) { @@ -212,7 +222,9 @@ static void dd_decide_on_sampling(ddtrace_root_span_data *span) { if (result.rule != INT32_MAX) { sample_rate = result.sampling_rate; - } else if (default_sample_rate < 0) { + } else if (default_sample_rate >= 0) { + result.mechanism = DD_MECHANISM_RULE; + } else { explicit_rule = false; ddtrace_try_read_agent_rate(); @@ -262,7 +274,7 @@ static void dd_decide_on_sampling(ddtrace_root_span_data *span) { // this must be stable on re-evaluation bool sampling = (double)span->trace_id.low < sample_rate * (double)~0ULL; bool limited = false; - if (result.rule >= 0 && ddtrace_limiter_active() && sampling) { + if (result.mechanism != DD_MECHANISM_MANUAL && ddtrace_limiter_active() && sampling) { if (span->trace_is_limited == DD_TRACE_LIMIT_UNCHECKED) { span->trace_is_limited = ddtrace_limiter_allow() ? DD_TRACE_UNLIMITED : DD_TRACE_LIMITED; } @@ -274,8 +286,7 @@ static void dd_decide_on_sampling(ddtrace_root_span_data *span) { zend_array *metrics = ddtrace_property_array(&span->property_metrics); if (explicit_rule) { - // manual.keep and manual.drop count as manual - mechanism = result.rule < 0 ? DD_MECHANISM_MANUAL : DD_MECHANISM_RULE; + mechanism = result.mechanism; priority = sampling && !limited ? PRIORITY_SAMPLING_USER_KEEP : PRIORITY_SAMPLING_USER_REJECT; if (mechanism == DD_MECHANISM_MANUAL) { diff --git a/ext/priority_sampling/priority_sampling.h b/ext/priority_sampling/priority_sampling.h index 7d162a025f..eb38913239 100644 --- a/ext/priority_sampling/priority_sampling.h +++ b/ext/priority_sampling/priority_sampling.h @@ -18,6 +18,8 @@ enum dd_sampling_mechanism { DD_MECHANISM_REMOTE_RATE = 2, DD_MECHANISM_RULE = 3, DD_MECHANISM_MANUAL = 4, + DD_MECHANISM_REMOTE_USER_RULE = 11, + DD_MECHANISM_REMOTE_DYNAMIC_RULE = 12, }; void ddtrace_set_priority_sampling_on_root(zend_long priority, enum dd_sampling_mechanism mechanism); diff --git a/ext/random.c b/ext/random.c index 4501433b61..9ddbdcd096 100644 --- a/ext/random.c +++ b/ext/random.c @@ -32,8 +32,8 @@ void ddtrace_seed_prng(void) { } // Allow for usage in phpunit testsuite -bool ddtrace_reseed_seed_change(zval *old_value, zval *new_value) { - UNUSED(old_value, new_value); +bool ddtrace_reseed_seed_change(zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(old_value, new_value, new_str); ddtrace_seed_prng_with_optional_seed(Z_LVAL_P(new_value)); return true; } diff --git a/ext/random.h b/ext/random.h index 3e924033e0..d14c971ded 100644 --- a/ext/random.h +++ b/ext/random.h @@ -11,7 +11,7 @@ #define DD_TRACE_MAX_ID_LEN 40 // uint64_t -> 2**128 = 20 chars max ID void ddtrace_seed_prng(void); -bool ddtrace_reseed_seed_change(zval *old_value, zval *new_value); +bool ddtrace_reseed_seed_change(zval *old_value, zval *new_value, zend_string *new_str); uint64_t ddtrace_generate_span_id(void); uint64_t ddtrace_peek_span_id(void); ddtrace_trace_id ddtrace_peek_trace_id(void); diff --git a/ext/remote_config.c b/ext/remote_config.c new file mode 100644 index 0000000000..a4291804bc --- /dev/null +++ b/ext/remote_config.c @@ -0,0 +1,123 @@ +#include "remote_config.h" +#include "ddtrace.h" +#include "sidecar.h" +#include "hook/uhook.h" +#include "span.h" +#include +#include +#include +#include +#include "threads.h" +#include "live_debugger.h" +#ifndef _WIN32 +#include +#endif + +#if PHP_VERSION_ID < 70100 +#include +#define zend_interrupt_function zai_interrupt_function +#define zend_vm_interrupt zai_vm_interrupt +#endif + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); + +static void (*dd_prev_interrupt_function)(zend_execute_data *execute_data); +static void dd_vm_interrupt(zend_execute_data *execute_data) { + if (dd_prev_interrupt_function) { + dd_prev_interrupt_function(execute_data); + } + if (DDTRACE_G(remote_config_state) && DDTRACE_G(reread_remote_configuration)) { + LOG(INFO, "Rereading remote configurations after interrupt"); + DDTRACE_G(reread_remote_configuration) = 0; + ddog_process_remote_configs(DDTRACE_G(remote_config_state)); + } +} + +// We need this exported to call it via CreateRemoteThread on Windows +DDTRACE_PUBLIC void ddtrace_set_all_thread_vm_interrupt(void) { + // broadcast interrupt to all threads on ZTS +#if ZTS + tsrm_mutex_lock(ddtrace_threads_mutex); + + void *TSRMLS_CACHE; // EG() accesses a variable named TSRMLS_CACHE. Make use of variable shadowing in scopes... + ZEND_HASH_FOREACH_PTR(&ddtrace_tls_bases, TSRMLS_CACHE) { +#endif +#if PHP_VERSION_ID >= 80200 + zend_atomic_bool_store_ex(&EG(vm_interrupt), 1); +#elif PHP_VERSION_ID >= 70100 + EG(vm_interrupt) = 1; +#else + DDTRACE_G(zai_vm_interrupt) = 1; +#endif + DDTRACE_G(reread_remote_configuration) = 1; +#if ZTS + } ZEND_HASH_FOREACH_END(); + + tsrm_mutex_unlock(ddtrace_threads_mutex); +#endif +} + +DDTRACE_PUBLIC const char *ddtrace_remote_config_get_path() { + if (DDTRACE_G(remote_config_state)) { + return ddog_remote_config_get_path(DDTRACE_G(remote_config_state)); + } + return NULL; +} + +#ifndef _WIN32 +static void dd_sigvtalarm_handler(int signal, siginfo_t *siginfo, void *ctx) { + UNUSED(signal, siginfo, ctx); + ddtrace_set_all_thread_vm_interrupt(); +} +#endif + +static struct ddog_Vec_CChar *dd_dynamic_instrumentation_update(ddog_CharSlice config, ddog_CharSlice value, bool return_old) { + zend_string *name = dd_CharSlice_to_zend_string(config); + zend_string *old; + struct ddog_Vec_CChar *ret = NULL; + if (return_old) { + old = zend_string_copy(zend_ini_get_value(name)); + } + if (zend_alter_ini_entry_chars(name, value.ptr, value.len, ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME) == SUCCESS) { + if (return_old) { + ret = ddog_CharSlice_to_owned(dd_zend_string_to_CharSlice(old)); + } + } + if (return_old) { + zend_string_release(old); + } + zend_string_release(name); + return ret; +} + +void ddtrace_minit_remote_config(void) { + ddog_setup_remote_config(dd_dynamic_instrumentation_update, &ddtrace_live_debugger_setup); + dd_prev_interrupt_function = zend_interrupt_function; + zend_interrupt_function = dd_vm_interrupt; + +#ifndef _WIN32 + struct sigaction act = {0}; + act.sa_flags = SA_SIGINFO | SA_RESTART; + act.sa_sigaction = dd_sigvtalarm_handler; + sigaction(SIGVTALRM, &act, NULL); +#endif +} + +void ddtrace_mshutdown_remote_config(void) { +#ifndef _WIN32 + struct sigaction act = {0}; + act.sa_handler = SIG_IGN; + sigaction(SIGVTALRM, &act, NULL); +#endif +} + +void ddtrace_rinit_remote_config(void) { + zend_hash_init(&DDTRACE_G(active_rc_hooks), 8, NULL, NULL, 0); + DDTRACE_G(reread_remote_configuration) = 0; + ddog_rinit_remote_config(DDTRACE_G(remote_config_state)); +} + +void ddtrace_rshutdown_remote_config(void) { + ddog_rshutdown_remote_config(DDTRACE_G(remote_config_state)); + zend_hash_destroy(&DDTRACE_G(active_rc_hooks)); +} diff --git a/ext/remote_config.h b/ext/remote_config.h new file mode 100644 index 0000000000..9e27fc403f --- /dev/null +++ b/ext/remote_config.h @@ -0,0 +1,14 @@ +#ifndef DD_REMOTE_CONFIG_H +#define DD_REMOTE_CONFIG_H + +#include "ddtrace_export.h" + +void ddtrace_minit_remote_config(void); +void ddtrace_mshutdown_remote_config(void); +void ddtrace_rinit_remote_config(void); +void ddtrace_rshutdown_remote_config(void); + +DDTRACE_PUBLIC void ddtrace_set_all_thread_vm_interrupt(void); +DDTRACE_PUBLIC const char *ddtrace_remote_config_get_path(void); + +#endif diff --git a/ext/serializer.c b/ext/serializer.c index 279ed53fcd..aa82bd9d62 100644 --- a/ext/serializer.c +++ b/ext/serializer.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "arrays.h" #include "compat_string.h" @@ -40,6 +41,9 @@ #include "user_request.h" #include "ddshared.h" #include "zend_hrtime.h" +#include "sidecar.h" +#include "live_debugger.h" +#include "exception_serialize.h" ZEND_EXTERN_MODULE_GLOBALS(ddtrace); @@ -251,146 +255,6 @@ static void _add_assoc_zval_copy(zval *el, const char *name, zval *prop) { add_assoc_zval(el, (name), &value); } -typedef zend_result (*add_tag_fn_t)(void *context, ddtrace_string key, ddtrace_string value); - -#if PHP_VERSION_ID < 70100 -#define ZEND_STR_LINE "line" -#define ZEND_STR_FILE "file" -#define ZEND_STR_PREVIOUS "previous" -#endif - -enum dd_exception { - DD_EXCEPTION_THROWN, - DD_EXCEPTION_CAUGHT, - DD_EXCEPTION_UNCAUGHT, -}; - -static zend_result dd_exception_to_error_msg(zend_object *exception, void *context, add_tag_fn_t add_tag, enum dd_exception exception_state) { - zend_string *msg = zai_exception_message(exception); - zend_long line = zval_get_long(ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_LINE)); - zend_string *file = ddtrace_convert_to_str(ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_FILE)); - - char *error_text, *status_line = NULL; - - if (SG(sapi_headers).http_response_code >= 500) { - if (SG(sapi_headers).http_status_line) { - UNUSED(asprintf(&status_line, " (%s)", SG(sapi_headers).http_status_line)); - } else { - UNUSED(asprintf(&status_line, " (%d)", SG(sapi_headers).http_response_code)); - } - } - - const char *exception_type; - switch (exception_state) { - case DD_EXCEPTION_CAUGHT: exception_type = "Caught"; break; - case DD_EXCEPTION_UNCAUGHT: exception_type = "Uncaught"; break; - default: exception_type = "Thrown"; break; - } - - int error_len = asprintf(&error_text, "%s %s%s%s%s in %s:" ZEND_LONG_FMT, exception_type, - ZSTR_VAL(exception->ce->name), status_line ? status_line : "", ZSTR_LEN(msg) > 0 ? ": " : "", - ZSTR_VAL(msg), ZSTR_VAL(file), line); - - free(status_line); - - ddtrace_string key = DDTRACE_STRING_LITERAL("error.message"); - ddtrace_string value = {error_text, error_len}; - zend_result result = add_tag(context, key, value); - - zend_string_release(file); - free(error_text); - return result; -} - -static zend_result dd_exception_to_error_type(zend_object *exception, void *context, add_tag_fn_t add_tag) { - ddtrace_string value, key = DDTRACE_STRING_LITERAL("error.type"); - - if (instanceof_function(exception->ce, ddtrace_ce_fatal_error)) { - zval *code = ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_CODE); - const char *error_type_string = "{unknown error}"; - - if (Z_TYPE_P(code) == IS_LONG) { - switch (Z_LVAL_P(code)) { - case E_ERROR: - error_type_string = "E_ERROR"; - break; - case E_CORE_ERROR: - error_type_string = "E_CORE_ERROR"; - break; - case E_COMPILE_ERROR: - error_type_string = "E_COMPILE_ERROR"; - break; - case E_USER_ERROR: - error_type_string = "E_USER_ERROR"; - break; - default: - LOG_UNREACHABLE( - "Unhandled error type in DDTrace\\FatalError; is a fatal error case missing?"); - } - - } else { - LOG_UNREACHABLE("Exception was a DDTrace\\FatalError but failed to get an exception code"); - } - - value = ddtrace_string_cstring_ctor((char *)error_type_string); - } else { - zend_string *type_name = exception->ce->name; - value.ptr = ZSTR_VAL(type_name); - value.len = ZSTR_LEN(type_name); - } - - return add_tag(context, key, value); -} - -static zend_result dd_exception_trace_to_error_stack(zend_string *trace, void *context, add_tag_fn_t add_tag) { - ddtrace_string key = DDTRACE_STRING_LITERAL("error.stack"); - ddtrace_string value = {ZSTR_VAL(trace), ZSTR_LEN(trace)}; - zend_result result = add_tag(context, key, value); - zend_string_release(trace); - return result; -} - -// Guarantees that add_tag will only be called once per tag, will stop trying to add tags if one fails. -static zend_result ddtrace_exception_to_meta(zend_object *exception, void *context, add_tag_fn_t add_meta, enum dd_exception exception_state) { - zend_object *exception_root = exception; - zend_string *full_trace = zai_get_trace_without_args_from_exception(exception); - - zval *previous = ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_PREVIOUS); - while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && - instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)) { - zend_string *trace_string = zai_get_trace_without_args_from_exception(Z_OBJ_P(previous)); - - zend_string *msg = zai_exception_message(exception); - zend_long line = zval_get_long(ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_LINE)); - zend_string *file = ddtrace_convert_to_str(ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_FILE)); - - zend_string *complete_trace = - zend_strpprintf(0, "%s\n\nNext %s%s%s in %s:" ZEND_LONG_FMT "\nStack trace:\n%s", ZSTR_VAL(trace_string), - ZSTR_VAL(exception->ce->name), ZSTR_LEN(msg) ? ": " : "", ZSTR_VAL(msg), ZSTR_VAL(file), - line, ZSTR_VAL(full_trace)); - zend_string_release(trace_string); - zend_string_release(full_trace); - zend_string_release(file); - full_trace = complete_trace; - - Z_PROTECT_RECURSION_P(previous); - exception = Z_OBJ_P(previous); - previous = ZAI_EXCEPTION_PROPERTY(exception, ZEND_STR_PREVIOUS); - } - - previous = ZAI_EXCEPTION_PROPERTY(exception_root, ZEND_STR_PREVIOUS); - while (Z_TYPE_P(previous) == IS_OBJECT && !Z_IS_RECURSIVE_P(previous) && - instanceof_function(Z_OBJCE_P(previous), zend_ce_throwable)) { - Z_UNPROTECT_RECURSION_P(previous); - previous = ZAI_EXCEPTION_PROPERTY(Z_OBJ_P(previous), ZEND_STR_PREVIOUS); - } - - bool success = dd_exception_to_error_msg(exception, context, add_meta, exception_state) == SUCCESS && - dd_exception_to_error_type(exception, context, add_meta) == SUCCESS && - dd_exception_trace_to_error_stack(full_trace, context, add_meta) == SUCCESS; - return success ? SUCCESS : FAILURE; -} - typedef struct dd_error_info { zend_string *type; zend_string *msg; @@ -1243,7 +1107,7 @@ static void dd_set_entrypoint_root_span_props_end(zend_array *meta, int status, } } -static void _serialize_meta(zval *el, ddtrace_span_data *span) { +static void _serialize_meta(zval *el, ddtrace_span_data *span, zend_string *service_name) { bool is_root_span = span->std.ce == ddtrace_ce_root_span_data; zval meta_zv, *meta = &span->property_meta; bool ignore_error = false; @@ -1307,7 +1171,7 @@ static void _serialize_meta(zval *el, ddtrace_span_data *span) { if (is_root_span) { exception_type = Z_PROP_FLAG_P(exception_zv) == 2 ? DD_EXCEPTION_CAUGHT : DD_EXCEPTION_UNCAUGHT; } - ddtrace_exception_to_meta(Z_OBJ_P(exception_zv), meta, dd_add_meta_array, exception_type); + ddtrace_exception_to_meta(Z_OBJ_P(exception_zv), service_name, span->start, meta, dd_add_meta_array, exception_type); } zend_array *span_links = ddtrace_property_array(&span->property_links); @@ -1326,6 +1190,23 @@ static void _serialize_meta(zval *el, ddtrace_span_data *span) { EG(exception) = current_exception; } + zend_array *span_events = ddtrace_property_array(&span->property_events); + if (zend_hash_num_elements(span_events) > 0) { + // Save the current exception, if any, and clear it for php_json_encode_serializable_object not to fail + // and zend_call_function to actually call the jsonSerialize method + // Restored after span events are serialized + zend_object* current_exception = EG(exception); + EG(exception) = NULL; + + smart_str buf = {0}; + _dd_serialize_json(span_events, &buf, 0); + add_assoc_str(meta, "events", buf.s); + + // Restore the exception + EG(exception) = current_exception; + } + + zval *git_metadata = &span->root->property_git_metadata; if (git_metadata && Z_TYPE_P(git_metadata) == IS_OBJECT) { ddtrace_git_metadata *metadata = (ddtrace_git_metadata *)Z_OBJ_P(git_metadata); @@ -1412,8 +1293,7 @@ static void _serialize_meta(zval *el, ddtrace_span_data *span) { if (!zend_string_equals_ci(Z_STR(prop_service_as_string), Z_STR(prop_root_service_as_string))) { add_assoc_str(meta, "_dd.base_service", Z_STR(prop_root_service_as_string)); - } - else { + } else { zend_string_release(Z_STR(prop_root_service_as_string)); } @@ -1811,8 +1691,7 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { zend_hash_str_del(meta, ZEND_STRL("operation.name")); } - _serialize_meta(el, span); - + _serialize_meta(el, span, Z_TYPE_P(prop_service) > IS_NULL ? Z_STR(prop_service_as_string) : ZSTR_EMPTY_ALLOC()); zval metrics_zv; array_init(&metrics_zv); @@ -1830,8 +1709,14 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { } } - if (ddtrace_span_is_entrypoint_root(span) && get_DD_TRACE_MEASURE_COMPILE_TIME()) { - add_assoc_double(&metrics_zv, "php.compilation.total_time_ms", ddtrace_compile_time_get() / 1000.); + if (ddtrace_span_is_entrypoint_root(span)) { + if (get_DD_TRACE_MEASURE_COMPILE_TIME()) { + add_assoc_double(&metrics_zv, "php.compilation.total_time_ms", ddtrace_compile_time_get() / 1000.); + } + if (get_DD_TRACE_MEASURE_PEAK_MEMORY_USAGE()) { + add_assoc_double(&metrics_zv, "php.memory.peak_usage_bytes", zend_memory_peak_usage(false)); + add_assoc_double(&metrics_zv, "php.memory.peak_real_usage_bytes", zend_memory_peak_usage(true)); + } } LOGEV(SPAN, { diff --git a/ext/serializer.h b/ext/serializer.h index 4c2d2c2f93..3c5f3cb87b 100644 --- a/ext/serializer.h +++ b/ext/serializer.h @@ -1,6 +1,7 @@ #ifndef DD_SERIALIZER_H #define DD_SERIALIZER_H #include "span.h" +#include "ddtrace_string.h" int ddtrace_serialize_simple_array(zval *trace, zval *retval); int ddtrace_serialize_simple_array_into_c_string(zval *trace, char **data_p, size_t *size_p); @@ -21,4 +22,6 @@ void ddtrace_shutdown_span_sampling_limiter(void); void ddtrace_serializer_startup(void); +typedef zend_result (*add_tag_fn_t)(void *context, ddtrace_string key, ddtrace_string value); + #endif // DD_SERIALIZER_H diff --git a/ext/sidecar.c b/ext/sidecar.c index ce7bbc5f82..f3d5d6037e 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -6,11 +6,17 @@ #include "logging.h" #include #include +#include #include "sidecar.h" #include "telemetry.h" #include "serializer.h" +#include "remote_config.h" +#ifndef _WIN32 +#include "coms.h" +#endif + +ZEND_EXTERN_MODULE_GLOBALS(ddtrace); -ddog_SidecarTransport *ddtrace_sidecar; ddog_Endpoint *ddtrace_endpoint; struct ddog_InstanceId *ddtrace_sidecar_instance_id; static uint8_t dd_sidecar_formatted_session_id[36]; @@ -18,14 +24,7 @@ static uint8_t dd_sidecar_formatted_session_id[36]; // Set the globals that stay unchanged in case of fork static void ddtrace_set_non_resettable_sidecar_globals(void) { ddtrace_format_runtime_id(&dd_sidecar_formatted_session_id); - - if (get_global_DD_TRACE_AGENTLESS() && ZSTR_LEN(get_global_DD_API_KEY())) { - ddtrace_endpoint = ddog_endpoint_from_api_key(dd_zend_string_to_CharSlice(get_global_DD_API_KEY())); - } else { - char *agent_url = ddtrace_agent_url(); - ddtrace_endpoint = ddog_endpoint_from_url((ddog_CharSlice) {.ptr = agent_url, .len = strlen(agent_url)}); - free(agent_url); - } + ddtrace_endpoint = ddtrace_sidecar_agent_endpoint(); } // Set the globals that must be updated in case of fork @@ -52,6 +51,10 @@ ddog_SidecarTransport *dd_sidecar_connection_factory(void) { free(dogstatsd_url); } + if (ZSTR_LEN(get_global_DD_TRACE_AGENT_TEST_SESSION_TOKEN())) { + ddog_endpoint_set_test_token(dogstatsd_endpoint, dd_zend_string_to_CharSlice(get_global_DD_TRACE_AGENT_TEST_SESSION_TOKEN())); + } + #ifdef _WIN32 DDOG_PHP_FUNCTION = (const uint8_t *)zend_hash_func; #endif @@ -70,13 +73,21 @@ ddog_SidecarTransport *dd_sidecar_connection_factory(void) { ddog_CharSlice session_id = (ddog_CharSlice) {.ptr = (char *) dd_sidecar_formatted_session_id, .len = sizeof(dd_sidecar_formatted_session_id)}; ddog_sidecar_session_set_config(&sidecar_transport, session_id, ddtrace_endpoint, dogstatsd_endpoint, + DDOG_CHARSLICE_C("php"), + DDOG_CHARSLICE_C(PHP_DDTRACE_VERSION), get_global_DD_TRACE_AGENT_FLUSH_INTERVAL(), + (int)(get_global_DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS() * 1000), // for historical reasons in seconds get_global_DD_TELEMETRY_HEARTBEAT_INTERVAL() * 1000, get_global_DD_TRACE_BUFFER_SIZE(), get_global_DD_TRACE_AGENT_STACK_BACKLOG() * get_global_DD_TRACE_AGENT_MAX_PAYLOAD_SIZE(), get_global_DD_TRACE_DEBUG() ? DDOG_CHARSLICE_C("debug") : dd_zend_string_to_CharSlice(get_global_DD_TRACE_LOG_LEVEL()), - (ddog_CharSlice){ .ptr = logpath, .len = strlen(logpath) }); + (ddog_CharSlice){ .ptr = logpath, .len = strlen(logpath) }, + ddtrace_set_all_thread_vm_interrupt, + DDTRACE_REMOTE_CONFIG_PRODUCTS.ptr, + DDTRACE_REMOTE_CONFIG_PRODUCTS.len, + DDTRACE_REMOTE_CONFIG_CAPABILITIES.ptr, + DDTRACE_REMOTE_CONFIG_CAPABILITIES.len); ddog_endpoint_drop(dogstatsd_endpoint); @@ -87,10 +98,49 @@ ddog_SidecarTransport *dd_sidecar_connection_factory(void) { return sidecar_transport; } +static void maybe_enable_appsec(bool *appsec_features, bool *appsec_config) { + *appsec_features = false; + *appsec_config = false; + + // this must be done in ddtrace rather than ddappsec because + // the sidecar is launched by ddtrace before ddappsec has a chance + // to run its first rinit + +#if defined(__linux__) || defined(__APPLE__) + if (get_global_DD_APPSEC_TESTING()) { + return; + } + zend_module_entry *appsec_module = zend_hash_str_find_ptr(&module_registry, "ddappsec", sizeof("ddappsec") - 1); + if (!appsec_module) { + return; + } + void *handle = dlsym(appsec_module->handle, "dd_appsec_maybe_enable_helper"); + if (!handle) { + return; + } + void (*dd_appsec_maybe_enable_helper)(typeof(&ddog_sidecar_enable_appsec) enable_appsec) = handle; + dd_appsec_maybe_enable_helper(ddog_sidecar_enable_appsec); + + typedef void (*dd_appsec_rc_conf_t)(bool *, bool *); + dd_appsec_rc_conf_t dd_appsec_rc_conf = dlsym(RTLD_DEFAULT, "dd_appsec_rc_conf"); + if (dd_appsec_rc_conf) { + dd_appsec_rc_conf(appsec_features, appsec_config); + } else { + LOG(WARN, "Could not resolve dd_appsec_rc_conf"); + } +#endif +} + void ddtrace_sidecar_setup(void) { ddtrace_set_non_resettable_sidecar_globals(); ddtrace_set_resettable_sidecar_globals(); + bool appsec_features; + bool appsec_config; + maybe_enable_appsec(&appsec_features, &appsec_config); + + ddog_init_remote_config(get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED(), appsec_features, appsec_config); + ddtrace_sidecar = dd_sidecar_connection_factory(); if (!ddtrace_sidecar && ddtrace_endpoint) { // Something went wrong ddog_endpoint_drop(ddtrace_endpoint); @@ -104,7 +154,7 @@ void ddtrace_sidecar_setup(void) { void ddtrace_sidecar_ensure_active(void) { if (ddtrace_sidecar) { - ddog_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory); + ddtrace_sidecar_reconnect(&ddtrace_sidecar, dd_sidecar_connection_factory); } } @@ -127,7 +177,25 @@ void ddtrace_reset_sidecar_globals(void) { } } -static inline void ddtrace_sidecar_dogstatsd_push_tag(ddog_Vec_Tag *vec, ddog_CharSlice key, ddog_CharSlice value) { +ddog_Endpoint *ddtrace_sidecar_agent_endpoint(void) { + ddog_Endpoint *agent_endpoint; + + if (get_global_DD_TRACE_AGENTLESS() && ZSTR_LEN(get_global_DD_API_KEY())) { + agent_endpoint = ddog_endpoint_from_api_key(dd_zend_string_to_CharSlice(get_global_DD_API_KEY())); + } else { + char *agent_url = ddtrace_agent_url(); + agent_endpoint = ddog_endpoint_from_url((ddog_CharSlice) {.ptr = agent_url, .len = strlen(agent_url)}); + free(agent_url); + } + + if (ZSTR_LEN(get_global_DD_TRACE_AGENT_TEST_SESSION_TOKEN())) { + ddog_endpoint_set_test_token(agent_endpoint, dd_zend_string_to_CharSlice(get_global_DD_TRACE_AGENT_TEST_SESSION_TOKEN())); + } + + return agent_endpoint; +} + +void ddtrace_sidecar_push_tag(ddog_Vec_Tag *vec, ddog_CharSlice key, ddog_CharSlice value) { ddog_Vec_Tag_PushResult tag_result = ddog_Vec_Tag_push(vec, key, value); if (tag_result.tag == DDOG_VEC_TAG_PUSH_RESULT_ERR) { zend_string *msg = dd_CharSlice_to_zend_string(ddog_Error_message(&tag_result.err)); @@ -137,7 +205,7 @@ static inline void ddtrace_sidecar_dogstatsd_push_tag(ddog_Vec_Tag *vec, ddog_Ch } } -static void ddtrace_sidecar_dogstatsd_push_tags(ddog_Vec_Tag *vec, zval *tags) { +void ddtrace_sidecar_push_tags(ddog_Vec_Tag *vec, zval *tags) { // Global tags (https://github.com/DataDog/php-datadogstatsd/blob/0efdd1c38f6d3dd407efbb899ad1fd2e5cd18085/src/DogStatsd.php#L113-L125) ddtrace_span_data *span = ddtrace_active_span(); zend_string *env; @@ -147,12 +215,12 @@ static void ddtrace_sidecar_dogstatsd_push_tags(ddog_Vec_Tag *vec, zval *tags) { env = zend_string_copy(get_DD_ENV()); } if (ZSTR_LEN(env) > 0) { - ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("env"), dd_zend_string_to_CharSlice(env)); + ddtrace_sidecar_push_tag(vec, DDOG_CHARSLICE_C("env"), dd_zend_string_to_CharSlice(env)); } zend_string_release(env); zend_string *service = ddtrace_active_service_name(); if (ZSTR_LEN(service) > 0) { - ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("service"), dd_zend_string_to_CharSlice(service)); + ddtrace_sidecar_push_tag(vec, DDOG_CHARSLICE_C("service"), dd_zend_string_to_CharSlice(service)); } zend_string_release(service); zend_string *version; @@ -162,10 +230,14 @@ static void ddtrace_sidecar_dogstatsd_push_tags(ddog_Vec_Tag *vec, zval *tags) { version = zend_string_copy(get_DD_VERSION()); } if (ZSTR_LEN(version) > 0) { - ddtrace_sidecar_dogstatsd_push_tag(vec, DDOG_CHARSLICE_C("version"), dd_zend_string_to_CharSlice(version)); + ddtrace_sidecar_push_tag(vec, DDOG_CHARSLICE_C("version"), dd_zend_string_to_CharSlice(version)); } zend_string_release(version); + if (ZSTR_LEN(get_DD_TRACE_AGENT_TEST_SESSION_TOKEN())) { + ddtrace_sidecar_push_tag(vec, DDOG_CHARSLICE_C("x-datadog-test-session-token"), dd_zend_string_to_CharSlice(get_DD_TRACE_AGENT_TEST_SESSION_TOKEN())); + } + // Specific tags if (!tags || Z_TYPE_P(tags) != IS_ARRAY) { return; @@ -179,7 +251,7 @@ static void ddtrace_sidecar_dogstatsd_push_tags(ddog_Vec_Tag *vec, zval *tags) { } zval value_str; ddtrace_convert_to_string(&value_str, tag_val); - ddtrace_sidecar_dogstatsd_push_tag(vec, dd_zend_string_to_CharSlice(key), dd_zend_string_to_CharSlice(Z_STR(value_str))); + ddtrace_sidecar_push_tag(vec, dd_zend_string_to_CharSlice(key), dd_zend_string_to_CharSlice(Z_STR(value_str))); zend_string_release(Z_STR(value_str)); } ZEND_HASH_FOREACH_END(); @@ -191,8 +263,9 @@ void ddtrace_sidecar_dogstatsd_count(zend_string *metric, zend_long value, zval } ddog_Vec_Tag vec = ddog_Vec_Tag_new(); - ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); - ddog_sidecar_dogstatsd_count(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddtrace_sidecar_push_tags(&vec, tags); + ddtrace_ffi_try("Failed sending dogstatsd count metric", + ddog_sidecar_dogstatsd_count(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec)); ddog_Vec_Tag_drop(vec); } @@ -202,8 +275,9 @@ void ddtrace_sidecar_dogstatsd_distribution(zend_string *metric, double value, z } ddog_Vec_Tag vec = ddog_Vec_Tag_new(); - ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); - ddog_sidecar_dogstatsd_distribution(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddtrace_sidecar_push_tags(&vec, tags); + ddtrace_ffi_try("Failed sending dogstatsd distribution metric", + ddog_sidecar_dogstatsd_distribution(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec)); ddog_Vec_Tag_drop(vec); } @@ -213,8 +287,9 @@ void ddtrace_sidecar_dogstatsd_gauge(zend_string *metric, double value, zval *ta } ddog_Vec_Tag vec = ddog_Vec_Tag_new(); - ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); - ddog_sidecar_dogstatsd_gauge(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddtrace_sidecar_push_tags(&vec, tags); + ddtrace_ffi_try("Failed sending dogstatsd gauge metric", + ddog_sidecar_dogstatsd_gauge(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec)); ddog_Vec_Tag_drop(vec); } @@ -224,8 +299,9 @@ void ddtrace_sidecar_dogstatsd_histogram(zend_string *metric, double value, zval } ddog_Vec_Tag vec = ddog_Vec_Tag_new(); - ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); - ddog_sidecar_dogstatsd_histogram(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddtrace_sidecar_push_tags(&vec, tags); + ddtrace_ffi_try("Failed sending dogstatsd histogram metric", + ddog_sidecar_dogstatsd_histogram(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec)); ddog_Vec_Tag_drop(vec); } @@ -235,7 +311,118 @@ void ddtrace_sidecar_dogstatsd_set(zend_string *metric, zend_long value, zval *t } ddog_Vec_Tag vec = ddog_Vec_Tag_new(); - ddtrace_sidecar_dogstatsd_push_tags(&vec, tags); - ddog_sidecar_dogstatsd_set(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec); + ddtrace_sidecar_push_tags(&vec, tags); + ddtrace_ffi_try("Failed sending dogstatsd set metric", + ddog_sidecar_dogstatsd_set(&ddtrace_sidecar, ddtrace_sidecar_instance_id, dd_zend_string_to_CharSlice(metric), value, &vec)); ddog_Vec_Tag_drop(vec); } + +void ddtrace_sidecar_submit_root_span_data_direct(ddtrace_root_span_data *root) { + if (!ddtrace_sidecar || !get_global_DD_REMOTE_CONFIG_ENABLED()) { + return; + } + + ddog_CharSlice service_slice = DDOG_CHARSLICE_C("unnamed-php-service"); + zend_string *free_string = NULL; + if (root) { + zval *service = &root->property_service; + if (Z_TYPE_P(service) == IS_STRING && Z_STRLEN_P(service) > 0) { + service_slice = dd_zend_string_to_CharSlice(Z_STR_P(service)); + } + } else if (ZSTR_LEN(get_DD_SERVICE())) { + service_slice = dd_zend_string_to_CharSlice(get_DD_SERVICE()); + } else { + free_string = ddtrace_default_service_name(); + service_slice = dd_zend_string_to_CharSlice(free_string); + } + + ddog_CharSlice env_slice = DDOG_CHARSLICE_C("none"); + if (root) { + zval *env = zend_hash_str_find(ddtrace_property_array(&root->property_meta), ZEND_STRL("env")); + if (!env) { + env = &root->property_env; + } + if (Z_TYPE_P(env) == IS_STRING && Z_STRLEN_P(env) > 0) { + env_slice = dd_zend_string_to_CharSlice(Z_STR_P(env)); + } + } else if (ZSTR_LEN(get_DD_ENV())) { + env_slice = dd_zend_string_to_CharSlice(get_DD_ENV()); + } + + ddog_CharSlice version_slice = DDOG_CHARSLICE_C(""); + if (root) { + zval *version = zend_hash_str_find(ddtrace_property_array(&root->property_meta), ZEND_STRL("version")); + if (!version) { + version = &root->property_version; + } + if (version && Z_TYPE_P(version) == IS_STRING && Z_STRLEN_P(version) > 0) { + version_slice = dd_zend_string_to_CharSlice(Z_STR_P(version)); + } + } else if (ZSTR_LEN(get_DD_VERSION())) { + version_slice = dd_zend_string_to_CharSlice(get_DD_VERSION()); + } + + bool changed = true; + if (DDTRACE_G(remote_config_state)) { + changed = ddog_remote_configs_service_env_change(DDTRACE_G(remote_config_state), service_slice, env_slice, version_slice); + } + if (changed || !root) { + ddog_sidecar_set_remote_config_data(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), service_slice, env_slice, version_slice, &DDTRACE_G(active_global_tags)); + } + + if (free_string) { + zend_string_release(free_string); + } +} + +void ddtrace_sidecar_submit_root_span_data(void) { + if (DDTRACE_G(active_stack)) { + ddtrace_root_span_data *root = DDTRACE_G(active_stack)->root_span; + if (root) { + ddtrace_sidecar_submit_root_span_data_direct(root); + } + } +} + +void ddtrace_sidecar_send_debugger_data(ddog_Vec_DebuggerPayload payloads) { + LOGEV(DEBUG, UNUSED(log); ddog_log_debugger_data(&payloads);); + ddog_sidecar_send_debugger_data(&ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), payloads); +} + +void ddtrace_sidecar_send_debugger_datum(ddog_DebuggerPayload *payload) { + LOGEV(DEBUG, UNUSED(log); ddog_log_debugger_datum(payload);); + ddog_sidecar_send_debugger_datum(&ddtrace_sidecar, ddtrace_sidecar_instance_id, DDTRACE_G(sidecar_queue_id), payload); +} + +void ddtrace_sidecar_rinit(void) { + DDTRACE_G(sidecar_queue_id) = ddog_sidecar_queueId_generate(); + + DDTRACE_G(active_global_tags) = ddog_Vec_Tag_new(); + zend_string *tag; + zval *value; + ZEND_HASH_FOREACH_STR_KEY_VAL(get_DD_TAGS(), tag, value) { + UNUSED(ddog_Vec_Tag_push(&DDTRACE_G(active_global_tags), dd_zend_string_to_CharSlice(tag), dd_zend_string_to_CharSlice(Z_STR_P(value)))); + } ZEND_HASH_FOREACH_END(); + ddtrace_sidecar_submit_root_span_data_direct(NULL); +} + +void ddtrace_sidecar_rshutdown(void) { + ddog_Vec_Tag_drop(DDTRACE_G(active_global_tags)); +} + +bool ddtrace_alter_test_session_token(zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(old_value, new_str); + if (ddtrace_sidecar) { + ddog_CharSlice session_id = (ddog_CharSlice) {.ptr = (char *) dd_sidecar_formatted_session_id, .len = sizeof(dd_sidecar_formatted_session_id)}; + ddtrace_ffi_try("Failed updating test session token", + ddog_sidecar_set_test_session_token(&ddtrace_sidecar, session_id, dd_zend_string_to_CharSlice(Z_STR_P(new_value)))); + } +#ifndef _WIN32 + ddtrace_coms_set_test_session_token(Z_STRVAL_P(new_value), Z_STRLEN_P(new_value)); +#endif + return true; +} + +bool ddtrace_exception_debugging_is_active(void) { + return ddtrace_sidecar && ddtrace_sidecar_instance_id && get_DD_EXCEPTION_REPLAY_ENABLED(); +} diff --git a/ext/sidecar.h b/ext/sidecar.h index 4ccf350357..57a22629aa 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -1,5 +1,8 @@ +#ifndef DD_SIDECAR_H +#define DD_SIDECAR_H #include #include +#include extern ddog_SidecarTransport *ddtrace_sidecar; extern ddog_Endpoint *ddtrace_endpoint; @@ -9,6 +12,16 @@ void ddtrace_sidecar_setup(void); void ddtrace_sidecar_ensure_active(void); void ddtrace_sidecar_shutdown(void); void ddtrace_reset_sidecar_globals(void); +void ddtrace_sidecar_submit_root_span_data(void); +void ddtrace_sidecar_push_tag(ddog_Vec_Tag *vec, ddog_CharSlice key, ddog_CharSlice value); +void ddtrace_sidecar_push_tags(ddog_Vec_Tag *vec, zval *tags); +ddog_Endpoint *ddtrace_sidecar_agent_endpoint(void); + +void ddtrace_sidecar_send_debugger_data(ddog_Vec_DebuggerPayload payloads); +void ddtrace_sidecar_send_debugger_datum(ddog_DebuggerPayload *payload); + +void ddtrace_sidecar_rinit(void); +void ddtrace_sidecar_rshutdown(void); void ddtrace_sidecar_dogstatsd_count(zend_string *metric, zend_long value, zval *tags); void ddtrace_sidecar_dogstatsd_distribution(zend_string *metric, double value, zval *tags); @@ -16,6 +29,8 @@ void ddtrace_sidecar_dogstatsd_gauge(zend_string *metric, double value, zval *ta void ddtrace_sidecar_dogstatsd_histogram(zend_string *metric, double value, zval *tags); void ddtrace_sidecar_dogstatsd_set(zend_string *metric, zend_long value, zval *tags); +bool ddtrace_alter_test_session_token(zval *old_value, zval *new_value, zend_string *new_str); + static inline ddog_CharSlice dd_zend_string_to_CharSlice(zend_string *str) { return (ddog_CharSlice){ .len = str->len, .ptr = str->val }; } @@ -24,8 +39,8 @@ static inline ddog_CharSlice dd_zai_string_to_CharSlice(zai_string str) { return (ddog_CharSlice){ .len = str.len, .ptr = str.ptr }; } -static inline zend_string *dd_CharSlice_to_zend_string(ddog_CharSlice str) { - return zend_string_init(str.ptr, str.len, 0); +static inline zend_string *dd_CharSlice_to_zend_string(ddog_CharSlice slice) { + return zend_string_init(slice.ptr, slice.len, 0); } static inline bool ddtrace_ffi_try(const char *msg, ddog_MaybeError maybe_error) { @@ -37,3 +52,7 @@ static inline bool ddtrace_ffi_try(const char *msg, ddog_MaybeError maybe_error) } return true; } + +bool ddtrace_exception_debugging_is_active(void); + +#endif // DD_SIDECAR_H diff --git a/ext/signals.c b/ext/signals.c index c90f91b990..4c9e6512b7 100644 --- a/ext/signals.c +++ b/ext/signals.c @@ -15,8 +15,16 @@ #include "configuration.h" #include "ddtrace.h" +#include "sidecar.h" +#include "auto_flush.h" #include "ext/version.h" #include +#include "logging.h" +#undef ddtrace_bgs_logf + +#include +#include +#include #if defined HAVE_EXECINFO_H && defined backtrace_size_t && defined HAVE_BACKTRACE #define DDTRACE_HAVE_BACKTRACE 1 @@ -31,6 +39,7 @@ // true globals; only modify in MINIT/MSHUTDOWN static stack_t ddtrace_altstack; static struct sigaction ddtrace_sigaction; +static char crashtracker_socket_path[100] = {0}; #define MAX_STACK_SIZE 1024 #define MIN_STACKSZ 16384 // enough to hold void *array[MAX_STACK_SIZE] plus a couple kilobytes @@ -40,7 +49,7 @@ ZEND_EXTERN_MODULE_GLOBALS(ddtrace); static void ddtrace_sigsegv_handler(int sig) { if (!DDTRACE_G(backtrace_handler_already_run)) { DDTRACE_G(backtrace_handler_already_run) = true; - LOG(ERROR, "Segmentation fault"); + ddtrace_bgs_logf("[crash] Segmentation fault encountered"); #if HAVE_SIGACTION bool health_metrics_enabled = get_DD_TRACE_HEALTH_METRICS_ENABLED(); @@ -51,27 +60,27 @@ static void ddtrace_sigsegv_handler(int sig) { dogstatsd_client_status status = dogstatsd_client_count(client, metric, "1", tags); if (status == DOGSTATSD_CLIENT_OK) { - LOG(ERROR, "sigsegv health metric sent"); + ddtrace_bgs_logf("[crash] sigsegv health metric sent"); } } #endif #if DDTRACE_HAVE_BACKTRACE - LOG(ERROR, "Datadog PHP Trace extension (DEBUG MODE)"); - LOG(ERROR, "Received Signal %d", sig); + ddtrace_bgs_logf("Datadog PHP Trace extension (DEBUG MODE)"); + ddtrace_bgs_logf("Received Signal %d", sig); void *array[MAX_STACK_SIZE]; backtrace_size_t size = backtrace(array, MAX_STACK_SIZE); if (size == MAX_STACK_SIZE) { - LOG(ERROR, "Note: max stacktrace size reached"); + ddtrace_bgs_logf("Note: max stacktrace size reached"); } - LOG(ERROR, "Note: Backtrace below might be incomplete and have wrong entries due to optimized runtime"); - LOG(ERROR, "Backtrace:"); + ddtrace_bgs_logf("Note: Backtrace below might be incomplete and have wrong entries due to optimized runtime"); + ddtrace_bgs_logf("Backtrace:"); char **backtraces = backtrace_symbols(array, size); if (backtraces) { for (backtrace_size_t i = 0; i < size; i++) { - LOG(ERROR, backtraces[i]); + ddog_log_callback((ddog_CharSlice){ .ptr = backtraces[i], .len = strlen(backtraces[i]) }); } free(backtraces); } @@ -82,20 +91,107 @@ static void ddtrace_sigsegv_handler(int sig) { _Exit(128 + sig); } +static bool ddtrace_crashtracker_check_result(ddog_crasht_Result result, const char *msg) { + if (result.tag != DDOG_CRASHT_RESULT_OK) { + ddog_CharSlice error_msg = ddog_Error_message(&result.err); + LOG(ERROR, "%s : %.*s", msg, (int) error_msg.len, error_msg.ptr); + ddog_Error_drop(&result.err); + return false; + } + + return true; +} + +static void ddtrace_init_crashtracker() { + ddog_CharSlice socket_path = ddog_sidecar_get_crashtracker_unix_socket_path(); + if (socket_path.len > sizeof(crashtracker_socket_path) - 1) { + LOG(ERROR, "Cannot initialize CrashTracker : the socket path is too long."); + free((void *) socket_path.ptr); + return; + } + + // Copy the string to a global buffer to avoid a use-after-free error + memcpy(crashtracker_socket_path, socket_path.ptr, socket_path.len); + crashtracker_socket_path[socket_path.len] = '\0'; + free((void *) socket_path.ptr); + socket_path.ptr = crashtracker_socket_path; + + ddog_Endpoint *agent_endpoint = ddtrace_sidecar_agent_endpoint(); + + ddog_crasht_Config config = { + .endpoint = agent_endpoint, + .timeout_secs = 5, + .resolve_frames = DDOG_CRASHT_STACKTRACE_COLLECTION_ENABLED_WITH_INPROCESS_SYMBOLS, + // Likely running in a container, so wait until the report is uploaded. + // Otherwise, the container shutdown may stop the sidecar before it has finished uploading the crash report. + .wait_for_receiver = getpid() == 1, + }; + + ddog_Vec_Tag tags = ddog_Vec_Tag_new(); + ddtrace_sidecar_push_tags(&tags, NULL); + + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("is_crash"), DDOG_CHARSLICE_C("true")); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("severity"), DDOG_CHARSLICE_C("crash")); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("library_version"), DDOG_CHARSLICE_C(PHP_DDTRACE_VERSION)); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("language"), DDOG_CHARSLICE_C("php")); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("runtime"), DDOG_CHARSLICE_C("php")); + + uint8_t runtime_id[36]; + ddtrace_format_runtime_id(&runtime_id); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("runtime-id"), (ddog_CharSlice) {.ptr = (char *) runtime_id, .len = sizeof(runtime_id)}); + + const char *runtime_version = zend_get_module_version("Reflection"); + ddtrace_sidecar_push_tag(&tags, DDOG_CHARSLICE_C("runtime_version"), (ddog_CharSlice) {.ptr = (char *) runtime_version, .len = strlen(runtime_version)}); + + const ddog_crasht_Metadata metadata = { + .library_name = DDOG_CHARSLICE_C_BARE("dd-trace-php"), + .library_version = DDOG_CHARSLICE_C_BARE(PHP_DDTRACE_VERSION), + .family = DDOG_CHARSLICE_C("php"), + .tags = &tags + }; + + ddtrace_crashtracker_check_result( + ddog_crasht_init_with_unix_socket( + config, + socket_path, + metadata + ), + "Cannot initialize CrashTracker" + ); + + ddog_endpoint_drop(agent_endpoint); + ddog_Vec_Tag_drop(tags); +} + void ddtrace_signals_first_rinit(void) { - bool install_handler = get_DD_TRACE_HEALTH_METRICS_ENABLED(); + DDTRACE_G(backtrace_handler_already_run) = false; + + // Signal handlers are causing issues with FrankenPHP. + if (ddtrace_active_sapi == DATADOG_PHP_SAPI_FRANKENPHP) { + return; + } + + bool install_crashtracker = get_DD_INSTRUMENTATION_TELEMETRY_ENABLED() && get_DD_CRASHTRACKING_ENABLED(); + bool install_backtrace_handler = get_DD_TRACE_HEALTH_METRICS_ENABLED(); #if DDTRACE_HAVE_BACKTRACE - install_handler |= get_DD_LOG_BACKTRACE(); + install_backtrace_handler |= get_DD_LOG_BACKTRACE(); #endif - DDTRACE_G(backtrace_handler_already_run) = false; + if (install_crashtracker) { + ddtrace_init_crashtracker(); + } /* Install a signal handler for SIGSEGV and run it on an alternate stack. * Using an alternate stack allows the handler to run even when the main * stack overflows. */ - if (install_handler) { + if (install_backtrace_handler) { + if (install_crashtracker) { + LOG(WARN, "Settings 'datadog.log_backtrace' and 'datadog.crashtracking_enabled' are mutually exclusive. Cannot enable the backtrace."); + return; + } + size_t stack_size = SIGSTKSZ < MIN_STACKSZ ? MIN_STACKSZ : SIGSTKSZ; if ((ddtrace_altstack.ss_sp = malloc(stack_size))) { ddtrace_altstack.ss_size = stack_size; @@ -110,7 +206,9 @@ void ddtrace_signals_first_rinit(void) { } } -void ddtrace_signals_mshutdown(void) { free(ddtrace_altstack.ss_sp); } +void ddtrace_signals_mshutdown(void) { + free(ddtrace_altstack.ss_sp); +} #else void ddtrace_signals_first_rinit(void) {} diff --git a/ext/span.c b/ext/span.c index b3ffc4aec9..c2de1238ce 100644 --- a/ext/span.c +++ b/ext/span.c @@ -17,6 +17,7 @@ #include #include "user_request.h" #include "zend_types.h" +#include "sidecar.h" #define USE_REALTIME_CLOCK 0 #define USE_MONOTONIC_CLOCK 1 @@ -143,6 +144,12 @@ static ddtrace_span_data *ddtrace_init_span(enum ddtrace_span_dataype type, zend return span; } +uint64_t ddtrace_nanoseconds_realtime(void) { + struct timespec ts; + timespec_get(&ts, TIME_UTC); + return ts.tv_sec * ZEND_NANO_IN_SEC + ts.tv_nsec; +} + ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) { ddtrace_span_stack *stack = DDTRACE_G(active_stack); // The primary stack is ancestor to all stacks, which signifies that any root spans created on top of it will inherit the distributed tracing context @@ -167,9 +174,7 @@ ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) { span->duration_start = zend_hrtime(); // Start time is nanoseconds from unix epoch // @see https://docs.datadoghq.com/api/?lang=python#send-traces - struct timespec ts; - timespec_get(&ts, TIME_UTC); - span->start = ts.tv_sec * ZEND_NANO_IN_SEC + ts.tv_nsec; + span->start = ddtrace_nanoseconds_realtime(); span->span_id = ddtrace_generate_span_id(); @@ -212,6 +217,10 @@ ddtrace_span_data *ddtrace_open_span(enum ddtrace_span_dataype type) { if (root_span) { ddtrace_root_span_data *root = ROOTSPANDATA(&span->std); LOG(SPAN_TRACE, "Starting new root span: trace_id=%s, span_id=%" PRIu64 ", parent_id=%" PRIu64 ", SpanStack=%d, parent_SpanStack=%d", Z_STRVAL(root->property_trace_id), span->span_id, root->parent_id, root->stack->std.handle, root->stack->parent_stack->std.handle); + + if (ddtrace_span_is_entrypoint_root(span)) { + ddtrace_sidecar_submit_root_span_data(); + } } else { LOG(SPAN_TRACE, "Starting new span: trace_id=%s, span_id=%" PRIu64 ", parent_id=%" PRIu64 ", SpanStack=%d", Z_STRVAL(span->root->property_trace_id), span->span_id, SPANDATA(span->parent)->span_id, span->stack->std.handle); } @@ -311,8 +320,9 @@ void ddtrace_switch_span_stack(ddtrace_span_stack *target_stack) { } GC_ADDREF(&target_stack->std); - OBJ_RELEASE(&DDTRACE_G(active_stack)->std); + ddtrace_span_stack *active_stack = DDTRACE_G(active_stack); DDTRACE_G(active_stack) = target_stack; + OBJ_RELEASE(&active_stack->std); } ddtrace_span_data *ddtrace_init_dummy_span(void) { @@ -377,7 +387,9 @@ DDTRACE_PUBLIC zend_object *ddtrace_get_root_span() return &rsd->std; } -bool ddtrace_span_alter_root_span_config(zval *old_value, zval *new_value) { +bool ddtrace_span_alter_root_span_config(zval *old_value, zval *new_value, zend_string *new_str) { + UNUSED(new_str); + if (Z_TYPE_P(old_value) == Z_TYPE_P(new_value) || !DDTRACE_G(active_stack)) { return true; } @@ -713,6 +725,8 @@ void ddtrace_drop_span(ddtrace_span_data *span) { void ddtrace_serialize_closed_spans(zval *serialized) { if (DDTRACE_G(top_closed_stack)) { + memset(&DDTRACE_G(exception_debugger_buffer), 0, sizeof(DDTRACE_G(exception_debugger_buffer))); + ddtrace_span_stack *rootstack = DDTRACE_G(top_closed_stack); DDTRACE_G(top_closed_stack) = NULL; do { @@ -745,6 +759,14 @@ void ddtrace_serialize_closed_spans(zval *serialized) { } } while (stack); } while (rootstack); + + if (ddtrace_exception_debugging_is_active()) { + ddtrace_sidecar_send_debugger_data(DDTRACE_G(exception_debugger_buffer)); + if (DDTRACE_G(debugger_capture_arena)) { + zend_arena_destroy(DDTRACE_G(debugger_capture_arena)); + DDTRACE_G(debugger_capture_arena) = NULL; + } + } } // Reset closed span counter for limit-refresh, don't touch open spans diff --git a/ext/span.h b/ext/span.h index cefb20e0a7..c664ead825 100644 --- a/ext/span.h +++ b/ext/span.h @@ -9,6 +9,7 @@ #include "compatibility.h" #include "ddtrace.h" #include "ddtrace_export.h" +#include "priority_sampling/priority_sampling.h" #define DDTRACE_DROPPED_SPAN (-1ull) #define DDTRACE_SILENTLY_DROPPED_SPAN (-2ull) @@ -28,6 +29,7 @@ enum ddtrace_span_dataype { typedef struct { double sampling_rate; int rule; + enum dd_sampling_mechanism mechanism; } ddtrace_rule_result; enum ddtrace_trace_limited { @@ -55,6 +57,7 @@ typedef union ddtrace_span_properties { zval property_id; }; zval property_links; + zval property_events; zval property_peer_service_sources; union { union ddtrace_span_properties *parent; @@ -171,6 +174,23 @@ struct ddtrace_span_link { }; }; +struct ddtrace_span_event { + union { + zend_object std; + struct { + char object_placeholder[sizeof(zend_object) - sizeof(zval)]; + zval property_name; + zval property_attributes; + zval property_timestamp; + }; + }; +}; + +struct ddtrace_exception_span_event { + ddtrace_span_event span_event; + zval property_exception; +}; + struct ddtrace_git_metadata { union { zend_object std; @@ -204,6 +224,7 @@ void ddtrace_clear_execute_data_span(zend_ulong invocation, bool keep); // Note that this function is used externally by the appsec extension. DDTRACE_PUBLIC zend_object *ddtrace_get_root_span(void); +uint64_t ddtrace_nanoseconds_realtime(void); void dd_trace_stop_span_time(ddtrace_span_data *span); bool ddtrace_has_top_internal_span(ddtrace_span_data *end); void ddtrace_close_stack_userland_spans_until(ddtrace_span_data *until); @@ -221,7 +242,7 @@ zend_string *ddtrace_trace_id_as_string(ddtrace_trace_id id); zend_string *ddtrace_span_id_as_hex_string(uint64_t id); zend_string *ddtrace_trace_id_as_hex_string(ddtrace_trace_id id); -bool ddtrace_span_alter_root_span_config(zval *old_value, zval *new_value); +bool ddtrace_span_alter_root_span_config(zval *old_value, zval *new_value, zend_string *new_str); static inline bool ddtrace_span_is_dropped(ddtrace_span_data *span) { return span->duration == DDTRACE_DROPPED_SPAN || span->duration == DDTRACE_SILENTLY_DROPPED_SPAN; diff --git a/ext/telemetry.c b/ext/telemetry.c index 13277ed81f..333b6a81fa 100644 --- a/ext/telemetry.c +++ b/ext/telemetry.c @@ -4,6 +4,7 @@ #include #include #include "telemetry.h" +#include "serializer.h" #include "sidecar.h" ZEND_EXTERN_MODULE_GLOBALS(ddtrace); @@ -23,7 +24,7 @@ static bool dd_check_for_composer_autoloader(zend_ulong invocation, zend_execute ddog_CharSlice composer_path = dd_zend_string_to_CharSlice(execute_data->func->op_array.filename); if (!ddtrace_sidecar // if sidecar connection was broken, let's skip immediately - || ddtrace_detect_composer_installed_json(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id), composer_path)) { + || ddtrace_detect_composer_installed_json(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), composer_path)) { zai_hook_remove((zai_str)ZAI_STR_EMPTY, (zai_str)ZAI_STR_EMPTY, dd_composer_hook_id); } return true; @@ -53,14 +54,16 @@ void ddtrace_telemetry_register_services(ddog_SidecarTransport *sidecar) { ddog_sidecar_telemetry_register_metric_buffer(buffer, DDOG_CHARSLICE_C("trace_api.errors"), DDOG_METRIC_NAMESPACE_TRACERS); // FIXME: it seems we must call "enqueue_actions" (even with an empty list of actions) for things to work properly - ddog_sidecar_telemetry_buffer_flush(&sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, buffer); + ddtrace_ffi_try("Failed flushing background sender telemetry buffer", + ddog_sidecar_telemetry_buffer_flush(&sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, buffer)); ddog_CharSlice php_version = dd_zend_string_to_CharSlice(Z_STR_P(zend_get_constant_str(ZEND_STRL("PHP_VERSION")))); struct ddog_RuntimeMetadata *meta = ddog_sidecar_runtimeMeta_build(DDOG_CHARSLICE_C("php"), php_version, DDOG_CHARSLICE_C(PHP_DDTRACE_VERSION)); - ddog_sidecar_telemetry_flushServiceData( - &sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, meta, - DDOG_CHARSLICE_C("background_sender-php-service"), DDOG_CHARSLICE_C("none") - ); + ddtrace_ffi_try("Failed flushing background sender service data", + ddog_sidecar_telemetry_flushServiceData( + &sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, meta, + DDOG_CHARSLICE_C("background_sender-php-service"), DDOG_CHARSLICE_C("none") + )); ddog_sidecar_runtimeMeta_drop(meta); } @@ -140,32 +143,44 @@ void ddtrace_telemetry_finalize(void) { } } - ddog_sidecar_telemetry_buffer_flush(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id), buffer); + ddtrace_ffi_try("Failed flushing telemetry buffer", + ddog_sidecar_telemetry_buffer_flush(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), buffer)); + zend_string *free_string = NULL; ddog_CharSlice service_name = DDOG_CHARSLICE_C_BARE("unnamed-php-service"); if (DDTRACE_G(last_flushed_root_service_name)) { service_name = dd_zend_string_to_CharSlice(DDTRACE_G(last_flushed_root_service_name)); + } else if (ZSTR_LEN(get_DD_SERVICE())) { + service_name = dd_zend_string_to_CharSlice(get_DD_SERVICE()); + } else { + free_string = ddtrace_default_service_name(); + service_name = dd_zend_string_to_CharSlice(free_string); } ddog_CharSlice env_name = DDOG_CHARSLICE_C_BARE("none"); if (DDTRACE_G(last_flushed_root_env_name)) { env_name = dd_zend_string_to_CharSlice(DDTRACE_G(last_flushed_root_env_name)); + } else if (ZSTR_LEN(get_DD_ENV())) { + env_name = dd_zend_string_to_CharSlice(get_DD_ENV()); } ddog_CharSlice php_version = dd_zend_string_to_CharSlice(Z_STR_P(zend_get_constant_str(ZEND_STRL("PHP_VERSION")))); struct ddog_RuntimeMetadata *meta = ddog_sidecar_runtimeMeta_build(DDOG_CHARSLICE_C("php"), php_version, DDOG_CHARSLICE_C(PHP_DDTRACE_VERSION)); - ddog_sidecar_telemetry_flushServiceData(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id), meta, service_name, env_name); + ddtrace_ffi_try("Failed flushing service data", + ddog_sidecar_telemetry_flushServiceData(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), meta, service_name, env_name)); - ddog_sidecar_runtimeMeta_drop(meta); + if (free_string) { + zend_string_release(free_string); + } - ddog_sidecar_telemetry_end(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id)); + ddog_sidecar_runtimeMeta_drop(meta); } void ddtrace_telemetry_notify_integration(const char *name, size_t name_len) { if (ddtrace_sidecar && get_global_DD_INSTRUMENTATION_TELEMETRY_ENABLED()) { ddog_CharSlice integration = (ddog_CharSlice) {.len = name_len, .ptr = name}; - ddog_sidecar_telemetry_addIntegration(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(telemetry_queue_id), integration, + ddog_sidecar_telemetry_addIntegration(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &DDTRACE_G(sidecar_queue_id), integration, DDOG_CHARSLICE_C(""), true); } } @@ -238,5 +253,6 @@ void ddtrace_telemetry_send_trace_api_metrics(trace_api_metrics metrics) { ddog_sidecar_telemetry_add_span_metric_point_buffer(buffer, DDOG_CHARSLICE_C("trace_api.errors"), (double)metrics.errors_status_code, DDOG_CHARSLICE_C("type:status_code")); } - ddog_sidecar_telemetry_buffer_flush(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, buffer); + ddtrace_ffi_try("Failed flushing background sender metrics", + ddog_sidecar_telemetry_buffer_flush(&ddtrace_sidecar, ddtrace_sidecar_instance_id, &dd_bgs_queued_id, buffer)); } diff --git a/ext/threads.c b/ext/threads.c new file mode 100644 index 0000000000..d77e48b3da --- /dev/null +++ b/ext/threads.c @@ -0,0 +1,77 @@ +#include "threads.h" +#define zend_signal_globals_id zend_signal_globals_id_dummy +#define zend_signal_globals_offset zend_signal_globals_offset_dummy +#include +#undef zend_signal_globals_id +#undef zend_signal_globals_offset +#include "ddtrace.h" + +#if ZTS + +#ifdef ZEND_SIGNALS +#ifdef __APPLE__ +extern __attribute__((weak, weak_import)) int zend_signal_globals_id; +extern __attribute__((weak, weak_import)) size_t zend_signal_globals_offset; +#elif defined(_WIN32) +#error "Found zend_signals under windows!?" +#else +__attribute__((weak)) int zend_signal_globals_id; +__attribute__((weak)) size_t zend_signal_globals_offset; +#endif +#endif + +HashTable ddtrace_tls_bases; // map thread id to TSRMLS_CACHE +MUTEX_T ddtrace_threads_mutex = NULL; + +void ddtrace_thread_ginit() { + if (!ddtrace_threads_mutex) { + ddtrace_threads_mutex = tsrm_mutex_alloc(); + zend_hash_init(&ddtrace_tls_bases, 8, NULL, NULL, 1); + } + +#ifdef ZEND_SIGNALS + // avoid deadlocks due to signal handlers accessing this + if (zend_signal_globals_id) { + HANDLE_BLOCK_INTERRUPTIONS(); + } +#endif + tsrm_mutex_lock(ddtrace_threads_mutex); + + zend_hash_index_add_new_ptr(&ddtrace_tls_bases, (zend_ulong)(uintptr_t)tsrm_thread_id(), TSRMLS_CACHE); + + tsrm_mutex_unlock(ddtrace_threads_mutex); +#ifdef ZEND_SIGNALS + if (zend_signal_globals_id) { + HANDLE_UNBLOCK_INTERRUPTIONS(); + } +#endif +} + +void ddtrace_thread_gshutdown() { + if (ddtrace_threads_mutex) { +#ifdef ZEND_SIGNALS + // avoid deadlocks due to signal handlers accessing this + if (zend_signal_globals_id) { + HANDLE_BLOCK_INTERRUPTIONS(); + } +#endif + tsrm_mutex_lock(ddtrace_threads_mutex); + + zend_hash_index_del(&ddtrace_tls_bases, (zend_ulong)(uintptr_t)tsrm_thread_id()); + + tsrm_mutex_unlock(ddtrace_threads_mutex); +#ifdef ZEND_SIGNALS + if (zend_signal_globals_id) { + HANDLE_UNBLOCK_INTERRUPTIONS(); + } +#endif + + if (zend_hash_num_elements(&ddtrace_tls_bases) == 0) { + tsrm_mutex_free(ddtrace_threads_mutex); + ddtrace_threads_mutex = NULL; + zend_hash_destroy(&ddtrace_tls_bases); + } + } +} + +#endif diff --git a/ext/threads.h b/ext/threads.h new file mode 100644 index 0000000000..58a564ec66 --- /dev/null +++ b/ext/threads.h @@ -0,0 +1,15 @@ +#ifndef DD_THREADS_H +#define DD_THREADS_H + +#include +#include + +#if ZTS +extern HashTable ddtrace_tls_bases; +extern MUTEX_T ddtrace_threads_mutex; + +void ddtrace_thread_ginit(void); +void ddtrace_thread_gshutdown(void); +#endif + +#endif // DD_THREADS_H diff --git a/github-actions-helpers/Build.Github.cs b/github-actions-helpers/Build.Github.cs index 87b4978907..37a0377f96 100644 --- a/github-actions-helpers/Build.Github.cs +++ b/github-actions-helpers/Build.Github.cs @@ -164,7 +164,7 @@ string CleanValue(string value) char[] charsToTrim = { ' ', ',' }; string cleaned = value.TrimStart('-', '+').Trim(charsToTrim); - string[] keysToReplace = { "start", "duration", "php.compilation.total_time_ms", "process_id" }; + string[] keysToReplace = { "start", "duration", "php.compilation.total_time_ms", "metrics.php.memory.peak_usage_bytes", "metrics.php.memory.peak_real_usage_bytes", "process_id" }; foreach (var key in keysToReplace) { if (cleaned.Contains(key)) diff --git a/libdatadog b/libdatadog index 89d4831877..f363618c74 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 89d4831877096f19e49888999fc8d1e4148237b9 +Subproject commit f363618c74f039e77fece732e68b9f9faddd9113 diff --git a/loader/compat_php.c b/loader/compat_php.c index 95bb842f11..07100312ec 100644 --- a/loader/compat_php.c +++ b/loader/compat_php.c @@ -190,3 +190,14 @@ zval *ddloader_zend_hash_update(HashTable *ht, zend_string *key, zval *pData) { return NULL; } + +// From PHP 8.0 +bool ddloader_zend_ini_parse_bool(zend_string *str) { + if ((ZSTR_LEN(str) == 4 && strcasecmp(ZSTR_VAL(str), "true") == 0) + || (ZSTR_LEN(str) == 3 && strcasecmp(ZSTR_VAL(str), "yes") == 0) + || (ZSTR_LEN(str) == 2 && strcasecmp(ZSTR_VAL(str), "on") == 0)) { + return true; + } else { + return atoi(ZSTR_VAL(str)) != 0; + } +} diff --git a/loader/compat_php.h b/loader/compat_php.h index 5973eeb335..6ac783cb62 100644 --- a/loader/compat_php.h +++ b/loader/compat_php.h @@ -10,5 +10,6 @@ zval *ddloader_zend_hash_set_bucket_key(int php_api_no, HashTable *ht, Bucket *b void ddloader_replace_zend_error_cb(int php_api_no); void ddloader_restore_zend_error_cb(); zval *ddloader_zend_hash_update(HashTable *ht, zend_string *key, zval *pData); +bool ddloader_zend_ini_parse_bool(zend_string *str); #endif /* DD_LIBRARY_LOADER_COMPAT_PHP_H */ diff --git a/loader/dd_library_loader.c b/loader/dd_library_loader.c index 93d0929409..c614bbb144 100644 --- a/loader/dd_library_loader.c +++ b/loader/dd_library_loader.c @@ -8,6 +8,7 @@ #include #include #include +#include
#include #include "compat_php.h" @@ -28,6 +29,8 @@ static bool injection_forced = false; # define OS_PATH "linux-gnu/" #endif +static void ddloader_telemetryf(telemetry_reason reason, const char *format, ...); + static char *ddtrace_pre_load_hook(void) { char *libddtrace_php; int res = asprintf(&libddtrace_php, "%s/%sloader/libddtrace_php.so", package_path, OS_PATH); @@ -60,6 +63,74 @@ static char *ddtrace_pre_load_hook(void) { return NULL; } +static bool ddloader_is_ext_loaded(const char *name) { + return zend_hash_str_find_ptr(&module_registry, name, strlen(name)) + || zend_get_extension(name) + ; +} + +static zval *ddloader_ini_get_configuration(const char *name, size_t name_len) { + HashTable *configuration_hash = php_ini_get_configuration_hash(); + if (!configuration_hash) { + return NULL; + } + zend_string *ini = ddloader_zend_string_init(php_api_no, name, name_len, 1); + zval *val = zend_hash_find(configuration_hash, ini); + ddloader_zend_string_release(php_api_no, ini); + + return val; +} + +static void ddloader_ini_set_configuration(const char *name, size_t name_len, const char *value, size_t value_len) { + HashTable *configuration_hash = php_ini_get_configuration_hash(); + if (!configuration_hash) { + return; + } + + zend_string *zstr_name = ddloader_zend_string_init(php_api_no, name, name_len, 1); + zend_string *zstr_value = ddloader_zend_string_init(php_api_no, value, value_len, 1); + + zval tmp; + ZVAL_STR(&tmp, zstr_value); + ddloader_zend_hash_update(configuration_hash, zstr_name, &tmp); + ddloader_zend_string_release(php_api_no, zstr_name); +} + +static bool ddloader_is_opcache_jit_enabled() { + // JIT is only PHP 8.0+ + if (php_api_no < 20200930 || !ddloader_is_ext_loaded("Zend OPcache")) { + return false; + } + + // opcache.enable = false (default: true) + zval *opcache_enable = ddloader_ini_get_configuration(ZEND_STRL("opcache.enable")); + if (opcache_enable && Z_TYPE_P(opcache_enable) == IS_STRING && !ddloader_zend_ini_parse_bool(Z_STR_P(opcache_enable))) { + return false; + } + if (strcmp("cli", sapi_module.name) == 0) { + // opcache.enable_cli = false (default: false) + zval *opcache_enable_cli = ddloader_ini_get_configuration(ZEND_STRL("opcache.enable_cli")); + if (!opcache_enable_cli || Z_TYPE_P(opcache_enable_cli) != IS_STRING || !ddloader_zend_ini_parse_bool(Z_STR_P(opcache_enable_cli))) { + return false; + } + } + if (php_api_no > 20230831) { // PHP > 8.3 (https://php.watch/versions/8.4/opcache-jit-ini-default-changes) + // opcache.jit == disable (default: disable) + zval *opcache_jit = ddloader_ini_get_configuration(ZEND_STRL("opcache.jit")); + if (!opcache_jit || Z_TYPE_P(opcache_jit) != IS_STRING || strcmp(Z_STRVAL_P(opcache_jit), "disable") == 0 || strcmp(Z_STRVAL_P(opcache_jit), "off") == 0) { + return false; + } + } else { + // opcache.jit_buffer_size = 0 (default: 0) + zval *opcache_jit_buffer_size = ddloader_ini_get_configuration(ZEND_STRL("opcache.jit_buffer_size")); + if (!opcache_jit_buffer_size || Z_TYPE_P(opcache_jit_buffer_size) != IS_STRING || strcmp(Z_STRVAL_P(opcache_jit_buffer_size), "0") == 0) { + return false; + } + } + + return true; +} + static void ddtrace_pre_minit_hook(void) { HashTable *configuration_hash = php_ini_get_configuration_hash(); if (configuration_hash) { @@ -69,13 +140,36 @@ static void ddtrace_pre_minit_hook(void) { } // Set 'datadog.trace.sources_path' setting - zend_string *name = ddloader_zend_string_init(php_api_no, ZEND_STRL("datadog.trace.sources_path"), 1); - zend_string *value = ddloader_zend_string_init(php_api_no, sources_path, strlen(sources_path), 1); + ddloader_ini_set_configuration(ZEND_STRL("datadog.trace.sources_path"), sources_path, strlen(sources_path)); free(sources_path); + } - zval tmp; - ZVAL_STR(&tmp, value); - ddloader_zend_hash_update(configuration_hash, name, &tmp); + // Load, but disable the tracer if runtime configuration is not safe for auto-injection + bool disable_tracer = false; + + char *incompatible_exts[] = {"Xdebug", "the ionCube PHP Loader", "newrelic", "blackfire", "pcov"}; + for (size_t i = 0; i < sizeof(incompatible_exts) / sizeof(incompatible_exts[0]); ++i) { + if (ddloader_is_ext_loaded(incompatible_exts[i])) { + if (force_load) { + LOG(WARN, "Potentially incompatible extension detected: %s. Ignoring as DD_INJECT_FORCE is enabled", incompatible_exts[i]); + } else { + LOG(WARN, "Potentially incompatible extension detected: %s. ddtrace will be disabled unless the environment DD_INJECT_FORCE is set to '1', 'true', 'yes' or 'on'", incompatible_exts[i]); + disable_tracer = true; + } + } + } + + if (ddloader_is_opcache_jit_enabled()) { + if (force_load) { + LOG(WARN, "OPcache JIT is enabled and may cause instability. Ignoring as DD_INJECT_FORCE is enabled"); + } else { + LOG(WARN, "OPcache JIT is enabled and may cause instability. ddtrace will be disabled unless the environment DD_INJECT_FORCE is set to '1', 'true', 'yes' or 'on'"); + disable_tracer = true; + } + } + + if (disable_tracer) { + ddloader_ini_set_configuration(ZEND_STRL("ddtrace.disable"), ZEND_STRL("1")); } } @@ -460,26 +554,19 @@ static int ddloader_load_extension(unsigned int php_api_no, char *module_build_i config->module_number = module_entry->module_number; config->version = (char *)module_entry->version; - return SUCCESS; + goto ok; abort_and_unload: LOG(INFO, "Unloading the library"); DL_UNLOAD(handle); abort: LOG(INFO, "Abort the loader"); +ok: free(ext_path); return SUCCESS; } -static void ddloader_strtolower(char *dest, char *src) { - while (*src) { - *dest = (char)tolower((int)*src); - ++dest; - ++src; - } -} - static bool ddloader_is_truthy(char *str) { if (!str) { return false; @@ -490,10 +577,7 @@ static bool ddloader_is_truthy(char *str) { return false; } - char lower[5] = {0}; - ddloader_strtolower(lower, str); - - return (strcmp(lower, "1") == 0 || strcmp(lower, "true") == 0 || strcmp(lower, "yes") == 0 || strcmp(lower, "on") == 0); + return (strcasecmp(str, "1") == 0 || strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0); } static inline void ddloader_configure() { diff --git a/loader/packaging/allow_tests.json b/loader/packaging/allow_tests.json new file mode 100644 index 0000000000..18374c58e8 --- /dev/null +++ b/loader/packaging/allow_tests.json @@ -0,0 +1,11 @@ +[ + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "musl"}}, + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "arm64", "libc": "musl:1.5"}}, + {"filepath": "/elephp5ant/thing", "args": [], "envars": [], "host": {"os": "linux", "arch": "amd64", "libc": "musl"}}, + {"filepath": "/usr/bin/gluglu", "args": [], "envars": [], "host": {"os": "linux", "arch": "amd64", "libc": "glibc:3.42"}}, + {"filepath": "/opt/php/7/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/opt/php/7.4/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php83", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}} +] diff --git a/loader/packaging/block_tests.json b/loader/packaging/block_tests.json new file mode 100644 index 0000000000..969fc51923 --- /dev/null +++ b/loader/packaging/block_tests.json @@ -0,0 +1,14 @@ +[ + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "amd64", "libc": "glibc:2.15"}}, + {"filepath": "/usr/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "amd64", "libc": "gluglu:2.20"}}, + + {"filepath": "/opt/php5/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/opt/php5.3/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/opt/php/5/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/opt/php/5.4/bin/php", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + + {"filepath": "/usr/bin/php5", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php53", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php-54", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}}, + {"filepath": "/usr/bin/php.54", "args": [], "envars": [], "host": {"os": "linux", "arch": "x64", "libc": "glibc:2.17"}} +] diff --git a/loader/packaging/requirements.json b/loader/packaging/requirements.json new file mode 100644 index 0000000000..ad2ccdefb8 --- /dev/null +++ b/loader/packaging/requirements.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "native_deps": { + "glibc": [ + { + "arch": "x64", + "supported": true, + "description": "From centOS 7", + "min": "2.17" + }, + { + "arch": "arm64", + "supported": true, + "description": "From centOS 7", + "min": "2.17" + } + ], + "musl": [ + { + "arch": "x64", + "supported": true + }, + { + "arch": "arm64", + "supported": true + } + ] + }, + "deny": [ + { + "id": "php5", + "description": "Do not inject if PHP 5", + "os": null, + "cmds": [ + "**/php/5*/**", + "**/php5*/**", + "**/php5*", + "**/php-5*", + "**/php.5*" + ], + "args": [], + "envars": null + } + ] +} diff --git a/loader/php_dd_library_loader.h b/loader/php_dd_library_loader.h index c625acc873..270f4c07c4 100644 --- a/loader/php_dd_library_loader.h +++ b/loader/php_dd_library_loader.h @@ -3,7 +3,9 @@ #ifndef PHP_DD_LIBRARY_LOADER_H #define PHP_DD_LIBRARY_LOADER_H +#ifndef PHP_DD_LIBRARY_LOADER_VERSION #define PHP_DD_LIBRARY_LOADER_VERSION "0.1.0" +#endif #define UNUSED(x) (void)(x) @@ -27,10 +29,12 @@ typedef enum { #define TELEMETRY(reason, format, ...) ddloader_telemetryf(reason, format, ##__VA_ARGS__); -#define DECLARE_INJECTED_EXT(name, dir, _pre_load_hook, _pre_minit_hook, deps) \ - { \ - .ext_name = name, .ext_dir = dir, .tmp_name = name "_injected", .tmp_deps = deps, .pre_load_hook = _pre_load_hook, .pre_minit_hook = _pre_minit_hook, .orig_module_startup_func = NULL, \ - .orig_module_deps = NULL, .orig_module_functions = NULL, .module_number = -1, .version = NULL \ +#define DECLARE_INJECTED_EXT(name, dir, _pre_load_hook, _pre_minit_hook, deps) \ + { \ + .ext_name = name, .ext_dir = dir, .tmp_name = name "_injected", .tmp_deps = deps, \ + .pre_load_hook = _pre_load_hook, .pre_minit_hook = _pre_minit_hook, \ + .orig_module_startup_func = NULL, .orig_module_deps = NULL, .orig_module_functions = NULL, \ + .module_number = -1, .version = NULL \ } typedef struct _injected_ext { diff --git a/loader/tests/functional/fixtures/opcache_jit.php b/loader/tests/functional/fixtures/opcache_jit.php new file mode 100644 index 0000000000..2106ad86cd --- /dev/null +++ b/loader/tests/functional/fixtures/opcache_jit.php @@ -0,0 +1,23 @@ +getTrace(); $file = $trace[0]['file'] ?: ''; $line = $trace[0]['line'] ?: ''; @@ -22,8 +22,20 @@ function runCLI($args, $useLoader = true, $env = [], $noIni = true) { if (!isset($_SERVER['DD_LOADER_PACKAGE_PATH'])) { $env[] = "DD_LOADER_PACKAGE_PATH=/home/circleci/app/dd-library-php"; } + + $valgrind = use_valgrind(); + if ($valgrind) { + $env[] = 'USE_ZEND_ALLOC=0'; + $env[] = 'ZEND_DONT_UNLOAD_MODULES=1'; + } + $cmd = implode(' ', $env).' '; + $valgrindLogFile = tempnam(sys_get_temp_dir(), 'valgrind_loader_test_');; + if ($valgrind) { + $cmd .= "valgrind -q --log-file=$valgrindLogFile --suppressions=./valgrind.supp --gen-suppressions=all --tool=memcheck --trace-children=no --undef-value-errors=no --vex-iropt-register-updates=allregs-at-mem-access --leak-check=full "; + } + $cmd .= 'php'; if ($noIni) { $cmd .= ' -n'; @@ -40,10 +52,22 @@ function runCLI($args, $useLoader = true, $env = [], $noIni = true) { } $res = exec($cmd, $output, $result_code); + + if ($valgrind) { + $valgrindOutput = file_get_contents($valgrindLogFile); + @unlink($valgrindLogFile); + } + if (!is_string($res) || $result_code !== 0) { throw new \Exception(sprintf('Error while executing "%s" (exit code: %d): \n\n', $cmd, $result_code, $output)); } + if ($valgrind) { + if (strlen($valgrindOutput)) { + throw new \Exception("Memory leak detected:\n".$valgrindOutput); + } + } + return implode("\n", $output); } @@ -55,6 +79,10 @@ function debug() { return (bool) (isset($_SERVER['DEBUG']) ? $_SERVER['DEBUG'] : false); } +function use_valgrind() { + return (bool) (isset($_SERVER['TEST_USE_VALGRIND']) ? $_SERVER['TEST_USE_VALGRIND'] : false); +} + function skip_if_php5() { if (PHP_MAJOR_VERSION <= 5) { echo "Skip: test is not compatible with PHP 5\n"; @@ -69,6 +97,13 @@ function skip_if_not_php5() { } } +function skip_if_not_php8() { + if (PHP_MAJOR_VERSION < 8) { + echo "Skip: test requires PHP 8\n"; + exit(0); + } +} + function skip_if_opcache_missing() { $output = runCLI('-dzend_extension=opcache -m'); if (strpos($output, 'Zend OPcache') === false) { diff --git a/loader/tests/functional/test_ddtrace_is_fully_loaded.php b/loader/tests/functional/test_ddtrace_is_fully_loaded.php index 43f309fe78..6fefda6bd7 100644 --- a/loader/tests/functional/test_ddtrace_is_fully_loaded.php +++ b/loader/tests/functional/test_ddtrace_is_fully_loaded.php @@ -3,7 +3,7 @@ require_once __DIR__."/includes/autoload.php"; skip_if_php5(); -$output = runCLI(__DIR__.'/fixtures/ddtrace.php'); +$output = runCLI('-ddatadog.trace.cli_enabled=0 '.__DIR__.'/fixtures/ddtrace.php'); assertEquals($output, << "-dzend_extension=opcache -ddatadog.trace.cli_enabled=1", + "must_not_contain" => [$msg_disabled], + "must_contain" => [<< "-dzend_extension=opcache -dopcache.enable_cli=1 -ddatadog.trace.cli_enabled=1", + "must_not_contain" => [$msg_disabled], + "must_contain" => [<< "-dzend_extension=opcache -dopcache.enable_cli=1 -ddatadog.trace.cli_enabled=1 -dopcache.jit_buffer_size=32M", + "must_not_contain" => [], + "must_contain" => [ + $msg_disabled, + << "-dzend_extension=opcache -dopcache.enable_cli=1 -ddatadog.trace.cli_enabled=1 -dopcache.jit_buffer_size=32M", + "env" => ['DD_INJECT_FORCE=1'], + "must_not_contain" => [], + "must_contain" => [ + $msg_forced, + << + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:UnknownInlinedFun + fun:UnknownInlinedFun + fun:alloc + fun:alloc_impl + fun:allocate + fun:do_alloc + fun:new_uninitialized + fun:fallible_with_capacity + fun:prepare_resize + fun:resize_inner + fun:reserve_rehash_inner + ... +} diff --git a/package.xml b/package.xml index ab1036b174..1558e260a2 100644 --- a/package.xml +++ b/package.xml @@ -28,12 +28,6 @@ bwoebi@php.net yes - - Joe Watkins - krakjoe - krakjoe@php.net - yes - Pierre Bonet pierotibou @@ -74,52 +68,60 @@ BSD 3-Clause diff --git a/profiling/src/allocation.rs b/profiling/src/allocation.rs index 4ade967867..8c05881a94 100644 --- a/profiling/src/allocation.rs +++ b/profiling/src/allocation.rs @@ -2,7 +2,8 @@ use crate::bindings::{ self as zend, datadog_php_install_handler, datadog_php_zif_handler, ddog_php_prof_copy_long_into_zval, }; -use crate::{PROFILER, PROFILER_NAME, REQUEST_LOCALS}; +use crate::profiling::Profiler; +use crate::{PROFILER_NAME, REQUEST_LOCALS}; use lazy_static::lazy_static; use libc::{c_char, c_int, c_void, size_t}; use log::{debug, error, trace, warn}; @@ -94,7 +95,7 @@ impl AllocationProfilingStats { self.next_sampling_interval(); - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it. unsafe { profiler.collect_allocations( diff --git a/profiling/src/exception.rs b/profiling/src/exception.rs index 71da753b06..5f35f3647d 100644 --- a/profiling/src/exception.rs +++ b/profiling/src/exception.rs @@ -1,5 +1,5 @@ +use crate::profiling::Profiler; use crate::zend::{self, zend_execute_data, zend_generator, zval, InternalFunctionHandler}; -use crate::PROFILER; use crate::REQUEST_LOCALS; use log::{error, info}; use rand::rngs::ThreadRng; @@ -83,7 +83,7 @@ impl ExceptionProfilingStats { self.next_sampling_interval(); - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it. unsafe { profiler.collect_exception( diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index 75e82af14d..02cb66a8e8 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -41,16 +41,12 @@ use std::ffi::CStr; use std::ops::Deref; use std::os::raw::c_int; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; -use std::sync::{Arc, Mutex, Once}; +use std::sync::{Arc, Once}; use std::time::{Duration, Instant}; use uuid::Uuid; use crate::zend::datadog_sapi_globals_request_info; -/// The global profiler. Profiler gets made during the first rinit after an -/// minit, and is destroyed on mshutdown. -static PROFILER: Mutex> = Mutex::new(None); - /// Name of the profiling module and zend_extension. Must not contain any /// interior null bytes and must be null terminated. static PROFILER_NAME: &[u8] = b"datadog-profiling\0"; @@ -508,18 +504,7 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { return ZendResult::Success; } - // reminder: this cannot be done in minit because of Apache forking model - { - /* It would be nice if this could be cheaper. OnceCell would be cheaper - * but it doesn't quite fit the model, as going back to uninitialized - * requires either a &mut or .take(), and neither works for us (unless - * we go for unsafe, which is what we are trying to avoid). - */ - let mut profiler = PROFILER.lock().unwrap(); - if profiler.is_none() { - *profiler = Some(Profiler::new(system_settings)) - } - }; + Profiler::init(system_settings); if system_settings.profiling_enabled { REQUEST_LOCALS.with(|cell| { @@ -556,7 +541,7 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { return; } - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { let interrupt = VmInterrupt { interrupt_count_ptr: &locals.interrupt_count as *const AtomicU32, engine_ptr: locals.vm_interrupt_addr, @@ -612,7 +597,7 @@ extern "C" fn rshutdown(_type: c_int, _module_number: c_int) -> ZendResult { // wall-time is not expected to ever be disabled, except in testing, // and we don't need to optimize for that. if system_settings.profiling_enabled { - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { let interrupt = VmInterrupt { interrupt_count_ptr: &locals.interrupt_count, engine_ptr: locals.vm_interrupt_addr, @@ -822,10 +807,7 @@ extern "C" fn mshutdown(_type: c_int, _module_number: c_int) -> ZendResult { #[cfg(feature = "exception_profiling")] exception::exception_profiling_mshutdown(); - let mut profiler = PROFILER.lock().unwrap(); - if let Some(profiler) = profiler.as_mut() { - profiler.stop(Duration::from_secs(1)); - } + Profiler::stop(Duration::from_secs(1)); ZendResult::Success } @@ -859,10 +841,7 @@ extern "C" fn shutdown(_extension: *mut ZendExtension) { #[cfg(debug_assertions)] trace!("shutdown({:p})", _extension); - let mut profiler = PROFILER.lock().unwrap(); - if let Some(profiler) = profiler.take() { - profiler.shutdown(Duration::from_secs(2)); - } + Profiler::shutdown(Duration::from_secs(2)); // SAFETY: calling in shutdown before zai config is shutdown, and after // all configuration is done being accessed. Well... in the happy-path, @@ -891,7 +870,7 @@ fn notify_trace_finished(local_root_span_id: u64, span_type: Cow, resource: return; } - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { let message = LocalRootSpanResourceMessage { local_root_span_id, resource: resource.into_owned(), diff --git a/profiling/src/pcntl.rs b/profiling/src/pcntl.rs index 8a26c76be3..d0e230fb4a 100644 --- a/profiling/src/pcntl.rs +++ b/profiling/src/pcntl.rs @@ -3,11 +3,10 @@ use crate::bindings::{ datadog_php_install_handler, datadog_php_zif_handler, zend_execute_data, zend_long, zval, InternalFunctionHandler, }; -use crate::{config, PROFILER}; +use crate::{config, Profiler}; use ddcommon::cstr; use log::{error, warn}; use std::ffi::CStr; -use std::mem::forget; use std::ptr; static mut PCNTL_FORK_HANDLER: InternalFunctionHandler = None; @@ -62,14 +61,13 @@ unsafe fn handle_fork( // Hold mutexes across the handler. If there are any spurious wakeups by // the threads while the fork is occurring, they cannot acquire locks // since this thread holds them, preventing a deadlock situation. - let mut profiler_lock = PROFILER.lock().unwrap(); - if let Some(profiler) = profiler_lock.as_ref() { - profiler.fork_prepare(); + if let Some(profiler) = Profiler::get() { + let _ = profiler.fork_prepare(); } match dispatch(handler, execute_data, return_value) { Ok(ForkId::Parent) => { - if let Some(profiler) = profiler_lock.as_ref() { + if let Some(profiler) = Profiler::get() { profiler.post_fork_parent(); } return; @@ -77,7 +75,7 @@ unsafe fn handle_fork( Ok(ForkId::Child) => { /* fallthrough */ } Err(ForkError::NullRetval) => { // Skip error message if no profiler. - if profiler_lock.is_some() { + if Profiler::get().is_some() { error!( "Failed to read return value of forking function. A crash or deadlock may occur." ); @@ -87,7 +85,7 @@ unsafe fn handle_fork( Err(ForkError::ZvalType(r#type)) => { // Skip error message if no profiler. - if profiler_lock.is_some() { + if Profiler::get().is_some() { warn!( "Return type of forking function was unexpected: {type}. A crash or deadlock may occur." ); @@ -101,9 +99,7 @@ unsafe fn handle_fork( // 2. Something went wrong, and disable it to be safe. // And then leak the old profiler. Its drop method is not safe to run in // these situations. - if let Some(profiler) = profiler_lock.take() { - forget(profiler); - } + Profiler::kill(); alloc_prof_rshutdown(); diff --git a/profiling/src/profiling/mod.rs b/profiling/src/profiling/mod.rs index a812074a29..f98fee3484 100644 --- a/profiling/src/profiling/mod.rs +++ b/profiling/src/profiling/mod.rs @@ -25,11 +25,13 @@ use datadog_profiling::api::{ }; use datadog_profiling::exporter::Tag; use datadog_profiling::internal::Profile as InternalProfile; -use log::{debug, error, info, trace, warn}; +use log::{debug, info, trace, warn}; +use once_cell::sync::OnceCell; use std::borrow::Cow; use std::collections::HashMap; use std::hash::Hash; use std::intrinsics::transmute; +use std::mem::forget; use std::num::NonZeroI64; use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering}; use std::sync::{Arc, Barrier}; @@ -56,6 +58,10 @@ pub const NO_TIMESTAMP: i64 = 0; // magnitude for the capacity. const UPLOAD_CHANNEL_CAPACITY: usize = 8; +/// The global profiler. Profiler gets made during the first rinit after an +/// minit, and is destroyed on mshutdown. +static mut PROFILER: OnceCell = OnceCell::new(); + /// Order this array this way: /// 1. Always enabled types. /// 2. On by default types. @@ -510,7 +516,25 @@ pub enum UploadMessage { #[cfg(feature = "timeline")] const COW_EVAL: Cow = Cow::Borrowed("[eval]"); +const DDPROF_TIME: &str = "ddprof_time"; +const DDPROF_UPLOAD: &str = "ddprof_upload"; + impl Profiler { + /// Will initialize the `PROFILER` OnceCell and makes sure that only one thread will do so. + pub fn init(system_settings: &mut SystemSettings) { + // SAFETY: the `get_or_init` access is a thread-safe API, and the + // PROFILER is not being mutated outside single-threaded phases such + // as minit/mshutdown. + unsafe { PROFILER.get_or_init(|| Profiler::new(system_settings)) }; + } + + pub fn get() -> Option<&'static Profiler> { + // SAFETY: the `get` access is a thread-safe API, and the PROFILER is + // not being mutated outside single-threaded phases such as minit and + // mshutdown. + unsafe { PROFILER.get() } + } + pub fn new(system_settings: &mut SystemSettings) -> Self { let fork_barrier = Arc::new(Barrier::new(3)); let interrupt_manager = Arc::new(InterruptManager::new()); @@ -532,21 +556,19 @@ impl Profiler { Utc::now(), ); - let ddprof_time = "ddprof_time"; - let ddprof_upload = "ddprof_upload"; let sample_types_filter = SampleTypeFilter::new(system_settings); Profiler { fork_barrier, interrupt_manager, message_sender, upload_sender, - time_collector_handle: thread_utils::spawn(ddprof_time, move || { + time_collector_handle: thread_utils::spawn(DDPROF_TIME, move || { time_collector.run(); - trace!("thread {ddprof_time} complete, shutting down"); + trace!("thread {DDPROF_TIME} complete, shutting down"); }), - uploader_handle: thread_utils::spawn(ddprof_upload, move || { + uploader_handle: thread_utils::spawn(DDPROF_UPLOAD, move || { uploader.run(); - trace!("thread {ddprof_upload} complete, shutting down"); + trace!("thread {DDPROF_UPLOAD} complete, shutting down"); }), should_join: AtomicBool::new(true), sample_types_filter, @@ -576,22 +598,26 @@ impl Profiler { } /// Call before a fork, on the thread of the parent process that will fork. - pub fn fork_prepare(&self) { - // Send the message to the uploader first, as it has a longer worst- + pub fn fork_prepare(&self) -> anyhow::Result<()> { + // Send the message to the uploader first, as it has a longer worst // case time to wait. let uploader_result = self.upload_sender.send(UploadMessage::Pause); let profiler_result = self.message_sender.send(ProfilerMessage::Pause); - // todo: handle fails more gracefully, but it's tricky to sync 3 - // threads, any of which could have crashed or be delayed. This - // could also deadlock. match (uploader_result, profiler_result) { (Ok(_), Ok(_)) => { self.fork_barrier.wait(); + Ok(()) + } + (Err(err), Ok(_)) => { + anyhow::bail!("failed to prepare {DDPROF_UPLOAD} thread for forking: {err}") } - (_, _) => { - error!("failed to prepare the profiler for forking, a deadlock could occur") + (Ok(_), Err(err)) => { + anyhow::bail!("failed to prepare {DDPROF_TIME} thread for forking: {err}") } + (Err(_), Err(_)) => anyhow::bail!( + "failed to prepare both {DDPROF_UPLOAD} and {DDPROF_TIME} threads for forking" + ), } } @@ -622,7 +648,16 @@ impl Profiler { /// Note that you must call [Profiler::shutdown] afterwards; it's two /// parts of the same operation. It's split so you (or other extensions) /// can do something while the other threads finish up. - pub fn stop(&mut self, timeout: Duration) { + pub fn stop(timeout: Duration) { + // SAFETY: the `get_mut` access is a thread-safe API, and the PROFILER + // is not being mutated outside single-threaded phases such as minit + // and mshutdown. + if let Some(profiler) = unsafe { PROFILER.get_mut() } { + profiler.join_and_drop_sender(timeout); + } + } + + pub fn join_and_drop_sender(&mut self, timeout: Duration) { debug!("Stopping profiler."); let sent = match self @@ -653,7 +688,18 @@ impl Profiler { /// Completes the shutdown process; to start it, call [Profiler::stop] /// before calling [Profiler::shutdown]. /// Note the timeout is per thread, and there may be multiple threads. - pub fn shutdown(self, timeout: Duration) { + /// + /// Safety: only safe to be called in `SHUTDOWN`/`MSHUTDOWN` phase + pub fn shutdown(timeout: Duration) { + // SAFETY: the `take` access is a thread-safe API, and the PROFILER is + // not being mutated outside single-threaded phases such as minit and + // mshutdown. + if let Some(profiler) = unsafe { PROFILER.take() } { + profiler.join_collector_and_uploader(timeout); + } + } + + pub fn join_collector_and_uploader(self, timeout: Duration) { if self.should_join.load(Ordering::SeqCst) { thread_utils::join_timeout( self.time_collector_handle, @@ -672,6 +718,34 @@ impl Profiler { } } + /// Throws away the profiler and moves it to uninitialized. + /// + /// In a forking situation, the currently active profiler may not be valid + /// because it has join handles and other state shared by other threads, + /// and threads are not copied when the process is forked. + /// Additionally, if we've hit certain other issues like not being able to + /// determine the return type of the pcntl_fork function, we don't know if + /// we're the parent or child. + /// So, we throw away the current profiler and forget it, which avoids + /// running the destructor. Yes, this will leak some memory. + /// + /// # Safety + /// Must be called when no other thread is using the PROFILER object. That + /// includes this thread in some kind of recursive manner. + pub unsafe fn kill() { + // SAFETY: see this function's safety conditions. + if let Some(mut profiler) = PROFILER.take() { + // Drop some things to reduce memory. + profiler.interrupt_manager = Arc::new(InterruptManager::new()); + profiler.message_sender = crossbeam_channel::bounded(0).0; + profiler.upload_sender = crossbeam_channel::bounded(0).0; + + // But we're not 100% sure everything is safe to drop, notably the + // join handles, so we leak the rest. + forget(profiler) + } + } + /// Collect a stack sample with elapsed wall time. Collects CPU time if /// it's enabled and available. pub fn collect_time(&self, execute_data: *mut zend_execute_data, interrupt_count: u32) { @@ -684,7 +758,9 @@ impl Profiler { let (wall_time, cpu_time) = CLOCKS.with(|cell| cell.borrow_mut().rotate_clocks()); let labels = Profiler::common_labels(0); - let mut timestamp = 0; + let n_labels = labels.len(); + + let mut timestamp = NO_TIMESTAMP; #[cfg(feature = "timeline")] { let system_settings = self.system_settings.load(Ordering::SeqCst); @@ -696,8 +772,6 @@ impl Profiler { } } - let n_labels = labels.len(); - match self.prepare_and_send_message( frames, SampleValues { @@ -790,6 +864,18 @@ impl Profiler { let n_labels = labels.len(); + let mut timestamp = NO_TIMESTAMP; + #[cfg(feature = "timeline")] + { + let system_settings = self.system_settings.load(Ordering::SeqCst); + // SAFETY: system settings are stable during a request. + if unsafe { *ptr::addr_of!((*system_settings).profiling_timeline_enabled) } { + if let Ok(now) = SystemTime::now().duration_since(UNIX_EPOCH) { + timestamp = now.as_nanos() as i64; + } + } + } + match self.prepare_and_send_message( frames, SampleValues { @@ -797,7 +883,7 @@ impl Profiler { ..Default::default() }, labels, - NO_TIMESTAMP, + timestamp, ) { Ok(_) => trace!( "Sent stack sample of {depth} frames, {n_labels} labels with Exception {exception} to profiler." diff --git a/profiling/src/timeline.rs b/profiling/src/timeline.rs index f0e8928526..596c887a14 100644 --- a/profiling/src/timeline.rs +++ b/profiling/src/timeline.rs @@ -1,10 +1,11 @@ +use crate::profiling::Profiler; use crate::zend::{ self, zai_str_from_zstr, zend_execute_data, zend_get_executed_filename_ex, zval, InternalFunctionHandler, }; -use crate::{PROFILER, REQUEST_LOCALS}; +use crate::REQUEST_LOCALS; use libc::c_char; -use log::{error, trace, warn}; +use log::{error, trace}; use std::cell::RefCell; use std::ffi::CStr; use std::ptr; @@ -45,13 +46,12 @@ impl State { } } -#[inline] -fn try_sleeping_fn( +fn sleeping_fn( func: unsafe extern "C" fn(execute_data: *mut zend_execute_data, return_value: *mut zval), execute_data: *mut zend_execute_data, return_value: *mut zval, state: State, -) -> anyhow::Result<()> { +) { let timeline_enabled = REQUEST_LOCALS.with(|cell| { cell.try_borrow() .map(|locals| locals.system_settings().profiling_timeline_enabled) @@ -60,7 +60,7 @@ fn try_sleeping_fn( if !timeline_enabled { unsafe { func(execute_data, return_value) }; - return Ok(()); + return; } let start = Instant::now(); @@ -74,34 +74,18 @@ fn try_sleeping_fn( let duration = start.elapsed(); - // > Returns an Err if earlier is later than self, and the error contains - // > how far from self the time is. - // This shouldn't ever happen (now is always later than the epoch) but in - // case it does, short-circuit the function. - let now = SystemTime::now().duration_since(UNIX_EPOCH)?; - - match PROFILER.lock() { - Ok(guard) => { - // If the profiler isn't there, it's probably just not enabled. - if let Some(profiler) = guard.as_ref() { - let now = now.as_nanos() as i64; - let duration = duration.as_nanos() as i64; - profiler.collect_idle(now, duration, state.as_str()) - } - } - Err(err) => anyhow::bail!("profiler mutex: {err:#}"), + // This shouldn't ever happen (now is always later than the epoch) + let now = SystemTime::now().duration_since(UNIX_EPOCH); + + if now.is_err() { + return; } - Ok(()) -} -fn sleeping_fn( - func: unsafe extern "C" fn(execute_data: *mut zend_execute_data, return_value: *mut zval), - execute_data: *mut zend_execute_data, - return_value: *mut zval, - state: State, -) { - if let Err(err) = try_sleeping_fn(func, execute_data, return_value, state) { - warn!("error creating profiling timeline sample for an internal function: {err:#}"); + if let Some(profiler) = Profiler::get() { + // Safety: `unwrap` can be unchecked, as we checked for `is_err()` + let now = unsafe { now.unwrap_unchecked().as_nanos() } as i64; + let duration = duration.as_nanos() as i64; + profiler.collect_idle(now, duration, state.as_str()); } } @@ -234,7 +218,7 @@ pub unsafe fn timeline_rinit() { return; } - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { profiler.collect_idle( // Safety: checked for `is_err()` above SystemTime::now() @@ -293,7 +277,7 @@ pub(crate) unsafe fn timeline_mshutdown() { return; } - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { profiler.collect_idle( // Safety: checked for `is_err()` above SystemTime::now() @@ -357,7 +341,7 @@ unsafe extern "C" fn ddog_php_prof_compile_string( duration.as_nanos(), ); - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { profiler.collect_compile_string( // Safety: checked for `is_err()` above now.unwrap().as_nanos() as i64, @@ -423,7 +407,7 @@ unsafe extern "C" fn ddog_php_prof_compile_file( duration.as_nanos(), ); - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { profiler.collect_compile_file( // Safety: checked for `is_err()` above now.unwrap().as_nanos() as i64, @@ -493,7 +477,7 @@ unsafe extern "C" fn ddog_php_prof_gc_collect_cycles() -> i32 { duration.as_nanos() ); - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { cfg_if::cfg_if! { if #[cfg(php_gc_status)] { profiler.collect_garbage_collection( diff --git a/profiling/src/wall_time.rs b/profiling/src/wall_time.rs index 2635092b92..42f7277843 100644 --- a/profiling/src/wall_time.rs +++ b/profiling/src/wall_time.rs @@ -5,7 +5,7 @@ use crate::bindings::{ zend_execute_data, zend_execute_internal, zend_interrupt_function, zval, VmInterruptFn, ZEND_ACC_CALL_VIA_TRAMPOLINE, }; -use crate::{zend, PROFILER, REQUEST_LOCALS}; +use crate::{profiling::Profiler, zend, REQUEST_LOCALS}; use std::mem::MaybeUninit; use std::sync::atomic::Ordering; @@ -55,7 +55,7 @@ pub extern "C" fn ddog_php_prof_interrupt_function(execute_data: *mut zend_execu return; } - if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + if let Some(profiler) = Profiler::get() { // Safety: execute_data was provided by the engine, and the profiler doesn't mutate it. profiler.collect_time(execute_data, interrupt_count); } diff --git a/src/DDTrace/Integrations/Curl/CurlIntegration.php b/src/DDTrace/Integrations/Curl/CurlIntegration.php index 623c41f1d1..61304f8d29 100644 --- a/src/DDTrace/Integrations/Curl/CurlIntegration.php +++ b/src/DDTrace/Integrations/Curl/CurlIntegration.php @@ -121,12 +121,15 @@ public function init(): int list($ch, $requestSpan) = $requestSpan; $requestSpan->metrics["_dd.measured"] = 1; $info = curl_getinfo($ch); - if (isset($requestSpan->meta['network.destination.name']) && $requestSpan->meta['network.destination.name'] !== 'unparsable-host') { + if (empty($info["http_code"])) { + $saveSpans = true; + } + + if (isset($requestSpan->meta[Tag::NETWORK_DESTINATION_NAME]) && $requestSpan->meta[Tag::NETWORK_DESTINATION_NAME] !== 'unparsable-host') { continue; } if (empty($info["http_code"])) { - $saveSpans = true; if (!isset($error_trace)) { $error_trace = \DDTrace\get_sanitized_exception_trace(new \Exception(), 1); } @@ -183,16 +186,15 @@ public function init(): int } list(, $spans) = $data; + if (empty($spans)) { + return; + } if (!isset($hook->returned["result"]) || $hook->returned["result"] == CURLE_OK) { - if (empty($spans)) { - return; - } - foreach ($spans as $requestSpan) { list($ch, $requestSpan) = $requestSpan; if ($ch === $handle) { - if (isset($requestSpan->meta['network.destination.name']) && $requestSpan->meta['network.destination.name'] !== 'unparsable-host') { + if (isset($requestSpan->meta[Tag::NETWORK_DESTINATION_NAME]) && $requestSpan->meta[Tag::NETWORK_DESTINATION_NAME] !== 'unparsable-host') { continue; } $info = curl_getinfo($ch); diff --git a/src/DDTrace/Integrations/MongoDB/MongoDBIntegration.php b/src/DDTrace/Integrations/MongoDB/MongoDBIntegration.php index a8bc4be9ec..51dbd62d1b 100644 --- a/src/DDTrace/Integrations/MongoDB/MongoDBIntegration.php +++ b/src/DDTrace/Integrations/MongoDB/MongoDBIntegration.php @@ -17,13 +17,25 @@ function register_subscriber() { class DatadogSubscriber implements \MongoDB\Driver\Monitoring\CommandSubscriber { + private static $useDeprecatedMethods = null; + #[\ReturnTypeWillChange] public function commandStarted(\MongoDB\Driver\Monitoring\CommandStartedEvent $event) { $span = \DDTrace\active_span(); if ($span) { - $span->meta['out.host'] = $event->getServer()->getHost(); - $span->meta['out.port'] = $event->getServer()->getPort(); + if (is_null(self::$useDeprecatedMethods)) { + // v1.20+: getServer() is deprecated in favor of getHost() and getPort() + self::$useDeprecatedMethods = !method_exists($event, 'getHost') || !method_exists($event, 'getPort'); + } + + if (self::$useDeprecatedMethods) { + $span->meta['out.host'] = $event->getServer()->getHost(); + $span->meta['out.port'] = $event->getServer()->getPort(); + } else { + $span->meta['out.host'] = $event->getHost(); + $span->meta['out.port'] = $event->getPort(); + } } } diff --git a/src/DDTrace/Integrations/Roadrunner/RoadrunnerIntegration.php b/src/DDTrace/Integrations/Roadrunner/RoadrunnerIntegration.php index 4f85e9c14b..27b2b8a0bf 100644 --- a/src/DDTrace/Integrations/Roadrunner/RoadrunnerIntegration.php +++ b/src/DDTrace/Integrations/Roadrunner/RoadrunnerIntegration.php @@ -111,7 +111,7 @@ function ($upload) { } private static function getHost(array $parsed_url) { - $port = $parsed_url['port']; + $port = $parsed_url['port'] ?? 80; $scheme = $parsed_url['scheme']; if ($scheme === 'https') { if ($port === 443) { diff --git a/src/DDTrace/Integrations/Symfony/SymfonyIntegration.php b/src/DDTrace/Integrations/Symfony/SymfonyIntegration.php index be854b939a..c633513ec0 100644 --- a/src/DDTrace/Integrations/Symfony/SymfonyIntegration.php +++ b/src/DDTrace/Integrations/Symfony/SymfonyIntegration.php @@ -9,10 +9,8 @@ use DDTrace\Tag; use DDTrace\Type; use DDTrace\Util\Normalizer; -use DDTrace\Util\Versions; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\KernelEvents; -use function DDTrace\install_hook; class SymfonyIntegration extends Integration { @@ -258,6 +256,18 @@ function ($This, $scope, $args) { return false; } + $commandName = $this->getName(); + + if (\dd_trace_env_config('DD_TRACE_REMOVE_ROOT_SPAN_SYMFONY_MESSENGER') + && $commandName === 'messenger:consume' + ) { + \DDTrace\set_priority_sampling(DD_TRACE_PRIORITY_SAMPLING_AUTO_REJECT); + \dd_trace_close_all_spans_and_flush(); + ini_set("datadog.trace.auto_flush_enabled", 1); + ini_set("datadog.trace.generate_root_span", 0); + return false; + } + $namespace = \get_class($this); if (strpos($namespace, DrupalIntegration::NAME) !== false) { $integration->frameworkPrefix = DrupalIntegration::NAME; @@ -266,7 +276,7 @@ function ($This, $scope, $args) { } $span->name = 'symfony.console.command.run'; - $span->resource = $this->getName() ?: $span->name; + $span->resource = $commandName ?: $span->name; $span->service = \ddtrace_config_app_name($integration->frameworkPrefix); $span->type = Type::CLI; $span->meta['symfony.console.command.class'] = \get_class($this); diff --git a/src/DDTrace/Integrations/SymfonyMessenger/DDTraceStamp.php b/src/DDTrace/Integrations/SymfonyMessenger/DDTraceStamp.php new file mode 100644 index 0000000000..87233d882a --- /dev/null +++ b/src/DDTrace/Integrations/SymfonyMessenger/DDTraceStamp.php @@ -0,0 +1,20 @@ +headers = $headers; + } + + public function getHeaders(): array + { + return $this->headers; + } +} diff --git a/src/DDTrace/Integrations/SymfonyMessenger/SymfonyMessengerIntegration.php b/src/DDTrace/Integrations/SymfonyMessenger/SymfonyMessengerIntegration.php new file mode 100644 index 0000000000..afc521f5d2 --- /dev/null +++ b/src/DDTrace/Integrations/SymfonyMessenger/SymfonyMessengerIntegration.php @@ -0,0 +1,348 @@ +setSpanAttributes($span, 'symfony.messenger.dispatch', null, $retval ?? $args[0], null, null, true); + if ($exception) { + // Worker::handleMessage() will catch the exception. We need to manually attach it to the root span. + \DDTrace\root_span()->exception = $exception; + } + } + ); + + // Attach current context to Envelope before sender sends it to remote queue + install_hook( + 'Symfony\Component\Messenger\Transport\Sender\SenderInterface::send', + function (HookData $hook) { + /** @var \Symfony\Component\Messenger\Envelope $envelope */ + $envelope = $hook->args[0]; + + if (\ddtrace_config_distributed_tracing_enabled()) { + $ddTraceStamp = $envelope->last(DDTraceStamp::class); + + // Add distributed tracing stamp only if not already on the envelope + if ($ddTraceStamp === null) { + $tracingHeaders = \DDTrace\generate_distributed_tracing_headers(); + $hook->overrideArguments([ + $envelope->with(new DDTraceStamp($tracingHeaders)) + ]); + } + } + } + ); + + trace_method( + 'Symfony\Component\Messenger\Transport\TransportInterface', + 'send', + function (SpanData $span, array $args, $envelope) use ($integration) { + $integration->setSpanAttributes($span, 'symfony.messenger.send', null, $envelope ?? $args[0], null, 'send', true); + } + ); + + trace_method( + 'Symfony\Component\Messenger\Worker', + 'handleMessage', + [ + 'prehook' => function (SpanData $span, array $args) use ($integration) { + /** @var \Symfony\Component\Messenger\Envelope $envelope */ + $envelope = $args[0]; + /** @var string|ReceiverInterface $transportName */ + $transportName = $args[1]; + if (\is_object($transportName)) { + $transportName = \get_class($transportName); + } + + $integration->setSpanAttributes( + $span, + 'symfony.messenger.consume', + null, + $envelope, + $transportName, + 'receive' + ); + + $ddTraceStamp = $envelope->last(DDTraceStamp::class); + if ($ddTraceStamp instanceof DDTraceStamp) { + $tracingHeaders = $ddTraceStamp->getHeaders(); + if (\dd_trace_env_config('DD_TRACE_SYMFONY_MESSENGER_DISTRIBUTED_TRACING')) { + \DDTrace\consume_distributed_tracing_headers($tracingHeaders); + } else { + $span->links[] = \DDTrace\SpanLink::fromHeaders($tracingHeaders); + } + } + }, + 'posthook' => function (SpanData $span) use ($integration) { + if ($span->exception !== null) { + // Used by Logs Correlation to track the origin of an exception + ObjectKVStore::put( + $span->exception, + 'exception_trace_identifiers', + [ + 'trace_id' => \DDTrace\logs_correlation_trace_id(), + 'span_id' => \dd_trace_peek_span_id() + ] + ); + } + }, + 'recurse' => true, + ] + ); + + $callHandlerExists = \method_exists('Symfony\Component\Messenger\Middleware\HandleMessageMiddleware', 'callHandler'); + if ($callHandlerExists) { + // Symfony Messenger 6.2+ + hook_method( + 'Symfony\Component\Messenger\Middleware\HandleMessageMiddleware', + 'callHandler', + function ($This, $scope, $args) use ($integration) { + $message = $args[1]; + install_hook($args[0], function (HookData $hook) use ($integration, $message) { + $integration->setSpanAttributes($hook->span(), 'symfony.messenger.handle', \get_class($this), $message, false, 'process'); + remove_hook($hook->id); + }); + } + ); + } else { + hook_method( + 'Symfony\Component\Messenger\Middleware\HandleMessageMiddleware', + '__construct', + function ($This, $scope, $args) { + /** @var HandlersLocatorInterface $handlersLocator */ + $handlersLocator = $args[0]; + ObjectKVStore::put($This, 'handlersLocator', $handlersLocator); + } + ); + + hook_method( + 'Symfony\Component\Messenger\Middleware\HandleMessageMiddleware', + 'handle', + function ($This, $scope, $args) use ($integration) { + $envelope = $args[0]; + $handlersLocator = ObjectKVStore::get($This, 'handlersLocator'); + $message = $envelope->getMessage(); + foreach ($handlersLocator->getHandlers($envelope) as $handlerDescriptor) { + if ($integration->messageHasAlreadyBeenHandled($envelope, $handlerDescriptor)) { + continue; + } + + $handler = $handlerDescriptor->getHandler(); + install_hook($handler, function (HookData $hook) use ($integration, $message) { + $integration->setSpanAttributes($hook->span(), 'symfony.messenger.handle', \get_class($this), $message, false, 'process'); + remove_hook($hook->id); + }); + } + } + ); + } + + if (dd_trace_env_config('DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES')) { + $handleFn = function (SpanData $span, array $args) use ($integration) { + $integration->setSpanAttributes($span, 'symfony.messenger.middleware', \get_class($this), $args[0]); + }; + + trace_method( + 'Symfony\Component\Messenger\Middleware\MiddlewareInterface', + 'handle', + [ + 'posthook' => $handleFn, + 'recurse' => true + ] + ); + + // Symfony Messenger 6.2+ + trace_method( + 'Symfony\Component\Messenger\Middleware\HandleMessageMiddleware', + 'handle', + [ + 'posthook' => $handleFn, + 'recurse' => true + ] + ); + } + + return Integration::LOADED; + } + + public function messageHasAlreadyBeenHandled(Envelope $envelope, HandlerDescriptor $handlerDescriptor): bool + { + $some = array_filter($envelope + ->all(HandledStamp::class), function (HandledStamp $stamp) use ($handlerDescriptor) { + return $stamp->getHandlerName() === $handlerDescriptor->getName(); + }); + + return \count($some) > 0; + } + + public function setSpanAttributes( + SpanData $span, + string $name, + $resource = null, + $envelopeOrMessage = null, + $transportName = null, + $operation = null, + bool $addStampsInformation = false + ) { + if ($envelopeOrMessage instanceof Envelope) { + $this->resolveMetadataFromEnvelope($span, $envelopeOrMessage, $resource, $transportName, $operation, $addStampsInformation); + } else { + $this->tryResolveMetadataFromMessage($span, $envelopeOrMessage, $resource, $transportName, $operation); + } + + $span->name = $name; + $span->service = \ddtrace_config_app_name('symfony'); + $span->type = 'queue'; + $span->meta[Tag::MQ_SYSTEM] = 'symfony'; + $span->meta[Tag::MQ_DESTINATION_KIND] = 'queue'; + $span->meta[Tag::COMPONENT] = SymfonyMessengerIntegration::NAME; + } + + public function resolveMetadataFromEnvelope( + SpanData $span, + Envelope $envelope, + $resource = null, + $transportName = null, + $operation = null, + bool $addStampsInformation = false + ) { + $busStamp = $envelope->last(BusNameStamp::class); + $consumedByWorkerStamp = $envelope->last(ConsumedByWorkerStamp::class); + $delayStamp = $envelope->last(DelayStamp::class); + $handledStamp = $envelope->last(HandledStamp::class); + $receivedStamp = $envelope->last(ReceivedStamp::class); + $redeliveryStamp = $envelope->last(RedeliveryStamp::class); + $sentStamp = $envelope->last(SentStamp::class); + $transportMessageIdStamp = $envelope->last(TransportMessageIdStamp::class); + + $messageName = \get_class($envelope->getMessage()); + $transportName = $sentStamp + ? $sentStamp->getSenderAlias() + : ($receivedStamp ? $receivedStamp->getTransportName() : $transportName); + $senderClass = $sentStamp ? $sentStamp->getSenderClass() : null; + $transportMessageId = $transportMessageIdStamp ? $transportMessageIdStamp->getId() : null; + + // amazon-sqs-messenger doesn't add TransportMessageIdStamp to the envelope + if (\class_exists(AmazonSqsReceivedStamp::class) + && ($amazonSqsReceivedStamp = $envelope->last(AmazonSqsReceivedStamp::class)) + ) { + $transportMessageId = $amazonSqsReceivedStamp->getId(); + } + + $stamps = []; + if ($addStampsInformation) { + foreach ($envelope->all() as $stampFqcn => $instances) { + $stamps[$stampFqcn] = \count($instances); + } + } + + if ($operation !== 'receive' && $operation !== 'send' && ($consumedByWorkerStamp || $receivedStamp)) { + $operation = 'process'; + } + + $metadata = [ + 'messaging.symfony.bus' => $busStamp ? $busStamp->getBusName() : null, + 'messaging.symfony.handler' => $handledStamp ? $handledStamp->getHandlerName() : null, + 'messaging.symfony.message' => $messageName, + 'messaging.symfony.redelivered_at' => $redeliveryStamp ? $redeliveryStamp->getRedeliveredAt()->format('Y-m-d\TH:i:sP') : null, + 'messaging.symfony.sender' => $senderClass, + Tag::MQ_DESTINATION => $transportName, + Tag::MQ_MESSAGE_ID => $transportMessageId, + Tag::MQ_OPERATION => $operation, + Tag::SPAN_KIND => $this->determineSpanKind($operation), + ]; + + $metrics = [ + 'messaging.symfony.delay' => $delayStamp ? $delayStamp->getDelay() : null, + 'messaging.symfony.retry_count' => $redeliveryStamp ? $redeliveryStamp->getRetryCount() : null, + 'messaging.symfony.stamps' => $stamps, + ]; + + if (empty($resource)) { + if (empty($transportName)) { + $resource = $messageName; + } elseif ($operation === 'send') { + $resource = "$messageName -> $transportName"; + } elseif ($operation === 'receive' || $receivedStamp) { + $resource = "$transportName -> $messageName"; + } else { + $resource = "$messageName -> $transportName"; + } + } + $span->resource = $resource; + $span->meta = \array_merge($span->meta, \array_filter($metadata)); + $span->metrics = \array_merge($span->metrics, \array_filter($metrics)); + } + + public function tryResolveMetadataFromMessage(SpanData $span, $message, $resource, $transportName, $operation) { + if ($message) { + $messageName = \get_class($message); + $resource = $resource ?? $messageName; + $span->meta['messaging.symfony.message'] = $messageName; + } + + if ($resource) { + $span->resource = $resource; + } + if ($transportName) { + $span->meta[Tag::MQ_DESTINATION] = $transportName; + } + if ($operation) { + $span->meta[Tag::MQ_OPERATION] = $operation; + } + + $spanKind = $this->determineSpanKind($operation); + if ($spanKind) { + $span->meta[Tag::SPAN_KIND] = $spanKind; + } + } + + public function determineSpanKind($operation) { + switch ($operation) { + case 'receive': + return Tag::SPAN_KIND_VALUE_CONSUMER; + case 'send': + return Tag::SPAN_KIND_VALUE_PRODUCER; + default: + return null; // Internal operation is implicit + } + } +} diff --git a/src/DDTrace/OpenTelemetry/Context.php b/src/DDTrace/OpenTelemetry/Context.php index d5aa42765c..a05f6b1445 100644 --- a/src/DDTrace/OpenTelemetry/Context.php +++ b/src/DDTrace/OpenTelemetry/Context.php @@ -27,6 +27,9 @@ final class Context implements ContextInterface /** @var ContextStorageInterface&ExecutionContextAwareInterface */ private static ContextStorageInterface $storage; + /** @var string $storageClass */ + private static string $storageClass = ''; + // Optimization for spans to avoid copying the context array. private static ContextKeyInterface $spanContextKey; private ?object $span = null; @@ -58,8 +61,13 @@ public static function setStorage(ContextStorageInterface $storage): void */ public static function storage(): ContextStorageInterface { - /** @psalm-suppress RedundantPropertyInitializationCheck */ - return self::$storage ??= new ContextStorage(); + if (self::$storageClass === '') { + self::$storageClass = class_exists('OpenTelemetry\Context\FiberBoundContextStorageExecutionAwareBC') + ? 'OpenTelemetry\Context\FiberBoundContextStorageExecutionAwareBC' // v1.1+ + : 'OpenTelemetry\Context\ContextStorage'; + } + + return self::$storage ??= new self::$storageClass(); } /** @@ -189,6 +197,12 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface $links[] = new SDK\Link($linkSpanContext, Attributes::create($spanLink->attributes ?? [])); } + // Check for span events + $events = []; + foreach ($currentSpan->events as $spanEvent) { + $events[] = new SDK\Event($spanEvent->name, (int)$spanEvent->timestamp, Attributes::create((array)$spanEvent->attributes ?? [])); + } + $OTelCurrentSpan = SDK\Span::startSpan( $currentSpan, API\SpanContext::create($currentTraceId, $currentSpanId, $traceFlags, $traceState), // $context @@ -201,6 +215,7 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface [], // $attributesBuilder $links, // $links count($links), // $totalRecordedLinks + $events, //$events false // The span was created using the DD Api ); ObjectKVStore::put($currentSpan, 'otel_span', $OTelCurrentSpan); diff --git a/src/DDTrace/OpenTelemetry/Span.php b/src/DDTrace/OpenTelemetry/Span.php index e459c8b8a0..c6aa4ae95e 100644 --- a/src/DDTrace/OpenTelemetry/Span.php +++ b/src/DDTrace/OpenTelemetry/Span.php @@ -6,6 +6,8 @@ use DDTrace\SpanData; use DDTrace\SpanLink; +use DDTrace\SpanEvent; +use DDTrace\ExceptionSpanEvent; use DDTrace\Tag; use DDTrace\OpenTelemetry\Convention; use DDTrace\Util\ObjectKVStore; @@ -46,6 +48,16 @@ final class Span extends API\Span implements ReadWriteSpanInterface /** @readonly */ private int $totalRecordedLinks; + /** + * @readonly + * + * @var list + */ + private array $events; + + /** @readonly */ + private int $totalRecordedEvents; + /** @readonly */ private int $kind; @@ -69,6 +81,7 @@ private function __construct( ResourceInfo $resource, array $links = [], int $totalRecordedLinks = 0, + array $events = [], bool $isRemapped = true ) { $this->span = $span; @@ -80,6 +93,7 @@ private function __construct( $this->resource = $resource; $this->links = $links; $this->totalRecordedLinks = $totalRecordedLinks; + $this->events = $events; $this->status = StatusData::unset(); @@ -91,24 +105,28 @@ private function __construct( $span->name = $this->operationNameConvention = Convention::defaultOperationName($span); } - // Set the span links + // Set the span links and events if ($isRemapped) { // At initialization time (now), only set the links if the span was created using the OTel API // Otherwise, the links were already set in DD's OpenTelemetry\Context\Context foreach ($links as $link) { /** @var LinkInterface $link */ $linkContext = $link->getSpanContext(); + $span->links[] = $this->createAndSaveSpanLink($linkContext, $link->getAttributes()->toArray(), $link); + } - $spanLink = new SpanLink(); - $spanLink->traceId = $linkContext->getTraceId(); - $spanLink->spanId = $linkContext->getSpanId(); - $spanLink->traceState = (string)$linkContext->getTraceState(); // __toString() - $spanLink->attributes = $link->getAttributes()->toArray(); - $spanLink->droppedAttributesCount = 0; // Attributes limit aren't supported/meaningful in DD + foreach ($events as $event) { + /** @var EventInterface $event */ - // Save the link - ObjectKVStore::put($spanLink, "link", $link); - $span->links[] = $spanLink; + $spanEvent = new SpanEvent( + $event->getName(), + $event->getAttributes()->toArray(), + $event->getEpochNanos() + ); + + // Save the event + ObjectKVStore::put($spanEvent, "event", $event); + $span->events[] = $spanEvent; } } } @@ -136,6 +154,7 @@ public static function startSpan( array $attributes, array $links, int $totalRecordedLinks, + array $events, bool $isRemapped = true // Answers the question "Was the span created using the OTel API?" ): self { self::_setAttributes($span, $attributes); @@ -156,6 +175,7 @@ public static function startSpan( $resource, $links, $totalRecordedLinks, + $events, $isRemapped ); @@ -203,18 +223,35 @@ public function toSpanData(): SpanDataInterface $hasEnded = $this->hasEnded(); $this->updateSpanLinks(); - - return new ImmutableSpan( - $this, - $this->getName(), - $this->links, - [], // TODO: Handle Span Events - Attributes::create(array_merge($this->span->meta, $this->span->metrics)), - 0, - StatusData::create($this->status->getCode(), $this->status->getDescription()), - $hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0, - $this->hasEnded() - ); + $this->updateSpanEvents(); + + if (method_exists(SpanInterface::class, 'addLink')) { + // v1.1 backward compatibility: totalRecordedLinks parameter added + return new ImmutableSpan( + $this, + $this->getName(), + $this->links, + $this->events, + Attributes::create(array_merge($this->span->meta, $this->span->metrics)), + $this->totalRecordedEvents, + $this->totalRecordedLinks, + StatusData::create($this->status->getCode(), $this->status->getDescription()), + $hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0, + $this->hasEnded(), + ); + } else { + return new ImmutableSpan( + $this, + $this->getName(), + $this->links, + $this->events, + Attributes::create(array_merge($this->span->meta, $this->span->metrics)), + $this->totalRecordedEvents, + StatusData::create($this->status->getCode(), $this->status->getDescription()), + $hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0, + $this->hasEnded(), + ); + } } /** @@ -253,7 +290,7 @@ public function getTotalRecordedLinks(): int public function getTotalRecordedEvents(): int { - return 0; + return $this->totalRecordedEvents; } /** @@ -327,12 +364,32 @@ public function setAttributes(iterable $attributes): SpanInterface return $this; } + /** + * @inheritDoc + */ + public function addLink(SpanContextInterface $context, iterable $attributes = []): SpanInterface + { + if ($this->hasEnded() || !$context->isValid()) { + return $this; + } + + $this->span->links[] = $this->createAndSaveSpanLink($context, $attributes); + return $this; + } + /** * @inheritDoc */ public function addEvent(string $name, iterable $attributes = [], int $timestamp = null): SpanInterface { - // no-op + if (!$this->hasEnded()) { + $this->span->events[] = new SpanEvent( + $name, + $attributes, + $timestamp ?? (int)(microtime(true) * 1e9) + ); + } + return $this; } @@ -342,9 +399,13 @@ public function addEvent(string $name, iterable $attributes = [], int $timestamp public function recordException(Throwable $exception, iterable $attributes = []): SpanInterface { if (!$this->hasEnded()) { - $this->span->meta[Tag::ERROR_MSG] = $exception->getMessage(); - $this->span->meta[Tag::ERROR_TYPE] = get_class($exception); - $this->span->meta[Tag::ERROR_STACK] = $exception->getTraceAsString(); + // Update span metadata based on exception stack + $this->setAttribute(Tag::ERROR_STACK, \DDTrace\get_sanitized_exception_trace($exception)); + + $this->span->events[] = new ExceptionSpanEvent( + $exception, + $attributes + ); } return $this; @@ -475,4 +536,57 @@ private function updateSpanLinks() $this->links = $otel; $this->totalRecordedLinks = count($otel); } + + private function updateSpanEvents() + { + $datadogSpanEvents = $this->span->events; + $this->span->meta["events"] = count($this->events); + + $otel = []; + foreach ($datadogSpanEvents as $datadogSpanEvent) { + $exceptionAttributes = []; + $event = ObjectKVStore::get($datadogSpanEvent, "event"); + if ($event === null) { + if ($datadogSpanEvent instanceof ExceptionSpanEvent) { + // Standardized exception attributes + $exceptionAttributes = [ + 'exception.message' => $attributes['exception.message'] ?? $datadogSpanEvent->exception->getMessage(), + 'exception.type' => $attributes['exception.type'] ?? get_class($datadogSpanEvent->exception), + 'exception.stacktrace' => $attributes['exception.stacktrace'] ?? \DDTrace\get_sanitized_exception_trace($datadogSpanEvent->exception) + ]; + } + $event = new Event( + $datadogSpanEvent->name, + (int)$datadogSpanEvent->timestamp, + Attributes::create(array_merge($exceptionAttributes, \is_array($datadogSpanEvent->attributes) ? $datadogSpanEvent->attributes : iterator_to_array($datadogSpanEvent->attributes))) + ); + + // Save the event + ObjectKVStore::put($datadogSpanEvent, "event", $event); + } + $otel[] = $event; + } + + // Update the events + $this->events = $otel; + $this->totalRecordedEvents = count($otel); + } + + private function createAndSaveSpanLink(SpanContextInterface $context, iterable $attributes = [], LinkInterface $link = null) + { + $spanLink = new SpanLink(); + $spanLink->traceId = $context->getTraceId(); + $spanLink->spanId = $context->getSpanId(); + $spanLink->traceState = (string)$context->getTraceState(); // __toString() + $spanLink->attributes = $attributes; + $spanLink->droppedAttributesCount = 0; // Attributes limit aren't supported/meaningful in DD + + // Save the link + if (is_null($link)) { + $link = new Link($context, Attributes::create($attributes)); + } + ObjectKVStore::put($spanLink, "link", $link); + + return $spanLink; + } } diff --git a/src/DDTrace/OpenTelemetry/SpanBuilder.php b/src/DDTrace/OpenTelemetry/SpanBuilder.php index 29f1a92438..c6937ab36d 100644 --- a/src/DDTrace/OpenTelemetry/SpanBuilder.php +++ b/src/DDTrace/OpenTelemetry/SpanBuilder.php @@ -17,6 +17,7 @@ use OpenTelemetry\SDK\Common\Attribute\AttributesFactory; use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface; use OpenTelemetry\SDK\Resource\ResourceInfoFactory; +use Throwable; final class SpanBuilder implements API\SpanBuilderInterface { @@ -43,6 +44,9 @@ final class SpanBuilder implements API\SpanBuilderInterface /** @var list */ private array $links = []; + /** @var list */ + private array $events = []; + /** @var array */ private array $attributes; @@ -92,6 +96,42 @@ public function addLink(SpanContextInterface $context, iterable $attributes = [] return $this; } + public function addEvent(string $name, iterable $attributes = [], int $timestamp = null): SpanBuilderInterface + { + $this->events[] = new Event( + $name, + $timestamp ?? (int)(microtime(true) * 1e9), + $this->tracerSharedState + ->getSpanLimits() + ->getEventAttributesFactory() + ->builder($attributes) + ->build(), + ); + + return $this; + } + + public function recordException(Throwable $exception, iterable $attributes = []): SpanBuilderInterface + { + // Standardized exception attributes + $exceptionAttributes = [ + 'exception.message' => $attributes['exception.message'] ?? $exception->getMessage(), + 'exception.type' => $attributes['exception.type'] ?? get_class($exception), + 'exception.stacktrace' => $attributes['exception.stacktrace'] ?? \DDTrace\get_sanitized_exception_trace($exception), + ]; + + // Update span metadata based on exception stack + $this->setAttribute(Tag::ERROR_STACK, $exceptionAttributes['exception.stacktrace']); + + // Merge additional attributes + $allAttributes = array_merge($exceptionAttributes, \is_array($attributes) ? $attributes : iterator_to_array($attributes)); + + // Record the exception event + $this->addEvent('exception', $allAttributes); + + return $this; + } + /** @inheritDoc */ public function setAttribute(string $key, $value): API\SpanBuilderInterface { @@ -156,6 +196,7 @@ public function startSpan(): SpanInterface $this->spanKind, Attributes::create($this->attributes), $this->links, + $this->events ); $span = $span ?? \DDTrace\start_trace_span($this->startEpochNanos); @@ -204,6 +245,7 @@ public function startSpan(): SpanInterface $this->attributes, $this->links, $this->totalNumberOfLinksAdded, + $this->events ); } diff --git a/tests/Appsec/Mock.php b/tests/Appsec/Mock.php index cc3002bb34..5333dab7ee 100644 --- a/tests/Appsec/Mock.php +++ b/tests/Appsec/Mock.php @@ -6,6 +6,7 @@ class AppsecStatus { private static $instance = null; + private $connection; protected function __construct() { } @@ -21,7 +22,12 @@ public static function getInstance() protected function getDbPdo() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + if (!isset($this->connection)) { + $pdo = new \PDO('mysql:host=mysql_integration', 'test', 'test'); + $pdo->exec("CREATE DATABASE IF NOT EXISTS test"); + $this->connection = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + } + return $this->connection; } /** @@ -36,21 +42,15 @@ private function initiated() public function init() { - $this->getDbPdo()->exec("CREATE TABLE IF NOT EXISTS appsec_events (event varchar(1000))"); - } - - public function destroy() - { - $this->getDbPdo()->exec("DROP TABLE appsec_events"); + $this->getDbPdo()->exec("CREATE TABLE IF NOT EXISTS appsec_events (event varchar(1000), token varchar(100))"); } - public function setDefaults() { if (!$this->initiated()) { return; } - $this->getDbPdo()->exec("DELETE FROM appsec_events"); + $this->getDbPdo()->exec("DELETE FROM appsec_events WHERE token = '" . ini_get("datadog.trace.agent_test_session_token") . "'"); } public function addEvent(array $event, $eventName) @@ -60,7 +60,7 @@ public function addEvent(array $event, $eventName) } $event['eventName'] = $eventName; - $this->getDbPdo()->exec(sprintf("INSERT INTO appsec_events VALUES ('%s')", json_encode($event))); + $this->getDbPdo()->exec(sprintf("INSERT INTO appsec_events VALUES ('%s', '%s')", json_encode($event), ini_get("datadog.trace.agent_test_session_token"))); } public function getEvents(array $names = [], array $addresses = []) @@ -71,7 +71,7 @@ public function getEvents(array $names = [], array $addresses = []) return $result; } - $events = $this->getDbPdo()->query("SELECT * FROM appsec_events")->fetchAll(); + $events = $this->getDbPdo()->query("SELECT * FROM appsec_events WHERE token = '" . ini_get("datadog.trace.agent_test_session_token") . "'")->fetchAll(); foreach ($events as $event) { $new = json_decode($event['event'], true); @@ -158,4 +158,4 @@ function push_address($key, $value) { } AppsecStatus::getInstance()->addEvent([$key => $value], 'push_address'); } -} \ No newline at end of file +} diff --git a/tests/Benchmarks/Integrations/EmptyFileBench.php b/tests/Benchmarks/Integrations/EmptyFileBench.php index ba58b35cf3..1de5efd968 100644 --- a/tests/Benchmarks/Integrations/EmptyFileBench.php +++ b/tests/Benchmarks/Integrations/EmptyFileBench.php @@ -6,11 +6,12 @@ use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; +use Benchmarks\Integrations\FrameworkBenchmarksCase; -class EmptyFileBench extends WebFrameworkTestCase +class EmptyFileBench extends FrameworkBenchmarksCase { /** - * @BeforeMethods("disabledTracing") + * @BeforeMethods("disableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -27,7 +28,7 @@ public function benchEmptyFileBaseline() } /** - * @BeforeMethods("enableTracing") + * @BeforeMethods("enableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -48,22 +49,8 @@ public static function getAppIndexScript() return __DIR__ . '/../../Frameworks/Custom/Version_Not_Autoloaded/index.php'; } - public function disabledTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 0, - ]); - } - public function afterMethod() { $this->TearDownAfterClass(); } - - public function enableTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 1, - ]); - } } diff --git a/tests/Benchmarks/Integrations/FrameworkBenchmarksCase.php b/tests/Benchmarks/Integrations/FrameworkBenchmarksCase.php new file mode 100644 index 0000000000..c3b0a9bd42 --- /dev/null +++ b/tests/Benchmarks/Integrations/FrameworkBenchmarksCase.php @@ -0,0 +1,36 @@ +getClassName(); + $this->setUpWebServer([ + 'DD_TRACE_ENABLED' => 0, + 'DD_APPSEC_ENABLED' => 0, + ], ['error_log' => "/tmp/logs/$name.log"]); + } + + public function enableDatadog() + { + $name = $this->getClassName(); + $this->setUpWebServer([ + 'DD_TRACE_ENABLED' => 1, + 'DD_APPSEC_ENABLED' => 1, + ], ['error_log' => "/tmp/logs/$name.log"]); + } +} diff --git a/tests/Benchmarks/Integrations/LaravelBench.php b/tests/Benchmarks/Integrations/LaravelBench.php index d411fa2181..eaf6e6bb2d 100644 --- a/tests/Benchmarks/Integrations/LaravelBench.php +++ b/tests/Benchmarks/Integrations/LaravelBench.php @@ -7,10 +7,13 @@ use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; -class LaravelBench extends WebFrameworkTestCase +/** +* @Groups({"frameworks"}) +*/ +class LaravelBench extends FrameworkBenchmarksCase { /** - * @BeforeMethods("disableLaravelTracing") + * @BeforeMethods("disableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -27,7 +30,7 @@ public function benchLaravelBaseline() } /** - * @BeforeMethods("enableLaravelTracing") + * @BeforeMethods("enableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -48,22 +51,8 @@ public static function getAppIndexScript() return __DIR__ . '/../../Frameworks/Laravel/Version_10_x/public/index.php'; } - public function disableLaravelTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 0, - ]); - } - public function afterMethod() { $this->TearDownAfterClass(); } - - public function enableLaravelTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 1, - ]); - } } diff --git a/tests/Benchmarks/Integrations/SymfonyBench.php b/tests/Benchmarks/Integrations/SymfonyBench.php index 634224cf55..b43deff579 100644 --- a/tests/Benchmarks/Integrations/SymfonyBench.php +++ b/tests/Benchmarks/Integrations/SymfonyBench.php @@ -7,10 +7,13 @@ use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; -class SymfonyBench extends WebFrameworkTestCase +/** +* @Groups({"frameworks"}) +*/ +class SymfonyBench extends FrameworkBenchmarksCase { /** - * @BeforeMethods("disableSymfonyTracing") + * @BeforeMethods("disableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -27,7 +30,7 @@ public function benchSymfonyBaseline() } /** - * @BeforeMethods("enableSymfonyTracing") + * @BeforeMethods("enableDatadog") * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -48,22 +51,8 @@ public static function getAppIndexScript() return __DIR__ . '/../../Frameworks/Symfony/Version_5_2/public/index.php'; } - public function disableSymfonyTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 0, - ]); - } - public function afterMethod() { $this->TearDownAfterClass(); } - - public function enableSymfonyTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 1, - ]); - } } diff --git a/tests/Benchmarks/Integrations/WordPressBench.php b/tests/Benchmarks/Integrations/WordPressBench.php index d33838021c..41b88d862c 100644 --- a/tests/Benchmarks/Integrations/WordPressBench.php +++ b/tests/Benchmarks/Integrations/WordPressBench.php @@ -7,10 +7,13 @@ use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; -class WordPressBench extends WebFrameworkTestCase +/** +* @Groups({"frameworks"}) +*/ +class WordPressBench extends FrameworkBenchmarksCase { /** - * @BeforeMethods("enableWordPressTracing") + * @BeforeMethods({"enableDatadog", "createDatabase"}) * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) @@ -31,13 +34,11 @@ public static function getAppIndexScript() return __DIR__ . '/../../Frameworks/WordPress/Version_6_1/index.php'; } - public function disableWordPressTracing() + public function createDatabase(): void { - $pdo = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + $pdo = new \PDO('mysql:host=mysql_integration', 'test', 'test'); + $pdo->exec('CREATE DATABASE IF NOT EXISTS wp61'); $pdo->exec(file_get_contents(__DIR__ . '/../../Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql')); - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 0, - ]); } public function afterMethod() @@ -45,15 +46,8 @@ public function afterMethod() $this->TearDownAfterClass(); } - public function enableWordPressTracing() - { - $this->setUpWebServer([ - 'DD_TRACE_ENABLED' => 1 - ]); - } - /** - * @BeforeMethods("disableWordPressTracing") + * @BeforeMethods({"disableDatadog", "createDatabase"}) * @AfterMethods("afterMethod") * @Revs(10) * @Iterations(10) diff --git a/tests/Benchmarks/composer.json b/tests/Benchmarks/composer.json new file mode 100644 index 0000000000..94a0188cc8 --- /dev/null +++ b/tests/Benchmarks/composer.json @@ -0,0 +1,14 @@ +{ + "require": { + "monolog/monolog": "~2.0", + "open-telemetry/sdk": "@stable", + "phpbench/phpbench": "^1.0", + "phpunit/phpunit": "<10", + "symfony/process": "<5" + }, + "autoload-dev": { + "psr-4": { + "Benchmarks\\": "./" + } + } +} diff --git a/tests/Common/AppsecTestCase.php b/tests/Common/AppsecTestCase.php index fb9074c5dc..8edb6521cc 100644 --- a/tests/Common/AppsecTestCase.php +++ b/tests/Common/AppsecTestCase.php @@ -10,6 +10,8 @@ */ abstract class AppsecTestCase extends WebFrameworkTestCase { + private static $connection; + protected static function getEnvs() { return array_merge(parent::getEnvs(), [ @@ -19,7 +21,10 @@ protected static function getEnvs() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + if (!isset(self::$connection)) { + self::$connection = new \PDO('mysql:host=mysql_integration;dbname=' . static::$database, 'test', 'test'); + } + return self::$connection; } protected function databaseDump() @@ -45,7 +50,7 @@ public static function ddSetUpBeforeClass() public static function ddTearDownAfterClass() { - AppsecStatus::getInstance()->destroy(); + AppsecStatus::getInstance()->setDefaults(); parent::ddTearDownAfterClass(); } diff --git a/tests/Common/BaseTestCase.php b/tests/Common/BaseTestCase.php index c2aac693ed..d6b6d29ad9 100644 --- a/tests/Common/BaseTestCase.php +++ b/tests/Common/BaseTestCase.php @@ -18,12 +18,20 @@ */ abstract class BaseTestCase extends MultiPHPUnitVersionAdapter { + public static $activeResourceLock; + public static function ddSetUpBeforeClass() { + if (isset(static::$lockedResource)) { + $lock = fopen("/tmp/ddtrace-phpunit/lock-" . static::$lockedResource, "c+"); + flock($lock, LOCK_EX); + self::$activeResourceLock = $lock; + } } public static function ddTearDownAfterClass() { + self::$activeResourceLock = null; } protected function ddSetUp() @@ -102,7 +110,7 @@ public static function putEnvAndReloadConfig($putenvs = []) protected function assertStringContains($needle, $haystack, $message = '') { - if (PHPUNIT_MAJOR >= 9) { + if (PHPUNIT_MAJOR >= 8) { parent::assertStringContainsString($needle, $haystack, $message); } else { parent::assertContains($needle, $haystack, $message); @@ -111,7 +119,7 @@ protected function assertStringContains($needle, $haystack, $message = '') protected function assertStringNotContains($needle, $haystack, $message = '') { - if (PHPUNIT_MAJOR >= 9) { + if (PHPUNIT_MAJOR >= 8) { parent::assertStringNotContainsString($needle, $haystack, $message); } else { parent::assertNotContains($needle, $haystack, $message); diff --git a/tests/Common/CLITestCase.php b/tests/Common/CLITestCase.php index 59ca71cd13..646e16ea92 100644 --- a/tests/Common/CLITestCase.php +++ b/tests/Common/CLITestCase.php @@ -25,15 +25,16 @@ abstract protected function getScriptLocation(); protected static function getEnvs() { $envs = [ - 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_AGENT_HOST' => 'test-agent', 'DD_TRACE_AGENT_PORT' => '9126', // Uncomment to see debug-level messages 'DD_TRACE_DEBUG' => 'true', 'DD_TEST_INTEGRATION' => 'true', + 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'false', 'DD_TRACE_EXEC_ENABLED' => 'false', 'DD_TRACE_SHUTDOWN_TIMEOUT' => '666666', // Arbitrarily high value to avoid flakiness - 'DD_TRACE_AGENT_RETRIES' => '3' + 'DD_TRACE_AGENT_RETRIES' => '3', + 'DD_TRACE_AGENT_TEST_SESSION_TOKEN' => ini_get("datadog.trace.agent_test_session_token"), ]; return $envs; } diff --git a/tests/Common/IntegrationTestCase.php b/tests/Common/IntegrationTestCase.php index c89c94a164..4131606733 100644 --- a/tests/Common/IntegrationTestCase.php +++ b/tests/Common/IntegrationTestCase.php @@ -14,6 +14,10 @@ abstract class IntegrationTestCase extends BaseTestCase use SpanAssertionTrait; private $errorReportingBefore; + public static $autoloadPath = null; + + public static $database = "test"; + private static $createdDatabases = ["test" => true]; public static function ddSetUpBeforeClass() { @@ -38,14 +42,21 @@ public static function ddSetUpBeforeClass() file_put_contents($artifactsDir . "/extension_versions.csv", $csv, FILE_APPEND); $csv = ''; - $output = shell_exec('DD_TRACE_ENABLED=0 composer --working-dir=./tests show -f json'); - $data = json_decode($output, true); - foreach ($data['installed'] as $package) { - $csv = $csv . $package['name'] . ";" . $package['version'] . "\n"; + if (self::$autoloadPath && file_exists(dirname(self::$autoloadPath). "/composer/installed.json")) { + $data = json_decode(file_get_contents(dirname(self::$autoloadPath). "/composer/installed.json"), true); + foreach ($data['packages'] as $package) { + $csv = $csv . $package['name'] . ";" . $package['version'] . "\n"; + } } file_put_contents($artifactsDir . "/composer_versions.csv", $csv, FILE_APPEND); + + if (isset(static::$database) && !isset(self::$createdDatabases[static::$database])) { + $pdo = new \PDO('mysql:host=mysql_integration', 'test', 'test'); + $pdo->exec("CREATE DATABASE IF NOT EXISTS " . static::$database); + self::$createdDatabases[static::$database] = true; + } } public static function ddTearDownAfterClass() @@ -57,6 +68,7 @@ public static function ddTearDownAfterClass() protected function ddSetUp() { $this->errorReportingBefore = error_reporting(); + $this->resetTracer(); // Needs reset so we can remove root span $this->putEnv("DD_TRACE_GENERATE_ROOT_SPAN=0"); parent::ddSetUp(); } diff --git a/tests/Common/SnapshotTestTrait.php b/tests/Common/SnapshotTestTrait.php index 7c7534a469..49a5de1be1 100644 --- a/tests/Common/SnapshotTestTrait.php +++ b/tests/Common/SnapshotTestTrait.php @@ -10,10 +10,6 @@ trait SnapshotTestTrait { protected static $testAgentUrl = 'http://test-agent:9126'; - protected static $dogstatsdAddr = '127.0.0.1'; - protected static $dogstatsdPort = 9876; - /** @var UDPServer */ - protected $server; protected $logFileSize = 0; private function decamelize($string): string @@ -55,11 +51,6 @@ private function generateToken(): string return $class . '.' . $function; } - private function startMetricsSnapshotSession() - { - $this->server = new UDPServer(self::$dogstatsdAddr, self::$dogstatsdPort); - } - /** * Start a snapshotting session associated with a given token. * @@ -68,9 +59,8 @@ private function startMetricsSnapshotSession() * @param string $token The token to associate with the snapshotting session * @return void */ - private function startSnapshotSession(string $token, $snapshotMetrics = false, $logsFile = null) + private function startSnapshotSession(string $token, $logsFile = null) { - $url = self::$testAgentUrl . '/test/session/start?test_session_token=' . $token; $ch = curl_init($url); @@ -81,10 +71,6 @@ private function startSnapshotSession(string $token, $snapshotMetrics = false, $ TestCase::fail('Error starting snapshot session: ' . $response); } - if ($snapshotMetrics) { - $this->startMetricsSnapshotSession(); - } - if ($logsFile) { if (file_exists($logsFile)) { $this->logFileSize = (int)filesize($logsFile); @@ -137,7 +123,7 @@ private function waitForTraces(string $token, int $numExpectedTraces = 0) */ private function stopAndCompareSnapshotSession( string $token, - array $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'meta.error.stack', 'meta._dd.p.tid'], + array $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'metrics.php.memory.peak_usage_bytes', 'metrics.php.memory.peak_real_usage_bytes', 'meta.error.stack', 'meta._dd.p.tid'], int $numExpectedTraces = 1, bool $snapshotMetrics = false, array $fieldsToIgnoreMetrics = ['openai.request.duration'], @@ -214,8 +200,10 @@ private function stopAndCompareMetricsSnapshotSession( string $token, array $fieldsToIgnore = ['openai.request.duration'] ) { - $receivedMetrics = $this->server->dump(); - $this->server->close(); + $receivedMetrics = $this->retrieveDumpedMetrics(function($metrics) { + return $metrics["name"] == "tracer-snapshot-end"; + }); + array_pop($receivedMetrics); $basePath = implode('/', array_slice(explode('/', getcwd()), 0, 4)); // /home/circleci/[app|datadog] $expectedMetricsFile = $basePath . '/tests/snapshots/metrics/' . $token . '.txt'; @@ -230,10 +218,7 @@ private function stopAndCompareMetricsSnapshotSession( private function compareMetrics($expectedMetrics, $receivedMetrics, $fieldsToIgnore) { $expectedMetrics = explode("\n", $expectedMetrics); - $receivedMetrics = explode("\n", $receivedMetrics); - $expectedMetrics = $this->decodeDogStatsDMetrics($expectedMetrics); - $receivedMetrics = $this->decodeDogStatsDMetrics($receivedMetrics); $this->compareMetricsArrays($expectedMetrics, $receivedMetrics, $fieldsToIgnore); } @@ -282,6 +267,12 @@ private function compareMetricsArrays($expectedMetrics, $receivedMetrics, $field $expectedMetrics = $this->filterMetrics($expectedMetrics, $fieldsToIgnore); $receivedMetrics = $this->filterMetrics($receivedMetrics, $fieldsToIgnore); + $alg = function ($a, $b) { + return strcmp($a['name'], $b['name']); + }; + usort($expectedMetrics, $alg); + usort($receivedMetrics, $alg); + TestCase::assertEquals($expectedMetrics, $receivedMetrics, "Metrics don't match"); } @@ -299,7 +290,7 @@ private function filterMetrics($metrics, $fieldsToIgnore) public function tracesFromWebRequestSnapshot( $fn, - $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'meta.error.stack', 'meta._dd.p.tid', 'start', 'duration'], + $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'metrics.php.memory.peak_usage_bytes', 'metrics.php.memory.peak_real_usage_bytes', 'meta.error.stack', 'meta._dd.p.tid', 'start', 'duration'], $numExpectedTraces = 1, $tracer = null ) { @@ -312,9 +303,12 @@ public function tracesFromWebRequestSnapshot( $token = $this->generateToken(); $this->startSnapshotSession($token); + $originalToken = ini_get("datadog.trace.agent_test_session_token"); + update_test_agent_session_token($token); $fn($tracer); + update_test_agent_session_token($originalToken); self::putEnv('DD_TRACE_SHUTDOWN_TIMEOUT'); self::putEnv('DD_TRACE_AGENT_RETRIES'); @@ -323,7 +317,7 @@ public function tracesFromWebRequestSnapshot( public function isolateTracerSnapshot( $fn, - $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'meta.error.stack', 'meta._dd.p.tid'], + $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'metrics.php.memory.peak_usage_bytes', 'metrics.php.memory.peak_real_usage_bytes', 'meta.error.stack', 'meta._dd.p.tid'], $numExpectedTraces = 1, $tracer = null, $config = [], @@ -336,8 +330,11 @@ public function isolateTracerSnapshot( self::putEnv('DD_TRACE_AGENT_RETRIES=3'); $token = $this->generateToken(); - $this->startSnapshotSession($token, $snapshotMetrics, $logsFile); + $this->startSnapshotSession($token, $logsFile); + $originalToken = ini_get("datadog.trace.agent_test_session_token"); + update_test_agent_session_token($token); + $this->resetRequestDumper(); $this->resetTracer($tracer, $config); $tracer = GlobalTracer::get(); @@ -346,8 +343,10 @@ public function isolateTracerSnapshot( } $fn($tracer); - self::putEnv('DD_TRACE_SHUTDOWN_TIMEOUT'); - self::putEnv('DD_TRACE_AGENT_RETRIES'); + if ($snapshotMetrics) { + usleep(50000); // Add a slight delay to avoid a race condition where the "tracer-snapshot-end" metric is handled before a test metric. + \DDTrace\dogstatsd_count("tracer-snapshot-end", 1); + } $traces = $this->flushAndGetTraces($tracer); if (!empty($traces)) { @@ -363,5 +362,52 @@ public function isolateTracerSnapshot( $logsFile, $fieldsToIgnoreLogs ); + + update_test_agent_session_token($originalToken); + self::putEnv('DD_TRACE_SHUTDOWN_TIMEOUT'); + self::putEnv('DD_TRACE_AGENT_RETRIES'); + } + + public function snapshotFromTraces( + $traces, + $fieldsToIgnore = ['metrics.php.compilation.total_time_ms', 'metrics.php.memory.peak_usage_bytes', 'metrics.php.memory.peak_real_usage_bytes', 'meta.error.stack', 'meta._dd.p.tid'], + $tokenSubstitute = null, + $ignoreSampledAway = false + ) { + $token = $tokenSubstitute ?: $this->generateToken(); + $this->startSnapshotSession($token); + $originalToken = ini_get("datadog.trace.agent_test_session_token"); + update_test_agent_session_token($token); + + if ($ignoreSampledAway) { + $traces = $this->ignoreSampledTraces($traces); + } + + $this->sendTracesToTestAgent($traces); + + $this->stopAndCompareSnapshotSession($token, $fieldsToIgnore, \count($traces)); + update_test_agent_session_token($originalToken); + } + + protected function ignoreSampledTraces($traces) { + $filteredSpans = []; + $sampledTraceIDs = []; + foreach ($traces as $trace) { + foreach ($trace as $span) { + if (isset($span['metrics']['_sampling_priority_v1']) && $span['metrics']['_sampling_priority_v1'] === 0) { + $sampledTraceIDs[$span['trace_id']] = true; + } + } + } + + foreach ($traces as $trace) { + foreach ($trace as $span) { + if (!isset($sampledTraceIDs[$span['trace_id']])) { + $filteredSpans[] = $span; + } + } + } + + return [$filteredSpans]; } } diff --git a/tests/Common/SpanChecker.php b/tests/Common/SpanChecker.php index 39912ace58..292d1caa8a 100644 --- a/tests/Common/SpanChecker.php +++ b/tests/Common/SpanChecker.php @@ -21,8 +21,6 @@ function array_filter_by_key($fn, array $input) */ final class SpanChecker { - const DEFAULTS_ATTRIBUTES = ['_dd.p.tid']; - /** * Asserts a flame graph with parent child relations. * @@ -30,14 +28,8 @@ final class SpanChecker * @param SpanAssertion[] $expectedFlameGraph * @param bool $assertExactCount */ - public function assertFlameGraph(array $traces, array $expectedFlameGraph, bool $assertExactCount = true, bool $applyDefaults = true) + public function assertFlameGraph(array $traces, array $expectedFlameGraph, bool $assertExactCount = true) { - if ($applyDefaults) { - foreach ($expectedFlameGraph as $spanAssertion) { - $spanAssertion->withExistingTagsNames(self::DEFAULTS_ATTRIBUTES); - } - } - $flattenTraces = $this->flattenTraces($traces); $actualGraph = $this->buildSpansGraph($flattenTraces); if ($assertExactCount && \count($actualGraph) != \count($expectedFlameGraph)) { @@ -87,6 +79,7 @@ public static function dumpSpansGraph(array $spansGraph, int $indent = 0) $out .= "\n"; if (isset($span['meta'])) { unset($span['meta']['_dd.p.dm']); + unset($span['meta']['_dd.p.tid']); unset($span['meta']['http.client_ip']); foreach ($span['meta'] as $k => $v) { $out .= str_repeat(' ', $indent) . ' ' . $k . ' => ' . $v . "\n"; @@ -94,6 +87,8 @@ public static function dumpSpansGraph(array $spansGraph, int $indent = 0) } if (isset($span['metrics'])) { unset($span['metrics']['php.compilation.total_time_ms']); + unset($span['metrics']['php.memory.peak_usage_bytes']); + unset($span['metrics']['php.memory.peak_real_usage_bytes']); unset($span['metrics']['process_id']); foreach ($span['metrics'] as $k => $v) { $out .= str_repeat(' ', $indent) . ' ' . $k . ' => ' . $v . "\n"; @@ -200,7 +195,7 @@ private function findOne(array $graph, SpanAssertion $expectedNodeRoot, $parentN // Not using a TestCase::markTestAsIncomplete() because it exits immediately, // while with an error log we are still able to proceed with tests. error_log(sprintf( - "WARNING: More then one candidate found for '%s' at the same level. " + "WARNING: More than one candidate found for '%s' at the same level. " . "Proceeding in the order they appears. " . "This might not work if this span is not a leaf span.", $expectedNodeRoot @@ -337,16 +332,9 @@ private function buildSpansGraph(array $flatSpans) * * @param $traces * @param SpanAssertion[] $expectedSpans - * @param bool $applyDefaults */ - public function assertSpans($traces, $expectedSpans, $applyDefaults = true) + public function assertSpans($traces, $expectedSpans) { - if ($applyDefaults) { - foreach ($expectedSpans as $spanAssertion) { - $spanAssertion->withExistingTagsNames(self::DEFAULTS_ATTRIBUTES); - } - } - $flattenTraces = $this->flattenTraces($traces); // The sandbox API pops closed spans off a stack so spans will be in reverse order $flattenTraces = array_reverse($flattenTraces); @@ -482,6 +470,10 @@ function ($key) use ($pattern) { if (!isset($expectedTags['_dd.p.dm'])) { unset($filtered['_dd.p.dm']); } + // Ignore _dd.p.tid unless explicitly tested + if (!isset($expectedTags['_dd.p.tid'])) { + unset($filtered['_dd.p.tid']); + } // Ignore runtime-id unless explicitly tested if (!isset($expectedTags['runtime-id'])) { unset($filtered['runtime-id']); @@ -534,6 +526,12 @@ function ($key) use ($pattern) { if (!isset($metrics['php.compilation.total_time_ms'])) { unset($spanMetrics['php.compilation.total_time_ms']); } + if (!isset($metrics['php.memory.peak_usage_bytes'])) { + unset($spanMetrics['php.memory.peak_usage_bytes']); + } + if (!isset($metrics['php.memory.peak_real_usage_bytes'])) { + unset($spanMetrics['php.memory.peak_real_usage_bytes']); + } if (isset($metrics['process_id'])) { unset($metrics['process_id']); } diff --git a/tests/Common/TracerTestTrait.php b/tests/Common/TracerTestTrait.php index 746d26c489..e10f91cf96 100644 --- a/tests/Common/TracerTestTrait.php +++ b/tests/Common/TracerTestTrait.php @@ -16,6 +16,8 @@ trait TracerTestTrait protected static $agentRequestDumperUrl = 'http://request-replayer'; protected static $testAgentUrl = 'http://test-agent:9126'; + protected static $webserverPort = 6666 + GLOBAL_PORT_OFFSET; + public function resetTracer($tracer = null, $config = []) { // Reset the current C-level array of generated spans @@ -36,7 +38,10 @@ public static function setResponse($content) "http" => [ "method" => "POST", "content" => json_encode($content), - "header" => "Content-Type: application/json" + "header" => [ + "Content-Type: application/json", + 'X-Datadog-Test-Session-Token: ' . ini_get("datadog.trace.agent_test_session_token"), + ], ] ])); } @@ -80,7 +85,8 @@ public function sendTracesToTestAgent($traces) 'Content-Type: application/json', 'Datadog-Meta-Lang: php', 'X-Datadog-Agent-Proxy-Disabled: true', - 'X-Datadog-Trace-Count: ' . count($traces) + 'X-Datadog-Trace-Count: ' . count($traces), + 'X-Datadog-Test-Session-Token: ' . ini_get("datadog.trace.agent_test_session_token"), ); // add environment variables to headers @@ -106,7 +112,7 @@ public function sendTracesToTestAgent($traces) curl_close($curl); // Output the response for debugging purposes - // echo $response; + //echo $response; } /** @@ -169,7 +175,7 @@ public function inWebServer($fn, $rootPath, $envs = [], $inis = [], &$curlInfo = self::putEnv('DD_TRACE_AGENT_RETRIES=3'); $this->resetTracer(); - $webServer = new WebServer($rootPath, '0.0.0.0', 6666); + $webServer = new WebServer($rootPath, '0.0.0.0', self::$webserverPort); $webServer->mergeEnvs($envs); $webServer->mergeInis($inis); $webServer->start(); @@ -177,7 +183,7 @@ public function inWebServer($fn, $rootPath, $envs = [], $inis = [], &$curlInfo = $fn(function (RequestSpec $request) use ($webServer, &$curlInfo) { if ($request instanceof GetSpec) { - $curl = curl_init('http://127.0.0.1:6666' . $request->getPath()); + $curl = curl_init('http://127.0.0.1:' . self::$webserverPort . $request->getPath()); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, $request->getHeaders()); $response = curl_exec($curl); @@ -210,11 +216,12 @@ public function inWebServer($fn, $rootPath, $envs = [], $inis = [], &$curlInfo = /** * This method executes a single script with the provided configuration. */ - public function inCli($scriptPath, $customEnvs = [], $customInis = [], $arguments = '', $withOutput = false) + public function inCli($scriptPath, $customEnvs = [], $customInis = [], $arguments = '', $withOutput = false, $until = null, $throw = true) { $this->resetRequestDumper(); $output = $this->executeCli($scriptPath, $customEnvs, $customInis, $arguments, $withOutput); - $out = [$this->parseTracesFromDumpedData()]; + usleep(100000); // Add a slight delay to give the request-replayer time to handle and store all requests. + $out = [$this->parseTracesFromDumpedData($until, $throw)]; if ($withOutput) { $out[] = $output; } @@ -226,7 +233,6 @@ public function executeCli($scriptPath, $customEnvs = [], $customInis = [], $arg $envs = (string) new EnvSerializer(array_merge( [ 'DD_AUTOLOAD_NO_COMPILE' => getenv('DD_AUTOLOAD_NO_COMPILE'), - 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_AGENT_HOST' => 'test-agent', 'DD_TRACE_AGENT_PORT' => '9126', // Uncomment to see debug-level messages @@ -237,9 +243,10 @@ public function executeCli($scriptPath, $customEnvs = [], $customInis = [], $arg $customEnvs )); + if (GLOBAL_AUTO_PREPEND_FILE) { + $customInis['auto_prepend_file'] = GLOBAL_AUTO_PREPEND_FILE; + } if (getenv('PHPUNIT_COVERAGE')) { - $customInis['auto_prepend_file'] = __DIR__ . '/../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $customInis['zend_extension'] = $xdebugExtension; @@ -254,7 +261,11 @@ public function executeCli($scriptPath, $customEnvs = [], $customInis = [], $arg )); $script = escapeshellarg($scriptPath); - $arguments = escapeshellarg($arguments); + if (\is_string($arguments)) { + $arguments = escapeshellarg($arguments); + } elseif (\is_array($arguments)) { + $arguments = implode(' ', array_map('escapeshellarg', $arguments)); + } $commandToExecute = "$envs " . PHP_BINARY . " $inis $script $arguments"; if ($withOutput) { $ret = (string) `$commandToExecute 2>&1`; @@ -274,6 +285,7 @@ public function executeCli($scriptPath, $customEnvs = [], $customInis = [], $arg public function resetRequestDumper() { $curl = curl_init(self::$agentRequestDumperUrl . '/clear-dumped-data'); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['x-datadog-test-session-token: ' . ini_get("datadog.trace.agent_test_session_token")]); curl_exec($curl); } @@ -374,9 +386,9 @@ private function parseRawDumpedSpans($rawSpans) * @return array * @throws \Exception */ - private function parseTracesFromDumpedData(callable $until = null) + private function parseTracesFromDumpedData(callable $until = null, $throw = false) { - $loaded = $this->retrieveDumpedTraceData($until); + $loaded = $this->retrieveDumpedTraceData($until, $throw); if (!$loaded) { return []; } @@ -430,11 +442,19 @@ public function parseMultipleRequestsFromDumpedData() */ public function retrieveDumpedData(callable $until = null, $throw = false) { - if (!$until) { - $until = function ($request) { - return (strpos($request["uri"] ?? "", "/telemetry/") !== 0); - }; - } + return $this->retrieveAnyDumpedData($until, $throw); + } + + /** + * Returns the raw response body, if any, or null otherwise. + */ + public function retrieveDumpedMetrics(callable $until = null, $throw = false) + { + return $this->retrieveAnyDumpedData($until, $throw, true); + } + + public function retrieveAnyDumpedData(callable $until = null, $throw, $metrics = false) { + $until = $until ?? $this->untilFirstTraceRequest(); $allResponses = []; @@ -442,8 +462,9 @@ public function retrieveDumpedData(callable $until = null, $throw = false) // and actually sent. While we should find a smart way to tackle this, for now we do it quick and dirty, in a // for loop. for ($attemptNumber = 1; $attemptNumber <= 50; $attemptNumber++) { - $curl = curl_init(self::$agentRequestDumperUrl . '/replay'); + $curl = curl_init(self::$agentRequestDumperUrl . '/replay' . ($metrics ? '-metrics' : '')); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['x-datadog-test-session-token: ' . ini_get("datadog.trace.agent_test_session_token")]); // Retrieving data $response = curl_exec($curl); if (!$response) { @@ -463,7 +484,7 @@ public function retrieveDumpedData(callable $until = null, $throw = false) return $allResponses; } } - \usleep(1000); + \usleep(10000); } } @@ -474,17 +495,69 @@ public function retrieveDumpedData(callable $until = null, $throw = false) return $allResponses; } - public function retrieveDumpedTraceData(callable $until = null) + public function retrieveDumpedTraceData(callable $until = null, $throw = false) { - if ($until) { - return array_values(array_filter($this->retrieveDumpedData($until), function ($request) use ($until) { - return $until($request); - })); - } else { - return array_values(array_filter($this->retrieveDumpedData(), function ($request) { - return strpos($request["uri"] ?? "", "/telemetry/") !== 0; - })); - } + return array_values(array_filter($this->retrieveDumpedData($until, $throw), function ($request) { + // Filter telemetry requests + return strpos($request["uri"] ?? "", "/telemetry/") !== 0; + })); + } + + function untilNumberOfTraces($number) { + $count = 0; + return function ($request) use (&$count, $number) { + $count += $request['headers']['X-Datadog-Trace-Count'] ?? $request["headers"]["x-datadog-trace-count"] ?? 0; + return $count >= $number; + }; + } + + function untilFirstTraceRequest() { + return function ($request) { + return (strpos($request["uri"] ?? "", "/v0.4/traces") === 0) + || (strpos($request["uri"] ?? "", "/v0.7/traces") === 0) + ; + }; + } + + function untilTelemetryRequest($metricName) { + return function ($request) use ($metricName) { + return (strpos($request["uri"] ?? "", "/telemetry/") === 0) + && (strpos($request["body"] ?? "", $metricName) !== false) + ; + }; + } + + function untilSpan(SpanAssertion $assertion) { + return function ($request) use ($assertion) { + if (strpos($request["uri"] ?? "", "/telemetry/") === 0 || !isset($request['body'])) { + return false; + } + $traces = $this->parseRawDumpedTraces(json_decode($request['body'], true)); + + foreach ($traces as $trace) { + try { + (new SpanChecker())->assertFlameGraph([$trace], [$assertion]); + } catch (\Exception $e) { + continue; + } + + return true; + } + + return false; + }; + } + + function until(...$expectations) { + return function ($request) use (&$expectations) { + foreach ($expectations as $key => $expect) { + if ($expect($request)) { + unset($expectations[$key]); + } + } + + return !count($expectations); + }; } /** diff --git a/tests/Common/UDPServer.php b/tests/Common/UDPServer.php deleted file mode 100644 index 2c4c72cbd1..0000000000 --- a/tests/Common/UDPServer.php +++ /dev/null @@ -1,41 +0,0 @@ -socket = socket_create(AF_INET, SOCK_DGRAM, 0))) { - $errorCode = socket_last_error(); - $errorMessage = socket_strerror($errorCode); - die("Couldn't create socket: [$errorCode] $errorMessage\n"); - } - - if (!socket_bind($this->socket, $addr, $port)) { - $errorCode = socket_last_error(); - $errorMessage = socket_strerror($errorCode); - die("Couldn't bind socket: [$errorCode] $errorMessage\n"); - } - - - } - - public function dump($iter = 100, $usleep = 100) { - $ret = ''; - $buf = ''; - for ($i = 0; $i < $iter; $i++) { - usleep($usleep); - $r = socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port); - if ($buf) { - $ret .= $buf . PHP_EOL; - $buf = ''; - } - } - return $ret; - } - - public function close() { - socket_close($this->socket); - } -} diff --git a/tests/Common/WebFrameworkTestCase.php b/tests/Common/WebFrameworkTestCase.php index 97e2634592..c1ae0bdadb 100644 --- a/tests/Common/WebFrameworkTestCase.php +++ b/tests/Common/WebFrameworkTestCase.php @@ -18,7 +18,7 @@ abstract class WebFrameworkTestCase extends IntegrationTestCase // host and port for the testing framework const HOST = 'http://localhost'; const HOST_WITH_CREDENTIALS = 'http://my_user:my_password@localhost'; - const PORT = 9999; + const PORT = 9999 - GLOBAL_PORT_OFFSET; const ERROR_LOG_NAME = 'phpunit_error.log'; const COOKIE_JAR = 'cookies.txt'; @@ -215,7 +215,7 @@ protected function call(RequestSpec $spec, $options = []) { $response = $this->sendRequest( $spec->getMethod(), - self::HOST . ':' . self::PORT . $spec->getPath(), + self::HOST . $spec->getPath(), $spec->getHeaders(), $spec->getBody(), $options @@ -245,6 +245,7 @@ protected function sendRequest($method, $url, $headers = [], $body = [], $change for ($i = 0; $i < 10; ++$i) { $ch = curl_init($url); + curl_setopt($ch, CURLOPT_CONNECT_TO, ["localhost:80:localhost:" . self::PORT]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, $options[CURLOPT_RETURNTRANSFER]); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $options[CURLOPT_FOLLOWLOCATION]); diff --git a/tests/Composer/ComposerInteroperabilityTest.php b/tests/Composer/ComposerInteroperabilityTest.php index 0e0752161c..4a73d1b577 100644 --- a/tests/Composer/ComposerInteroperabilityTest.php +++ b/tests/Composer/ComposerInteroperabilityTest.php @@ -94,7 +94,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-manual-tracing', 'http.status_code' => '200', ]), ]); @@ -128,7 +128,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/manual-tracing', 'http.status_code' => '200', ]) ->withChildren([ @@ -168,7 +168,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/manual-tracing', 'http.status_code' => '200', ]) ->withChildren([ @@ -208,7 +208,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-manual-tracing', 'http.status_code' => '200', ]), ]); @@ -242,7 +242,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/manual-tracing', 'http.status_code' => '200', ]) ->withChildren([ @@ -276,7 +276,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-manual-tracing') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-manual-tracing', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-manual-tracing', 'http.status_code' => '200', ]), ]); @@ -304,7 +304,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-composer') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-composer', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-composer', 'http.status_code' => '200', ]), ]); @@ -337,7 +337,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-composer') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-composer', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-composer', 'http.status_code' => '200', ]), ]); @@ -372,7 +372,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /no-composer-autoload-fails') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/no-composer-autoload-fails', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/no-composer-autoload-fails', 'http.status_code' => '200', ]), ]); @@ -407,7 +407,7 @@ function ($execute) { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /composer-autoload-fails') ->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://127.0.0.1:6666/composer-autoload-fails', + 'http.url' => 'http://127.0.0.1:' . self::$webserverPort . '/composer-autoload-fails', 'http.status_code' => '200', ]), ]); diff --git a/tests/DistributedTracing/SyntheticsTest.php b/tests/DistributedTracing/SyntheticsTest.php index 760e0fea92..4180ca8455 100644 --- a/tests/DistributedTracing/SyntheticsTest.php +++ b/tests/DistributedTracing/SyntheticsTest.php @@ -51,7 +51,7 @@ public function testSyntheticsRequest() 'GET /index.php' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/index.php', + 'http.url' => 'http://localhost/index.php', 'http.status_code' => '200', '_dd.origin' => 'synthetics-browser', ])->withExactMetrics([ diff --git a/tests/Frameworks/CakePHP/Version_2_8/app/Config/database.php b/tests/Frameworks/CakePHP/Version_2_8/app/Config/database.php index 6d4011bf25..4ca4baeb67 100644 --- a/tests/Frameworks/CakePHP/Version_2_8/app/Config/database.php +++ b/tests/Frameworks/CakePHP/Version_2_8/app/Config/database.php @@ -49,7 +49,7 @@ * database. Uses database default not specified. * * sslmode => - * For Postgres specifies whether to 'disable', 'allow', 'prefer', or 'require' SSL for the + * For Postgres specifies whether to 'disable', 'allow', 'prefer', or 'require' SSL for the * connection. The default value is 'allow'. * * unix_socket => @@ -72,7 +72,7 @@ class DATABASE_CONFIG { 'host' => 'mysql_integration', 'login' => 'test', 'password' => 'test', - 'database' => 'test', + 'database' => 'cake28', 'prefix' => '', //'encoding' => 'utf8', ); diff --git a/tests/Frameworks/Custom/OpenTelemetry/index.php b/tests/Frameworks/Custom/OpenTelemetry/index.php index 10d3a610c3..b061f02ae1 100644 --- a/tests/Frameworks/Custom/OpenTelemetry/index.php +++ b/tests/Frameworks/Custom/OpenTelemetry/index.php @@ -4,13 +4,9 @@ require __DIR__ . '/vendor/autoload.php'; -try { - $tracerProvider = new TracerProvider(); - $tracer = $tracerProvider->getTracer('foo'); - $span = $tracer->spanBuilder('barbar') - ->startSpan() - ; - $span->end(); -} finally { - \dd_trace_internal_fn("finalize_telemetry"); -} +$tracerProvider = new TracerProvider(); +$tracer = $tracerProvider->getTracer('foo'); +$span = $tracer->spanBuilder('barbar') + ->startSpan() +; +$span->end(); diff --git a/tests/Frameworks/Custom/OpenTracing/index.php b/tests/Frameworks/Custom/OpenTracing/index.php index 9b2228f38b..4e98e06918 100644 --- a/tests/Frameworks/Custom/OpenTracing/index.php +++ b/tests/Frameworks/Custom/OpenTracing/index.php @@ -2,15 +2,11 @@ require __DIR__ . '/vendor/autoload.php'; -try { - $otTracer = new \DDTrace\OpenTracer1\Tracer(\DDTrace\GlobalTracer::get()); - $scope = $otTracer->startActiveSpan('web.request'); - $span = $scope->getSpan(); - $span->setTag('service.name', 'service_name'); - $span->setTag('resource.name', 'resource_name'); - $span->setTag('span.type', 'web'); - $span->setTag('http.method', $_SERVER['REQUEST_METHOD']); - $span->finish(); -} finally { - \dd_trace_internal_fn("finalize_telemetry"); -} +$otTracer = new \DDTrace\OpenTracer1\Tracer(\DDTrace\GlobalTracer::get()); +$scope = $otTracer->startActiveSpan('web.request'); +$span = $scope->getSpan(); +$span->setTag('service.name', 'service_name'); +$span->setTag('resource.name', 'resource_name'); +$span->setTag('span.type', 'web'); +$span->setTag('http.method', $_SERVER['REQUEST_METHOD']); +$span->finish(); diff --git a/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Command/InstallCommand.php b/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Command/InstallCommand.php index 8b1d139eda..4929fe811b 100644 --- a/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Command/InstallCommand.php +++ b/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Command/InstallCommand.php @@ -143,7 +143,7 @@ protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, 'install_settings_form' => [ 'driver' => 'mysql', 'mysql' => [ - 'database' => 'test', + 'database' => 'drupal101', 'username' => 'test', 'password' => 'test', 'host' => 'mysql_integration', diff --git a/tests/Frameworks/Drupal/Version_10_1/scripts/erase_drupal_db.php b/tests/Frameworks/Drupal/Version_10_1/scripts/erase_drupal_db.php index df21a6ce8d..5d371abf5b 100644 --- a/tests/Frameworks/Drupal/Version_10_1/scripts/erase_drupal_db.php +++ b/tests/Frameworks/Drupal/Version_10_1/scripts/erase_drupal_db.php @@ -1,36 +1,38 @@ query('DROP TABLE IF EXISTS cache_bootstrap'); -$pdo->query('DROP TABLE IF EXISTS cache_config'); -$pdo->query('DROP TABLE IF EXISTS cache_container'); // Drupal doesn't like us dropping this table (PDOException) -$pdo->query('DROP TABLE IF EXISTS cache_data'); -$pdo->query('DROP TABLE IF EXISTS cache_default'); -$pdo->query('DROP TABLE IF EXISTS cache_discovery'); -$pdo->query('DROP TABLE IF EXISTS cache_entity'); -$pdo->query('DROP TABLE IF EXISTS config'); -$pdo->query('DROP TABLE IF EXISTS file_managed'); -$pdo->query('DROP TABLE IF EXISTS file_usage'); -$pdo->query('DROP TABLE IF EXISTS key_value'); -$pdo->query('DROP TABLE IF EXISTS key_value_expire'); -$pdo->query('DROP TABLE IF EXISTS menu_tree'); -$pdo->query('DROP TABLE IF EXISTS node'); -$pdo->query('DROP TABLE IF EXISTS node__body'); -$pdo->query('DROP TABLE IF EXISTS node_access'); -$pdo->query('DROP TABLE IF EXISTS node_field_data'); -$pdo->query('DROP TABLE IF EXISTS node_field_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision__body'); -$pdo->query('DROP TABLE IF EXISTS path_alias'); -$pdo->query('DROP TABLE IF EXISTS path_alias_revision'); -$pdo->query('DROP TABLE IF EXISTS queue'); -$pdo->query('DROP TABLE IF EXISTS router'); -$pdo->query('DROP TABLE IF EXISTS semaphore'); -$pdo->query('DROP TABLE IF EXISTS sequences'); -$pdo->query('DROP TABLE IF EXISTS sessions'); -$pdo->query('DROP TABLE IF EXISTS user__roles'); -$pdo->query('DROP TABLE IF EXISTS users'); -$pdo->query('DROP TABLE IF EXISTS users_data'); -$pdo->query('DROP TABLE IF EXISTS users_field_data'); -$pdo->query('DROP TABLE IF EXISTS watchdog'); +$pdo->query("CREATE DATABASE IF NOT EXISTS drupal101"); + +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_bootstrap'); +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_config'); +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_container'); // Drupal doesn't like us dropping this table (PDOException) +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_data'); +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_default'); +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_discovery'); +$pdo->query('DROP TABLE IF EXISTS drupal101.cache_entity'); +$pdo->query('DROP TABLE IF EXISTS drupal101.config'); +$pdo->query('DROP TABLE IF EXISTS drupal101.file_managed'); +$pdo->query('DROP TABLE IF EXISTS drupal101.file_usage'); +$pdo->query('DROP TABLE IF EXISTS drupal101.key_value'); +$pdo->query('DROP TABLE IF EXISTS drupal101.key_value_expire'); +$pdo->query('DROP TABLE IF EXISTS drupal101.menu_tree'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node__body'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node_access'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node_field_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal101.node_revision__body'); +$pdo->query('DROP TABLE IF EXISTS drupal101.path_alias'); +$pdo->query('DROP TABLE IF EXISTS drupal101.path_alias_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal101.queue'); +$pdo->query('DROP TABLE IF EXISTS drupal101.router'); +$pdo->query('DROP TABLE IF EXISTS drupal101.semaphore'); +$pdo->query('DROP TABLE IF EXISTS drupal101.sequences'); +$pdo->query('DROP TABLE IF EXISTS drupal101.sessions'); +$pdo->query('DROP TABLE IF EXISTS drupal101.user__roles'); +$pdo->query('DROP TABLE IF EXISTS drupal101.users'); +$pdo->query('DROP TABLE IF EXISTS drupal101.users_data'); +$pdo->query('DROP TABLE IF EXISTS drupal101.users_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal101.watchdog'); diff --git a/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Command/InstallCommand.php b/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Command/InstallCommand.php index d0f4f82640..f38a635e52 100644 --- a/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Command/InstallCommand.php +++ b/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Command/InstallCommand.php @@ -143,7 +143,7 @@ protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, 'install_settings_form' => [ 'driver' => 'mysql', 'mysql' => [ - 'database' => 'test', + 'database' => 'drupal89', 'username' => 'test', 'password' => 'test', 'host' => 'mysql_integration', diff --git a/tests/Frameworks/Drupal/Version_8_9/scripts/erase_drupal_db.php b/tests/Frameworks/Drupal/Version_8_9/scripts/erase_drupal_db.php index eb4916833d..0e8b12c66e 100644 --- a/tests/Frameworks/Drupal/Version_8_9/scripts/erase_drupal_db.php +++ b/tests/Frameworks/Drupal/Version_8_9/scripts/erase_drupal_db.php @@ -1,36 +1,38 @@ query('DROP TABLE IF EXISTS cache_bootstrap'); -$pdo->query('DROP TABLE IF EXISTS cache_config'); -$pdo->query('DROP TABLE IF EXISTS cache_container'); -$pdo->query('DROP TABLE IF EXISTS cache_data'); -$pdo->query('DROP TABLE IF EXISTS cache_default'); -$pdo->query('DROP TABLE IF EXISTS cache_discovery'); -$pdo->query('DROP TABLE IF EXISTS cache_entity'); -$pdo->query('DROP TABLE IF EXISTS config'); -$pdo->query('DROP TABLE IF EXISTS file_managed'); -$pdo->query('DROP TABLE IF EXISTS file_usage'); -$pdo->query('DROP TABLE IF EXISTS key_value'); -$pdo->query('DROP TABLE IF EXISTS key_value_expire'); -$pdo->query('DROP TABLE IF EXISTS menu_tree'); -$pdo->query('DROP TABLE IF EXISTS node'); -$pdo->query('DROP TABLE IF EXISTS node__body'); -$pdo->query('DROP TABLE IF EXISTS node_access'); -$pdo->query('DROP TABLE IF EXISTS node_field_data'); -$pdo->query('DROP TABLE IF EXISTS node_field_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision__body'); -$pdo->query('DROP TABLE IF EXISTS path_alias'); -$pdo->query('DROP TABLE IF EXISTS path_alias_revision'); -$pdo->query('DROP TABLE IF EXISTS queue'); -$pdo->query('DROP TABLE IF EXISTS router'); -$pdo->query('DROP TABLE IF EXISTS semaphore'); -$pdo->query('DROP TABLE IF EXISTS sequences'); -$pdo->query('DROP TABLE IF EXISTS sessions'); -$pdo->query('DROP TABLE IF EXISTS user__roles'); -$pdo->query('DROP TABLE IF EXISTS users'); -$pdo->query('DROP TABLE IF EXISTS users_data'); -$pdo->query('DROP TABLE IF EXISTS users_field_data'); -$pdo->query('DROP TABLE IF EXISTS watchdog'); +$pdo->query("CREATE DATABASE IF NOT EXISTS drupal89"); + +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_bootstrap'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_config'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_container'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_data'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_default'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_discovery'); +$pdo->query('DROP TABLE IF EXISTS drupal89.cache_entity'); +$pdo->query('DROP TABLE IF EXISTS drupal89.config'); +$pdo->query('DROP TABLE IF EXISTS drupal89.file_managed'); +$pdo->query('DROP TABLE IF EXISTS drupal89.file_usage'); +$pdo->query('DROP TABLE IF EXISTS drupal89.key_value'); +$pdo->query('DROP TABLE IF EXISTS drupal89.key_value_expire'); +$pdo->query('DROP TABLE IF EXISTS drupal89.menu_tree'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node__body'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node_access'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node_field_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal89.node_revision__body'); +$pdo->query('DROP TABLE IF EXISTS drupal89.path_alias'); +$pdo->query('DROP TABLE IF EXISTS drupal89.path_alias_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal89.queue'); +$pdo->query('DROP TABLE IF EXISTS drupal89.router'); +$pdo->query('DROP TABLE IF EXISTS drupal89.semaphore'); +$pdo->query('DROP TABLE IF EXISTS drupal89.sequences'); +$pdo->query('DROP TABLE IF EXISTS drupal89.sessions'); +$pdo->query('DROP TABLE IF EXISTS drupal89.user__roles'); +$pdo->query('DROP TABLE IF EXISTS drupal89.users'); +$pdo->query('DROP TABLE IF EXISTS drupal89.users_data'); +$pdo->query('DROP TABLE IF EXISTS drupal89.users_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal89.watchdog'); diff --git a/tests/Frameworks/Drupal/Version_8_9/sites/default/default.settings.php b/tests/Frameworks/Drupal/Version_8_9/sites/default/default.settings.php index ff2c41f1ce..2fdeb8455a 100755 --- a/tests/Frameworks/Drupal/Version_8_9/sites/default/default.settings.php +++ b/tests/Frameworks/Drupal/Version_8_9/sites/default/default.settings.php @@ -90,7 +90,7 @@ */ $databases['default']['default'] = [ 'driver' => 'mysql', - 'database' => 'test', + 'database' => 'drupal89', 'username' => 'test', 'password' => 'test', 'host' => 'mysql_integration', @@ -258,7 +258,7 @@ */ $databases['default']['default'] = [ 'driver' => 'mysql', - 'database' => 'test', + 'database' => 'drupal89', 'username' => 'test', 'password' => 'test', 'host' => 'mysql_integration', diff --git a/tests/Frameworks/Drupal/Version_9_5/composer.lock b/tests/Frameworks/Drupal/Version_9_5/composer.lock deleted file mode 100644 index 1bc25f38ea..0000000000 --- a/tests/Frameworks/Drupal/Version_9_5/composer.lock +++ /dev/null @@ -1,8625 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "75276288e7e5acb61a63d2db0c1e5eb0", - "packages": [ - { - "name": "asm89/stack-cors", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/asm89/stack-cors.git", - "reference": "b9c31def6a83f84b4d4a40d35996d375755f0e08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/asm89/stack-cors/zipball/b9c31def6a83f84b4d4a40d35996d375755f0e08", - "reference": "b9c31def6a83f84b4d4a40d35996d375755f0e08", - "shasum": "" - }, - "require": { - "php": ">=5.5.9", - "symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0", - "symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.0 || ^4.8.10", - "squizlabs/php_codesniffer": "^2.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, - "autoload": { - "psr-4": { - "Asm89\\Stack\\": "src/Asm89/Stack/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alexander", - "email": "iam.asm89@gmail.com" - } - ], - "description": "Cross-origin resource sharing library and stack middleware", - "homepage": "https://github.com/asm89/stack-cors", - "keywords": [ - "cors", - "stack" - ], - "support": { - "issues": "https://github.com/asm89/stack-cors/issues", - "source": "https://github.com/asm89/stack-cors/tree/1.3.0" - }, - "time": "2019-12-24T22:41:47+00:00" - }, - { - "name": "composer/installers", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/composer/installers.git", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19", - "reference": "d20a64ed3c94748397ff5973488761b22f6d3f19", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" - }, - "require-dev": { - "composer/composer": "1.6.* || ^2.0", - "composer/semver": "^1 || ^3", - "phpstan/phpstan": "^0.12.55", - "phpstan/phpstan-phpunit": "^0.12.16", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.3" - }, - "type": "composer-plugin", - "extra": { - "class": "Composer\\Installers\\Plugin", - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Installers\\": "src/Composer/Installers" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle Robinson Young", - "email": "kyle@dontkry.com", - "homepage": "https://github.com/shama" - } - ], - "description": "A multi-framework Composer library installer", - "homepage": "https://composer.github.io/installers/", - "keywords": [ - "Craft", - "Dolibarr", - "Eliasis", - "Hurad", - "ImageCMS", - "Kanboard", - "Lan Management System", - "MODX Evo", - "MantisBT", - "Mautic", - "Maya", - "OXID", - "Plentymarkets", - "Porto", - "RadPHP", - "SMF", - "Starbug", - "Thelia", - "Whmcs", - "WolfCMS", - "agl", - "aimeos", - "annotatecms", - "attogram", - "bitrix", - "cakephp", - "chef", - "cockpit", - "codeigniter", - "concrete5", - "croogo", - "dokuwiki", - "drupal", - "eZ Platform", - "elgg", - "expressionengine", - "fuelphp", - "grav", - "installer", - "itop", - "joomla", - "known", - "kohana", - "laravel", - "lavalite", - "lithium", - "magento", - "majima", - "mako", - "mediawiki", - "miaoxing", - "modulework", - "modx", - "moodle", - "osclass", - "pantheon", - "phpbb", - "piwik", - "ppi", - "processwire", - "puppet", - "pxcms", - "reindex", - "roundcube", - "shopware", - "silverstripe", - "sydes", - "sylius", - "symfony", - "tastyigniter", - "typo3", - "wordpress", - "yawik", - "zend", - "zikula" - ], - "support": { - "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v1.12.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-09-13T08:19:44+00:00" - }, - { - "name": "composer/semver", - "version": "3.3.2", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-04-01T19:23:25+00:00" - }, - { - "name": "doctrine/annotations", - "version": "1.14.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1 || ^2", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.14.3" - }, - "time": "2023-02-01T09:20:38+00:00" - }, - { - "name": "doctrine/deprecations", - "version": "v1.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" - }, - "time": "2023-06-03T09:27:29+00:00" - }, - { - "name": "doctrine/lexer", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2022-12-14T08:49:07+00:00" - }, - { - "name": "doctrine/reflection", - "version": "1.2.4", - "source": { - "type": "git", - "url": "https://github.com/doctrine/reflection.git", - "reference": "6bcea3e81ab8b3d0abe5fde5300bbc8a968960c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/reflection/zipball/6bcea3e81ab8b3d0abe5fde5300bbc8a968960c7", - "reference": "6bcea3e81ab8b3d0abe5fde5300bbc8a968960c7", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0 || ^2.0", - "ext-tokenizer": "*", - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "doctrine/common": "^3.3", - "phpstan/phpstan": "^1.4.10", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Reflection project is a simple library used by the various Doctrine projects which adds some additional functionality on top of the reflection functionality that comes with PHP. It allows you to get the reflection information about classes, methods and properties statically.", - "homepage": "https://www.doctrine-project.org/projects/reflection.html", - "keywords": [ - "reflection", - "static" - ], - "support": { - "issues": "https://github.com/doctrine/reflection/issues", - "source": "https://github.com/doctrine/reflection/tree/1.2.4" - }, - "abandoned": "roave/better-reflection", - "time": "2023-07-27T18:11:59+00:00" - }, - { - "name": "drupal/core", - "version": "dev-master", - "dist": { - "type": "path", - "url": "core", - "reference": "737286c20cf976748c4a3e46add09b526d577681" - }, - "require": { - "asm89/stack-cors": "^1.3", - "composer/semver": "^3.3", - "doctrine/annotations": "^1.13", - "doctrine/reflection": "^1.2", - "egulias/email-validator": "^2.1.22|^3.2", - "ext-date": "*", - "ext-dom": "*", - "ext-filter": "*", - "ext-gd": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-pdo": "*", - "ext-session": "*", - "ext-simplexml": "*", - "ext-spl": "*", - "ext-tokenizer": "*", - "ext-xml": "*", - "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "laminas/laminas-feed": "^2.17", - "longwave/laminas-diactoros": "^2.14", - "masterminds/html5": "^2.7", - "pear/archive_tar": "^1.4.14", - "php": ">=7.3.0", - "psr/log": "^1.1", - "stack/builder": "^1.0", - "symfony-cmf/routing": "^2.3", - "symfony/console": "^4.4", - "symfony/dependency-injection": "^4.4", - "symfony/event-dispatcher": "^4.4", - "symfony/http-foundation": "^4.4.7", - "symfony/http-kernel": "^4.4", - "symfony/mime": "^5.4", - "symfony/polyfill-iconv": "^1.26", - "symfony/polyfill-php80": "^1.26", - "symfony/process": "^4.4", - "symfony/psr-http-message-bridge": "^2.1", - "symfony/routing": "^4.4", - "symfony/serializer": "^4.4", - "symfony/translation": "^4.4", - "symfony/validator": "^4.4", - "symfony/yaml": "^4.4.19", - "twig/twig": "^2.15.3", - "typo3/phar-stream-wrapper": "^3.1.3" - }, - "conflict": { - "drush/drush": "<8.1.10", - "symfony/http-foundation": "4.4.42" - }, - "replace": { - "drupal/core-annotation": "self.version", - "drupal/core-assertion": "self.version", - "drupal/core-bridge": "self.version", - "drupal/core-class-finder": "self.version", - "drupal/core-datetime": "self.version", - "drupal/core-dependency-injection": "self.version", - "drupal/core-diff": "self.version", - "drupal/core-discovery": "self.version", - "drupal/core-event-dispatcher": "self.version", - "drupal/core-file-cache": "self.version", - "drupal/core-file-security": "self.version", - "drupal/core-filesystem": "self.version", - "drupal/core-front-matter": "self.version", - "drupal/core-gettext": "self.version", - "drupal/core-graph": "self.version", - "drupal/core-http-foundation": "self.version", - "drupal/core-php-storage": "self.version", - "drupal/core-plugin": "self.version", - "drupal/core-proxy-builder": "self.version", - "drupal/core-render": "self.version", - "drupal/core-serialization": "self.version", - "drupal/core-transliteration": "self.version", - "drupal/core-utility": "self.version", - "drupal/core-uuid": "self.version", - "drupal/core-version": "self.version" - }, - "type": "drupal-core", - "extra": { - "drupal-scaffold": { - "file-mapping": { - "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig", - "[project-root]/.gitattributes": "assets/scaffold/files/gitattributes", - "[web-root]/.csslintrc": "assets/scaffold/files/csslintrc", - "[web-root]/.eslintignore": "assets/scaffold/files/eslintignore", - "[web-root]/.eslintrc.json": "assets/scaffold/files/eslintrc.json", - "[web-root]/.ht.router.php": "assets/scaffold/files/ht.router.php", - "[web-root]/.htaccess": "assets/scaffold/files/htaccess", - "[web-root]/example.gitignore": "assets/scaffold/files/example.gitignore", - "[web-root]/index.php": "assets/scaffold/files/index.php", - "[web-root]/INSTALL.txt": "assets/scaffold/files/drupal.INSTALL.txt", - "[web-root]/README.md": "assets/scaffold/files/drupal.README.md", - "[web-root]/robots.txt": "assets/scaffold/files/robots.txt", - "[web-root]/update.php": "assets/scaffold/files/update.php", - "[web-root]/web.config": "assets/scaffold/files/web.config", - "[web-root]/sites/README.txt": "assets/scaffold/files/sites.README.txt", - "[web-root]/sites/development.services.yml": "assets/scaffold/files/development.services.yml", - "[web-root]/sites/example.settings.local.php": "assets/scaffold/files/example.settings.local.php", - "[web-root]/sites/example.sites.php": "assets/scaffold/files/example.sites.php", - "[web-root]/sites/default/default.services.yml": "assets/scaffold/files/default.services.yml", - "[web-root]/sites/default/default.settings.php": "assets/scaffold/files/default.settings.php", - "[web-root]/modules/README.txt": "assets/scaffold/files/modules.README.txt", - "[web-root]/profiles/README.txt": "assets/scaffold/files/profiles.README.txt", - "[web-root]/themes/README.txt": "assets/scaffold/files/themes.README.txt" - } - } - }, - "autoload": { - "psr-4": { - "Drupal\\Core\\": "lib/Drupal/Core", - "Drupal\\Component\\": "lib/Drupal/Component", - "Drupal\\Driver\\": "../drivers/lib/Drupal/Driver" - }, - "classmap": [ - "lib/Drupal.php", - "lib/Drupal/Component/DependencyInjection/Container.php", - "lib/Drupal/Component/DependencyInjection/PhpArrayContainer.php", - "lib/Drupal/Component/FileCache/FileCacheFactory.php", - "lib/Drupal/Component/Utility/Timer.php", - "lib/Drupal/Component/Utility/Unicode.php", - "lib/Drupal/Core/Cache/Cache.php", - "lib/Drupal/Core/Cache/CacheBackendInterface.php", - "lib/Drupal/Core/Cache/CacheTagsChecksumInterface.php", - "lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php", - "lib/Drupal/Core/Cache/CacheTagsInvalidatorInterface.php", - "lib/Drupal/Core/Cache/DatabaseBackend.php", - "lib/Drupal/Core/Cache/DatabaseCacheTagsChecksum.php", - "lib/Drupal/Core/Database/Connection.php", - "lib/Drupal/Core/Database/Database.php", - "lib/Drupal/Core/Database/Statement.php", - "lib/Drupal/Core/Database/StatementInterface.php", - "lib/Drupal/Core/DependencyInjection/Container.php", - "lib/Drupal/Core/DrupalKernel.php", - "lib/Drupal/Core/DrupalKernelInterface.php", - "lib/Drupal/Core/Http/InputBag.php", - "lib/Drupal/Core/Installer/InstallerRedirectTrait.php", - "lib/Drupal/Core/Site/Settings.php" - ], - "files": [ - "includes/bootstrap.inc", - "includes/guzzle_php81_shim.php" - ] - }, - "scripts": { - "pre-autoload-dump": [ - "Drupal\\Core\\Composer\\Composer::preAutoloadDump" - ] - }, - "license": [ - "GPL-2.0-or-later" - ], - "description": "Drupal is an open source content management platform powering millions of websites and applications.", - "transport-options": { - "relative": true - } - }, - { - "name": "drupal/core-project-message", - "version": "dev-master", - "dist": { - "type": "path", - "url": "composer/Plugin/ProjectMessage", - "reference": "b4efdbe26634b41a1b89e4f3770a8074769088a6" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2", - "php": ">=7.3.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin" - }, - "autoload": { - "psr-4": { - "Drupal\\Composer\\Plugin\\ProjectMessage\\": "." - } - }, - "license": [ - "GPL-2.0-or-later" - ], - "description": "Adds a message after Composer installation.", - "homepage": "https://www.drupal.org/project/drupal", - "keywords": [ - "drupal" - ], - "transport-options": { - "relative": true - } - }, - { - "name": "drupal/core-vendor-hardening", - "version": "dev-master", - "dist": { - "type": "path", - "url": "composer/Plugin/VendorHardening", - "reference": "d54f0b3cc8b4237f3a41a0860a808db242f9da9e" - }, - "require": { - "composer-plugin-api": "^1.1 || ^2", - "php": ">=7.3.0" - }, - "type": "composer-plugin", - "extra": { - "class": "Drupal\\Composer\\Plugin\\VendorHardening\\VendorHardeningPlugin" - }, - "autoload": { - "psr-4": { - "Drupal\\Composer\\Plugin\\VendorHardening\\": "." - } - }, - "license": [ - "GPL-2.0-or-later" - ], - "description": "Hardens the vendor directory for when it's in the docroot.", - "homepage": "https://www.drupal.org/project/drupal", - "keywords": [ - "drupal" - ], - "transport-options": { - "relative": true - } - }, - { - "name": "egulias/email-validator", - "version": "3.2.6", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.6" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2023-06-01T07:04:22+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "7.7.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", - "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2023-05-21T14:04:53+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2023-08-03T15:11:55+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.6.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", - "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2023-08-03T15:06:02+00:00" - }, - { - "name": "laminas/laminas-escaper", - "version": "2.12.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-escaper.git", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", - "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-mbstring": "*", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0" - }, - "conflict": { - "zendframework/zend-escaper": "*" - }, - "require-dev": { - "infection/infection": "^0.26.6", - "laminas/laminas-coding-standard": "~2.4.0", - "maglnet/composer-require-checker": "^3.8.0", - "phpunit/phpunit": "^9.5.18", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.22.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Escaper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", - "homepage": "https://laminas.dev", - "keywords": [ - "escaper", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-escaper/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-escaper/issues", - "rss": "https://github.com/laminas/laminas-escaper/releases.atom", - "source": "https://github.com/laminas/laminas-escaper" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2022-10-10T10:11:09+00:00" - }, - { - "name": "laminas/laminas-feed", - "version": "2.21.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-feed.git", - "reference": "52918789a417bc292ccd6fbb4b91bd78a65d50ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/52918789a417bc292ccd6fbb4b91bd78a65d50ab", - "reference": "52918789a417bc292ccd6fbb4b91bd78a65d50ab", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "laminas/laminas-escaper": "^2.9", - "laminas/laminas-stdlib": "^3.6", - "php": "~8.1.0 || ~8.2.0" - }, - "conflict": { - "laminas/laminas-servicemanager": "<3.3", - "zendframework/zend-feed": "*" - }, - "require-dev": { - "laminas/laminas-cache": "^2.13.2 || ^3.10.1", - "laminas/laminas-cache-storage-adapter-memory": "^1.1.0 || ^2.2", - "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-db": "^2.18", - "laminas/laminas-http": "^2.18", - "laminas/laminas-servicemanager": "^3.21.0", - "laminas/laminas-validator": "^2.30.1", - "phpunit/phpunit": "^10.2.6", - "psalm/plugin-phpunit": "^0.18.4", - "psr/http-message": "^2.0", - "vimeo/psalm": "^5.13.1" - }, - "suggest": { - "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", - "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", - "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", - "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", - "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent", - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Feed\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides functionality for creating and consuming RSS and Atom feeds", - "homepage": "https://laminas.dev", - "keywords": [ - "atom", - "feed", - "laminas", - "rss" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-feed/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-feed/issues", - "rss": "https://github.com/laminas/laminas-feed/releases.atom", - "source": "https://github.com/laminas/laminas-feed" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2023-07-24T09:21:16+00:00" - }, - { - "name": "laminas/laminas-stdlib", - "version": "3.17.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "dd35c868075bad80b6718959740913e178eb4274" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/dd35c868075bad80b6718959740913e178eb4274", - "reference": "dd35c868075bad80b6718959740913e178eb4274", - "shasum": "" - }, - "require": { - "php": "~8.1.0 || ~8.2.0" - }, - "conflict": { - "zendframework/zend-stdlib": "*" - }, - "require-dev": { - "laminas/laminas-coding-standard": "^2.5", - "phpbench/phpbench": "^1.2.9", - "phpunit/phpunit": "^10.0.16", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Stdlib\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "SPL extensions, array utilities, error handlers, and more", - "homepage": "https://laminas.dev", - "keywords": [ - "laminas", - "stdlib" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-stdlib/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-stdlib/issues", - "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", - "source": "https://github.com/laminas/laminas-stdlib" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2023-03-20T13:51:37+00:00" - }, - { - "name": "longwave/laminas-diactoros", - "version": "2.14.2", - "source": { - "type": "git", - "url": "https://github.com/longwave/laminas-diactoros.git", - "reference": "ae4f0becf249ae8eea8f2f8f9fb927104e55a885" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/longwave/laminas-diactoros/zipball/ae4f0becf249ae8eea8f2f8f9fb927104e55a885", - "reference": "ae4f0becf249ae8eea8f2f8f9fb927104e55a885", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0 || ~8.2.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "replace": { - "laminas/laminas-diactoros": "2.18.1" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.9.0", - "laminas/laminas-coding-standard": "~2.3.0", - "php-http/psr7-integration-tests": "^1.1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.24.0" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "time": "2023-04-26T21:27:14+00:00" - }, - { - "name": "masterminds/html5", - "version": "2.8.1", - "source": { - "type": "git", - "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf", - "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7-dev" - } - }, - "autoload": { - "psr-4": { - "Masterminds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matt Butcher", - "email": "technosophos@gmail.com" - }, - { - "name": "Matt Farina", - "email": "matt@mattfarina.com" - }, - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - } - ], - "description": "An HTML5 parser and serializer.", - "homepage": "http://masterminds.github.io/html5-php", - "keywords": [ - "HTML5", - "dom", - "html", - "parser", - "querypath", - "serializer", - "xml" - ], - "support": { - "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.1" - }, - "time": "2023-05-10T11:58:31+00:00" - }, - { - "name": "pear/archive_tar", - "version": "1.4.14", - "source": { - "type": "git", - "url": "https://github.com/pear/Archive_Tar.git", - "reference": "4d761c5334c790e45ef3245f0864b8955c562caa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/4d761c5334c790e45ef3245f0864b8955c562caa", - "reference": "4d761c5334c790e45ef3245f0864b8955c562caa", - "shasum": "" - }, - "require": { - "pear/pear-core-minimal": "^1.10.0alpha2", - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "suggest": { - "ext-bz2": "Bz2 compression support.", - "ext-xz": "Lzma2 compression support.", - "ext-zlib": "Gzip compression support." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "Archive_Tar": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "./" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Vincent Blavet", - "email": "vincent@phpconcept.net" - }, - { - "name": "Greg Beaver", - "email": "greg@chiaraquartet.net" - }, - { - "name": "Michiel Rook", - "email": "mrook@php.net" - } - ], - "description": "Tar file management class with compression support (gzip, bzip2, lzma2)", - "homepage": "https://github.com/pear/Archive_Tar", - "keywords": [ - "archive", - "tar" - ], - "support": { - "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Archive_Tar", - "source": "https://github.com/pear/Archive_Tar" - }, - "funding": [ - { - "url": "https://github.com/mrook", - "type": "github" - }, - { - "url": "https://www.patreon.com/michielrook", - "type": "patreon" - } - ], - "time": "2021-07-20T13:53:39+00:00" - }, - { - "name": "pear/console_getopt", - "version": "v1.4.3", - "source": { - "type": "git", - "url": "https://github.com/pear/Console_Getopt.git", - "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/a41f8d3e668987609178c7c4a9fe48fecac53fa0", - "reference": "a41f8d3e668987609178c7c4a9fe48fecac53fa0", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-0": { - "Console": "./" - } - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "./" - ], - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Andrei Zmievski", - "email": "andrei@php.net", - "role": "Lead" - }, - { - "name": "Stig Bakken", - "email": "stig@php.net", - "role": "Developer" - }, - { - "name": "Greg Beaver", - "email": "cellog@php.net", - "role": "Helper" - } - ], - "description": "More info available on: http://pear.php.net/package/Console_Getopt", - "support": { - "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt", - "source": "https://github.com/pear/Console_Getopt" - }, - "time": "2019-11-20T18:27:48+00:00" - }, - { - "name": "pear/pear-core-minimal", - "version": "v1.10.13", - "source": { - "type": "git", - "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/aed862e95fd286c53cc546734868dc38ff4b5b1d", - "reference": "aed862e95fd286c53cc546734868dc38ff4b5b1d", - "shasum": "" - }, - "require": { - "pear/console_getopt": "~1.4", - "pear/pear_exception": "~1.0" - }, - "replace": { - "rsky/pear-core-min": "self.version" - }, - "type": "library", - "autoload": { - "psr-0": { - "": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "src/" - ], - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@php.net", - "role": "Lead" - } - ], - "description": "Minimal set of PEAR core files to be used as composer dependency", - "support": { - "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", - "source": "https://github.com/pear/pear-core-minimal" - }, - "time": "2023-04-19T19:15:47+00:00" - }, - { - "name": "pear/pear_exception", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/pear/PEAR_Exception.git", - "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", - "reference": "b14fbe2ddb0b9f94f5b24cf08783d599f776fff0", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "<9" - }, - "type": "class", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "PEAR/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "." - ], - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Helgi Thormar", - "email": "dufuz@php.net" - }, - { - "name": "Greg Beaver", - "email": "cellog@php.net" - } - ], - "description": "The PEAR Exception base class.", - "homepage": "https://github.com/pear/PEAR_Exception", - "keywords": [ - "exception" - ], - "support": { - "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR_Exception", - "source": "https://github.com/pear/PEAR_Exception" - }, - "time": "2021-03-21T15:43:46+00:00" - }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, - { - "name": "psr/container", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" - }, - "time": "2021-11-05T16:50:12+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" - }, - "time": "2023-04-10T20:12:12+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" - }, - "time": "2023-04-10T20:10:41+00:00" - }, - { - "name": "psr/http-message", - "version": "1.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" - }, - "time": "2023-04-04T09:50:52+00:00" - }, - { - "name": "psr/log", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" - }, - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "stack/builder", - "version": "v1.0.6", - "source": { - "type": "git", - "url": "https://github.com/stackphp/builder.git", - "reference": "a4faaa6f532c6086bc66c29e1bc6c29593e1ca7c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/stackphp/builder/zipball/a4faaa6f532c6086bc66c29e1bc6c29593e1ca7c", - "reference": "a4faaa6f532c6086bc66c29e1bc6c29593e1ca7c", - "shasum": "" - }, - "require": { - "php": ">=7.2.0", - "symfony/http-foundation": "~2.1|~3.0|~4.0|~5.0", - "symfony/http-kernel": "~2.1|~3.0|~4.0|~5.0" - }, - "require-dev": { - "phpunit/phpunit": "~8.0", - "symfony/routing": "^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Stack": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Builder for stack middleware based on HttpKernelInterface.", - "keywords": [ - "stack" - ], - "support": { - "issues": "https://github.com/stackphp/builder/issues", - "source": "https://github.com/stackphp/builder/tree/v1.0.6" - }, - "time": "2020-01-30T12:17:27+00:00" - }, - { - "name": "symfony-cmf/routing", - "version": "2.3.4", - "source": { - "type": "git", - "url": "https://github.com/symfony-cmf/Routing.git", - "reference": "bbcdf2f6301d740454ba9ebb8adaefd436c36a6b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony-cmf/Routing/zipball/bbcdf2f6301d740454ba9ebb8adaefd436c36a6b", - "reference": "bbcdf2f6301d740454ba9ebb8adaefd436c36a6b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/http-kernel": "^4.4 || ^5.0", - "symfony/routing": "^4.4 || ^5.0" - }, - "require-dev": { - "symfony-cmf/testing": "^3@dev", - "symfony/config": "^4.4 || ^5.0", - "symfony/dependency-injection": "^4.4 || ^5.0", - "symfony/event-dispatcher": "^4.4 || ^5.0", - "symfony/phpunit-bridge": "^5.0" - }, - "suggest": { - "symfony/event-dispatcher": "DynamicRouter can optionally trigger an event at the start of matching. Minimal version (^4.4 || ^5.0)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Cmf\\Component\\Routing\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony CMF Community", - "homepage": "https://github.com/symfony-cmf/Routing/contributors" - } - ], - "description": "Extends the Symfony routing component for dynamic routes and chaining several routers", - "homepage": "http://cmf.symfony.com", - "keywords": [ - "database", - "routing" - ], - "support": { - "issues": "https://github.com/symfony-cmf/Routing/issues", - "source": "https://github.com/symfony-cmf/Routing/tree/2.3.4" - }, - "time": "2021-11-08T16:33:10+00:00" - }, - { - "name": "symfony/console", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "reference": "33fa45ffc81fdcc1ca368d4946da859c8cdb58d9", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", - "symfony/lock": "<4.4", - "symfony/process": "<3.3" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/console/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-05T17:10:16+00:00" - }, - { - "name": "symfony/debug", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/1a692492190773c5310bc7877cb590c04c2f05be", - "reference": "1a692492190773c5310bc7877cb590c04c2f05be", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "abandoned": "symfony/error-handler", - "time": "2022-07-28T16:29:46+00:00" - }, - { - "name": "symfony/dependency-injection", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9065fe97dbd38a897e95ea254eb5ddfe1310f734", - "reference": "9065fe97dbd38a897e95ea254eb5ddfe1310f734", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/container": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1.6|^2" - }, - "conflict": { - "symfony/config": "<4.3|>=5.0", - "symfony/finder": "<3.4", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<4.4.26" - }, - "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" - }, - "require-dev": { - "symfony/config": "^4.3", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/yaml": "^4.4.26|^5.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-16T16:18:09+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-05-23T14:45:45+00:00" - }, - { - "name": "symfony/error-handler", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/error-handler.git", - "reference": "be731658121ef2d8be88f3a1ec938148a9237291" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/be731658121ef2d8be88f3a1ec938148a9237291", - "reference": "be731658121ef2d8be88f3a1ec938148a9237291", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3", - "symfony/debug": "^4.4.5", - "symfony/var-dumper": "^4.4|^5.0" - }, - "require-dev": { - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ErrorHandler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to manage errors and ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/error-handler/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-28T16:29:46+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "reference": "1e866e9e5c1b22168e0ce5f0b467f19bba61266a", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "~3.4|~4.4", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e", - "shasum": "" - }, - "require": { - "php": ">=7.1.3" - }, - "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:41:36+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/http-client-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-04-12T15:48:08+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v4.4.49", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "191413c7b832c015bb38eae963f2e57498c3c173" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/191413c7b832c015bb38eae963f2e57498c3c173", - "reference": "191413c7b832c015bb38eae963f2e57498c3c173", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/mime": "^4.3|^5.0", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Defines an object-oriented layer for the HTTP specification", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-foundation/tree/v4.4.49" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-04T16:17:57+00:00" - }, - { - "name": "symfony/http-kernel", - "version": "v4.4.50", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "aa6df6c045f034aa13ac752fc234bb300b9488ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aa6df6c045f034aa13ac752fc234bb300b9488ef", - "reference": "aa6df6c045f034aa13ac752fc234bb300b9488ef", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2", - "symfony/error-handler": "^4.4", - "symfony/event-dispatcher": "^4.4", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4.30|^5.3.7", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/browser-kit": "<4.3", - "symfony/config": "<3.4", - "symfony/console": ">=5", - "symfony/dependency-injection": "<4.3", - "symfony/translation": "<4.2", - "twig/twig": "<1.43|<2.13,>=2" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.3|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0", - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^4.3|^5.0", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/routing": "^3.4|^4.0|^5.0", - "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/templating": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^1.43|^2.13|^3.0.4" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a structured process for converting a Request into a Response", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/http-kernel/tree/v4.4.50" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-02-01T08:01:31+00:00" - }, - { - "name": "symfony/mime", - "version": "v5.4.13", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "bb2ccf759e2b967dcd11bdee5bdf30dddd2290bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bb2ccf759e2b967dcd11bdee5bdf30dddd2290bd", - "reference": "bb2ccf759e2b967dcd11bdee5bdf30dddd2290bd", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.1|^6.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.2|^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows manipulating MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.13" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-01T18:18:29+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-iconv", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "927013f3aac555983a5059aada98e1907d842695" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695", - "reference": "927013f3aac555983a5059aada98e1907d842695", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-iconv": "*" - }, - "suggest": { - "ext-iconv": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Iconv extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, - { - "name": "symfony/process", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "reference": "5cee9cdc4f7805e2699d9fd66991a0e6df8252a2", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T13:16:42+00:00" - }, - { - "name": "symfony/psr-http-message-bridge", - "version": "v2.1.4", - "source": { - "type": "git", - "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "a125b93ef378c492e274f217874906fb9babdebb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/a125b93ef378c492e274f217874906fb9babdebb", - "reference": "a125b93ef378c492e274f217874906fb9babdebb", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0", - "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" - }, - "require-dev": { - "nyholm/psr7": "^1.1", - "psr/log": "^1.1 || ^2 || ^3", - "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4@dev || ^6.0" - }, - "suggest": { - "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" - }, - "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-main": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bridge\\PsrHttpMessage\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - } - ], - "description": "PSR HTTP message bridge", - "homepage": "http://symfony.com", - "keywords": [ - "http", - "http-message", - "psr-17", - "psr-7" - ], - "support": { - "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-28T22:46:34+00:00" - }, - { - "name": "symfony/routing", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "f7751fd8b60a07f3f349947a309b5bdfce22d6ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/f7751fd8b60a07f3f349947a309b5bdfce22d6ae", - "reference": "f7751fd8b60a07f3f349947a309b5bdfce22d6ae", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/config": "<4.2", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4", - "psr/log": "^1|^2|^3", - "symfony/config": "^4.2|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation loader", - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Routing\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Maps an HTTP request to a set of configuration variables", - "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], - "support": { - "source": "https://github.com/symfony/routing/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-20T09:59:04+00:00" - }, - { - "name": "symfony/serializer", - "version": "v4.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/serializer.git", - "reference": "6e01d63c55657930a6de03d6e36aae50af98888d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/6e01d63c55657930a6de03d6e36aae50af98888d", - "reference": "6e01d63c55657930a6de03d6e36aae50af98888d", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.0|>=3.2.0,<3.2.2", - "phpdocumentor/type-resolver": "<0.3.0|1.3.*", - "symfony/dependency-injection": "<3.4", - "symfony/property-access": "<3.4", - "symfony/property-info": "<3.4", - "symfony/yaml": "<3.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4", - "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^3.4|^4.0|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "^4.4|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/property-access": "^4.4.36|^5.3.13", - "symfony/property-info": "^3.4.13|~4.0|^5.0", - "symfony/validator": "^3.4|^4.0|^5.0", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping.", - "psr/cache-implementation": "For using the metadata cache.", - "symfony/config": "For using the XML mapping loader.", - "symfony/http-foundation": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/property-info": "To deserialize relations.", - "symfony/yaml": "For using the default YAML mapping loader." - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Serializer\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/serializer/tree/v4.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-19T08:38:33+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-30T19:17:29+00:00" - }, - { - "name": "symfony/translation", - "version": "v4.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/45036b1d53accc48fe9bab71ccd86d57eba0dd94", - "reference": "45036b1d53accc48fe9bab71ccd86d57eba0dd94", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1.6|^2" - }, - "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/yaml": "<3.4" - }, - "provide": { - "symfony/translation-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/finder": "~2.8|~3.0|~4.0|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/intl": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Translation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to internationalize your application", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/translation/tree/v4.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-03T15:15:11+00:00" - }, - { - "name": "symfony/translation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to translation", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T16:58:25+00:00" - }, - { - "name": "symfony/validator", - "version": "v4.4.48", - "source": { - "type": "git", - "url": "https://github.com/symfony/validator.git", - "reference": "54781a4c41efbd283b779110bf8ae7f263737775" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/54781a4c41efbd283b779110bf8ae7f263737775", - "reference": "54781a4c41efbd283b779110bf8ae7f263737775", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2" - }, - "conflict": { - "doctrine/lexer": "<1.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.3", - "symfony/translation": ">=5.0", - "symfony/yaml": "<3.4" - }, - "require-dev": { - "doctrine/annotations": "^1.10.4", - "doctrine/cache": "^1.0|^2.0", - "egulias/email-validator": "^2.1.10|^3", - "symfony/cache": "^3.4|^4.0|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/http-foundation": "^4.1|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/intl": "^4.3|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/property-access": "^3.4|^4.0|^5.0", - "symfony/property-info": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2", - "symfony/yaml": "^3.4|^4.0|^5.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", - "doctrine/cache": "For using the default cached annotation reader.", - "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", - "symfony/config": "", - "symfony/expression-language": "For using the Expression validator", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For accessing properties within comparison constraints", - "symfony/property-info": "To automatically add NotNull and Type constraints", - "symfony/translation": "For translating validation errors.", - "symfony/yaml": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Validator\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to validate values", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/validator/tree/v4.4.48" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-10-25T13:54:11+00:00" - }, - { - "name": "symfony/var-dumper", - "version": "v5.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "e706c99b4a6f4d9383b52b80dd8c74880501e314" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e706c99b4a6f4d9383b52b80dd8c74880501e314", - "reference": "e706c99b4a6f4d9383b52b80dd8c74880501e314", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/console": "<4.4" - }, - "require-dev": { - "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, - "bin": [ - "Resources/bin/var-dump-server" - ], - "type": "library", - "autoload": { - "files": [ - "Resources/functions/dump.php" - ], - "psr-4": { - "Symfony\\Component\\VarDumper\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", - "homepage": "https://symfony.com", - "keywords": [ - "debug", - "dump" - ], - "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.26" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-13T07:32:46+00:00" - }, - { - "name": "symfony/yaml", - "version": "v4.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-02T15:47:23+00:00" - }, - { - "name": "twig/twig", - "version": "v2.15.5", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "fc02a6af3eeb97c4bf5650debc76c2eda85ac22e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/fc02a6af3eeb97c4bf5650debc76c2eda85ac22e", - "reference": "fc02a6af3eeb97c4bf5650debc76c2eda85ac22e", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.8" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.15-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "support": { - "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.15.5" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/twig/twig", - "type": "tidelift" - } - ], - "time": "2023-05-03T17:49:41+00:00" - }, - { - "name": "typo3/phar-stream-wrapper", - "version": "v3.1.7", - "source": { - "type": "git", - "url": "https://github.com/TYPO3/phar-stream-wrapper.git", - "reference": "5cc2f04a4e2f5c7e9cc02a3bdf80fae0f3e11a8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/5cc2f04a4e2f5c7e9cc02a3bdf80fae0f3e11a8c", - "reference": "5cc2f04a4e2f5c7e9cc02a3bdf80fae0f3e11a8c", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.0 || ^8.0" - }, - "require-dev": { - "ext-xdebug": "*", - "phpspec/prophecy": "^1.10", - "symfony/phpunit-bridge": "^5.1" - }, - "suggest": { - "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "v3.x-dev" - } - }, - "autoload": { - "psr-4": { - "TYPO3\\PharStreamWrapper\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Interceptors for PHP's native phar:// stream handling", - "homepage": "https://typo3.org/", - "keywords": [ - "phar", - "php", - "security", - "stream-wrapper" - ], - "support": { - "issues": "https://github.com/TYPO3/phar-stream-wrapper/issues", - "source": "https://github.com/TYPO3/phar-stream-wrapper/tree/v3.1.7" - }, - "time": "2021-09-20T19:19:13+00:00" - } - ], - "packages-dev": [ - { - "name": "behat/mink", - "version": "v1.10.0", - "source": { - "type": "git", - "url": "https://github.com/minkphp/Mink.git", - "reference": "19e58905632e7cfdc5b2bafb9b950a3521af32c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/Mink/zipball/19e58905632e7cfdc5b2bafb9b950a3521af32c5", - "reference": "19e58905632e7cfdc5b2bafb9b950a3521af32c5", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/css-selector": "^4.4 || ^5.0 || ^6.0" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.22 || ^9.5.11", - "symfony/error-handler": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.4 || ^6.0" - }, - "suggest": { - "behat/mink-browserkit-driver": "fast headless driver for any app without JS emulation", - "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)", - "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)", - "dmore/chrome-mink-driver": "fast and JS-enabled driver for any app (requires chromium or google chrome)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Browser controller/emulator abstraction for PHP", - "homepage": "https://mink.behat.org/", - "keywords": [ - "browser", - "testing", - "web" - ], - "support": { - "issues": "https://github.com/minkphp/Mink/issues", - "source": "https://github.com/minkphp/Mink/tree/v1.10.0" - }, - "time": "2022-03-28T14:22:43+00:00" - }, - { - "name": "behat/mink-selenium2-driver", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/minkphp/MinkSelenium2Driver.git", - "reference": "e5f8421654930da725499fb92983e6948c6f973e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/e5f8421654930da725499fb92983e6948c6f973e", - "reference": "e5f8421654930da725499fb92983e6948c6f973e", - "shasum": "" - }, - "require": { - "behat/mink": "^1.9@dev", - "ext-json": "*", - "instaclick/php-webdriver": "^1.4", - "php": ">=7.2" - }, - "require-dev": { - "mink/driver-testsuite": "dev-master", - "phpunit/phpunit": "^8.5.22 || ^9.5.11", - "symfony/error-handler": "^4.4 || ^5.0" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Pete Otaqui", - "email": "pete@otaqui.com", - "homepage": "https://github.com/pete-otaqui" - }, - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Selenium2 (WebDriver) driver for Mink framework", - "homepage": "https://mink.behat.org/", - "keywords": [ - "ajax", - "browser", - "javascript", - "selenium", - "testing", - "webdriver" - ], - "support": { - "issues": "https://github.com/minkphp/MinkSelenium2Driver/issues", - "source": "https://github.com/minkphp/MinkSelenium2Driver/tree/v1.6.0" - }, - "time": "2022-03-28T14:55:17+00:00" - }, - { - "name": "composer/ca-bundle", - "version": "1.3.6", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.6" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-06-06T12:02:59+00:00" - }, - { - "name": "composer/composer", - "version": "2.2.21", - "source": { - "type": "git", - "url": "https://github.com/composer/composer.git", - "reference": "978198befc71de0b18fc1fc5a472c03b184b504a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/978198befc71de0b18fc1fc5a472c03b184b504a", - "reference": "978198befc71de0b18fc1fc5a472c03b184b504a", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "composer/metadata-minifier": "^1.0", - "composer/pcre": "^1.0", - "composer/semver": "^3.0", - "composer/spdx-licenses": "^1.2", - "composer/xdebug-handler": "^2.0 || ^3.0", - "justinrainbow/json-schema": "^5.2.11", - "php": "^5.3.2 || ^7.0 || ^8.0", - "psr/log": "^1.0 || ^2.0", - "react/promise": "^1.2 || ^2.7", - "seld/jsonlint": "^1.4", - "seld/phar-utils": "^1.0", - "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0", - "symfony/filesystem": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", - "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" - }, - "require-dev": { - "phpspec/prophecy": "^1.10", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" - }, - "suggest": { - "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", - "ext-zip": "Enabling the zip extension allows you to unzip archives", - "ext-zlib": "Allow gzip compression of HTTP requests" - }, - "bin": [ - "bin/composer" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.2-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\": "src/Composer" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "https://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", - "homepage": "https://getcomposer.org/", - "keywords": [ - "autoload", - "dependency", - "package" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/composer/issues", - "source": "https://github.com/composer/composer/tree/2.2.21" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-02-15T12:07:40+00:00" - }, - { - "name": "composer/metadata-minifier", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/composer/metadata-minifier.git", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", - "reference": "c549d23829536f0d0e984aaabbf02af91f443207", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "composer/composer": "^2", - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\MetadataMinifier\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Small utility library that handles metadata minification and expansion.", - "keywords": [ - "composer", - "compression" - ], - "support": { - "issues": "https://github.com/composer/metadata-minifier/issues", - "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-04-07T13:37:33+00:00" - }, - { - "name": "composer/pcre", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/1.0.1" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-21T20:24:37+00:00" - }, - { - "name": "composer/spdx-licenses", - "version": "1.5.7", - "source": { - "type": "git", - "url": "https://github.com/composer/spdx-licenses.git", - "reference": "c848241796da2abf65837d51dce1fae55a960149" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149", - "reference": "c848241796da2abf65837d51dce1fae55a960149", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Spdx\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "SPDX licenses list and validation library.", - "keywords": [ - "license", - "spdx", - "validator" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/spdx-licenses/issues", - "source": "https://github.com/composer/spdx-licenses/tree/1.5.7" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-05-23T07:37:50+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-02-25T21:32:43+00:00" - }, - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "4be43904336affa5c2f70744a348312336afd0da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", - "reference": "4be43904336affa5c2f70744a348312336afd0da", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "*", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "franck.nijhof@dealerdirect.com", - "homepage": "http://www.frenck.nl", - "role": "Developer / IT Manager" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://www.dealerdirect.com", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "source": "https://github.com/PHPCSStandards/composer-installer" - }, - "time": "2023-01-05T11:28:13+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:23:10+00:00" - }, - { - "name": "drupal/coder", - "version": "8.3.21", - "source": { - "type": "git", - "url": "https://github.com/pfrenssen/coder.git", - "reference": "a0b76c6c8ea277b07d58fa75dcacf102a203ad51" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/pfrenssen/coder/zipball/a0b76c6c8ea277b07d58fa75dcacf102a203ad51", - "reference": "a0b76c6c8ea277b07d58fa75dcacf102a203ad51", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1 || ^1.0.0", - "ext-mbstring": "*", - "php": ">=7.2", - "sirbrillig/phpcs-variable-analysis": "^2.11.7", - "slevomat/coding-standard": "^8.11", - "squizlabs/php_codesniffer": "^3.7.1", - "symfony/yaml": ">=3.4.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.7.12", - "phpunit/phpunit": "^8.0" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "Drupal\\": "coder_sniffer/Drupal/", - "DrupalPractice\\": "coder_sniffer/DrupalPractice/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "description": "Coder is a library to review Drupal code.", - "homepage": "https://www.drupal.org/project/coder", - "keywords": [ - "code review", - "phpcs", - "standards" - ], - "support": { - "issues": "https://www.drupal.org/project/issues/coder", - "source": "https://www.drupal.org/project/coder" - }, - "time": "2023-07-17T15:36:49+00:00" - }, - { - "name": "easyrdf/easyrdf", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/easyrdf/easyrdf.git", - "reference": "c7b0a9dbcb211eb7de03ee99ff5b52d17f2a8e64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/easyrdf/easyrdf/zipball/c7b0a9dbcb211eb7de03ee99ff5b52d17f2a8e64", - "reference": "c7b0a9dbcb211eb7de03ee99ff5b52d17f2a8e64", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "ext-pcre": "*", - "ext-xmlreader": "*", - "lib-libxml": "*", - "php": ">=7.1.0" - }, - "require-dev": { - "code-lts/doctum": "^5", - "ml/json-ld": "~1.0", - "phpunit/phpunit": "^7", - "semsol/arc2": "^2.4", - "squizlabs/php_codesniffer": "3.*", - "zendframework/zend-http": "~2.3" - }, - "suggest": { - "ml/json-ld": "~1.0", - "semsol/arc2": "~2.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "EasyRdf\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nicholas Humfrey", - "email": "njh@aelius.com", - "homepage": "http://www.aelius.com/njh/", - "role": "Developer" - }, - { - "name": "Alexey Zakhlestin", - "email": "indeyets@gmail.com", - "homepage": "http://indeyets.ru/", - "role": "Developer" - } - ], - "description": "EasyRdf is a PHP library designed to make it easy to consume and produce RDF.", - "homepage": "http://www.easyrdf.org/", - "keywords": [ - "Linked Data", - "RDF", - "Semantic Web", - "Turtle", - "rdfa", - "sparql" - ], - "support": { - "forum": "http://groups.google.com/group/easyrdf/", - "issues": "http://github.com/easyrdf/easyrdf/issues", - "source": "https://github.com/easyrdf/easyrdf/tree/1.1.1" - }, - "time": "2020-12-02T08:47:31+00:00" - }, - { - "name": "friends-of-behat/mink-browserkit-driver", - "version": "v1.6.1", - "source": { - "type": "git", - "url": "https://github.com/FriendsOfBehat/MinkBrowserKitDriver.git", - "reference": "b3c29f18fe20487846e4c2733b066ec5e47f4f76" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FriendsOfBehat/MinkBrowserKitDriver/zipball/b3c29f18fe20487846e4c2733b066ec5e47f4f76", - "reference": "b3c29f18fe20487846e4c2733b066ec5e47f4f76", - "shasum": "" - }, - "require": { - "behat/mink": "^1.7", - "php": "^7.4|^8.0", - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0" - }, - "replace": { - "behat/mink-browserkit-driver": "self.version" - }, - "require-dev": { - "friends-of-behat/mink-driver-testsuite": "dev-master", - "symfony/http-kernel": "^4.4|^5.0|^6.0" - }, - "type": "mink-driver", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Behat\\Mink\\Driver\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - } - ], - "description": "Symfony2 BrowserKit driver for Mink framework", - "homepage": "http://mink.behat.org/", - "keywords": [ - "Mink", - "Symfony2", - "browser", - "testing" - ], - "support": { - "source": "https://github.com/FriendsOfBehat/MinkBrowserKitDriver/tree/v1.6.1" - }, - "time": "2021-12-13T10:41:57+00:00" - }, - { - "name": "instaclick/php-webdriver", - "version": "1.4.16", - "source": { - "type": "git", - "url": "https://github.com/instaclick/php-webdriver.git", - "reference": "a39a1f6dc0f4ddd8b2438fa5eb1f67755730d606" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/a39a1f6dc0f4ddd8b2438fa5eb1f67755730d606", - "reference": "a39a1f6dc0f4ddd8b2438fa5eb1f67755730d606", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.5", - "satooshi/php-coveralls": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-0": { - "WebDriver": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Justin Bishop", - "email": "jubishop@gmail.com", - "role": "Developer" - }, - { - "name": "Anthon Pang", - "email": "apang@softwaredevelopment.ca", - "role": "Fork Maintainer" - } - ], - "description": "PHP WebDriver for Selenium 2", - "homepage": "http://instaclick.com/", - "keywords": [ - "browser", - "selenium", - "webdriver", - "webtest" - ], - "support": { - "issues": "https://github.com/instaclick/php-webdriver/issues", - "source": "https://github.com/instaclick/php-webdriver/tree/1.4.16" - }, - "time": "2022-10-28T13:30:35+00:00" - }, - { - "name": "justinrainbow/json-schema", - "version": "5.2.12", - "source": { - "type": "git", - "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "reference": "ad87d5a5ca981228e0e205c2bc7dfb8e24559b60", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", - "json-schema/json-schema-test-suite": "1.2.0", - "phpunit/phpunit": "^4.8.35" - }, - "bin": [ - "bin/validate-json" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "JsonSchema\\": "src/JsonSchema/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bruno Prieto Reis", - "email": "bruno.p.reis@gmail.com" - }, - { - "name": "Justin Rainbow", - "email": "justin.rainbow@gmail.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - }, - { - "name": "Robert Schönthal", - "email": "seroscho@googlemail.com" - } - ], - "description": "A library to validate a json schema.", - "homepage": "https://github.com/justinrainbow/json-schema", - "keywords": [ - "json", - "schema" - ], - "support": { - "issues": "https://github.com/justinrainbow/json-schema/issues", - "source": "https://github.com/justinrainbow/json-schema/tree/5.2.12" - }, - "time": "2022-04-13T08:02:27+00:00" - }, - { - "name": "mikey179/vfsstream", - "version": "v1.6.11", - "source": { - "type": "git", - "url": "https://github.com/bovigo/vfsStream.git", - "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", - "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.5|^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "org\\bovigo\\vfs\\": "src/main/php" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" - } - ], - "description": "Virtual file system to mock the real file system in unit tests.", - "homepage": "http://vfs.bovigo.org/", - "support": { - "issues": "https://github.com/bovigo/vfsStream/issues", - "source": "https://github.com/bovigo/vfsStream/tree/master", - "wiki": "https://github.com/bovigo/vfsStream/wiki" - }, - "time": "2022-02-23T02:02:42+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.11.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2023-03-08T13:26:56+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v4.17.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=7.0" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" - }, - "time": "2023-08-13T19:53:39+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" - }, - "time": "2021-07-20T11:28:43+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.7.3", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" - }, - "require-dev": { - "ext-tokenizer": "*", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.13.9", - "vimeo/psalm": "^4.25" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" - }, - "time": "2023-08-12T11:01:26+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.17.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/15873c65b207b07765dbc3c95d20fdf4a320cbe2", - "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.17.0" - }, - "time": "2023-02-02T15:41:36+00:00" - }, - { - "name": "phpstan/phpdoc-parser", - "version": "1.23.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" - }, - "time": "2023-08-03T16:32:59+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "9.2.27", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1", - "reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "PHP extension that provides line coverage", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-07-26T13:44:30+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2021-12-02T12:48:52+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:58:55+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T05:33:50+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "5.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:16:10+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "9.5.28", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e", - "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.3.1 || ^2", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" - }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" - } - ], - "time": "2023-01-14T12:32:24+00:00" - }, - { - "name": "react/promise", - "version": "v2.10.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", - "reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.10.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-05-02T15:15:43+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:08:49+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/comparator", - "version": "4.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T12:41:17+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T15:52:27+00:00" - }, - { - "name": "sebastian/diff", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-05-07T05:35:17+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:03:51+00:00" - }, - { - "name": "sebastian/exporter", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2022-09-14T06:03:37+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-08-02T09:26:13+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-11-28T06:42:11+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:45:17+00:00" - }, - { - "name": "sebastian/type", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:13:03+00:00" - }, - { - "name": "sebastian/version", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "seld/jsonlint", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1", - "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1", - "shasum": "" - }, - "require": { - "php": "^5.3 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.5", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" - }, - "bin": [ - "bin/jsonlint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "JSON Linter", - "keywords": [ - "json", - "linter", - "parser", - "validator" - ], - "support": { - "issues": "https://github.com/Seldaek/jsonlint/issues", - "source": "https://github.com/Seldaek/jsonlint/tree/1.10.0" - }, - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], - "time": "2023-05-11T13:16:46+00:00" - }, - { - "name": "seld/phar-utils", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", - "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\PharUtils\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "PHAR file format utilities, for when PHP phars you up", - "keywords": [ - "phar" - ], - "support": { - "issues": "https://github.com/Seldaek/phar-utils/issues", - "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" - }, - "time": "2022-08-31T10:31:18+00:00" - }, - { - "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.17", - "source": { - "type": "git", - "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/3b71162a6bf0cde2bff1752e40a1788d8273d049", - "reference": "3b71162a6bf0cde2bff1752e40a1788d8273d049", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "squizlabs/php_codesniffer": "^3.5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "phpcsstandards/phpcsdevcs": "^1.1", - "phpstan/phpstan": "^1.7", - "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0", - "sirbrillig/phpcs-import-detection": "^1.1", - "vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0@beta" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "VariableAnalysis\\": "VariableAnalysis/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Sam Graham", - "email": "php-codesniffer-variableanalysis@illusori.co.uk" - }, - { - "name": "Payton Swick", - "email": "payton@foolord.com" - } - ], - "description": "A PHPCS sniff to detect problems with variables.", - "keywords": [ - "phpcs", - "static analysis" - ], - "support": { - "issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues", - "source": "https://github.com/sirbrillig/phpcs-variable-analysis", - "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" - }, - "time": "2023-08-05T23:46:11+00:00" - }, - { - "name": "slevomat/coding-standard", - "version": "8.13.4", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "4b2af2fb17773656d02fbfb5d18024ebd19fe322" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/4b2af2fb17773656d02fbfb5d18024ebd19fe322", - "reference": "4b2af2fb17773656d02fbfb5d18024ebd19fe322", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.23.0", - "squizlabs/php_codesniffer": "^3.7.1" - }, - "require-dev": { - "phing/phing": "2.17.4", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.26", - "phpstan/phpstan-deprecation-rules": "1.1.3", - "phpstan/phpstan-phpunit": "1.3.13", - "phpstan/phpstan-strict-rules": "1.5.1", - "phpunit/phpunit": "7.5.20|8.5.21|9.6.8|10.2.6" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "8.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "keywords": [ - "dev", - "phpcs" - ], - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.13.4" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2023-07-25T10:28:55+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.7.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2023-02-22T23:07:41+00:00" - }, - { - "name": "symfony/browser-kit", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb", - "reference": "2a1ff40723ef6b29c8229a860a9c8f815ad7dbbb", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/polyfill-php80": "^1.16" - }, - "require-dev": { - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/mime": "^4.3|^5.0", - "symfony/process": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\BrowserKit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/browser-kit/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-25T12:56:14+00:00" - }, - { - "name": "symfony/css-selector", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", - "reference": "bd0a6737e48de45b4b0b7b6fc98c78404ddceaed", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Converts CSS selectors to XPath expressions", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-06-27T13:16:42+00:00" - }, - { - "name": "symfony/dom-crawler", - "version": "v4.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4b8daf6c56801e6d664224261cb100b73edc78a5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4b8daf6c56801e6d664224261cb100b73edc78a5", - "reference": "4b8daf6c56801e6d664224261cb100b73edc78a5", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" - }, - "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases DOM navigation for HTML and XML documents", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v4.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-08-03T12:57:57+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v4.4.42", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v4.4.42" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-05-20T08:49:14+00:00" - }, - { - "name": "symfony/finder", - "version": "v4.4.44", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/66bd787edb5e42ff59d3523f623895af05043e4f", - "reference": "66bd787edb5e42ff59d3523f623895af05043e4f", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.44" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-07-29T07:35:46+00:00" - }, - { - "name": "symfony/lock", - "version": "v4.4.46", - "source": { - "type": "git", - "url": "https://github.com/symfony/lock.git", - "reference": "8b060dd4e10f05219698ceb3a4ad735b19c1be4f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/lock/zipball/8b060dd4e10f05219698ceb3a4ad735b19c1be4f", - "reference": "8b060dd4e10f05219698ceb3a4ad735b19c1be4f", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/dbal": "<2.7" - }, - "require-dev": { - "doctrine/dbal": "^2.7|^3.0", - "predis/predis": "~1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Lock\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jérémy Derussé", - "email": "jeremy@derusse.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", - "homepage": "https://symfony.com", - "keywords": [ - "cas", - "flock", - "locking", - "mutex", - "redlock", - "semaphore" - ], - "support": { - "source": "https://github.com/symfony/lock/tree/v4.4.46" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-09-19T08:41:12+00:00" - }, - { - "name": "symfony/phpunit-bridge", - "version": "v5.4.26", - "source": { - "type": "git", - "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "d04639b395e25efa4260fc5b12a9fa1eafb38a64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/d04639b395e25efa4260fc5b12a9fa1eafb38a64", - "reference": "d04639b395e25efa4260fc5b12a9fa1eafb38a64", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "phpunit/phpunit": "<7.5|9.1.2" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" - }, - "bin": [ - "bin/simple-phpunit" - ], - "type": "symfony-bridge", - "extra": { - "thanks": { - "name": "phpunit/phpunit", - "url": "https://github.com/sebastianbergmann/phpunit" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides utilities for PHPUnit, especially user deprecation notices management", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v5.4.26" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-07-12T15:44:31+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - } - ], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": { - "drupal/core": 20, - "drupal/core-project-message": 20, - "drupal/core-vendor-hardening": 20 - }, - "prefer-stable": true, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "platform-overrides": { - "php": "7.3.0" - }, - "plugin-api-version": "2.2.0" -} diff --git a/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/CoreRecommended/composer.json b/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/CoreRecommended/composer.json index 83bbbcc7b0..92073a2784 100644 --- a/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/CoreRecommended/composer.json +++ b/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/CoreRecommended/composer.json @@ -9,25 +9,25 @@ "require": { "drupal/core": "9.5.x-dev", "asm89/stack-cors": "~1.3.0", - "composer/semver": "~3.3.2", + "composer/semver": "~3.4.2", "doctrine/annotations": "~1.14.3", - "doctrine/deprecations": "~v1.1.1", - "doctrine/lexer": "~2.1.0", + "doctrine/deprecations": "~1.1.3", + "doctrine/lexer": "~2.1.1", "doctrine/reflection": "~1.2.4", "egulias/email-validator": "~3.2.6", - "guzzlehttp/guzzle": "~7.7.0", - "guzzlehttp/promises": "~2.0.1", - "guzzlehttp/psr7": "~2.6.0", + "guzzlehttp/guzzle": "~7.9.2", + "guzzlehttp/promises": "~2.0.3", + "guzzlehttp/psr7": "~2.7.0", "longwave/laminas-diactoros": "~2.14.2", - "masterminds/html5": "~2.8.1", - "pear/archive_tar": "~1.4.14", + "masterminds/html5": "~2.9.0", + "pear/archive_tar": "~1.5.0", "pear/console_getopt": "~v1.4.3", - "pear/pear-core-minimal": "~v1.10.13", + "pear/pear-core-minimal": "~v1.10.15", "pear/pear_exception": "~v1.0.2", "psr/cache": "~3.0.0", "psr/container": "~1.1.2", - "psr/http-client": "~1.0.2", - "psr/http-factory": "~1.0.2", + "psr/http-client": "~1.0.3", + "psr/http-factory": "~1.1.0", "psr/http-message": "~1.1", "psr/log": "~1.1.4", "ralouphie/getallheaders": "~3.0.3", @@ -36,31 +36,31 @@ "symfony/console": "~v4.4.49", "symfony/debug": "~v4.4.44", "symfony/dependency-injection": "~v4.4.49", - "symfony/deprecation-contracts": "~v3.3.0", + "symfony/deprecation-contracts": "~v3.5.0", "symfony/error-handler": "~v4.4.44", "symfony/event-dispatcher": "~v4.4.44", - "symfony/event-dispatcher-contracts": "~v1.1.13", - "symfony/http-client-contracts": "~v2.5.2", + "symfony/event-dispatcher-contracts": "~v1.10.0", + "symfony/http-client-contracts": "~v2.5.3", "symfony/http-foundation": "~v4.4.49", - "symfony/http-kernel": "~v4.4.50", + "symfony/http-kernel": "~v4.4.51", "symfony/mime": "~v5.4.13", - "symfony/polyfill-ctype": "~v1.27.0", - "symfony/polyfill-iconv": "~v1.27.0", - "symfony/polyfill-intl-idn": "~v1.27.0", - "symfony/polyfill-intl-normalizer": "~v1.27.0", - "symfony/polyfill-mbstring": "~v1.27.0", - "symfony/polyfill-php80": "~v1.27.0", + "symfony/polyfill-ctype": "~v1.30.0", + "symfony/polyfill-iconv": "~v1.30.0", + "symfony/polyfill-intl-idn": "~v1.30.0", + "symfony/polyfill-intl-normalizer": "~v1.30.0", + "symfony/polyfill-mbstring": "~v1.30.0", + "symfony/polyfill-php80": "~v1.30.0", "symfony/process": "~v4.4.44", "symfony/psr-http-message-bridge": "~v2.1.4", "symfony/routing": "~v4.4.44", "symfony/serializer": "~v4.4.47", - "symfony/service-contracts": "~v2.5.2", + "symfony/service-contracts": "~v2.5.3", "symfony/translation": "~v4.4.47", - "symfony/translation-contracts": "~v2.5.2", + "symfony/translation-contracts": "~v2.5.3", "symfony/validator": "~v4.4.48", - "symfony/var-dumper": "~v5.4.26", + "symfony/var-dumper": "~v5.4.42", "symfony/yaml": "~v4.4.45", - "twig/twig": "~v2.15.5", + "twig/twig": "~v2.16.0", "typo3/phar-stream-wrapper": "~v3.1.7" } } diff --git a/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/PinnedDevDependencies/composer.json b/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/PinnedDevDependencies/composer.json index 01acdd1f7c..945c52aac1 100644 --- a/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/PinnedDevDependencies/composer.json +++ b/tests/Frameworks/Drupal/Version_9_5/composer/Metapackage/PinnedDevDependencies/composer.json @@ -8,67 +8,67 @@ }, "require": { "drupal/core": "9.5.x-dev", - "behat/mink": "v1.10.0", - "behat/mink-selenium2-driver": "v1.6.0", - "composer/ca-bundle": "1.3.6", - "composer/composer": "2.2.21", + "behat/mink": "v1.11.0", + "behat/mink-selenium2-driver": "v1.7.0", + "composer/ca-bundle": "1.5.1", + "composer/composer": "2.2.24", "composer/metadata-minifier": "1.0.0", "composer/pcre": "1.0.1", - "composer/spdx-licenses": "1.5.7", - "composer/xdebug-handler": "3.0.3", + "composer/spdx-licenses": "1.5.8", + "composer/xdebug-handler": "3.0.5", "dealerdirect/phpcodesniffer-composer-installer": "v1.0.0", "doctrine/instantiator": "2.0.0", - "drupal/coder": "8.3.21", + "drupal/coder": "8.3.24", "easyrdf/easyrdf": "1.1.1", - "friends-of-behat/mink-browserkit-driver": "v1.6.1", - "instaclick/php-webdriver": "1.4.16", - "justinrainbow/json-schema": "5.2.12", + "friends-of-behat/mink-browserkit-driver": "v1.6.2", + "instaclick/php-webdriver": "1.4.19", + "justinrainbow/json-schema": "5.3.0", "mikey179/vfsstream": "v1.6.11", - "myclabs/deep-copy": "1.11.1", - "nikic/php-parser": "v4.17.1", - "phar-io/manifest": "2.0.3", + "myclabs/deep-copy": "1.12.0", + "nikic/php-parser": "v5.1.0", + "phar-io/manifest": "2.0.4", "phar-io/version": "3.2.1", "phpdocumentor/reflection-common": "2.2.0", - "phpdocumentor/reflection-docblock": "5.3.0", - "phpdocumentor/type-resolver": "1.7.3", - "phpspec/prophecy": "v1.17.0", - "phpstan/phpdoc-parser": "1.23.1", - "phpunit/php-code-coverage": "9.2.27", + "phpdocumentor/reflection-docblock": "5.4.1", + "phpdocumentor/type-resolver": "1.8.2", + "phpspec/prophecy": "v1.19.0", + "phpstan/phpdoc-parser": "1.29.1", + "phpunit/php-code-coverage": "9.2.31", "phpunit/php-file-iterator": "3.0.6", "phpunit/php-invoker": "3.1.1", "phpunit/php-text-template": "2.0.4", "phpunit/php-timer": "5.0.3", "phpunit/phpunit": "9.5.28", - "react/promise": "v2.10.0", - "sebastian/cli-parser": "1.0.1", + "react/promise": "v2.11.0", + "sebastian/cli-parser": "1.0.2", "sebastian/code-unit": "1.0.8", "sebastian/code-unit-reverse-lookup": "2.0.3", "sebastian/comparator": "4.0.8", - "sebastian/complexity": "2.0.2", - "sebastian/diff": "4.0.5", + "sebastian/complexity": "2.0.3", + "sebastian/diff": "4.0.6", "sebastian/environment": "5.1.5", - "sebastian/exporter": "4.0.5", - "sebastian/global-state": "5.0.6", - "sebastian/lines-of-code": "1.0.3", + "sebastian/exporter": "4.0.6", + "sebastian/global-state": "5.0.7", + "sebastian/lines-of-code": "1.0.4", "sebastian/object-enumerator": "4.0.4", "sebastian/object-reflector": "2.0.4", "sebastian/recursion-context": "4.0.5", - "sebastian/resource-operations": "3.0.3", + "sebastian/resource-operations": "3.0.4", "sebastian/type": "3.2.1", "sebastian/version": "3.0.2", - "seld/jsonlint": "1.10.0", + "seld/jsonlint": "1.11.0", "seld/phar-utils": "1.2.1", - "sirbrillig/phpcs-variable-analysis": "v2.11.17", - "slevomat/coding-standard": "8.13.4", - "squizlabs/php_codesniffer": "3.7.2", + "sirbrillig/phpcs-variable-analysis": "v2.11.19", + "slevomat/coding-standard": "8.15.0", + "squizlabs/php_codesniffer": "3.10.2", "symfony/browser-kit": "v4.4.44", "symfony/css-selector": "v4.4.44", "symfony/dom-crawler": "v4.4.45", "symfony/filesystem": "v4.4.42", "symfony/finder": "v4.4.44", "symfony/lock": "v4.4.46", - "symfony/phpunit-bridge": "v5.4.26", - "theseer/tokenizer": "1.2.1", + "symfony/phpunit-bridge": "v5.4.42", + "theseer/tokenizer": "1.2.3", "webmozart/assert": "1.11.0" } } diff --git a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/DependencyInjection/composer.json b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/DependencyInjection/composer.json index fc523503db..be6eea2034 100644 --- a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/DependencyInjection/composer.json +++ b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/DependencyInjection/composer.json @@ -15,7 +15,7 @@ "require": { "php": ">=7.3.0", "symfony/dependency-injection": "^4.4", - "symfony/service-contracts": "v2.5.2" + "symfony/service-contracts": "v2.5.3" }, "suggest": { "symfony/expression-language": "For using expressions in service container configuration" diff --git a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/composer.json b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/composer.json index 3b934854fa..26e5b2ca3b 100644 --- a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/composer.json +++ b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/composer.json @@ -10,7 +10,7 @@ "php": ">=7.3.0", "symfony/dependency-injection": "^4.4", "symfony/event-dispatcher": "^4.4", - "symfony/event-dispatcher-contracts": "v1.1.13" + "symfony/event-dispatcher-contracts": "v1.10.0" }, "autoload": { "psr-4": { diff --git a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Command/InstallCommand.php b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Command/InstallCommand.php index 40f091c060..859f9769c4 100644 --- a/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Command/InstallCommand.php +++ b/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Command/InstallCommand.php @@ -143,7 +143,7 @@ protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, 'install_settings_form' => [ 'driver' => 'mysql', 'mysql' => [ - 'database' => 'test', + 'database' => 'drupal95', 'username' => 'test', 'password' => 'test', 'host' => 'mysql_integration', diff --git a/tests/Frameworks/Drupal/Version_9_5/scripts/erase_drupal_db.php b/tests/Frameworks/Drupal/Version_9_5/scripts/erase_drupal_db.php index df21a6ce8d..1fadcf78fd 100644 --- a/tests/Frameworks/Drupal/Version_9_5/scripts/erase_drupal_db.php +++ b/tests/Frameworks/Drupal/Version_9_5/scripts/erase_drupal_db.php @@ -1,36 +1,38 @@ query('DROP TABLE IF EXISTS cache_bootstrap'); -$pdo->query('DROP TABLE IF EXISTS cache_config'); -$pdo->query('DROP TABLE IF EXISTS cache_container'); // Drupal doesn't like us dropping this table (PDOException) -$pdo->query('DROP TABLE IF EXISTS cache_data'); -$pdo->query('DROP TABLE IF EXISTS cache_default'); -$pdo->query('DROP TABLE IF EXISTS cache_discovery'); -$pdo->query('DROP TABLE IF EXISTS cache_entity'); -$pdo->query('DROP TABLE IF EXISTS config'); -$pdo->query('DROP TABLE IF EXISTS file_managed'); -$pdo->query('DROP TABLE IF EXISTS file_usage'); -$pdo->query('DROP TABLE IF EXISTS key_value'); -$pdo->query('DROP TABLE IF EXISTS key_value_expire'); -$pdo->query('DROP TABLE IF EXISTS menu_tree'); -$pdo->query('DROP TABLE IF EXISTS node'); -$pdo->query('DROP TABLE IF EXISTS node__body'); -$pdo->query('DROP TABLE IF EXISTS node_access'); -$pdo->query('DROP TABLE IF EXISTS node_field_data'); -$pdo->query('DROP TABLE IF EXISTS node_field_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision'); -$pdo->query('DROP TABLE IF EXISTS node_revision__body'); -$pdo->query('DROP TABLE IF EXISTS path_alias'); -$pdo->query('DROP TABLE IF EXISTS path_alias_revision'); -$pdo->query('DROP TABLE IF EXISTS queue'); -$pdo->query('DROP TABLE IF EXISTS router'); -$pdo->query('DROP TABLE IF EXISTS semaphore'); -$pdo->query('DROP TABLE IF EXISTS sequences'); -$pdo->query('DROP TABLE IF EXISTS sessions'); -$pdo->query('DROP TABLE IF EXISTS user__roles'); -$pdo->query('DROP TABLE IF EXISTS users'); -$pdo->query('DROP TABLE IF EXISTS users_data'); -$pdo->query('DROP TABLE IF EXISTS users_field_data'); -$pdo->query('DROP TABLE IF EXISTS watchdog'); +$pdo->query("CREATE DATABASE IF NOT EXISTS drupal95"); + +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_bootstrap'); +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_config'); +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_container'); // Drupal doesn't like us dropping this table (PDOException) +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_data'); +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_default'); +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_discovery'); +$pdo->query('DROP TABLE IF EXISTS drupal95.cache_entity'); +$pdo->query('DROP TABLE IF EXISTS drupal95.config'); +$pdo->query('DROP TABLE IF EXISTS drupal95.file_managed'); +$pdo->query('DROP TABLE IF EXISTS drupal95.file_usage'); +$pdo->query('DROP TABLE IF EXISTS drupal95.key_value'); +$pdo->query('DROP TABLE IF EXISTS drupal95.key_value_expire'); +$pdo->query('DROP TABLE IF EXISTS drupal95.menu_tree'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node__body'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node_access'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node_field_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal95.node_revision__body'); +$pdo->query('DROP TABLE IF EXISTS drupal95.path_alias'); +$pdo->query('DROP TABLE IF EXISTS drupal95.path_alias_revision'); +$pdo->query('DROP TABLE IF EXISTS drupal95.queue'); +$pdo->query('DROP TABLE IF EXISTS drupal95.router'); +$pdo->query('DROP TABLE IF EXISTS drupal95.semaphore'); +$pdo->query('DROP TABLE IF EXISTS drupal95.sequences'); +$pdo->query('DROP TABLE IF EXISTS drupal95.sessions'); +$pdo->query('DROP TABLE IF EXISTS drupal95.user__roles'); +$pdo->query('DROP TABLE IF EXISTS drupal95.users'); +$pdo->query('DROP TABLE IF EXISTS drupal95.users_data'); +$pdo->query('DROP TABLE IF EXISTS drupal95.users_field_data'); +$pdo->query('DROP TABLE IF EXISTS drupal95.watchdog'); diff --git a/tests/Frameworks/Laravel/Octane/.gitignore b/tests/Frameworks/Laravel/Octane/.gitignore index 7fe978f851..50aea355c7 100644 --- a/tests/Frameworks/Laravel/Octane/.gitignore +++ b/tests/Frameworks/Laravel/Octane/.gitignore @@ -17,3 +17,4 @@ yarn-error.log /.fleet /.idea /.vscode +/swoole.ini diff --git a/tests/Frameworks/Laravel/Version_10_x/composer.json b/tests/Frameworks/Laravel/Version_10_x/composer.json index 9756f363d4..e0524363d1 100644 --- a/tests/Frameworks/Laravel/Version_10_x/composer.json +++ b/tests/Frameworks/Laravel/Version_10_x/composer.json @@ -36,6 +36,7 @@ "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel10');\"", "@php artisan package:discover --ansi", "@php artisan migrate:fresh --force" ], diff --git a/tests/Frameworks/Laravel/Version_10_x/config/database.php b/tests/Frameworks/Laravel/Version_10_x/config/database.php index cb7cc4d59e..53a0c43b15 100644 --- a/tests/Frameworks/Laravel/Version_10_x/config/database.php +++ b/tests/Frameworks/Laravel/Version_10_x/config/database.php @@ -48,7 +48,7 @@ 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'mysql_integration'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'test'), + 'database' => env('DB_DATABASE', 'laravel10'), 'username' => env('DB_USERNAME', 'test'), 'password' => env('DB_PASSWORD', 'test'), 'unix_socket' => env('DB_SOCKET', ''), diff --git a/tests/Frameworks/Laravel/Version_4_2/app/config/database.php b/tests/Frameworks/Laravel/Version_4_2/app/config/database.php index a883e58d8e..5d27cf0566 100644 --- a/tests/Frameworks/Laravel/Version_4_2/app/config/database.php +++ b/tests/Frameworks/Laravel/Version_4_2/app/config/database.php @@ -55,7 +55,7 @@ 'mysql' => array( 'driver' => 'mysql', 'host' => 'mysql_integration', - 'database' => 'test', + 'database' => 'laravel42', 'username' => 'test', 'password' => 'test', 'charset' => 'utf8', diff --git a/tests/Frameworks/Laravel/Version_4_2/composer.json b/tests/Frameworks/Laravel/Version_4_2/composer.json index 445d6415c9..ecf45b04f6 100644 --- a/tests/Frameworks/Laravel/Version_4_2/composer.json +++ b/tests/Frameworks/Laravel/Version_4_2/composer.json @@ -24,11 +24,13 @@ }, "scripts": { "post-install-cmd": [ + "php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel42');\"", "php artisan clear-compiled", "php artisan optimize", "php artisan migrate --force" ], "post-update-cmd": [ + "php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel42');\"", "php artisan clear-compiled", "php artisan optimize", "php artisan migrate --force" diff --git a/tests/Frameworks/Laravel/Version_5_7/composer.json b/tests/Frameworks/Laravel/Version_5_7/composer.json index 5b6da71e5c..19d687012c 100644 --- a/tests/Frameworks/Laravel/Version_5_7/composer.json +++ b/tests/Frameworks/Laravel/Version_5_7/composer.json @@ -45,6 +45,7 @@ ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel57');\"", "@php artisan package:discover", "@php artisan migrate:fresh --force" ], diff --git a/tests/Frameworks/Laravel/Version_5_7/config/database.php b/tests/Frameworks/Laravel/Version_5_7/config/database.php index 8b11981f9c..3594b0d549 100644 --- a/tests/Frameworks/Laravel/Version_5_7/config/database.php +++ b/tests/Frameworks/Laravel/Version_5_7/config/database.php @@ -43,7 +43,7 @@ 'driver' => 'mysql', 'host' => env('DB_HOST', 'mysql_integration'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'test'), + 'database' => env('DB_DATABASE', 'laravel57'), 'username' => env('DB_USERNAME', 'test'), 'password' => env('DB_PASSWORD', 'test'), 'unix_socket' => env('DB_SOCKET', ''), diff --git a/tests/Frameworks/Laravel/Version_5_8/composer.json b/tests/Frameworks/Laravel/Version_5_8/composer.json index 683f99ddce..cd13f2a6c9 100644 --- a/tests/Frameworks/Laravel/Version_5_8/composer.json +++ b/tests/Frameworks/Laravel/Version_5_8/composer.json @@ -54,6 +54,7 @@ "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel58');\"", "@php artisan package:discover --ansi", "@php artisan migrate:fresh --force" ], diff --git a/tests/Frameworks/Laravel/Version_5_8/config/database.php b/tests/Frameworks/Laravel/Version_5_8/config/database.php index b3c9ea298d..23fdd4eb69 100644 --- a/tests/Frameworks/Laravel/Version_5_8/config/database.php +++ b/tests/Frameworks/Laravel/Version_5_8/config/database.php @@ -44,7 +44,7 @@ 'driver' => 'mysql', 'host' => env('DB_HOST', 'mysql_integration'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'test'), + 'database' => env('DB_DATABASE', 'laravel58'), 'username' => env('DB_USERNAME', 'test'), 'password' => env('DB_PASSWORD', 'test'), 'unix_socket' => env('DB_SOCKET', ''), diff --git a/tests/Frameworks/Laravel/Version_8_x/.env b/tests/Frameworks/Laravel/Version_8_x/.env index 7573c140eb..528c285056 100644 --- a/tests/Frameworks/Laravel/Version_8_x/.env +++ b/tests/Frameworks/Laravel/Version_8_x/.env @@ -10,7 +10,7 @@ LOG_LEVEL=debug DB_CONNECTION=mysql DB_HOST=mysql_integration DB_PORT=3306 -DB_DATABASE=test +DB_DATABASE=laravel8 DB_USERNAME=test DB_PASSWORD=test diff --git a/tests/Frameworks/Laravel/Version_8_x/.env.example b/tests/Frameworks/Laravel/Version_8_x/.env.example index 7573c140eb..528c285056 100644 --- a/tests/Frameworks/Laravel/Version_8_x/.env.example +++ b/tests/Frameworks/Laravel/Version_8_x/.env.example @@ -10,7 +10,7 @@ LOG_LEVEL=debug DB_CONNECTION=mysql DB_HOST=mysql_integration DB_PORT=3306 -DB_DATABASE=test +DB_DATABASE=laravel8 DB_USERNAME=test DB_PASSWORD=test diff --git a/tests/Frameworks/Laravel/Version_8_x/composer.json b/tests/Frameworks/Laravel/Version_8_x/composer.json index 2a0e00ad25..59e68a4857 100644 --- a/tests/Frameworks/Laravel/Version_8_x/composer.json +++ b/tests/Frameworks/Laravel/Version_8_x/composer.json @@ -50,6 +50,7 @@ "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel8');\"", "@php artisan package:discover --ansi", "@php artisan migrate:fresh --force" ], diff --git a/tests/Frameworks/Laravel/Version_8_x/config/database.php b/tests/Frameworks/Laravel/Version_8_x/config/database.php index ba3dd46dda..4268fb0288 100644 --- a/tests/Frameworks/Laravel/Version_8_x/config/database.php +++ b/tests/Frameworks/Laravel/Version_8_x/config/database.php @@ -48,7 +48,7 @@ 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'mysql_integration'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'test'), + 'database' => env('DB_DATABASE', 'laravel8'), 'username' => env('DB_USERNAME', 'test'), 'password' => env('DB_PASSWORD', 'test'), 'unix_socket' => env('DB_SOCKET', ''), diff --git a/tests/Frameworks/Laravel/Version_9_x/composer.json b/tests/Frameworks/Laravel/Version_9_x/composer.json index 87fc0353f9..66ce2a9a7b 100644 --- a/tests/Frameworks/Laravel/Version_9_x/composer.json +++ b/tests/Frameworks/Laravel/Version_9_x/composer.json @@ -36,6 +36,7 @@ "scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php -r \"(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS laravel9');\"", "@php artisan package:discover --ansi", "@php artisan migrate:fresh --force" ], diff --git a/tests/Frameworks/Laravel/Version_9_x/config/database.php b/tests/Frameworks/Laravel/Version_9_x/config/database.php index 9a2d3e5648..a5778340eb 100644 --- a/tests/Frameworks/Laravel/Version_9_x/config/database.php +++ b/tests/Frameworks/Laravel/Version_9_x/config/database.php @@ -48,7 +48,7 @@ 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'mysql_integration'), 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'test'), + 'database' => env('DB_DATABASE', 'laravel9'), 'username' => env('DB_USERNAME', 'test'), 'password' => env('DB_PASSWORD', 'test'), 'unix_socket' => env('DB_SOCKET', ''), diff --git a/tests/Frameworks/Magento/Version_2_3/install-magento b/tests/Frameworks/Magento/Version_2_3/install-magento index c34cd5660f..90738182a5 100755 --- a/tests/Frameworks/Magento/Version_2_3/install-magento +++ b/tests/Frameworks/Magento/Version_2_3/install-magento @@ -1,5 +1,7 @@ #!/usr/bin/env bash +php -r "(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS magento23');" + php -d memory_limit=1G ./bin/magento setup:install \ --base-url=http://localhost/ \ --backend-frontname=admin \ @@ -7,7 +9,7 @@ php -d memory_limit=1G ./bin/magento setup:install \ --timezone=America/Los_Angeles \ --currency=USD \ --db-host=mysql_integration \ - --db-name=test \ + --db-name=magento23 \ --db-user=test \ --db-password=test \ --use-secure=0 \ diff --git a/tests/Frameworks/Magento/Version_2_4/install-magento b/tests/Frameworks/Magento/Version_2_4/install-magento index 8704e879bb..e2725abcd9 100755 --- a/tests/Frameworks/Magento/Version_2_4/install-magento +++ b/tests/Frameworks/Magento/Version_2_4/install-magento @@ -1,5 +1,7 @@ #!/usr/bin/env bash +php -r "(new PDO('mysql:host=mysql_integration', 'test', 'test'))->exec('CREATE DATABASE IF NOT EXISTS magento24');" + php -d memory_limit=1G ./bin/magento setup:install \ --base-url=http://localhost/ \ --backend-frontname=admin \ @@ -7,7 +9,7 @@ php -d memory_limit=1G ./bin/magento setup:install \ --timezone=America/Los_Angeles \ --currency=USD \ --db-host=mysql_integration \ - --db-name=test \ + --db-name=magento24 \ --db-user=test \ --db-password=test \ --use-secure=0 \ diff --git a/tests/Frameworks/Swoole/index.php b/tests/Frameworks/Swoole/index.php index 3eb0a4e914..0d90baade6 100644 --- a/tests/Frameworks/Swoole/index.php +++ b/tests/Frameworks/Swoole/index.php @@ -2,7 +2,7 @@ require __DIR__ . '/../../vendor/autoload.php'; -$http = new Swoole\Http\Server("0.0.0.0", 9999); +$http = new Swoole\Http\Server("0.0.0.0", $argv[1]); $http->set([ 'worker_num' => 2 ]); diff --git a/tests/Frameworks/Symfony/Version_3_3/app/config/parameters.yml.dist b/tests/Frameworks/Symfony/Version_3_3/app/config/parameters.yml.dist index e03069b4d3..cc3dcbfed5 100644 --- a/tests/Frameworks/Symfony/Version_3_3/app/config/parameters.yml.dist +++ b/tests/Frameworks/Symfony/Version_3_3/app/config/parameters.yml.dist @@ -4,7 +4,7 @@ parameters: database_host: mysql_integration database_port: null - database_name: test + database_name: symfony33 database_user: test database_password: test # You should uncomment this if you want to use pdo_sqlite diff --git a/tests/Frameworks/Symfony/Version_3_3/composer.json b/tests/Frameworks/Symfony/Version_3_3/composer.json index 0d7ae5555a..b6875580d0 100644 --- a/tests/Frameworks/Symfony/Version_3_3/composer.json +++ b/tests/Frameworks/Symfony/Version_3_3/composer.json @@ -50,7 +50,7 @@ "@symfony-scripts", "rm -rf var/cache/dev/*", "rm -rf var/cache/prod/*", - "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:drop --force || true", "@php bin/console doctrine:database:create", "@php bin/console doctrine:schema:update --force" ] diff --git a/tests/Frameworks/Symfony/Version_4_4/.env b/tests/Frameworks/Symfony/Version_4_4/.env index 3a3d8e3d2e..9df636ff8c 100644 --- a/tests/Frameworks/Symfony/Version_4_4/.env +++ b/tests/Frameworks/Symfony/Version_4_4/.env @@ -31,3 +31,10 @@ APP_SECRET=3758dd8072ced4110d2ae1858da9b1a2 # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7 ###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +MESSENGER_TRANSPORT_DSN=doctrine://default +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +###< symfony/messenger ### diff --git a/tests/Frameworks/Symfony/Version_4_4/composer.json b/tests/Frameworks/Symfony/Version_4_4/composer.json index 70a73793e7..b3132c6a7f 100644 --- a/tests/Frameworks/Symfony/Version_4_4/composer.json +++ b/tests/Frameworks/Symfony/Version_4_4/composer.json @@ -23,6 +23,7 @@ "symfony/http-client": "4.4.*", "symfony/intl": "4.4.*", "symfony/mailer": "4.4.*", + "symfony/messenger": "4.4.*", "symfony/monolog-bundle": "^3.1", "symfony/process": "4.4.*", "symfony/property-access": "4.4.*", @@ -90,7 +91,7 @@ "post-autoload-dump": [ "rm -rf var/cache/dev/*", "rm -rf var/cache/prod/*", - "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:drop --force || true", "@php bin/console doctrine:database:create", "@php bin/console doctrine:migrations:migrate -n" ] diff --git a/tests/Frameworks/Symfony/Version_4_4/config/packages/doctrine.yaml b/tests/Frameworks/Symfony/Version_4_4/config/packages/doctrine.yaml index c8ab818b9d..e858f69857 100644 --- a/tests/Frameworks/Symfony/Version_4_4/config/packages/doctrine.yaml +++ b/tests/Frameworks/Symfony/Version_4_4/config/packages/doctrine.yaml @@ -1,6 +1,6 @@ doctrine: dbal: - url: 'mysql://test:test@mysql_integration:3306/test?serverVersion=5&charset=utf8mb4' + url: 'mysql://test:test@mysql_integration:3306/symfony44?serverVersion=5&charset=utf8mb4' # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/tests/Frameworks/Symfony/Version_4_4/config/packages/messenger.yaml b/tests/Frameworks/Symfony/Version_4_4/config/packages/messenger.yaml new file mode 100644 index 0000000000..892bcafbbe --- /dev/null +++ b/tests/Frameworks/Symfony/Version_4_4/config/packages/messenger.yaml @@ -0,0 +1,15 @@ +framework: + messenger: + # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. + # failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: '%env(MESSENGER_TRANSPORT_DSN)%' + # failed: 'doctrine://default?queue_name=failed' + # sync: 'sync://' + + routing: + # Route your messages to the transports + # 'App\Message\YourMessage': async + 'App\Message\LuckyNumberNotification': async diff --git a/tests/Frameworks/Symfony/Version_4_4/migrations/Version20240717085117.php b/tests/Frameworks/Symfony/Version_4_4/migrations/Version20240717085117.php new file mode 100644 index 0000000000..e8717015b6 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_4_4/migrations/Version20240717085117.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, headers LONGTEXT CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, queue_name VARCHAR(190) CHARACTER SET utf8mb4 NOT NULL COLLATE `utf8mb4_unicode_ci`, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E016BA31DB (delivered_at), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E0FB7336F0 (queue_name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB COMMENT = \'\' '); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/tests/Frameworks/Symfony/Version_4_4/src/Controller/LuckyController.php b/tests/Frameworks/Symfony/Version_4_4/src/Controller/LuckyController.php new file mode 100644 index 0000000000..f9405bb43b --- /dev/null +++ b/tests/Frameworks/Symfony/Version_4_4/src/Controller/LuckyController.php @@ -0,0 +1,35 @@ +dispatch(new LuckyNumberNotification($number)); + + return new Response("$number"); + } + + /** + * @Route("/lucky/fail", name="lucky_fail") + + */ + public function fail(MessageBusInterface $bus): Response + { + $bus->dispatch(new LuckyNumberNotification(101)); + + return new Response("101"); + } +} diff --git a/tests/Frameworks/Symfony/Version_4_4/src/Message/LuckyNumberNotification.php b/tests/Frameworks/Symfony/Version_4_4/src/Message/LuckyNumberNotification.php new file mode 100644 index 0000000000..2912d401cb --- /dev/null +++ b/tests/Frameworks/Symfony/Version_4_4/src/Message/LuckyNumberNotification.php @@ -0,0 +1,12 @@ +content = $content; + } +} diff --git a/tests/Frameworks/Symfony/Version_4_4/src/MessageHandler/LuckyNumberNotificationHandler.php b/tests/Frameworks/Symfony/Version_4_4/src/MessageHandler/LuckyNumberNotificationHandler.php new file mode 100644 index 0000000000..b4d99444d1 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_4_4/src/MessageHandler/LuckyNumberNotificationHandler.php @@ -0,0 +1,19 @@ +content > 100 || $message->content < 0) { + throw new UnrecoverableMessageHandlingException("Number is out of bounds"); + } + + echo "Received number: {$message->content}\n"; + } +} diff --git a/tests/Frameworks/Symfony/Version_5_2/.env b/tests/Frameworks/Symfony/Version_5_2/.env index 67da29e1ef..6c79bc6b83 100644 --- a/tests/Frameworks/Symfony/Version_5_2/.env +++ b/tests/Frameworks/Symfony/Version_5_2/.env @@ -16,4 +16,19 @@ ###> symfony/framework-bundle ### APP_ENV=dev APP_SECRET=c8db676eda45ca0ed2cd13c0df87072e -###< symfony/framework-bundle ### \ No newline at end of file +###< symfony/framework-bundle ### +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4" +DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" +###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### diff --git a/tests/Frameworks/Symfony/Version_5_2/composer.json b/tests/Frameworks/Symfony/Version_5_2/composer.json index 38343a82db..13b3edcc34 100644 --- a/tests/Frameworks/Symfony/Version_5_2/composer.json +++ b/tests/Frameworks/Symfony/Version_5_2/composer.json @@ -17,7 +17,9 @@ "symfony/flex": "^1.3.1", "symfony/form": "5.2.*", "symfony/framework-bundle": "5.2.*", + "symfony/messenger": "5.2.*", "symfony/security-bundle": "5.2.*", + "symfony/serializer": "5.2.*", "symfony/twig-bundle": "5.2.*", "symfony/validator": "5.2.*", "symfony/yaml": "5.2.*" @@ -60,7 +62,7 @@ "@auto-scripts" ], "post-autoload-dump": [ - "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:drop --force || true", "@php bin/console doctrine:database:create", "@php bin/console doctrine:migrations:migrate -n" ] diff --git a/tests/Frameworks/Symfony/Version_5_2/config/packages/doctrine.yaml b/tests/Frameworks/Symfony/Version_5_2/config/packages/doctrine.yaml index 91b3d8253b..963c4347b9 100644 --- a/tests/Frameworks/Symfony/Version_5_2/config/packages/doctrine.yaml +++ b/tests/Frameworks/Symfony/Version_5_2/config/packages/doctrine.yaml @@ -1,6 +1,6 @@ doctrine: dbal: - url: 'mysql://test:test@mysql_integration:3306/test?serverVersion=5&charset=utf8mb4' + url: 'mysql://test:test@mysql_integration:3306/symfony52?serverVersion=5&charset=utf8mb4' # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/tests/Frameworks/Symfony/Version_5_2/config/packages/messenger.yaml b/tests/Frameworks/Symfony/Version_5_2/config/packages/messenger.yaml new file mode 100644 index 0000000000..270670b194 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_5_2/config/packages/messenger.yaml @@ -0,0 +1,18 @@ +framework: + messenger: + # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. + failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + retry_strategy: + max_retries: 0 + failed: 'doctrine://default?queue_name=failed' + # sync: 'sync://' + + routing: + # Route your messages to the transports + # 'App\Message\YourMessage': async + 'App\Message\LuckyNumberNotification': async diff --git a/tests/Frameworks/Symfony/Version_5_2/migrations/Version20240712130658.php b/tests/Frameworks/Symfony/Version_5_2/migrations/Version20240712130658.php new file mode 100644 index 0000000000..f51ca99c1b --- /dev/null +++ b/tests/Frameworks/Symfony/Version_5_2/migrations/Version20240712130658.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/tests/Frameworks/Symfony/Version_5_2/src/Controller/LuckyController.php b/tests/Frameworks/Symfony/Version_5_2/src/Controller/LuckyController.php new file mode 100644 index 0000000000..3637825701 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_5_2/src/Controller/LuckyController.php @@ -0,0 +1,34 @@ +dispatch(new LuckyNumberNotification($number)); + + return new Response("$number"); + } + + /** + * @Route("/lucky/fail", name="lucky_fail") + + */ + public function fail(MessageBusInterface $bus): Response + { + $bus->dispatch(new LuckyNumberNotification(101)); + + return new Response("101"); + } +} diff --git a/tests/Frameworks/Symfony/Version_5_2/src/Message/LuckyNumberNotification.php b/tests/Frameworks/Symfony/Version_5_2/src/Message/LuckyNumberNotification.php new file mode 100644 index 0000000000..2912d401cb --- /dev/null +++ b/tests/Frameworks/Symfony/Version_5_2/src/Message/LuckyNumberNotification.php @@ -0,0 +1,12 @@ +content = $content; + } +} diff --git a/tests/Frameworks/Symfony/Version_5_2/src/MessageHandler/LuckyNumberNotificationHandler.php b/tests/Frameworks/Symfony/Version_5_2/src/MessageHandler/LuckyNumberNotificationHandler.php new file mode 100644 index 0000000000..b4d99444d1 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_5_2/src/MessageHandler/LuckyNumberNotificationHandler.php @@ -0,0 +1,19 @@ +content > 100 || $message->content < 0) { + throw new UnrecoverableMessageHandlingException("Number is out of bounds"); + } + + echo "Received number: {$message->content}\n"; + } +} diff --git a/tests/Frameworks/Symfony/Version_6_2/.env b/tests/Frameworks/Symfony/Version_6_2/.env index 0fb400362e..798c34092b 100644 --- a/tests/Frameworks/Symfony/Version_6_2/.env +++ b/tests/Frameworks/Symfony/Version_6_2/.env @@ -27,3 +27,10 @@ APP_SECRET=c8db676eda45ca0ed2cd13c0df87072e # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" ###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +#MESSENGER_TRANSPORT_DSN=redis://redis_integration:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### diff --git a/tests/Frameworks/Symfony/Version_6_2/composer.json b/tests/Frameworks/Symfony/Version_6_2/composer.json index df2e44bbe0..3772cf2aa1 100644 --- a/tests/Frameworks/Symfony/Version_6_2/composer.json +++ b/tests/Frameworks/Symfony/Version_6_2/composer.json @@ -12,10 +12,12 @@ "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.15", "symfony/console": "6.2.*", + "symfony/doctrine-messenger": "6.2.*", "symfony/dotenv": "6.2.*", "symfony/flex": "^1.17.1", "symfony/form": "6.2.*", "symfony/framework-bundle": "6.2.*", + "symfony/messenger": "^6.2", "symfony/monolog-bundle": "^3.8", "symfony/security-bundle": "6.2.*", "symfony/twig-bundle": "6.2.*", @@ -67,7 +69,7 @@ "post-autoload-dump": [ "rm -rf var/cache/dev/*", "rm -rf var/cache/prod/*", - "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:drop --force || true", "@php bin/console doctrine:database:create", "@php bin/console doctrine:migrations:migrate -n" ] diff --git a/tests/Frameworks/Symfony/Version_6_2/config/packages/doctrine.yaml b/tests/Frameworks/Symfony/Version_6_2/config/packages/doctrine.yaml index 85f2faddb4..ab5991e154 100644 --- a/tests/Frameworks/Symfony/Version_6_2/config/packages/doctrine.yaml +++ b/tests/Frameworks/Symfony/Version_6_2/config/packages/doctrine.yaml @@ -1,6 +1,6 @@ doctrine: dbal: - url: 'mysql://test:test@mysql_integration:3306/test?serverVersion=5&charset=utf8mb4' + url: 'mysql://test:test@mysql_integration:3306/symfony62?serverVersion=5&charset=utf8mb4' # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/tests/Frameworks/Symfony/Version_6_2/config/packages/messenger.yaml b/tests/Frameworks/Symfony/Version_6_2/config/packages/messenger.yaml new file mode 100644 index 0000000000..460d08674a --- /dev/null +++ b/tests/Frameworks/Symfony/Version_6_2/config/packages/messenger.yaml @@ -0,0 +1,24 @@ +framework: + messenger: + # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. + # failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + failed: 'doctrine://default?queue_name=failed' + # sync: 'sync://' + + routing: + # Route your messages to the transports + # 'App\Message\YourMessage': async + 'App\Message\LuckyNumberNotification': async + +# when@test: +# framework: +# messenger: +# transports: +# # replace with your transport name here (e.g., my_transport: 'in-memory://') +# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test +# async: 'in-memory://' diff --git a/tests/Frameworks/Symfony/Version_6_2/migrations/Version20240822083531.php b/tests/Frameworks/Symfony/Version_6_2/migrations/Version20240822083531.php new file mode 100644 index 0000000000..e1cfd5eb83 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_6_2/migrations/Version20240822083531.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL, INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/tests/Frameworks/Symfony/Version_6_2/src/Controller/LuckyController.php b/tests/Frameworks/Symfony/Version_6_2/src/Controller/LuckyController.php new file mode 100644 index 0000000000..f82efd38cd --- /dev/null +++ b/tests/Frameworks/Symfony/Version_6_2/src/Controller/LuckyController.php @@ -0,0 +1,29 @@ +dispatch(new LuckyNumberNotification($number)); + + return new Response("$number"); + } + + #[Route('/lucky/fail', 'lucky_fail')] + public function fail(MessageBusInterface $bus): Response + { + $bus->dispatch(new LuckyNumberNotification(101)); + + return new Response("101"); + } +} diff --git a/tests/Frameworks/Symfony/Version_6_2/src/Message/LuckyNumberNotification.php b/tests/Frameworks/Symfony/Version_6_2/src/Message/LuckyNumberNotification.php new file mode 100644 index 0000000000..93c28e3527 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_6_2/src/Message/LuckyNumberNotification.php @@ -0,0 +1,8 @@ +content > 100 || $message->content < 0) { + throw new UnrecoverableMessageHandlingException("Number is out of bounds"); + } + + echo "Received number: {$message->content}\n"; + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/.env b/tests/Frameworks/Symfony/Version_7_0/.env index 4dbed61591..4c4d4e3ac6 100644 --- a/tests/Frameworks/Symfony/Version_7_0/.env +++ b/tests/Frameworks/Symfony/Version_7_0/.env @@ -28,3 +28,10 @@ APP_SECRET=7b46ee8a78f39224283035fe148d0a79 # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" ###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### diff --git a/tests/Frameworks/Symfony/Version_7_0/composer.json b/tests/Frameworks/Symfony/Version_7_0/composer.json index 7e89f29fc8..539329c62b 100644 --- a/tests/Frameworks/Symfony/Version_7_0/composer.json +++ b/tests/Frameworks/Symfony/Version_7_0/composer.json @@ -12,10 +12,12 @@ "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^2.17", "symfony/console": "7.0.*", + "symfony/doctrine-messenger": "^7.1", "symfony/dotenv": "7.0.*", "symfony/flex": "^2", "symfony/form": "7.0.*", "symfony/framework-bundle": "7.0.*", + "symfony/messenger": "^7.1", "symfony/monolog-bundle": "^3.10", "symfony/runtime": "7.0.*", "symfony/security-bundle": "7.0.*", @@ -67,7 +69,7 @@ "post-autoload-dump": [ "rm -rf var/cache/dev/*", "rm -rf var/cache/prod/*", - "@php bin/console doctrine:database:drop --force", + "@php bin/console doctrine:database:drop --force || true", "@php bin/console doctrine:database:create", "@php bin/console doctrine:migrations:migrate -n" ] diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml index 11d765735f..6e9e0bea02 100644 --- a/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/doctrine.yaml @@ -1,6 +1,6 @@ doctrine: dbal: - url: 'mysql://test:test@mysql_integration:3306/test?serverVersion=5&charset=utf8mb4' + url: 'mysql://test:test@mysql_integration:3306/symfony70?serverVersion=5&charset=utf8mb4' # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/tests/Frameworks/Symfony/Version_7_0/config/packages/messenger.yaml b/tests/Frameworks/Symfony/Version_7_0/config/packages/messenger.yaml new file mode 100644 index 0000000000..74adb8c6ff --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/packages/messenger.yaml @@ -0,0 +1,28 @@ +framework: + messenger: + # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. + failure_transport: failed + + transports: + # https://symfony.com/doc/current/messenger.html#transport-configuration + async: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%' + options: + queue_name: high + retry_strategy: + max_retries: 0 + failed: 'doctrine://default?queue_name=failed' + sync: 'sync://' + + routing: + # Route your messages to the transports + 'App\Message\LuckyNumberNotification': async + + +# when@test: +# framework: +# messenger: +# transports: +# # replace with your transport name here (e.g., my_transport: 'in-memory://') +# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test +# async: 'in-memory://' diff --git a/tests/Frameworks/Symfony/Version_7_0/config/routes/security.yaml b/tests/Frameworks/Symfony/Version_7_0/config/routes/security.yaml new file mode 100644 index 0000000000..f853be15cf --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/config/routes/security.yaml @@ -0,0 +1,3 @@ +_security_logout: + resource: security.route_loader.logout + type: service diff --git a/tests/Frameworks/Symfony/Version_7_0/migrations/Version20240711115224.php b/tests/Frameworks/Symfony/Version_7_0/migrations/Version20240711115224.php new file mode 100644 index 0000000000..b8145fbb96 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/migrations/Version20240711115224.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Controller/LuckyController.php b/tests/Frameworks/Symfony/Version_7_0/src/Controller/LuckyController.php new file mode 100644 index 0000000000..f82efd38cd --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Controller/LuckyController.php @@ -0,0 +1,29 @@ +dispatch(new LuckyNumberNotification($number)); + + return new Response("$number"); + } + + #[Route('/lucky/fail', 'lucky_fail')] + public function fail(MessageBusInterface $bus): Response + { + $bus->dispatch(new LuckyNumberNotification(101)); + + return new Response("101"); + } +} diff --git a/tests/Frameworks/Symfony/Version_7_0/src/Message/LuckyNumberNotification.php b/tests/Frameworks/Symfony/Version_7_0/src/Message/LuckyNumberNotification.php new file mode 100644 index 0000000000..93c28e3527 --- /dev/null +++ b/tests/Frameworks/Symfony/Version_7_0/src/Message/LuckyNumberNotification.php @@ -0,0 +1,8 @@ +content > 100 || $message->content < 0) { + throw new \OutOfBoundsException("Number is out of bounds"); + } + + echo "Received number: {$message->content}\n"; + } +} diff --git a/tests/Frameworks/WordPress/Version_4_8/wp-config.php b/tests/Frameworks/WordPress/Version_4_8/wp-config.php index b8f0eed437..35d490484c 100644 --- a/tests/Frameworks/WordPress/Version_4_8/wp-config.php +++ b/tests/Frameworks/WordPress/Version_4_8/wp-config.php @@ -20,7 +20,7 @@ // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ -define('DB_NAME', 'test'); +define('DB_NAME', 'wp48'); /** MySQL database username */ define('DB_USER', 'test'); diff --git a/tests/Frameworks/WordPress/Version_4_8/wp_2019-10-01.sql b/tests/Frameworks/WordPress/Version_4_8/wp_2019-10-01.sql index 344ae0e119..bf38aae351 100644 --- a/tests/Frameworks/WordPress/Version_4_8/wp_2019-10-01.sql +++ b/tests/Frameworks/WordPress/Version_4_8/wp_2019-10-01.sql @@ -121,8 +121,8 @@ LOCK TABLES `wp_options` WRITE; INSERT INTO `wp_options` (`option_id`, `option_name`, `option_value`, `autoload`) VALUES - (1,'siteurl','http://localhost:9999','yes'), - (2,'home','http://localhost:9999','yes'), + (1,'siteurl','http://localhost','yes'), + (2,'home','http://localhost','yes'), (3,'blogname','Datadog','yes'), (4,'blogdescription','Just another WordPress site','yes'), (5,'users_can_register','1','yes'), @@ -336,10 +336,10 @@ LOCK TABLES `wp_posts` WRITE; INSERT INTO `wp_posts` (`ID`, `post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_excerpt`, `post_status`, `comment_status`, `ping_status`, `post_password`, `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`, `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`, `comment_count`) VALUES - (1,1,'2019-09-16 21:10:55','2019-09-16 21:10:55','Welcome to WordPress. This is your first post. Edit or delete it, then start writing!','Hello world!','','publish','open','open','','hello-world','','','2019-09-16 21:10:55','2019-09-16 21:10:55','',0,'http://localhost:9999/?p=1',0,'post','',1), - (2,1,'2019-09-16 21:10:55','2019-09-16 21:10:55','This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:\r\n
Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)
\r\n...or something like this:\r\n
The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.
\r\nAs a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!','Sample Page','','publish','closed','open','','simple_view','','','2019-10-01 17:15:24','2019-10-01 17:15:24','',0,'http://localhost:9999/?page_id=2',0,'page','',0), - (3,1,'2019-09-16 21:11:07','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2019-09-16 21:11:07','0000-00-00 00:00:00','',0,'http://localhost:9999/?p=3',0,'post','',0), - (4,1,'2019-10-01 17:15:24','2019-10-01 17:15:24','This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:\r\n
Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)
\r\n...or something like this:\r\n
The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.
\r\nAs a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!','Sample Page','','inherit','closed','closed','','2-revision-v1','','','2019-10-01 17:15:24','2019-10-01 17:15:24','',2,'http://localhost:9999/2-revision-v1/',0,'revision','',0); + (1,1,'2019-09-16 21:10:55','2019-09-16 21:10:55','Welcome to WordPress. This is your first post. Edit or delete it, then start writing!','Hello world!','','publish','open','open','','hello-world','','','2019-09-16 21:10:55','2019-09-16 21:10:55','',0,'http://localhost/?p=1',0,'post','',1), + (2,1,'2019-09-16 21:10:55','2019-09-16 21:10:55','This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:\r\n
Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)
\r\n...or something like this:\r\n
The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.
\r\nAs a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!','Sample Page','','publish','closed','open','','simple_view','','','2019-10-01 17:15:24','2019-10-01 17:15:24','',0,'http://localhost/?page_id=2',0,'page','',0), + (3,1,'2019-09-16 21:11:07','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2019-09-16 21:11:07','0000-00-00 00:00:00','',0,'http://localhost/?p=3',0,'post','',0), + (4,1,'2019-10-01 17:15:24','2019-10-01 17:15:24','This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:\r\n
Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)
\r\n...or something like this:\r\n
The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.
\r\nAs a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!','Sample Page','','inherit','closed','closed','','2-revision-v1','','','2019-10-01 17:15:24','2019-10-01 17:15:24','',2,'http://localhost/2-revision-v1/',0,'revision','',0); /*!40000 ALTER TABLE `wp_posts` ENABLE KEYS */; UNLOCK TABLES; diff --git a/tests/Frameworks/WordPress/Version_5_5/wp-config.php b/tests/Frameworks/WordPress/Version_5_5/wp-config.php index dd44e66a6d..71300d72b7 100644 --- a/tests/Frameworks/WordPress/Version_5_5/wp-config.php +++ b/tests/Frameworks/WordPress/Version_5_5/wp-config.php @@ -20,7 +20,7 @@ // ** MySQL settings - You can get this info from your web host ** // /** The name of the database for WordPress */ -define( 'DB_NAME', 'test' ); +define( 'DB_NAME', 'wp55' ); /** MySQL database username */ define( 'DB_USER', 'test' ); diff --git a/tests/Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql b/tests/Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql index 19298c95f8..4504d239f1 100644 --- a/tests/Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql +++ b/tests/Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql @@ -172,7 +172,7 @@ CREATE TABLE `wp55_options` ( LOCK TABLES `wp55_options` WRITE; /*!40000 ALTER TABLE `wp55_options` DISABLE KEYS */; -INSERT INTO `wp55_options` VALUES (1,'siteurl','http://localhost:9999','yes'),(2,'home','http://localhost:9999','yes'),(3,'blogname','Datadog Test Application','yes'),(4,'blogdescription','Just another WordPress site','yes'),(5,'users_can_register','1','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwenty','yes'),(41,'stylesheet','twentytwenty','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','48748','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:2:{i:2;a:4:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:12:\"hierarchical\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1618936275','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'initial_db_version','48748','yes'),(96,'wp55_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(97,'fresh_site','0','yes'),(98,'widget_search','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(99,'widget_recent-posts','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(100,'widget_recent-comments','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(101,'widget_archives','a:2:{i:2;a:3:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(102,'widget_meta','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(103,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:8:\"search-2\";i:1;s:14:\"recent-posts-2\";i:2;s:17:\"recent-comments-2\";}s:9:\"sidebar-2\";a:3:{i:0;s:10:\"archives-2\";i:1;s:12:\"categories-2\";i:2;s:6:\"meta-2\";}s:13:\"array_version\";i:3;}','yes'),(104,'cron','a:7:{i:1674663182;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1674695582;a:4:{s:18:\"wp_https_detection\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674695658;a:1:{s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674738782;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738858;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738859;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}s:7:\"version\";i:2;}','yes'),(105,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(106,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'_transient_doing_cron','1603384688.0394918918609619140625','yes'),(115,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-5.5.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-5.5.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"5.5.1\";s:7:\"version\";s:5:\"5.5.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"5.3\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1603384286;s:15:\"version_checked\";s:5:\"5.5.1\";s:12:\"translations\";a:0:{}}','no'),(116,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384412;s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"4.1.6\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"4.1.6\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.4.1.6.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:59:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=969272\";s:2:\"1x\";s:59:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=969272\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}}s:9:\"hello.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}}}}','no'),(117,'_site_transient_timeout_theme_roots','1603386087','no'),(118,'_site_transient_theme_roots','a:3:{s:14:\"twentynineteen\";s:7:\"/themes\";s:15:\"twentyseventeen\";s:7:\"/themes\";s:12:\"twentytwenty\";s:7:\"/themes\";}','no'),(119,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384287;s:7:\"checked\";a:3:{s:14:\"twentynineteen\";s:3:\"1.7\";s:15:\"twentyseventeen\";s:3:\"2.4\";s:12:\"twentytwenty\";s:3:\"1.5\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:14:\"twentynineteen\";a:6:{s:5:\"theme\";s:14:\"twentynineteen\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:44:\"https://wordpress.org/themes/twentynineteen/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/theme/twentynineteen.1.7.zip\";s:8:\"requires\";s:5:\"4.9.6\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:15:\"twentyseventeen\";a:6:{s:5:\"theme\";s:15:\"twentyseventeen\";s:11:\"new_version\";s:3:\"2.4\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentyseventeen/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentyseventeen.2.4.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:12:\"twentytwenty\";a:6:{s:5:\"theme\";s:12:\"twentytwenty\";s:11:\"new_version\";s:3:\"1.5\";s:3:\"url\";s:42:\"https://wordpress.org/themes/twentytwenty/\";s:7:\"package\";s:58:\"https://downloads.wordpress.org/theme/twentytwenty.1.5.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}}s:12:\"translations\";a:0:{}}','no'),(120,'_site_transient_timeout_browser_6daa110c3e56e442b403473c9591e946','1603989088','no'),(121,'_site_transient_browser_6daa110c3e56e442b403473c9591e946','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:12:\"86.0.4240.80\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(122,'_site_transient_timeout_php_check_56babb1797dd31750a342dc4c8a11025','1603989088','no'),(123,'_site_transient_php_check_56babb1797dd31750a342dc4c8a11025','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(124,'_site_transient_timeout_community-events-e0e4f94be3c2d577e126ec3b012627f2','1603427490','no'),(125,'_site_transient_community-events-e0e4f94be3c2d577e126ec3b012627f2','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:12:\"192.168.16.0\";}s:6:\"events\";a:2:{i:0;a:10:{s:4:\"type\";s:6:\"meetup\";s:5:\"title\";s:58:\"Discussion Group: WordPress Troubleshooting Basics: Part 1\";s:3:\"url\";s:68:\"https://www.meetup.com/learn-wordpress-discussions/events/273993927/\";s:6:\"meetup\";s:27:\"Learn WordPress Discussions\";s:10:\"meetup_url\";s:51:\"https://www.meetup.com/learn-wordpress-discussions/\";s:4:\"date\";s:19:\"2020-10-23 06:00:00\";s:8:\"end_date\";s:19:\"2020-10-23 07:00:00\";s:20:\"start_unix_timestamp\";i:1603458000;s:18:\"end_unix_timestamp\";i:1603461600;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"US\";s:8:\"latitude\";d:37.779998779297;s:9:\"longitude\";d:-122.41999816895;}}i:1;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:17:\"WordCamp Bulgaria\";s:3:\"url\";s:35:\"https://bulgaria.wordcamp.org/2020/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2020-10-24 10:00:00\";s:8:\"end_date\";s:19:\"2020-10-24 10:00:00\";s:20:\"start_unix_timestamp\";i:1603522800;s:18:\"end_unix_timestamp\";i:1603522800;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"BG\";s:8:\"latitude\";d:42.733883;s:9:\"longitude\";d:25.48583;}}}}','no'),(126,'can_compress_scripts','0','no'),(127,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(128,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:49:\"\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:27:\"News – – WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 20:10:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=5.6-beta1-49274\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WordPress 5.6 Beta 1 is now available for testing!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8236:\"\n

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:363:\"This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September.  WordPress 5.5.1 Launch On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8713:\"\n

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"WordPress 5.5.1 Maintenance Release\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://wordpress.org/news/2020/09/wordpress-5-5-1-maintenance-release/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 19:13:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8979\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:460:\"WordPress 5.5.1 is now available! This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade. You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process. […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9020:\"\n

WordPress 5.5.1 is now available!

\n\n\n\n

This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade.

\n\n\n\n

You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process.

\n\n\n\n

WordPress 5.5.1 is a short-cycle maintenance release. The next major release will be version 5.6.

\n\n\n\n

To see a full list of changes, you can browse the list on Trac, read the 5.5.1 RC1 and 5.5.1 RC2 posts, or visit the 5.5.1 documentation page.

\n\n\n\n

Thanks and props!

\n\n\n\n

The 5.5.1 release was led by @audrasjb, @azhiyadev, @davidbaumwald, @desrosj, @johnbillion, @planningwrite, @sergeybiryukov and @whyisjake.

\n\n\n\n

Thank you to everyone who helped make WordPress 5.5.1 happen:

\n\n\n\nAmit Dudhat, Andrea Fercia, Andrey “Rarst” Savchenko, Andy Fragen, Angel Hess, avixansa, bobbingwide, Brian Hogg, chunkysteveo, Clayton Collie, David Baumwald, David Herrera, dd32, demetris, Dominik Schilling, dushakov, Earle Davies, Enrique Sánchez, Frankie Jarrett, fullofcaffeine, Garrett Hyder, Gary Jones, gchtr, Hauwa, Herre Groen, Howdy_McGee, Ipstenu (Mika Epstein), Jb Audras, Jeremy Felt, Jeroen Rotty, Joen A., Johanna de Vos, John Blackbourn, John James Jacoby, Jonathan Bossenger, Jonathan Desrosiers, Jonathan Stegall, Joost de Valk, Jorge Costa, Justin Ahinon, Kalpesh Akabari, Kevin Hagerty, Knut Sparhell, Kyle B. Johnson, landau, Laxman Prajapati, Lester Chan, mailnew2ster, Marius L. J., Mark Jaquith, Mark Uraine, Matt Gibson, Michael Beckwith, Mikey Arce, Mohammad Jangda, Mukesh Panchal, Nabil Moqbel, net, oakesjosh, O André, Omar Reiss, Ov3rfly, Paddy, Pascal Casier, Paul Biron, Peter Wilson, rajeshsingh520, Rami Yushuvaev, rebasaurus, riaanlom, Riad Benguella, Rodrigo Arias, rtagliento, salvoaranzulla, Sanjeev Aryal, sarahricker, Sergey Biryukov, Stephen Bernhardt, Steven Stern (sterndata), Thomas M, Timothy Jacobs, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), TwentyZeroTwo, Winstina, wittich, and Yoav Farhi.\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8979\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"The Month in WordPress: August 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2020/09/the-month-in-wordpress-august-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 09:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8983\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:362:\"August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world. WordPress 5.5 Launch […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9605:\"\n

August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Launch

\n\n\n\n

The team launched WordPress 5.5 on August 11. The major release comes with a host of features like automatic updates for plugins and themes, enabling updates over uploaded ZIP files, a block directory, XML sitemaps, block patterns, inline image editing, and lazy-loading images, to name a few. WordPress 5.5 is now available in 50 languages too! You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. Subsequent to the 5.5 release, the 5.5.1 release candidate came out on August 28, which will be followed by its official launch of the minor release on September 1.

\n\n\n\n

A record 805 people contributed to WordPress 5.5, hailing from 58 different countries. @audrasjb has compiled many more stats like that and they’re well worth a read!

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.7 and 8.8

\n\n\n\n

The core team launched Gutenberg 8.7 and 8.8. Version 8.7 saw many improvements to the Post Block suite, along with other changes like adding a block example to the Buttons block, consistently autosaving edits, and updating the group block description. Version 8.8 offers updates to Global Styles, the Post Block suite, and Template management. The release significantly improves the back-compatibility of the new Widget Screen, and also includes other important accessibility and mobile improvements to user interfaces like the Toolbar, navigation menus, and Popovers. For full details on the latest versions of these Gutenberg releases, visit these posts about 8.7 and 8.8.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Check out the brand new Learn WordPress platform!

\n\n\n\n

Learn WordPress is a brand new cross-team initiative led by the WordPress Community team, with support from the training team, the TV team, and the meta team. This platform is a learning repository on learn.wordpress.org, where WordPress learning content will be made available. Video workshops published on the site will be followed up by supplementary discussion groups based on workshop content. The first of these discussion groups have been scheduled, and you can join an upcoming discussion on the dedicated meetup group. The community team invites members to contribute to the project. You can apply to present a workshop, assist with reviewing submitted workshops, and add ideas for workshops that you would like to see on the site. You can also apply to be a discussion group leader to organize discussions directly through the learn.wordpress.org platform. We are also creating a dedicated Learn WordPress working group and have posted a call for volunteers. Meetup organizers can use Learn WordPress content for their meetup events (without applying as a discussion group leader). Simply ask your meetup group to watch one of the workshops in the weeks leading up to your scheduled event, and then host a discussion group for that content as your event.

\n\n\n\n

Want to get involved with the Community team? Follow the Community blog, or join them in the #community-events channel in the Making WordPress Slack group. To organize a local WordPress community event, visit the handbook page

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8983\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n\n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"WordPress 5.5 “Eckstine”\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://wordpress.org/news/2020/08/eckstine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 11 Aug 2020 19:03:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8799\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:354:\"Version 5.5 \"Eckstine\" of WordPress is available for download or update in your WordPress dashboard. With this release, your site gets new power in three major areas: \nspeed (lazy-loading images), search (sitemaps included by default), and security (auto-updates for plugins and themes), along with many new features and improvements to the block editor.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:3:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:48:\"https://s.w.org/images/core/5.5/auto-updates.mp4\";s:6:\"length\";s:6:\"238264\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:50:\"https://s.w.org/images/core/5.5/block-patterns.mp4\";s:6:\"length\";s:7:\"3518792\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:56:\"https://s.w.org/images/core/5.5/inline-image-editing.mp4\";s:6:\"length\";s:7:\"3145140\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Matt Mullenweg\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:71062:\"\n

Here it is! Named “Eckstine” in honor of Billy Eckstine, this latest and greatest version of WordPress is available for download or update in your dashboard.

\n\n\n\n
\"\"
\n\n\n\n
\n

Welcome to WordPress 5.5.

\n\n\n\n

In WordPress 5.5, your site gets new power in three major areas:
speed, search, and security.

\n
\n\n\n\n
\n
\n\n\n\n
\n

Speed

\n\n\n\n

Posts and pages feel faster, thanks to lazy-loaded images.

\n\n\n\n

Images give your story a lot of impact, but they can sometimes make your site seem slow.

\n\n\n\n

In WordPress 5.5, images wait to load until they’re just about to scroll into view. The technical term is ‘lazy loading.’

\n\n\n\n

On mobile, lazy loading can also keep browsers from loading files meant for other devices. That can save your readers money on data — and help preserve battery life.

\n\n\n\n

Search

\n\n\n\n

Say hello to your new sitemap.

\n\n\n\n

WordPress sites work well with search engines.

\n\n\n\n

Now, by default, WordPress 5.5 includes an XML sitemap that helps search engines discover your most important pages from the very minute you go live.

\n\n\n\n

So more people will find your site sooner, giving you more time to engage, retain and convert them to subscribers, customers or whatever fits your definition of success.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Security

\n\n\n\n
Now you can choose to update plugins and themes automatically–or pick just a few–from the screens you’ve always used.
\n\n\n\n

Auto-updates for Plugins and Themes

\n\n\n\n

Now you can set plugins and themes to update automatically — or not! — in the WordPress admin. So you always know your site is running the latest code available.

\n\n\n\n

You can also turn auto-updates on or off for each plugin or theme you have installed — all on the same screens you’ve always used.

\n\n\n\n

Update by uploading ZIP files

\n\n\n\n

If updating plugins and themes manually is your thing, now that’s easier too — just upload a ZIP file.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Highlights from the block editor

\n\n\n\n

Once again, the latest WordPress release packs a long list of exciting new features for the block editor. For example:

\n\n\n\n
\n\n\n\n
\n
\n

Block patterns

\n\n\n\n

New block patterns make it simple and fun to create complex, beautiful layouts, using combinations of text and media that you can mix and match to fit your story.

\n\n\n\n

You will also find block patterns in a wide variety of plugins and themes, with more added all the time. Pick any of them from a single place — just click and go!

\n
\n\n\n\n
\n

The new block directory

\n\n\n\n

Now it’s easier than ever to find the block you need. The new block directory is built right into the block editor, so you can install new block types to your site without ever leaving the editor.

\n\n\n\n

Inline image editing

\n\n\n\n

Crop, rotate, and zoom your photos right from the image block. If you spend a lot of time on images, this could save you hours!

\n
\n
\n\n\n\n
\n\n\n\n

And so much more.

\n\n\n\n

The highlights above are a tiny fraction of the new block editor features you’ve just installed. Open the block editor and enjoy!

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Accessibility

\n\n\n\n

Every release adds improvements to the accessible publishing experience, and that remains true for WordPress 5.5.

\n\n\n\n

Now you can copy links in media screens and modal dialogs with a button, instead of trying to highlight a line of text.

\n\n\n\n

You can also move meta boxes with the keyboard, and edit images in WordPress with your assistive device, as it can read you the instructions in the image editor.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

For developers

\n\n\n\n

5.5 also brings a big box of changes just for developers.

\n\n\n\n
\n
\n

Server-side registered blocks in the REST API

\n\n\n\n

The addition of block types endpoints means that JavaScript apps (like the block editor) can retrieve definitions for any blocks registered on the server.

\n\n\n\n

Defining environments

\n\n\n\n

WordPress now has a standardized way to define a site’s environment type (staging, production, etc). Retrieve that type with wp_get_environment_type() and execute only the appropriate code.

\n\n\n\n

Dashicons

\n\n\n\n

The Dashicons library has received its final update in 5.5. It adds 39 block editor icons along with 26 others.

\n\n\n\n

Passing data to template files

\n\n\n\n

The template loading functions (get_header()get_template_part(), etc.) have a new $args argument. So now you can pass an entire array’s worth of data to those templates.

\n
\n\n\n\n
\n

More changes for developers

\n\n\n\n
  • The PHPMailer library just got a major update, going from version 5.2.27 to 6.1.6.
  • Now get more fine-grained control of redirect_guess_404_permalink().
  • Sites that use PHP’s OPcache will see more reliable cache invalidation, thanks to the new wp_opcache_invalidate() function during updates (including to plugins and themes).
  • Custom post types associated with the category taxonomy can now opt-in to supporting the default term.
  • Default terms can now be specified for custom taxonomies in register_taxonomy().
  • The REST API now officially supports specifying default metadata values through register_meta().
  • You will find updated versions of these bundled libraries: SimplePie, Twemoji, Masonry, imagesLoaded, getID3, Moment.js, and clipboard.js.
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

The Squad

\n\n\n\n

Leading this release were Matt MullenwegJake Spurlock, and David Baumwald. Supporting them was this highly enthusiastic release squad:

\n\n\n\n\n\n\n\n

Joining the squad throughout the release cycle were 805 generous volunteer contributors who collectively worked on over 523 tickets on Trac and over 1660 pull requests on GitHub.

\n\n\n\n

Put on a Billy Eckstine playlist, click that update button (or download it directly), and check the profiles of the fine folks that helped:

\n\n\nA2 Hosting, a4jp . com, a6software, Aaron D. Campbell, Aaron Jorbin, abderrahman, Abha Thakor, Achal Jain, achbed, Achyuth Ajoy, acosmin, acsnaterse, Adam Silverstein, Addie, addyosmani, adnan.limdi, adrian, airamerica, Ajay Ghaghretiya, Ajit Bohra, akbarhusen, akbarhusen429, Akhilesh Sabharwal, Akira Tachibana, Alain Schlesser, Albert Juhé Lluveras, Alex Concha, Alex Kirk, Alex Lende, Alex Shiels, Ali Shan, ali11007, Allen Snook, amaschas, Amit Dudhat, anbumz, andfinally, Andrea Fercia, Andrea Middleton, Andrea Tarantini, Andrei Draganescu, Andrew Duthie, Andrew Nacin, Andrew Nevins, Andrew Ozz, Andrey \"Rarst\" Savchenko, Andrés Maneiro, Andy Fragen, Andy Meerwaldt, Andy Peatling, Angel Hess, Angela Jin, Angelika Reisiger, Anh Tran, Ankit Gade, Ankit K Gupta, Ankit Panchal, Anne McCarthy, Anthony Burchell, Anthony Hortin, Anton Timmermans, Antonis Lilis, apedog, archon810, argentite, Arpit G Shah, Arslan Ahmed, asalce, ashiagr, ashour, Atharva Dhekne, Aurélien Joahny, aussi, automaton, avixansa, Ayesh Karunaratne, BackuPs, Barry, Barry Ceelen, Bart Czyz, bartekcholewa, bartkalisz, Bastien Ho, Bastien Martinent, bcworkz, bdbch, bdcstr, Ben Dunkle, Bence Szalai, bencroskery, Benjamin Gosset, Benoit Chantre, Bernhard Reiter, BettyJJ, bgermann, bigcloudmedia, bigdawggi, Bill Erickson, Birgir Erlendsson (birgire), Birgit Pauli-Haack, BjornW, bobbingwide, bonger, Boone Gorges, Boris Brdarić, Boy Witthaya, Brandon Kraft, Brandon Payton, Brent Swisher, Brian Hogg, Brian Krogsgard, bruandet, Bunty, Burhan Nasir, caiocrcosta, Cameron Voell, cameronamcintyre, Carike, Carl Wuensche, Carlos Galarza, Carolina Nymark, Caroline Moore, Carrigan, ceyhun, Chad, Chad Butler, Charles Fulton, Chetan Prajapati, Chintan hingrajiya, Chip Snyder, Chloé Bringmann, Chouby, Chris Van Patten, chriscct7, Christian Chung, Christian Jongeneel, Christian Sabo, Christian Wach, Christoph Herr, Christopher Churchill, chunkysteveo, cklee, clayray, Clayton Collie, Clifford Paulick, codeforest, Commeuneimage, Copons, Corey McKrill, cpasqualini, Cristovao Verstraeten, Csaba (LittleBigThings), Curtis Belt, Cyrus Collier, D.PERONNE, d6, Daniel Bachhuber, Daniel Hüsken, Daniel James, Daniel Llewellyn, Daniel Richards, Daniel Roch, Daniele Scasciafratte, Danny, Darko G., Darren Ethier (nerrad), Dave McHale, Dave Whitley, David A. Kennedy, David Aguilera, David Anderson, David Artiss, David Baumwald, David Brumbaugh, David E. Smith, David Herrera, David Ryan, David Shanske, David Smith, david.binda, davidvee, dchymko, Debabrata Karfa, Deepak Lalwani, dekervit, Delowar Hossain, demetris, Denis Yanchevskiy, derekakelly, Derrick Hammer, Derrick Tennant, Diane Co, Dilip Bheda, Dimitris Mitsis, dingo-d, Dion Hulse, Dixita Dusara, djennez, dmenard, dmethvin, doc987, Dominik Schilling, donmhico, Dono12, Doobeedoo, Dossy Shiobara, dpacks, dratwas, Drew Jaynes, DrLightman, DrProtocols, dsifford, dudo, dushakov, Dustin Bolton, dvershinin, Dylan Kuhn, Earle Davies, ecotechie, Eddie Moya, Eddy, Edi Amin, ehtis, Eileen Violini, Ekaterina, Ella van Durpe, elmastudio, Emanuel Blagonic, Emilie LEBRUN, Emmanuel Hesry, Enej Bajgoric, Enrico Sorcinelli, Enrique Piqueras, Enrique Sánchez, Eric, Eric Andrew Lewis, Eric Binnion, Erik Betshammar, Erin \'Folletto\' Casali, esemlabel, esoj, espiat, Estela Rueda, etoledom, etruel, Ev3rywh3re, Evan Mullins, Fabian Kägy, Fabian Todt, Faisal Ahmed, Felix Arntz, Felix Edelmann, ferdiesletering, finomeno, Florian Brinkmann, Florian TIAR, Florian Truchot, florianatwhodunit, FolioVision, Francesca Marano, Francois Thibaud, Frank Goossens, Frank Klein, Frank.Prendergast, Frankie Jarrett, Franz Armas, fullofcaffeine, Gabriel Koen, Gabriel Maldonado, Gabriel Mays, gadgetroid, Gal Baras, Garavani, garethgillman, Garrett Hyder, Gary Cao, Gary Jones, Gary Pendergast, gchtr, Geert De Deckere, Gemini Labs, Gennady Kovshenin, geriux, Giorgio25b, gisselfeldt, glendaviesnz, goldsounds, Goto Hayato, Govind Kumar, Grégory Viguier, gradina, Greg Ziółkowski, gregmulhauser, grierson, Grzegorz.Janoszka, gsmumbo, Guido Scialfa, guidobras, Gunther Pilz, gwwar, H-var, hakre, Halacious, hankthetank, Hapiuc Robert, Hareesh, haukep, Hauwa, Haz, Hector Farahani, Helen Hou-Sandi, Henry Wright, Herre Groen, hlanggo, hommealone, Hoover, Howdy_McGee, Hronak Nahar, huntlyc, Ian Belanger, Ian Dunn, Ian Stewart, ianjvr, ifrins, infinum, Ipstenu (Mika Epstein), Isabel Brison, ishitaka, J.D. Grimes, jackfungi, jacklinkers, Jadon N, jadpm, jagirbahesh, Jake Spurlock, Jake Whiteley, James Koster, James Nylen, Jan Koch, Jan Reilink, Jan Thiel, Janvo Aldred, Jarret, Jason Adams, Jason Coleman, Jason Cosper, Jason Crouse, Jason LeMahieu (MadtownLems), Jason Rouet, JasWSInc, Javier Casares, Jayson Basanes, jbinda, jbouganim, Jean-Baptiste Audras, Jean-David Daviet, Jeff Chandler, Jeff Farthing, Jeff Ong, Jeff Paul, Jen, Jenil Kanani, Jeremy Felt, Jeremy Herve, Jeremy Yip, Jeroen Rotty, jeryj, Jesin A, Jignesh Nakrani, Jim_Panse, Jip Moors, jivanpal, Joe Dolson, Joe Hoyle, Joe McGill, Joen Asmussen, Johanna de Vos, John Blackbourn, John Dorner, John James Jacoby, John P. Green, John Richards II, John Watkins, johnnyb, Jon Quach, Jon Surrell, Jonathan Bossenger, Jonathan Champ, Jonathan Christopher, Jonathan Desrosiers, Jonathan Stegall, jonkolbert, Jonny Harris, jonnybot, Jono Alderson, Joost de Valk, Jorge Bernal, Jorge Costa, Joseph Dickson, Josepha Haden, Josh Smith, JoshuaWold, Joy, Juanfra Aldasoro, juanlopez4691, Jules Colle, julianm, Juliette Reinders Folmer, Julio Potier, Julka Grodel, Justin Ahinon, Justin de Vesine, Justin Tadlock, justlevine, justnorris, K. Adam White, kaggdesign, Kailey (trepmal), Kaira, Kaitlin Bolling, Kalpesh Akabari, KamataRyo, Kantari Samy, Kaspars, Kavya Gokul, keesiemeijer, Kelly Dwan, kennethroberson5556, Kevin Hagerty, Kharis Sulistiyono, Khokan Sardar, kinjaldalwadi, Kiril Zhelyazkov, Kirsty Burgoine, Kishan Jasani, kitchin, Kite, Kjell Reigstad, Knut Sparhell, Konstantin Obenland, Konstantinos Xenos, ksoares, KT Cheung, Kukhyeon Heo, Kyle B. Johnson, lalitpendhare, landau, Laterna Studio, laurelfulford, Laurens Offereins, Laxman Prajapati, Lester Chan, Levdbas, Lew Ayotte, Lex Robinson, linyows, lipathor, Lisa Schuyler, liuhaibin, ljharb, logig, lucasbustamante, luiswill, Luke Cavanagh, Luke Walczak, lukestramasonder, M Asif Rahman, M.K. Safi, Maarten de Boer, Mahfoudh Arous, mailnew2ster, manojlovic, Manuel Schmalstieg, maraki, Marcin Pietrzak, Marcio Zebedeu, Marco Pereirinha, MarcoZ, Marcus, Marcus Kazmierczak, Marek Dědič, Marek Hrabe, Mario Valney, Marius Jensen, Mark Chouinard, Mark Jaquith, Mark Parnell, Mark Uraine, markdubois, markgoho, Marko Andrijasevic, Marko Heijnen, MarkRH, markshep, markusthiel, Martijn van der Kooij, martychc23, Mary Baum, Matheus Martins, Mathieu Viet, Matias Ventura, matjack1, Matt Cromwell, Matt Gibson, Matt Mullenweg, Matt Radford, Matt van Andel, mattchowning, Matthew Boynes, Matthew Eppelsheimer, Matthew Gerring, Matthias Kittsteiner, Matthias Pfefferle, Matthieu Mota, mattyrob, Maxime Culea, Maxime Pertici, maxme, Mayank Majeji, mcshane, Mel Choyce-Dwan, Menaka S., mensmaximus, metalandcoffee, Michael, Michael Arestad, Michael Arestad, Michael Beckwith, Michael Fields, Michael Nelson, Michele Butcher-Jones, Michelle, Miguel Fonseca, mihdan, Miina Sikk, Mikael Korpela, mikaumoto, Mike Crantea, Mike Glendinning, Mike Haydon, Mike Schinkel [WPLib Box project lead], Mike Schroder, Mikey Arce, Milana Cap, Milind More, mimi, mislavjuric, Mohammad Jangda, Mohammad Rockeybul Alam, Mohsin Rasool, Monika Rao, Morgan Kay, Morten Rand-Hendriksen, Morteza Geransayeh, moto hachi ( mt8.biz ), mrgrt, mrmist, mrTall, msaggiorato, Muhammad Usama Masood, Mukesh Panchal, munyagu, Nabil Moqbel, Nadir Seghir, Nahid Ferdous Mohit, Nalini Thakor, Naoko Takano, narwen, Nate Gay, Nathan Rice, Navid, neonkowy, net, netpassprodsr, Nextendweb, Ngan Tengyuen, Nick Daugherty, Nicky Lim, nicolad, Nicolas Juen, NicolasKulka, Nidhi Jain, Niels de Blaauw, Niels Lange, nigro.simone, Nik Tsekouras, Nikhil Bhansi, Nikolay Bachiyski, Nilo Velez, Niresh, nmenescardi, Noah Allen, NumidWasNotAvailable, oakesjosh, obliviousharmony, ockham, Olga Gleckler, Omar Alshaker, Omar Reiss, onokazu, Optimizing Matters, Ov3rfly, ovann86, overclokk, p_enrique, Paal Joachim Romdahl, Pablo Honey, Paddy, palmiak, Paresh Shinde, Parvand, Pascal Birchler, Pascal Casier, Paul Bearne, Paul Biron, Paul Fernhout, Paul Gibbs, Paul Ryan, Paul Schreiber, Paul Stonier, Paul Von Schrottky, pavelevap, Pedro Mendonça, pentatonicfunk, pepe, Peter \"Pessoft\" Kolínek, Peter Westwood, Peter Wilson, Phil Derksen, Phil Johnston, Philip Jackson, Pierre Gordon, pigdog234, pikamander2, pingram, Pionect, Piyush Patel, pkarjala, pkvillanueva, Prashant Baldha, pratik028, Pravin Parmar, Presskopp, Presslabs, Priyank Patel, Priyo Mukul, ProGrafika, programmin, Puneet Sahalot, pvogel2, r-a-y, Raaj Trambadia, Rachel Peter, raine, rajeshsingh520, Ramanan, Rami Yushuvaev, RavanH, Ravat Parmar, ravenswd, rawrly, rebasaurus, Red Sand Media Group, Remy Perona, Remzi Cavdar, Renatho, renggo888, retlehs, retrofox, riaanlom, Riad Benguella, Rian Rietveld, riasat, Rich Tabor, Ringisha, ritterml, Rnaby, Rob Cutmore, Rob Migchels, rob006, Robert Anderson, Robert Chapin, Robert Peake, Robert Windisch, Rodrigo Arias, Ronald Huereca, Rostislav Wolný, Roy Tanck, rtagliento, ruxandra, Ryan Boren, Ryan Fredlund, Ryan Kienstra, Ryan McCue, Ryan Welcher, Ryota Sakamoto, ryotsun, Sören Wrede, Søren Brønsted, Sachit Tandukar, Sagar Jadhav, Sajjad Hossain Sagor, Sal Ferrarello, Salvatore Formisano, salvoaranzulla, Sam Fullalove, Sam Webster, Samir Shah, Samuel Wood (Otto), samueljseay, Sander van Dragt, Sanjeev Aryal, Sanket Mehta, sarahricker, Sathiyamoorthy V, Sayed Taqui, scarolan, scholdstrom, Scott Kingsley Clark, Scott Reilly, Scott Smith, Scott Taylor, scribu, scruffian, Sean Hayes, seanpaulrasmussen, seayou, senatorman, Sergey Biryukov, Sergey Predvoditelev, Sergio de Falco, sergiomdgomes, Shannon Smith, Shantanu Desai, shaunandrews, Shawn Hooper, shawnz, Shital Marakana, shulard, siliconforks, Simon Wheatley, simonjanin, sinatrateam, sjmur, skarabeq, skorasaurus, skoskie, slushman, snapfractalpop, SpearsMarketing, sphakka, squarecandy, sreedoap, Stanimir Stoyanov, Stefano Minoia, Stefanos Togoulidis, Steph Wells, Stephen Bernhardt, Stephen Cronin, Stephen Edgar, Steve Dufresne, stevegibson12, Steven Stern (sterndata), Steven Word, stevenkussmaul, stevenlinx, Stiofan, Subrata Sarkar, SUM1, Sunny, Sunny Ratilal, Sushyant Zavarzadeh, suzylah, Sybre Waaijer, Synchro, Sérgio Estêvão, Takayuki Miyauchi, Tammie Lister, Tang Rufus, TeBenachi, Tessa Watkins LLC, Tetsuaki Hamano, theMikeD, theolg, Thierry Muller, Thimal Wickremage, Thomas M, Thorsten Frommen, Thrijith Thankachan, Tiago Hillebrandt, Till Krüss, Timothy Jacobs, Tkama, tmdesigned, tmoore41, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), Tofandel, tomdude, Tommy Ferry, Tony G, Toro_Unit (Hiroshi Urabe), torres126, Torsten Landsiedel, Toru Miki, Travis Northcutt, treecutter, truongwp, tsimmons, Tung Du, Udit Desai, Ulrich, Vagios Vlachos, valchovski, Valentin Bora, Vayu Robins, veromary, Viktor Szépe, vinkla, virginienacci, Vladimir, Vladislav Abrashev, vortfu, voyager131, vtieu, webaware, Weston Ruter, William Earnhardt, williampatton, Winstina, wittich, wpdesk, WPDO, WPMarmite, wppinar, Yahil Madakiya, yashrs, yoancutillas, Yoav Farhi, yohannp, yuhin, Yui, Yuri Salame, Yvette Sonneveld, Zack Tollman, zaheerahmad, zakkath, Zebulan Stanphill, zieladam, and Česlav Przywara.\n\n\n\n

 

\n\n\n\n

Many thanks to all of the community volunteers who contribute in the support forums. They answer questions from people across the world, whether they are using WordPress for the first time or since the first release. These releases are more successful for their efforts!

\n\n\n\n

Finally, thanks to all the community translators who worked on WordPress 5.5. Their efforts bring WordPress fully translated to 46 languages at release time, with more on the way.

\n\n\n\n

If you want to learn more about volunteering with WordPress, check out Make WordPress or the core development blog.

\n
\n\n\n\n
\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8799\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"WordPress 5.5 Release Candidate 2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2020/08/wordpress-5-5-release-candidate-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 04 Aug 2020 19:12:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8764\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:420:\"The second release candidate for WordPress 5.5 is here! WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time! You can test the WordPress 5.5 release candidate in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding edge nightlies” option) Or download the release […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2503:\"\n

The second release candidate for WordPress 5.5 is here!

\n\n\n\n

WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

For a more detailed breakdown of the changes included in WordPress 5.5, check out the WordPress 5.5 beta 1 post. The WordPress 5.5 Field Guide is also out! It’s your source for details on all the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8764\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n\n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"The Month in WordPress: July 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"https://wordpress.org/news/2020/08/the-month-in-wordpress-july-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 03 Aug 2020 13:54:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8755\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:340:\"July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11539:\"\n

July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Updates

\n\n\n\n

July was full of WordPress 5.5 updates! The WordPress 5.5 Beta 1 came out on July 7, followed by Beta 2 on July 14, Beta 3 on July 21, and Beta 4 on July 27. Subsequently, the team also published the first release candidate of WordPress 5.5 on July 28. 

\n\n\n\n

WordPress 5.5, which is slated for release on August 11, 2020, is a major update with features like automatic updates for plugins and themes, a block directory, XML sitemaps, block patterns, and lazy-loading images, among others. To learn more about the release, check out its field guide post.

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.5 and 8.6

\n\n\n\n

The core team launched Gutenberg 8.5 and 8.6. Version 8.5 – the last plugin release will be included entirely (without experimental features) in WordPress 5.5, introduced improvements to block drag-and-drop and accessibility, easier updates for external images, and support for the block directory. Version 8.6 comes with features like Cover block video position controls and block pattern updates. For full details on the latest versions on these Gutenberg releases, visit these posts about 8.5 and 8.6.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Reimagining Online WordPress Events

\n\n\n\n

The Community team made the difficult decision to suspend in-person WordPress events for the rest of 2020 in light of the COVID-19 pandemic. The team has also started working on reimagining online events. Based on feedback from the community members, the team decided to make changes to the current online WordCamp format. Key changes include wrapping up financial support for A/V vendors, ending event swag support for newer online WordCamps, and suspending the Global Community Sponsorship program for 2020. The team encourages upcoming online WordCamps to experiment with their events to facilitate an effective learning experience for attendees while avoiding online event fatigue. The team is currently working on a proposal to organize community-supported recorded workshops and synchronous discussion groups to help community members learn WordPress.

Want to get involved with the Community team? Follow the Community blog here, or join them in the #community-events channel in the Making WordPress Slack group. To organize a Meetup or WordCamp, visit the handbook page

\n\n\n\n

WordCamp US 2020 is canceled

\n\n\n\n

The organizers of WordCamp US 2020 have canceled the event in light of the continued pandemic and online event fatigue. The flagship event, which was originally scheduled for October 27-29 as an in-person event, had already planned to transition to an online event. Several WCUS Organizers will be working with the WordPress Community team to focus on other formats and ideas for online events, including a 24-hour contributor day, and contributing to the workshops initiative currently being discussed. Matt Mullenweg’s State of the Word (which typically accompanies WordCamp US) is likely to take place in a different format later in 2020.

\n\n\n\n

Plugin and theme updates are now available over zip files

\n\n\n\n

After eleven years, WordPress now allows users to update plugins and themes by uploading a ZIP file, in WordPress 5.5.  The feature, which was merged on July 7, has been one of the most requested features in WordPress. Now, when a user tries to upload a plugin or theme zip file from the WordPress dashboard by clicking the “Install Now” button, WordPress will direct users to a new screen that compares the currently-installed extension with the uploaded versions. Users can then choose between continuing with the installation or canceling. WordPress 5.5 will also offer automatic plugin and theme updates

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n
  • The Block directory is coming to WordPress with the 5.5 release. Plugin authors can now submit their Block plugins to the directory.
  • The Core team has opened up the call for features in the WordPress 5.6 release. You can comment on the post with features that you’d like to be included, current UX pain points, or maintenance tickets that need to be addressed. August 20 is the deadline for feature requests. 
  • Editor features such as the new Navigation block, the navigation screen, and the widget screen that were originally planned to be merged with WordPress 5.5 have been pushed for the next release
  • The Theme team is inviting proposals on whether to allow themes to place an additional top-level menu link in the admin.
  • BuddyPress 6.2 beta is out in the wild, and the team will soon release the stable version. The update includes changes that will make BuddyPress fully compatible with WordPress 5.5.
  • WordCamp EU 2021, which was being planned as an in-person event in Porto, Portugal, is moving online. The team is considering an in-person WordCamp EU in 2022. 
  • The Polyglots team has prepared and finalized a Translation Editor & Locale Manager Vetting Criteria to provide more clarity on how global mentors assign PTE/GTE/Locale Managers and to help locale teams set their own guidelines. The document, which was finalized after a lot of discussion, is now available in the Polyglots handbook.
  • Members of the Community team are discussing whether WordCamp volunteers, WordCamp attendees, or Meetup attendees should be awarded a WordPress.org profile badge. The ongoing discussion will be open for comments until August 13.
  • The WP Notify project, which aims to create a better way to manage and deliver notifications to the relevant audience, is on to its next steps. The team has finalized the initial requirements, and is kicking off the project build.
  • The WordPress documentation team is considering a ban on links to commercial websites in a revision to its external linking policy. The policy change does not remove external links to commercial sites from WordPress.org and only applies to documentation sites. The idea is to protect documentation from being abused, and to prevent the WordPress project from being biased. Discussion on this post is still ongoing, and a decision has not yet been made. Feel free to comment on the discussion posts, if you would like to share your thoughts on the topic.
\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8755\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"WordPress 5.5 Release Candidate\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://wordpress.org/news/2020/07/wordpress-5-5-release-candidate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 28 Jul 2020 19:08:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8732\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:370:\"The first release candidate for WordPress 5.5 is now available! This is an important milestone in the community’s progress toward the final release of WordPress 5.5. “Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2970:\"\n

The first release candidate for WordPress 5.5 is now available!

\n\n\n\n

This is an important milestone in the community’s progress toward the final release of WordPress 5.5.

\n\n\n\n

“Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

What’s in WordPress 5.5?

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developer notes tag for updates on those and other changes that could affect your products.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

The WordPress 5.5 Field Guide, due very shortly, will give you a more detailed dive into the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8732\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 4\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-4/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 27 Jul 2020 20:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8719\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:313:\"WordPress 5.5 Beta 4 is now available! This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 4 in two ways: Try the WordPress Beta Tester plugin (choose the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"David Baumwald\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3812:\"\n

WordPress 5.5 Beta 4 is now available!

\n\n\n\n

This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 4 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 3 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 3, 43 bugs have been fixed. Here are a few changes in beta 4:

\n\n\n\n
  • Add \"loading\" as an allowed kses image attribute (see #50731).
  • Add filter for the plugin/theme auto-update message in the Info tab of Site health (see #50663).
  • $_SERVER[\'SERVER_NAME\'] not a reliable when generating email host names (see #25239)
  • Several backported fixes from Gutenberg are included in WordPress 5.5 Beta 4 (See PR #24218)
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8719\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 3\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-3/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 21 Jul 2020 17:51:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8706\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:324:\"WordPress 5.5 Beta 3 is now available! This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 3 in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3876:\"\n

WordPress 5.5 Beta 3 is now available!

\n\n\n\n

This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 3 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 2 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 2, 43 bugs have been fixed. Here are a few changes in beta 3:

\n\n\n\n
  • Plugin and theme versions are now shared in the emails when automatically updated (see #50350).
  • REST API routes without a permission_callback now trigger a _doing_it_wrong() warning (see #50075).
  • Over 23 Gutenberg changes and updates (see #24068 and #50712).
  • A bug with the new import and export database Dashicons has been fixed (see #49913).
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8706\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1 \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:9:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:30 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Wed, 21 Oct 2020 20:10:31 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(129,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(130,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603384291','no'),(131,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(132,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Loginizer Plugin Gets Forced Security Update for Vulnerabilities Affecting 1 Million Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106557\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users?utm_source=rss&utm_medium=rss&utm_campaign=loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5484:\"

WordPress.org has pushed out a forced security update for the Loginizer plugin, which is active on more than 1 million websites. The plugin offers brute force protection in its free version, along with other security features like two-factor auth, reCAPTCHA, and PasswordLess login in its commercial upgrade.

\n\n\n\n

Last week security researcher Slavco Mihajloski discovered an unauthenticated SQL injection vulnerability, and an XSS vulnerability, that he disclosed to the plugin’s authors. Loginizer version 1.6.4 was released on October 16, 2020, with patches for the two issues, summarized on the plugin’s blog:

\n\n\n\n

1) [Security Fix] : A properly crafted username used to login could lead to SQL injection. This has been fixed by using the prepare function in PHP which prepares the SQL query for safe execution.

2) [Security Fix] : If the IP HTTP header was modified to have a null byte it could lead to stored XSS. This has been fixed by properly sanitizing the IP HTTP header before using the same.

\n\n\n\n

Loginizer did not disclose the vulnerability until today in order to give users the time to upgrade. Given the severity of the vulnerability, the plugins team at WordPress.org pushed out the security update to all sites running Loginizer on WordPress 3.7+.

\n\n\n\n

In July, 2020, Loginizer was acquired by Softaculous, so the company was also able to automatically upgrade any WordPress installations with Loginizer that had been created using Softaculous. This effort, combined with the updates from WordPress.org, covered a large portion of Loginizer’s user base.

\n\n\n\n
\n

Any #WordPress install with @loginizer probably isn\'t using another WAF solution. As you can notice from the graph 600k+500k active installs were updated upside down, so … Preauth SQLi in it, reported by me. Update! Crunching write up :) https://t.co/gkEVWun9wt pic.twitter.com/XWXVMYO1ED

— mslavco (@mslavco) October 19, 2020
\n
\n\n\n\n

The automatic update took some of the plugin’s users by surprise, since they had not initiated it themselves and had not activated automatic updates for plugins. After several users posted on the plugin’s support forum, plugin team member Samuel Wood said that “WordPress.org has the ability to turn on auto-updates for security issues in plugins” and has used this capability many times.

\n\n\n\n

Mihajloski published a more detailed proof-of-concept on his blog earlier today. He also highlighted some concerns regarding the systems WordPress has in place that allowed this kind of of severe vulnerability to slip through the cracks. He claims the issue could have easily been prevented by the plugin review team since the plugin wasn’t using the prepare function for safe execution of SQL queries. Mihajloski also recommended recurring code audits for plugins in the official directory.

\n\n\n\n

“When a plugin gets into the repository, it must be reviewed, but when is it reviewed again?” Mihajloski said. “Everyone starts with 0 active installs, but what happens on 1k, 10k, 50k, 100k, 500k, 1mil+ active installs?”

\n\n\n\n

Mihajloski was at puzzled how such a glaring security issue could remain in the plugin’s code so long, given that it is a security plugin with an active install count that is more than many well known CMS’s. The plugin also recently changed hands when it was acquired by Softaculus and had been audited by multiple security organizations, including WPSec and Dewhurst Security.

\n\n\n\n

Mihajloski also recommends that WordPress improve the transparency around security, as some site owners and closed communities may not be comfortable with having their assets administered by unknown people at WordPress.org.

\n\n\n\n

“WordPress.org in general is transparent, but there isn’t any statement or document about who, how and when decides about and performs automatic updates,” Mihajloski said. “It is kind of [like] holding all your eggs in one basket.

\n\n\n\n

“I think those are the crucial points that WP.org should focus on and everything will came into place in a short time: complete WordPress tech documentation for security warnings, a guide for disclosure of the bugs (from researchers, but also from a vendor aspect), and recurring code audits for popular plugins.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Oct 2020 03:47:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"Post Status: Joe Casabona on creating quality content and courses\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=80099\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://poststatus.com/joe-casabona-on-creating-quality-content-and-courses/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1407:\"

David Bisset interviews Joe Casabona, an independent creator and teacher, and discusses what it\'s like to be a creator as his job, plus some news topics.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Sandhills Development

\n\n\n\n

Sandhills Development crafts ingenuity, developed with care:

\n\n\n\n
  • Easy Digital Downloads – Sell digital products with WordPress
  • AffiliateWP – A full-featured affiliate marketing solution
  • Sugar Calendar – WordPress event management made simple
  • WP Simple Pay – A lightweight Stripe payments plugin
\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:17:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: MakeStories 2.0 Launches Editor for WordPress, Rivaling Google’s Official Web Stories Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106327\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin?utm_source=rss&utm_medium=rss&utm_campaign=makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8860:\"Recipe slide from the MakeStories WordPress plugin.\n\n\n\n

Earlier today, MakeStories launched version 2.0 of its plugin for creating Web Stories with WordPress. In many ways, this is a new plugin launch. The previous version simply allowed users to connect their WordPress installs to the MakeStories site. With the new version, users can build and edit their stories directly from the WordPress admin.

\n\n\n\n

Version 2.0 of the plugin still requires an account and a connection with the MakeStories.io website. However, it is simple to set up. Users can log in without leaving their WordPress admin interface. This API connection means that user-created Stories are stored on the MakeStories servers. If an end-user wanted to jump platforms from WordPress to something else, this would allow them to take their Stories with them.

\n\n\n\n

“One of the things we would like to assure is your content is still yours, and none of the user data is being consumed or analyzed by us,” said Pratik Ghela, the founder and product manager at MakeStories. “We only take enough data to help serve you better.”

\n\n\n\n

The plugin is a competing solution to the official Web Stories plugin by Google. While the two share similarities in the final output (they are built to utilize the same front-end format for creating Stories on the web), they take different paths to get there.

\n\n\n\n

The two share similarities on the backend too. However, MakeStories may be more polished in some areas. For example, it allows users to zoom in on the small canvas area. Having the ability to reorder slides from the grid view also feels more intuitive.

\n\n\n\n

“The main unique selling proposition of our plugin is that it comes with a guarantee of the MakeStories team,” said Ghela. “We as a team have been building this for over two years, and we are proud to be one of the tools that has stood the test of time, and competition and is still growing at a very fast pace.”

\n\n\n\n

The team also wants to make the Story-creating process faster, safer, and rewarding. The goal is to cater to designers, developers, and content creators. Ghela also feels like his team’s support turnaround time of a few hours will be the key to success and is a good reason for users to give this plugin a try before settling on something else.

\n\n\n\n

“We feel that our goal is to see Web Stories flourish,” he said. “And we may have different types of users looking out for various options. So, the official plugin from Google and the one from MakeStories at least opens up the options for users to choose from. And we feel that the folks at Google are also building a great editor, and, at the end of the day, it’s up to the user to select what they feel is the best.

\n\n\n\n

Technically, MakeStories is a SaaS (software as a service) product. Even though it is a free plugin, there will eventually be a commercial component to it. Currently, it is free at least until the first quarter of 2021, which may be extended based on various factors. There is no word on what pricing tiers may be available after that.

\n\n\n\n

“There will always be a free tier, and we have always stood for it that your data belongs to you,” said Ghela. “In case you do not like the pricing, we will personally assist you to port out from using our editor and still keep the data and everything totally intact.”

\n\n\n\n

Diving Into the Plugin

\n\n\n\nStory management screen.\n\n\n\n

MakeStories is a drag-and-drop editor for building Web Stories. It works and feels much like typical design editors like Gimp or Photoshop. It shares similarities with QuarkXPress or InDesign, for those familiar with page layout programs. In some ways, it feels a lot like a light-colored version of Google’s Web Stories plugin with more features and a slightly more intuitive interface.

\n\n\n\n

The end goal is simple: create a Story through designing slides/pages that site visitors will click through as the narrative unfolds.

\n\n\n\n

The plugin provides a plethora of shapes, textures, and animations. These features are easy to find and implement. It also includes free access to images, GIFs, and videos. These are made possible via API integrations with Unsplash, Tenor, and Pexels.

\n\n\n\n

MakeStories includes access to 10 templates at the moment. However, what makes this feature stand out is that end-users can create and save custom templates for reuse down the road.

\n\n\n\nEditing a Story from a predesigned template.\n\n\n\n

One of the more interesting, almost hidden, features is the available text patterns. The plugin allows users to insert these patterns from a couple of dozen choices. This makes it easier to visualize a design without having to build everything from scratch.

\n\n\n\nInserting a text pattern and adjusting its size.\n\n\n\n

While the editing process is a carefully-crafted experience that makes the plugin worth a look, it is the actual publishing aspect of the workflow that is a bit painful. Traditional publishing in WordPress means hitting the “publish” button to make content live. This is not the case with the MakeStories plugin. It takes you through a four-step process of entering various publisher details, setting up metadata and SEO, validating the Story content, and analytics. It is not that these steps are necessarily bad. For example, MakeStories lets you know when images are missing alt text, which is needed information screen readers. The problem is that it feels out of place to go through all of these details when I, as a user, simply want my content published. And, many of these details, such as the publisher (author), should be automatically filled in.

\n\n\n\n

Updating a Story is not as simple as hitting an “update” button either. The system needs to run through some of the same steps too.

\n\n\n\n

Ghela said the publishing process might be a bit tough but will prove fruitful in the end. The plugin takes care of the technical aspects of adding title tags, meta, and other data on the front end after the user fills in the form fields.

\n\n\n\n

“We will definitely work on improving the flow as the community evolves and improve it a lot to be easier, faster, and, most importantly, still very customizable,” he said.

\n\n\n\n

The MakeStories team has no plans of stopping at its current point on the roadmap. Ghela sounded excited about some of the upcoming additions they are planning, including features like teams, branding, easy template customization, polls, and quizzes.

\n\n\n\n

On the Web Stories Format

\n\n\n\nUN report on COVID-19 and poverty published with MakeStories.\n\n\n\n

Many will ultimately hesitate to use any plugin that implements Web Stories given Google’s history of dropping projects. There is also a feeling that the format is a bit of a fad and will not stand the test of time.

\n\n\n\n

“We greatly believe in AMP and Web Stories as a content format,” said Ghela. “We, as an agency, have been involved a lot in AMP and have done a lot of experiments with it, including a totally custom WooCommerce site in fully-native, valid AMP with support for variable products, subscriptions, and other functionalities.”

\n\n\n\n

The company is all-in on the format and feels like it will be around for the long term, particularly if there is a good ecosystem around monetization.

\n\n\n\n

“We think that the initial reactions are because there are not enough proven results and because we never imagined the story format to come to the web,” said Ghela. “There were definitely plugins that did this. Few folks tried to build stories using good ol’ HTML, CSS, and JavaScript. But, the performance and UX were not that great. On the other hand, the engineers at the AMP Team are making sure that everything is just perfect. The UX, load time, WCV Score, just everything.”

\n\n\n\n

He feels that some of the early criticisms are unwarranted and that the web development community should give the format a try and provide feedback.

\n\n\n\n

“The more data we all get, actually gives the AMP team a clear idea of what’s needed, and they can design the roadmap accordingly,” he said. “So, just giving out early reactions won’t help, but constructive criticism and getting back to the AMP team with what you are doing will.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:12:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"WordPress.org blog: WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7956:\"

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"WPTavern: WordPress 5.6 Release Team Pulls the Plug on Block-Based Widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106466\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:193:\"https://wptavern.com/wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8762:\"Current block-based widgets admin screen design.\n\n\n\n

I was wrong. I assured our readers that “the block-based widget system will be ready for prime time when WordPress 5.6 lands” in my previous post on the new feature’s readiness. I also said that was on the condition of not trying to make it work with the customizer — that experience was still broken. However, the 5.6 team pulled the plug on block-based widgets for the second time this year.

\n\n\n\n

One week ago, WordPress 5.6 release lead Josepha Haden seemed to agree that it would be ready. However, things can change quickly in a development cycle, and tough decisions have to be made with beta release deadlines.

\n\n\n\n

This is not the first feature the team has punted to a future release. Two weeks ago, they dropped block-based nav menus from the 5.6 feature list. Both features were originally planned for WordPress 5.5.

\n\n\n\n

A new Widgets admin screen has been under development since January 2019, which was not long after the initial launch of the block editor in WordPress 5.0. For now, the block-based widgets feature has been punted to WordPress 5.7. It has also been given the “early” tag, which means it should go into core WordPress soon after the 5.7 release cycle begins. This will give it more time to mature and more people an opportunity to test it.

\n\n\n\n

Helen Hou-Sandì, the core tech lead for 5.6, provided a historical account of the decision and why it was not ready for inclusion in the new ticket:

\n\n\n\n

My question for features that affect the front-end is “can I try out this new thing without the penalty of messing up my site?” — that is, user trust. At this current moment, given that widget areas are not displayed anything like what you see on your site without themes really putting effort into it and that you have to save your changes live without revisions to get an actual contextual view, widget area blocks do not allow you to try this new feature without penalizing you for experimenting.

\n\n\n\n

She went on to say that the current experience is subpar at the moment. Problems related to the customizer experience, which I covered in detail over a month ago, were also mentioned.

\n\n\n\n

“So, when we come back to this again, let’s keep sight of what it means to keep users feeling secure that they can get their site looking the way they want with WordPress, and not like they are having to work around what we’ve given them,” said Hou-Sandì.

\n\n\n\n

This is a hopeful outlook despite the tough decision. Sometimes, these types of calls need to be made for the good of the project in the long term. Pushing back a feature to a future version for a better user experience can be better than launching early with a subpar experience.

\n\n\n\n

“The good part of this is that now widgets can continue to be ‘re-imagined’ for 5.7, and get even more enhancements,” said lead WordPress developer Andrew Ozz in the ticket. “Not sure how many people have tested this for a bit longer but having blocks in the widgets areas (a.k.a. sidebars) opens up many new possibilities and makes a lot of the old, limited widgets obsolete. The ‘widget areas’ become something like ‘specialized posts with more dynamic content,’ letting users (and designers) do a lot of stuff that was either hard or impossible with the old widgets.”

\n\n\n\n

After the letdown of seeing one of my most anticipated features of 5.6 being dropped, it is encouraging to see the positive outlook from community leaders on the project.

\n\n\n\n

“You know, I was really hopeful for it too, and that last-minute call was one I labored over,” said Haden. “When I last looked, it did seem close to ready, but then more focused testing was done and there were some interactions that are a little rough for users. I’m grateful for that because the time to discover painful user experiences is before launch rather than after!”

\n\n\n\n

Despite dropping its second major feature, WordPress 5.6 still has some big highlights that will be shipping in less than two months. The new Twenty Twenty-One theme looks to be a breath of fresh air and will explore block-related features not seen in previous default themes. Haden also pointed out auto-updates for major releases, application passwords support for the REST API, and accessibility improvements as features to look forward to.

\n\n\n\n

WordPress 5.6 Beta 1 is expected to ship today.

\n\n\n\n

Adding New Features To an Old Project

\n\n\n\n

At times, it feels like the Gutenberg project has bitten off more than it can chew. Many of the big feature plans continually miss projections. Between full-site editing, global styles, widgets, nav menus, and much more, it is tough to get hyper-focused on one feature and have it ready to ship. On the other hand, too much focus one way can be to the detriment to other features in the long run. All of these pieces must eventually come together to create a more cohesive whole.

\n\n\n\n

WordPress is also 17 years old. Any new feature could affect legacy features or code. The goal for block-based widgets is to transition an existing feature to work within a new system without breaking millions of websites in the process. Twenty-one months of work on a single feature shows that it is not an easy problem to solve.

\n\n\n\n

“You are so right about complex engineering problems!” said Haden. “We are now at a point in the history of the project where connecting all of the pieces can have us facing unforeseen complications.”

\n\n\n\n

The project also needs to think about how it can address some of the issues it has faced with not quite getting major features to completion. Is the team stretched too thin to focus on all the parts? Are there areas we can improve to push features forward?

\n\n\n\n

“There will be a retrospective where we can identify what parts of our process can be improved in the future, but I also feel like setting stretch goals is good for any software project,” said Haden. “Many contributors have a sense of urgency around bringing the power of blocks to more spaces in WordPress, which I share, but when it’s time to ship, we have to balance that with our deep commitment to usability.”

\n\n\n\n

One problem that has become increasingly obvious is that front-end editing has become tougher over the years. Currently, widgets and nav menus can be edited in two places in WordPress with wildly different interfaces. Full-site editing stands to add an entirely new interface to the mix.

\n\n\n\n

“I think one of the problems that we’re trying to solve with Gutenberg has always been a more consistent experience for editing elements across the WordPress interface,” said Haden. “No user should have to learn five different workflows to make sure their page looks the way they imagined it when it’s published.”

\n\n\n\n

In the meantime, which may be numbered in years, end-users will likely have these multiple interfaces to deal with — overlap while new features are being developed. This may simply be a necessary growing pain of an aging project, one that is trying to lead the pack of hungry competitors in the CMS space.

\n\n\n\n

“There’s a lot of interest in reducing the number of workflows, and I’m hopeful that we can consolidate down to just one beautiful, intuitive interface,” said Haden.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 21:16:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce Tests New Instagram Shopping Checkout Feature, Now in Closed Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106398\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2878:\"

Instagram’s checkout feature, which allows users to purchase products without leaving the app, has become an even more important part of Facebook’s long-term investment in e-commerce now that the pandemic has so heavily skewed consumer behavior towards online shopping. When Instagram introduced checkout in 2019, it reported that 130 million users were tapping to reveal product tags in shopping posts every month.

\n\n\n\nimage credit: Instagram\n\n\n\n

Business owners who operate an existing store can extend their audience to Instagram by funneling orders from the social network into their own stores, without shoppers having to leave Instagram. Checkout supports integration with several e-commerce platform partners, including Shopify and BigCommerce, and will soon be available for WooCommerce merchants.

\n\n\n\n

WooCommerce is testing a new Instagram Shopping Checkout feature for its Facebook for WooCommerce plugin. The free extension is used on more than 900,000 websites and will provide the bridge for store owners who want to tap into Instagram’s market. The checkout capabilities are currently in closed beta. Anyone interested to test the feature can sign up for consideration. Businesses registered in the USA that meet certain other requirements may be selected to participate, and the beta is also expanding to other regions soon.

\n\n\n\n

WooCommerce currently supports shoppable posts, which are essentially products sourced from a product catalog created on Facebook that are then linked to the live store through an Instagram business account. Instagram’s checkout takes it one step further to provide a native checkout experience inside the app. Merchants pay no selling fees until December 31, 2020. After that time, the fee is 5% per shipment or a flat fee of $0.40 for shipments of $8.00 or less. 

\n\n\n\n

On the customer side, shoppers only have to enter their information once and thereafter it is stored for future Instagram purchases. Instagram also pushes shipment and delivery notifications inside the app. Store owners will need to weigh whether the convenience of the in-app checkout experience is worth forking over 5% to Facebook, or if they prefer funneling users over to the live store instead.

\n\n\n\n

Instagram Shopping Checkout is coming to WooCommerce in the near future but the company has not yet announced a launch date, as the feature is just now entering closed beta.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 04:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Past Twenty* WordPress Themes To Get New Block Patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106396\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/past-twenty-wordpress-themes-to-get-new-block-patterns?utm_source=rss&utm_medium=rss&utm_campaign=past-twenty-wordpress-themes-to-get-new-block-patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6608:\"

Mel Choyce-Dwan, the Default Theme Design Lead for WordPress 5.6, kick-started 10 tickets around two months ago that would bring new features to the old default WordPress themes. The proposal is to add unique block patterns, a feature added to WordPress 5.5, to all of the previous 10 Twenty* themes. It is a lofty goal that could breathe some new life into old work from the previous decade.

\n\n\n\n

Currently, only the last four themes are marked for an update by the time WordPress 5.6 lands. Previous themes are on the list to receive their block patterns in a future release. For developers and designers interested in getting involved, the following is a list of the Trac tickets for each theme:

\n\n\n\n\n\n\n\n

If you are wondering where Twenty Eighteen is in that list, that theme does not actually exist. It is the one missing year the WordPress community has had since the one-default-theme-per-year era began with Twenty Ten. It is easy to forget that we did not get a new theme for the 2017-2018 season. With all that has happened in the world this year, we should count ourselves fortunate to see a new default theme land for WordPress this December. WordPress updates and its upcoming default theme are at least one consistency that we have had in an otherwise chaotic time.

\n\n\n\n

More than anything, it is nice to see some work going toward older themes — not just in terms of bug fixes but feature updates. The older defaults are still a part of the face of WordPress. Twenty Twenty and Twenty Seventeen each have over one million active installs. Twenty Nineteen has over half a million. The other default themes also have significant user bases in the hundreds of thousands — still some of the most-used themes in the directory. We owe it to those themes’ users to keep them fresh, at least as long as they maintain such levels of popularity.

\n\n\n\n

This is where the massive theme development community could pitch in. Do some testing of the existing patches. Write some code for missing patterns or introduce new ideas. This is the sort of low-hanging fruit that almost anyone could take some time to help with.

\n\n\n\n

First Look at the New Patterns

\n\n\n\n

None of the proposed patterns have landed in trunk, the development version of WordPress, yet. However, several people have created mockups or added patches that could be committed soon.

\n\n\n\n

One of my favorite patterns to emerge thus far is from Beatriz Fialho for the Twenty Nineteen theme. Fialho has created many of the pattern designs proposed thus far, but this one, in particular, stands out the most. It is a simple two-column, two-row pattern with a circular image, heading, and paragraph for each section. Its simplicity fits in well with the more elegant, business-friendly look of the Twenty Nineteen theme.

\n\n\n\nServices pattern for Twenty Nineteen.\n\n\n\n

It is also fitting that Twenty Nineteen get a nice refresh with new patterns because it was the default theme to launch with the block editor. Ideally, it would continually be updated to showcase block-related features.

\n\n\n\n

While many people will focus on some of the more recent default themes, perhaps the most interesting one is a bit more dated. Twenty Thirteen was meant to showcase the new post formats feature in WordPress 3.6. According to Joen Asmussen, the theme’s primary designer, the original idea was for users to compose a ribbon of alternating colors as each post varied its colors.

\n\n\n\n

“The alternating ribbon of colors did not really come to pass because post formats were simply not used enough to create an interesting ribbon,” he wrote in the Twenty Thirteen ticket. “However, perhaps for block patterns, we have an opportunity to revisit those alternating ribbons of colors. In other words, I’d love to see those warm bold colors used in big swathes that take up the whole pattern background.”

\n\n\n\n
Patterns designed to match post formats.\n\n\n\n

This could be a fun update for end-users who are still using that feature that shall not be named post formats.

\n\n\n\n

There is a lot to like about many of the pattern mockups so far. I look forward to seeing what lands along with WordPress 5.6 and in future updates.

\n\n\n\n

Establishing Pattern Category Standard

\n\n\n\n

With the more recent Twenty Twenty-One theme’s block patterns and the new patterns being added to some of the older default themes, it looks like a specific pattern category naming scheme is starting to become a standard. Of the patches thus far, each is creating a new pattern category named after the theme itself.

\n\n\n\n

This makes sense. Allowing users to find all of their theme’s patterns in one location means that they can differentiate between them and those from core or other plugins. Third-party theme authors should follow suit and stick with this convention for the same reason.

\n\n\n\n

Developers can also define multiple categories for a single pattern. This allows theme authors to create a category that houses all of their patterns in one location. However, they can also split them into more appropriate context-specific categories for discoverability.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 19 Oct 2020 21:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"BuddyPress: BuddyPress 7.0.0-beta1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://buddypress.org/?p=315150\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://buddypress.org/2020/10/buddypress-7-0-0-beta1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4332:\"

BuddyPress 7.0.0-beta1 is now available for testing!

\n\n\n\n

Please note the plugin is still in development, so we recommend running this beta release on a testing site.

\n\n\n\n

You can test BuddyPress 7.0.0-beta1 in 4 ways :

\n\n\n\n\n\n\n\n

The 7.0.0 stable release is slated to the beginning of December, and we’d love you to give us a hand to get there!

\n\n\n\n

Please note BuddyPress 7.0.0 will require at least WordPress 4.9.

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute. Here are some of the big changes and features to pay close attention to while testing (Check out this report on Trac for the full list).

\n\n\n\n
\n\n\n\n

New Administration screens to manage BuddyPress types

\n\n\n\n

In BuddyPress 7.0.0 site administrators will be able to add, edit or delete Member & Group types using their WordPress Administration Screens just like they would do for Post tags.

\n\n\n\n

Read this development note to learn more about it.

\n\n\n\n
\n\n\n\n

Let’s welcome 3 new BP Blocks into our Block Editor

\n\n\n\n
  • The Activity Embed block let authors embed an activity into their post or page.
  • Use the BP Members block to select community users you want to feature into a post or a page.
  • Enjoy the BP Groups block to pick the groups you want to highlight into a post or a page.
\n\n\n\n

Get to know these new blocks reading this development note.

\n\n\n\n
\n\n\n\n

Improved support for WP CLI

\n\n\n\n

WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs, and much more, without using a web browser. In 7.0.0, you will be able to Enjoy new BuddyPress CLI commands to manage BuddyPress Group Meta, BuddyPress Activity Meta, activate or deactivate the BuddyPress signup feature and create BuddyPress specific testing code for plugins.

\n\n\n\n

Discover more about it from this development note.

\n\n\n\n
\n\n\n\n

And so much more such as improvements to the BP REST API, our Template pack, images and iframes lazy loading support…

\n\n\n\n
\n\n\n\n

How You Can Help

\n\n\n\n

Do you speak a language other than English? Help us translate BuddyPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, file one on BuddyPress Trac.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 22:30:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:12:\"Mathieu Viet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"WPTavern: Using the Web Stories for WordPress Plugin? You Better Play By Google’s Rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105848\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:215:\"https://wptavern.com/using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules?utm_source=rss&utm_medium=rss&utm_campaign=using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4080:\"Web Stories dashboard screen in WordPress.\n\n\n\n

What comes as a surprise to few, Google has updated its content guidelines for its Web Stories format. For users of its recently-released Web Stories for WordPress plugin, they will want to follow the extended rules for their Stories to appear in the “richer experiences” across Google’s services. This includes the grid view on Search, Google Images, and Google Discover’s carousel.

\n\n\n\n

Google released its Web Stories plugin in late September to the WordPress community. It is a drag-and-drop editor that allows end-users to create custom Stories from a custom screen in their WordPress admin.

\n\n\n\n
Visual Stories on Search.
\n\n\n\n

The plugin does not directly link to Google’s content guidelines anywhere. For users who do not do a little digging, they may be caught unaware if their stories are not surfaced in various Google services.

\n\n\n\n

On top of the Discover and Webmaster guidelines, Web Stories have six additional restrictions related to the following:

\n\n\n\n
  • Copyrighted content
  • Text-heavy Web Stories
  • Low-quality assets
  • Lack of narrative
  • Incomplete stories
  • Overly commercial
\n\n\n\n

While not using copyrighted content is one of those reasonably-obvious guidelines, the others could trip up some users. Because Stories are meant to represent bite-sized bits of information on each page, they may become ineligible if most pages have more than 180 words of text. Videos should also be limited to fewer than 60 seconds on each page.

\n\n\n\n

Low-quality media could be a flag for Stories too. Google’s guidelines point toward “stretched out or pixelated” media that negatively impacts the reader’s experience. They do not offer any specific resolution guidelines, but this should mostly be a non-issue today. The opposite issue is far more likely — users uploading media that is too large and not optimized for viewing on the web.

\n\n\n\n

The “lack of narrative” guideline is perhaps the vaguest, and it is unclear how Google will monitor or police narrative. However, the Stories format is about storytelling.

\n\n\n\n

“Stories are the key here imo,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Google specifically states that Stories need a “binding theme or narrative structure” from one page to the next. Essentially, the company is telling users to use the format for the purpose it was created for. They also do not want users to create incomplete stories where readers must click a link to finish the Story or get information.

\n\n\n\nCNN’s Web Story on Remembering John Lennon.\n\n\n\n

Overly commercial Stories are frowned upon too. While Google will allow affiliate marketing links, they should be restricted to a minor part of the experience.

\n\n\n\n

Closing his Twitter thread, Marsland seemed to hit the point. “I’ve seen some initial Google Web Stories where the platform is being used as a replacement for a brochure or website,” he wrote. “In my view that’s a huge missed opportunity. If I was advising brands I would say ‘Tell Stories’ this is a platform for Story Telling.”

\n\n\n\n

If users of the plugin follow this advice, their Stories should surface on Google’s rich search experiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 20:51:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"WPTavern: Stripe Acquires Paystack for $200M+\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106269\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"https://wptavern.com/stripe-acquires-paystack-for-200m?utm_source=rss&utm_medium=rss&utm_campaign=stripe-acquires-paystack-for-200m\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3196:\"

The big news in the world of e-commerce today is Stripe’s acquisition of Paystack, a Nigeria-based payments system that is widely used throughout African markets. The company, which became informally known as “the Stripe of Africa” picked up $8 million in Series A funding in 2018, led by Stripe, Y Combinator, and Tencent. Paystack has grown to power more than 60,000 businesses, including FedEx, UPS, MTN, the Lagos Internal Revenue Service, and AXA Mansar.

\n\n\n\n

Stripe’s acquisition of the company is rumored to be more than $200M, a small price to pay for a foothold in emerging African markets. In the company’s announcement, Stripe noted that African online commerce is growing 21% year-over-year, 75% faster than the global average. Paystack dominates among payment systems, accounting for more than half of all online transactions in Nigeria.

\n\n\n\n

“In just five years, Paystack has done what many companies could not achieve in decades,” Stripe EMEA business lead Matt Henderson said. “Their tech-first approach, values, and ambition greatly align with our own. This acquisition will give Paystack resources to develop new products, support more businesses and consolidate the hyper-fragmented African payments market.”

\n\n\n\n

Long term, Stripe plans to embed Paystack’s capabilities in its Global Payments and Treasury Network (GPTN), the company’s programmable infrastructure for global money movement.

\n\n\n\n

“Paystack merchants and partners can look forward to more payment channels, more tools, accelerated geographic expansion, and deeper integrations with global platforms,” Paystack CEO and co-founder Shola Akinlade said. He also assured customers that there’s no need to make any changes to their technical integrations, as Paystack will continue expanding and operating independently in Africa.

\n\n\n\n

Paystack is used as a payment gateway for thousands of WordPress-powered stores through plugins for WooCommerce, Easy Digital Downloads, Paid Membership Pro, Give, Contact Form 7, and an assortment of booking plugins. The company has an official WordPress plugin, Payment Forms for Paystack, which is active on more than 6,000 sites, but most users come through the Paystack WooCommerce Payment Gateway (20,000+ active installations).

\n\n\n\n

Stripe’s acquisition was a bit of positive news during what is currently a turbulent time in Nigeria, as citizens are actively engaged in peaceful protests to end police brutality. Paystack’s journey is an encouraging example of the flourishing Nigerian tech ecosystem and the possibilities available for smaller e-commerce companies that are solving problems and removing barriers for businesses in emerging markets.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 22:26:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: Diving Into the Book Review Block Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106273\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:145:\"https://wptavern.com/diving-into-the-book-review-block-plugin?utm_source=rss&utm_medium=rss&utm_campaign=diving-into-the-book-review-block-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6791:\"

Created by Donna Peplinskie, a Product Wrangler at Automattic, the Book Review Block plugin is nearly three years old. However, it only came to my attention during a recent excursion to find interesting block plugins.

\n\n\n\n

The plugin does pretty much what it says on the cover. It is designed to review books. It generally has all the fields users might need to add to their reviews, such as a title, author, image, rating, and more. The interesting thing is that it can automatically fill in those details with a simple ISBN value. Plus, it supports Schema markup, which may help with SEO.

\n\n\n\n

Rain or shine, sick or well, I read every day. I am currently a month and a half shy of a two-year reading streak. When the mood strikes, I even venture to write a book review. As much as I want to share interesting WordPress projects with the community, I sometimes have personal motives for testing and writing about plugins like Book Review Block. Anything that might help me or other avid readers share our thoughts on the world of literature with others is of interest.

\n\n\n\n

Admittedly, I was excited as I plugged in the ISBN for Rhthym of War, the upcoming fourth book of my favorite fantasy series of all time, The Stormlight Archive. I merely needed to click the “Get Book Details” button.

\n\n\n\n

Success! The plugin worked its magic and pulled in the necessary information. It had my favorite author’s name, the publisher, the upcoming release date, and the page count. It even had a long description, which I could trim down in the editor.

\n\n\n\nDefault output of the Book Review block.\n\n\n\n

There was a little work to make this happen before the success. To automatically pull in the book details, end-users must have an API Key from Google. It took me around a minute to set that up and enter it into the field available in the block options sidebar. The great thing about the plugin is that it saves this key so that users do not have to enter each time they want to review a book.

\n\n\n\n

Book Review Block a good starting point. It is straightforward and simple to use. It is not yet at a point where I would call it a great plugin. However, it could be.

\n\n\n\n

Falling Short

\n\n\n\n

The plugin’s Book Review block should be taking its cues from the core Media & Text block. When you get right down to it, the two are essentially doing the same thing visually. Both are blocks with an image and some content sitting next to each other.

\n\n\n\n

The following is a list of items where it should be following core’s lead:

\n\n\n\n
  • No way to edit alt text (book title is automatically used).
  • The image is always aligned left and the content to the right with no way to flip them.
  • The media and content are not stackable on mobile views.
  • Cannot adjust the size of the image or content columns.
  • While inline rich-text controls are supported, users cannot add Heading, List, or Paragraph blocks to the content area and use their associated block options.
\n\n\n\n

That is the shortlist that could offer some quick improvements to the user experience. Ultimately, the problems with the plugin essentially come down to not offering a way to customize the output.

\n\n\n\n

One of the other consistent problems is that the book image the plugin loads is always a bit small. This seems to be more of an issue from the Google Books API than the plugin. Each time I tested a book, I opted to add a larger image — the plugin does allow you to replace the default.

\n\n\n\n

The color settings are limited. The block only offers a background color option with no way to adjust the text color. A better option for plugin users is to wrap it in a Group block and adjust the background and text colors there.

\n\n\n\nBook Review block wrapped inside a Group block.\n\n\n\n

It would also be nice to have wide and full-alignment options, which is an often-overlooked featured from many block plugin authors.

\n\n\n\n

Using the Media & Text Block to Recreate the Book Review Block

\n\n\n\n

The Book Review Block plugin has a lot of potential, and I want to see it evolve by providing more flexibility to end-users. Because the Media & Text block is the closest core block to what the plugin offers, I decided to recreate a more visually-appealing design with it.

\n\n\n\nBook review section created with the Media & Text block.\n\n\n\n

I made some adjustments on the content side of things. I used the Heading block for the book title, a List block for the book metadata, and a Paragraph block for the description.

\n\n\n\n

The Media & Text block also provided me the freedom to adjust the alignment, stack the image and content on mobile views, and tinker with the size of the image. Plus, it has that all-important field for customizing the image alt attribute.

\n\n\n\n

The Media & Text block gave me much more design mileage.

\n\n\n\n

However, there are limitations to the core block. It does not fully capture some of the features available via the Book Review block. The most obvious are the automatic book details via an ISBN and the Schema markup. Less obvious, there is no easy way to recreate the star rating — I used emoji stars — and long description text does not wrap under the image. To recreate that, you would have to opt to use a left-aligned image followed by content.

\n\n\n\n

Overall, the Media & Text block gives me the ability to better style the output, which is what I am more interested in as a user. I want to put my unique spin on things. That is where the Book Review Plugin misfires. It is also the sort of thing that the plugin author can iterate on, offering more flexibility in the future.

\n\n\n\n

This is where many block plugins go wrong, particularly when there is more than one or two bits of data users should enter. Blocks represent freedom in many ways. However, when plugin developers stick to a rigid structure, users can sometimes lose that sense of freedom that they would otherwise have with building their pages.

\n\n\n\n

One of the best blocks, hands down, that preserves that freedom is from the Recipe Block plugin. It has structured inputs and fields. However, it allows freeform content for end-users to make it their own.

\n\n\n\n

When block authors push beyond this rigidness, users win.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 20:44:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce 4.6 Makes New Home Screen the Default for New and Existing Stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3018:\"

WooCommerce 4.6 was released today. The minor release dropped during WooSesh, a global, virtual conference dedicated to WooCommerce and e-commerce topics. It features the new home screen as the default for all stores. Previously, the screen was only the default on new stores. Existing store owners had to turn the feature on in the settings.

\n\n\n\n
\n\n\n\n

The updated home screen, originally introduced in version 4.3, helps store admins see activity across the site at a glance and includes an inbox, quick access to store management links, and an overview of stats on sales, orders, and visitors. This redesigned virtual command center arrives not a moment too soon, as anything that makes order management more efficient is a welcome improvement, due to the sheer volume of sales increases that store owners have seen over the past eight months.

\n\n\n\n

In stark contrast to industries like hospitality and entertainment that have proven to be more vulnerable during the pandemic, e-commerce has seen explosive growth. During the State of the Woo address at WooSesh 2020, the WooCommerce team shared that e-commerce is currently estimated to be a $4 trillion market that will grow to $4.5 trillion by 2021. WooCommerce accounts for a sizable chunk of that market with an estimated total payment volume for 2020 projected to reach $20.6 billion, a 74% increase compared to 2019.

\n\n\n\n

The WooCommerce community is on the forefront of that growth and is deeply invested in the products that are driving stores’ success. The WooCommerce team shared that 75% of people who build extensions also build and maintain stores for merchants, and 70% of those who build stores for merchants also build and maintain extensions or plugins. In 2021, they plan to invest heavily in unlocking more features in more countries and will make WooCommerce Payments the native payment method for the global platform.

\n\n\n\n

A new report from eMarketer shows that US e-commerce growth has jumped 32.4%, accelerating the online shopping shift by nearly two years. Experts also predict the top 10 e-commerce players will swallow up more of US retail spending to account for 63.2% of all online sales this year, up from 57.9% in 2019.

\n\n\n\n

The increase in e-commerce spending may not be entirely tied to the pandemic, as some experts believe this historic time will mark permanent changes in consumer spending habits. This is where independent stores, powered by WooCommerce and other technologies, have the opportunity to establish a strong reputation for themselves by providing quality products and reliable service, as well as by being more nimble in the face of pandemic-driven increases in volume.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 03:48:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"WPTavern: The Future of Starter Content: WordPress Themes Need a Modern Onboarding and Importing Tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106177\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool?utm_source=rss&utm_medium=rss&utm_campaign=the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7385:\"Image credit: picjumbo.com on Pexels.\n\n\n\n

Starter content. It was a grand idea, one of those big dreams of WordPress. It was the new kid on the block in late 2016. Like the introduction of post formats in 2011, the developer community was all in for at least that particular release version. Then, it was on to the next new thing, with the feature dropping off the radar for all but the most ardent evangelists.

\n\n\n\n

Some of us were burned over the years, living and dying by the progress of features that we wanted most.

\n\n\n\n

Released in WordPress 4.7, starter content has since seemed to be going the way of post formats. After four years, only 141 themes in the WordPress theme directory support the feature. There has been no movement to push it beyond its initial implementation. And, it never really covered the things that theme authors wanted in the first place. It was a start. But, themers were ultimately left to their own devices, rolling custom solutions for something that never panned out — fully-featured demo and imported content. Four years is an eternity in the web development world, and there is no sense in waiting around to see if WordPress would push forward.

\n\n\n\n

Until Helen Hou-Sandí published Revisiting Starter Content last week, most would have likely assumed the feature would be relegated to legacy code used by old-school fans of the feature and those theme authors who consider themselves completionists.

\n\n\n\n

“Starter content in 4.7 was always meant to be a step one, not the end goal or even the resting point it’s become,” wrote Hou-Sandí. “There are still two major things that need to be done: themes should have a unified way of showing users how best to put that theme to use in both the individual site and broader preview contexts, and sites with existing content should also be able to take advantage of these sort of ‘ideal content’ definitions.”

\n\n\n\n

Step two should have been this four-year-old accompanying ticket to allow users to import starter content into existing, non-fresh sites.

\n\n\n\n

Since the initial feature dropped, the theme landscape has changed. Let’s face it. WordPress might simply not be able to compete with theme companies that are pushing the limits, creating experiences that users want at much swifter speeds.

\n\n\n\n

Look at where the Brainstorm Force’s Starter Templates plugin for its Astra theme is now. Users can click a button and import a full suite of content-filled pages or even individual templates. And, the Astra theme is not alone in this. It has become an increasingly-common standard to offer some sort of onboarding to users. GoDaddy’s managed WordPress service fills a similar need on the hosting end.

\n\n\n\nAstra’s starter templates and content.\n\n\n\n

As WordPress use becomes more widespread, the more it needs a way to onboard users.

\n\n\n\n

This essentially boils down to the question: how can I make it look like the demo?

\n\n\n\n

Ah, the age-old question that theme authors have been trying to solve. Whether it has been limitations in the software or, perhaps, antiquated theme review guidelines related to demo and imported content, this has been a hurdle that has been tough to jump. But, some have sailed over it and moved on. While WordPress has seemingly been twiddling its thumbs for years, Brainstorm Force and other theme companies have solved this and continued to innovate.

\n\n\n\n

This is not necessarily a bad thing. There are plenty of ideas to steal copy and pull into the core platform.

\n\n\n\n

One of the other problems facing the WordPress starter content feature is that it is tied to the customizer. With the direction of the block system, it is easy to ask what the future holds. The customizer — originally named the theme customizer — was essentially a project to allow users to make front-end adjustments and watch those customizations happen in real time. However, new features like global styles and full-site editing are happening on their own admin screens. Most theme options will ultimately be relegated to global styles, custom templates, block styles, and block patterns. There may not be much left for the customizer to do.

\n\n\n\n

Right now, there are too many places in WordPress to edit the front-end bits of a WordPress site. My hope is that all of these things are ultimately merged into one less-confusing interface. But, I digress…

\n\n\n\n

Starter content should be rethought. Whoever takes the reins on this needs a fresh take that adopts modern methods from leading theme companies.

\n\n\n\n

The ultimate goal should be to allow theme authors to create multiple sets of templates/content that end-users can preview and import. It should not be tied to whether it is a new site. Any site owner should be able to import content and have it automagically go live. It should also be extendable to allow themes to support page builders like Elementor, Beaver Builder, and many others.

\n\n\n\n

This seems to be in line with Hou-Sandí’s thoughts. “For a future release, we should start exploring what it might look like to opt into importing starter content into existing sites, whether wholesale or piecewise,” she wrote. “Many of us who work in the WordPress development/consulting space tend not to ever deal in switching between public themes on our sites, but let’s not forget that’s a significant portion of our user audience and we want to continue to enable them to not just publish but also publish in a way that matches their vision.”

\n\n\n\n

Let’s do it right this go-round, keep a broad vision, and provide an avenue for theme authors to adopt a standardized core WordPress method instead of having everyone build in-house solutions.

\n\n\n\n

I haven’t even touched on the recent call to use starter content for WordPress.org theme previews. It will take more than ideas to excite many theme authors about the possibility. That ticket has sat for seven years with no progress, and most have had it on their wish list for much longer. It is an interesting proposal, one that has been tossed around in various team meetings for years.

\n\n\n\n

Like so many other things, theme authors have either given up hope or moved onto doing their own thing. They need to be brought into the fold, not only as third parties who are building with core WordPress tools but as developers who are contributing to those features.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 14 Oct 2020 20:07:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:116:\"WPTavern: Google Podcasts Manager Adds More Data from Search: Impressions, Top-Discovered Episodes, and Search Terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:271:\"https://wptavern.com/google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms?utm_source=rss&utm_medium=rss&utm_campaign=google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2568:\"

Google announced an expansion of listener engagement metrics today for those using its Podcast Manager. Previously, audience insights included data about the types of devices listeners are using, where listeners tune in and drop off during a given episode, total number of listens, and listening duration, but the service lacked analytics regarding how visitors were discovering the podcast.

\n\n\n\n

Google is remedying that today by expanding the dashboard to show impressions, clicks, top-discovered episodes, and search terms that brought listeners to the podcast. This information can help podcasters understand how their content is getting discovered so they can better tailor their episodes to attract more new listeners.

\n\n\n\n

The podcasting industry has seen remarkable growth over the past five years, which previously led experts to project that marketers will spend over $1 billion in advertising by 2021. After the pandemic hit, podcast listening took a downturn in the U.S. but at the same time, podcast creators have found more time to create new shows and episodes. Businesses are turning to the medium to supplement traditional marketing methods that no longer have the same impact now that consumer spending habits heavily favor online products.

\n\n\n\n

Along with the new metrics available inside Google Podcasts Manager, the company also published a guide to optimizing podcasts for Google Search. It highlights four important items for making sure a podcast can be found:

\n\n\n\n
  • Detailed show and episode metadata
  • Ensure the podcast’s webpage and RSS data match
  • Include cover art
  • Ensure Googlebot can access your audio files
\n\n\n\n

A detailed breakdown of your audience’s listening habits isn’t worth much if you’re having trouble getting your podcast discovered. Any podcasting plugin for WordPress should handle these basic optimization recommendations, but if you are still having trouble being found via Google, you can dig deeper into the podcast setup guide for more detailed recommendations.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 22:57:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Are Block-Based Widgets Ready To Land in WordPress 5.6?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/are-block-based-widgets-ready-to-land-in-wordpress-5-6?utm_source=rss&utm_medium=rss&utm_campaign=are-block-based-widgets-ready-to-land-in-wordpress-5-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8214:\"

Two weeks ago, the Gutenberg team put out an open call for block-based widgets feedback. I had already written a lengthy review of the new system earlier in September but was asked by a member of the team to share my thoughts on the most recent iteration. With the upcoming freeze for WordPress 5.6 Beta 1 just a week away, I figured it would not hurt to do another deep dive.

\n\n\n\n

For reference, my latest testing is against version 9.2.0-alpha-172f589 of the Gutenberg plugin, which was a build from earlier today. Gutenberg development moves fast, but everything should be accurate to that point.

\n\n\n\n

Ultimately, many of the problems I pointed out over a month ago still exist. However, the team has cleaned most of the minor issues, such as pointing the open/close arrows for sidebars (block areas) in the correct direction and making it more consistent with the post-editing screen. The UI is much more polished.

\n\n\n\n

Before I dive into all the problems, I want to answer the question I am proposing. Yes, the block-based widget system will be ready for prime time when WordPress 5.6 lands. It is not there yet, but it is at a point where there is a clear finish line that is reachable in the next two months.

\n\n\n\n

I will ignore the failure of block-based widgets in the customizer, which landed in Gutenberg 8.9 and was removed in 9.1. I will also look past the recent proposal to reconstruct the widgets screen to use the Customize API, at least for now. There is a boatload of problems that block-based widgets present for the customizer, and those problems are insurmountable for WordPress 5.6. Long term, WordPress needs to have a single place for editing widget/block areas. Users will likely have to live with some inconsistencies for a while.

\n\n\n\n

Assuming the team does not try to throw a last-minute Hail Mary and implement full editing of blocks in the customizer this round, it is safe to say that block-based widgets are well on their way toward a successful WordPress 5.6 debut.

\n\n\n\n

The User Experience

\n\n\n\nBlock-based widgets screen.\n\n\n\n

As a user, I genuinely enjoy using the new Widgets admin screen. The open-ended, free-form block areas create untold possibilities for designing my WordPress sites. Traditional widgets were limited in scope. Users were buckled down to a handful of core widgets, possibly some plugin widgets, and whatever their theme author offered up. However, with blocks, the pool of choices expands to at least triple the out-of-the-box options (I am not counting embed-type blocks individually). Plus, blocks provide a far more extensive set of design options than a traditional widget.

\n\n\n\n

In comparison, traditional widgets are outdated. Blocks are superior in almost every way. However, there are still problems with this new system.

\n\n\n\n

The biggest issue right now is that end-users can exit the Widgets screen without saving their changes. There is no warning to let them know that all their work is about to be lost in the ether. This is one of those OMGBBQ-level items that need to happen before WordPress 5.6 drops.

\n\n\n\n

One nice-to-have-but-not-necessary feature would be the ability to drag blocks from one block area to another. In the old widgets system, users could move widgets from sidebar to sidebar. The current alternative is to copy a widget, paste it in a new block area, and remove the original.

\n\n\n\n

I am also not a fan of not having an option for the top toolbar, which is available on the post-editing screen. One of the reasons for using this toolbar is because I dislike the default popup toolbar on individual blocks. It is distracting and often gets in the way of my work.

\n\n\n\n

Legacy widgets seem to still be a work in progress. The Legacy Widget block did not work at all for me at times. Then, it magically began to work. However, Gutenberg does now automatically add registered third-party widgets to the block inserter just as if they were blocks.

\n\n\n\nGetting a plugin’s widget to work.\n\n\n\n

This presented its own problems. The only way I managed to make third-party plugin widgets work was to insert the widget, save, and refresh the widgets screen. At that point, the widgets appeared and became editable.

\n\n\n\n

The Theme Author Experience

\n\n\n\n

One of my biggest concerns for theme authors right now is that there does not seem to be any documentation in the block editor handbook. There is plenty of time to make that happen, but there are things theme authors need to be aware of. Having a centralized location, even while the feature is under development, would help them gear up for the 5.6 release.

\n\n\n\n

Some of these questions, which may be answered in various Make blog posts, should exist on a dedicated documentation page:

\n\n\n\n
  • How can a theme opt out of block-based widgets?
  • What are the hooks to add custom styles for the Widgets screen?
  • Can themes target specific sidebar styles on the Widgets screen?
  • Is it possible to consistently style sections like traditional widgets on the front end?
  • Can themes opt into wide and full-alignment within block areas, which could essentially be used similarly to the post content area?
\n\n\n\n

These are some of the questions I would want to be answered as a former theme author. I am no longer in the thick of the theme design game and presume that those who are would have a larger list of questions.

\n\n\n\n

One less-obvious piece of documentation should center on how to handle fallbacks or default widgets. Traditionally, themes that needed to show a default set of widgets would check if the sidebar has widgets and fall back to using the_widget() to output one or more defaults. While theme authors can still do that, we should start to transition them across the board to the block system.

\n\n\n\n

Should theme authors copy/paste block HTML as a fallback? Would the starter content system be better for this, and can starter widget content handle blocks? What is the recommended method for widget fallbacks in WordPress 5.6?

\n\n\n\n

There is still the ongoing issue of how theme authors should handle the traditional widget and widget title wrapper HTML in the new block paradigm. One patch added since the Gutenberg 9.1 release wraps every top-level block with the widget wrapper. If this lands in the 9.2 release, it will likely make the issue worse.

\n\n\n\n

In the traditional system, both the widget title and content are wrapped within a container together. However, if a user adds a Heading block (widget title) and another block (widget content), each block is wrapped separately with the theme’s widget wrappers. The only way to rectify the situation as it stands is for end-users to add a Group block for each “widget” they want, which would require an extensive amount of re-education for WordPress users. It is not an ideal scenario.

\n\n\n\nEach block is wrapped as an individual section.\n\n\n\n

Instead of attempting to directly “fix” this issue, WordPress should instead do nothing to the output. Blocks and traditional widgets are fundamentally different.

\n\n\n\n

Let theme authors take the reins on this one and explore possibilities. However, give them the tools to do so, such as supporting block patterns.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 21:35:39 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: WordCamp Austin 2020 Finds Success with VR Experience for Sessions and Networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106119\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking?utm_source=rss&utm_medium=rss&utm_campaign=wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7246:\"

WordCamp Austin 2020 attendees are raving about their experiences attending the virtual event last Friday. It was no secret that the camp’s organizers planned to use Hubs Virtual Rooms by Mozilla to create a unique environment, but few could imagine how much more interactive and personalized the experience would be than a purely Zoom-based WordCamp.

\n\n\n\n

After selecting a custom avatar, attendees entered the venue using a VR headset or the browser to check out sessions or network in the hallway track.

\n\n\n\n
\n

Here’s a small taste of the experience at @WordCampATX today. #WordPress logos and no sponsor banners on any elevator doors. #WCATX pic.twitter.com/Nv2p2VchXf

— David Bisset (@dimensionmedia) October 9, 2020
\n
\n\n\n\n

Speaker and Q&A sessions were broadcast through Zoom but organizers can also embed YouTube videos and streams within the standalone VR environment.

\n\n\n\n

“The VR experience was the most life-like WordCamp experience I’ve had since the start of global lockdowns,” attendee and speaker David Vogelpohl said. “You could attend sessions in one of two virtual presentation halls depending on what track you wanted to see at that time. The speaker presented on a virtual stage and you could see the other attendees watching the presentation.”

\n\n\n\n

Vogelpohl said he enjoyed his experience getting to know others in the Slack and VR venue. Organizers preserved the general vibe of the “hallway track” to recreate what is arguably one of the most valuable aspects of in-person WordCamps.

\n\n\n\n
\n

So cool – checking out the Virtual Space of WordCamp Austin – love the background noise of people talking, ran into @ChrisWiegman and @Josh412 #WCATX pic.twitter.com/68EdgDN2Om

— Birgit Pauli-Haack (@bph) October 9, 2020
\n
\n\n\n\n

“In the hallway track between the virtual presentation halls was a large foyer where you could meet new people, spot a friend speaking with someone else, and virtually step aside from a group conversation to have a private conversation,” Vogelpohl said.

\n\n\n\n

“It was great to see folks like Josepha circling around speaking with attendees, Josh Pollock nerding out in a corner with a group of advanced WP developers, and having random friends drop into a conversation I was having with a group of others. While VR WordCamp doesn’t wholly replace the value of attending a WordCamp live, a lot of the best parts of meeting and collaborating with others was captured in the VR context.”

\n\n\n\n

The live music interludes, which showcased talents from around the community, also provided a way for virtual attendees to stay connected while waiting for the next session.

\n\n\n\n

Behind the Scenes with Anthony Burchell: Creative Director for WordCamp Austin’s Virtual World

\n\n\n\n

WordPress core contributor Anthony Burchell, who started a company dedicated to creating interactive XR sound and art experiences, was the creative director behind the WordCamp Austin’s VR backdrop.

\n\n\n\n

“For WordCamp Austin we wanted to give folks something to be excited about outside of the typical webcam and chat networking,” Burchell said. “I feel that virtual events are not utilizing the networking layer nearly enough to make folks feel like they are really at an event. I’ve seen many compelling formats for virtual events utilizing webcams and chat rooms, but in the end, it feels like there’s been a missing element of presence; something video games and virtual reality excel at.”

\n\n\n\n
\n

Virtual mission control for #WCATX pic.twitter.com/WyrFkIsW2Q

— Anthony Burchell (@antpb) October 9, 2020
\n
\n\n\n\n

Setting up the virtual world involves spinning up a self-hosted instance of Hubs Cloud, which Burchell said is very similar to the complexity of making a WordPress site.

\n\n\n\n

“The most time consuming part of creating a 3D world for an event is making the 3D assets for the space,” Burchell said. “In total I streamed 11 hours of video leading up to the event to give a glimpse into the process.”

\n\n\n\n

Burchell’s YouTube playlist documents the incredible amount of work that went into creating the WordCamp’s virtual venue for attendees to enjoy.

\n\n\n\n

“While it took quite a bit of time to prepare, the code and assets are completely reusable for another event,” Burchell said. “A lot of the time was spent trying to make the space purpose built for the goals of the camp. Much like a real WordCamp, I found the majority of folks packing into the theater rooms for presentations and dipping out a little early to network with friends in the hallway area. That was very much by design!”

\n\n\n\n

Burchell and the other organizers were careful to ensure that the Hubs space was not the primary viewing experience of the camp but rather an extension of the networking activities that attendees could drop in on. The event had nearly identical numbers of attendees joining the virtual space as it did for those joining the video channels. At the end of the afterparty, Burchell turned on flying for all attendees to conclude the successful event:

\n\n\n\n
\n\n
\n\n\n\n

“With Hubs we were able to give attendees the ability to express themselves within a venue vs within a camera and chat box,” Burchell said. “It was incredible to see characteristics of folks in the community shine through a virtual avatar! Just the simple act of seeing your WordCamp friends in the hallway joking and chatting just as they would at a real life event was enough to make me feel like I was transported to a real WordCamp.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 22:31:02 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Privacy-Conscious WordPress Plugin Caches and Serves Gravatar Images Locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105825\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally?utm_source=rss&utm_medium=rss&utm_campaign=privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5285:\"

Ari Stathopoulos released his new Local Gravatars plugin last week. The goal of the plugin is to allow site owners to take advantage of the benefits of a global avatar system while mitigating privacy concerns by hosting the images locally.

\n\n\n\n

In essence, it is a caching system that stores the images on the site owner’s server. It is an idea that Peter Shaw proposed in the comments on an earlier Tavern article covering local avatar upload. It is a middle ground that may satisfy some users’ issues with how avatars currently work in WordPress.

\n\n\n\n

“I am one of the people that blocks analytics, uses private sessions when visiting social sites, I use DuckDuckGo instead of Google, and I don’t like the ‘implied’ consents,” said Stathopoulos. “I built the plugin for my own use because I don’t know what Gravatar does, I don’t understand the privacy policies, and I am too lazy to spend two hours analyzing them. It’s faster for me to build something that is safe and doesn’t leave any room for misunderstandings.”

\n\n\n\n

He is referring to Automattic’s extensive Privacy Policy. He said it looks benign. However, he does not like the idea of any company being able to track what sites he visits without explicit consent.

\n\n\n\n

“And when I visit a site that uses Gravatar, some information is exposed to the site that serves them — including my IP,” said Stathopoulos. “Even if it’s just for analytics purposes, I don’t think the company should know that page A on site B got 1,000 visitors today with these IPs from these countries. There is absolutely no reason why any company not related to the page I’m actually visiting should have any kind of information about my visit.”

\n\n\n\n

The Local Gravatars plugin must still connect to the Gravatar service. However, the connection is made on the server rather than the client. Stathopoulos explained that the only information exposed in this case is the server’s IP and nothing from the client, which eliminates any potential privacy concerns.

\n\n\n\n

The Latest Plugin Update

\n\n\n\n

Stathopoulos updated the plugin earlier today to address some performance concerns for pages that have hundreds or more Gravatar images. In the version 1.0.1 update, he added a maximum processing time of five seconds and changed the cache cleanup process from daily to weekly. Both of these are filterable via code.

\n\n\n\n

“Now, if there are Gravatars missing in a page request, it will get as many as it can, and, after five seconds, it will stop,” said Stathopoulos. “So if there are 100 Gravatars missing and it gets the first 20, the rest will be blank (can be filtered to use a fallback URL, or even fall back to the remote URL, though that would defeat the privacy improvement). The next page request will get the next 20, and so on. At some point, all will be there, and there will be no more delays.”

\n\n\n\n

He did point out that performance could temporarily suffer when installing it on a site that has individual posts with 1,000s of comments and a lot of traffic. However, nothing would crash on the site, and the plugin should eventually lead to a performance boost in this scenario. For such large sites, owners could use the existing filter hooks to tweak the settings.

\n\n\n\n

Right now, the plugin is primarily an itch he wanted to scratch for his own purposes. However, if given enough usage and feedback, he may include a settings screen to allow users to control some of the currently-filterable defaults, such as the cleanup timeframe and the maximum process time allowed.

\n\n\n\n

The Growing List of Alternatives

\n\n\n\n

With growing concerns around privacy in the modern world, Local Gravatars is another tool that end-users can employ if they have any concerns around the Gravatar service. For those who are OK with an auto-generated avatar, Pixel Avatars may be a solution.

\n\n\n\n

“I’ve seen some of them, and they are wonderful!” Stathopoulos said of alternatives for serving avatars. “However, this plugin is slightly different in that the avatars the user already has on Gravatar.com are actually used. They can see the image they have uploaded. The user doesn’t need to upload a separate avatar, and an automatic one is not used by default.”

\n\n\n\n

He would not mind using an auto-generated avatar when commenting on blogs or news sites at times. However, Stathopoulos prefers Gravatar for community-oriented sites.

\n\n\n\n

“My Gravatar is part of my online identity, and when I see, for example, a comment from someone on WordPress.org, I know who they are by their Gravatar,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 21:06:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress 5.6 to Introduce Application Passwords for REST API Authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105997\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2604:\"

In 2015, WordPress 4.4 introduced a REST API, but one thing that has severely limited its broader use is the lack of authentication capabilities for third-party applications. After considering the benefits and drawbacks of many different types of authentication systems, George Stephanis published a proposal for integrating Application Passwords, into core.

\n\n\n\n

Stephanis highlighted a few of the major benefit that were important factors in the decision to use Application Passwords: the ease of making API requests, ease of revoking credentials, and the ease of requesting API credentials. The project is available as a standalone feature plugin, but Stephanis and his collaborators recommended WordPress merge a pull request that is based off the feature plugin’s codebase.

\n\n\n\n

After WordPress 5.6 core tech lead Helen Hou-Sandi gave the green light for Application Passwords to be merged into core, the developer community responded enthusiastically to the news.

\n\n\n\n

“I am/we are 100% in favor of this,” Joost deValk commented on the proposal. “Opening this up is like opening the dawn of a new era of WordPress based web applications. Suddenly authentication is not something you need to fix when working with the API and you can just build awesome stuff.”

\n\n\n\n

Stephanis’ proposal also mentioned how beneficial a REST API authentication system would be for the Mobile teams‘ contributors who are relying on awkward workarounds while integrating Gutenberg support.

\n\n\n\n

“This would be a first step to replace the use of XMLRPC in the mobile apps and it would allow us to add more features for self hosted users,” Automattic mobile engineer Maxime Biais said.

\n\n\n\n

After the REST API was added to WordPress five years ago, many had the expectation that WordPress-based web applications would start popping up everywhere. Without a reliable authentication system, it wasn’t easy for developers to just get inspired and build something quickly. Application Passwords in WordPress 5.6 will open up a lot of possibilities for those who were previously deterred by the lack of core methods for authenticating third-party access.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 23:01:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"WPTavern: WP Agency Summit Begins Its Second Annual Virtual Event October 12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105160\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:197:\"https://wptavern.com/wp-agency-summit-begins-its-second-annual-virtual-event-october-12?utm_source=rss&utm_medium=rss&utm_campaign=wp-agency-summit-begins-its-second-annual-virtual-event-october-12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6357:\"

Jan Koch, the founder and host of WP Agency Summit, is kicking off his second annual event on October 12. The five-day event will feature 37 speakers from a wide range of backgrounds across the WordPress industry. It is a free virtual event that anyone can attend.

\n\n\n\n

“The focus for the 2020 WP Agency Summit is showing attendees how to bring back the fun into scaling their agencies,” said Koch. “It is all about reducing the daily hustle by teaching how to successfully build and manage teams, how to work with enterprises (allowing for fewer customers but bigger projects), how to build sustainable recurring revenue, and how to position your agency to dominate your niche.”

\n\n\n\n

This year’s event includes three major changes to make the content more accessible to a larger group of people. Each session will be available between October 12 – 16 instead of the previous 48-hour window that attendees had to find time for in 2019.

\n\n\n\n

After the event has concluded, access to the content will be behind a paywall. Koch reduced the price to $77 for lifetime access for those who purchase pre-launch, which will increase to $127 during the event. Last year’s prices ballooned to $497, which meant that it was simply not affordable for many who found it too late.

\n\n\n\n

Some of the proceeds this year are going toward transcribing all the videos so that hearing-impaired users can enjoy the content.

\n\n\n\n

This year’s event will also focus on a virtual networking lounge for attendees. “I’ve seen how well it worked at the WP FeedBack Summit — we even had BobWP record a podcast episode on the fly in that lounge!” said Koch. “I’ve seen many new friendships develop, people connecting with new suppliers or getting themselves booked on podcasts, and sharing experiences about their businesses.”

\n\n\n\n

The lounge will be open during the entirety of the summit, which will allow attendees to jump into the conversation on their own time.

\n\n\n\n

A More Diverse Speaker Lineup

\n\n\n\n

Koch received some backlash for the lack of gender diversity last year. The 2019 event had over 20 speakers from a diverse male lineup. However, only four women from our industry led sessions.

\n\n\n\n

When asked about this issue in 2019, Koch responded, “I recognize this as a problem with my event. The reason I have so much more male than female speakers is quite simple, the current speaker line-up is purely based on connections I had when I started planning for the event. It was a relatively short amount of time for me, so I wasn’t able to build relationships with more female WP experts beforehand.”

\n\n\n\n

The host said he paid attention to the feedback he received. While not hitting the 50/50 split goal he had for 2020’s event, 16 of the 37 speakers are women.

\n\n\n\n

Koch said he strived to get speakers from a wider range of backgrounds. He wanted to bring in both freelancers and multi-million dollar agency owners. He also focused on getting people from multiple countries to represent WordPress agencies.

\n\n\n\n

“I did reach out to around 130 people four months before the event to make new connections,” he said. “The community around the Big Orange Heart (a non-profit for mental well-being) also helped a lot with introducing me to new members of the WP community.”

\n\n\n\n

Koch said he learned two valuable lessons when branching out beyond his existing connections for this year’s event:

\n\n\n\n

Firstly, don’t hesitate to reach out to people you think will never talk to you because they’re running such big companies. For example, I immediately got confirmations from Mario Peshev from Devrix, Brad Touesnard from Delicious Brains, or Marieke van de Rakt from Yoast. When first messaging them, I had little hope they’d set aside time to jump on an interview with me – but they were super supportive and accommodating! The WordPress community really is a welcoming environment if you approach people in a humble way.

Secondly, build connections with sincerity. Do not just focus on what you can get from that connection but how you can help the other person. I know this sounds cheesy and you’ve heard this quite often — but it is true. Once I got the first response from new contacts and explained my goal of connecting fellow WordPress community members virtually, most immediately agreed because they also benefit from new connections and being positioned as a thought-leader in this event.

\n\n\n\n

WP Agency Summit? WP FeedBack Summit?

\n\n\n\n

For readers who recall the Tavern’s coverage of the WP FeedBack Summit earlier this year, the article specifically stated that the WP FeedBack Summit was a continuation of 2019’s WP Agency Summit. The official word at the time from WP FeedBack’s public relations team was the following:

\n\n\n\n

Last year’s event, the WP Agency Summit has been rebranded under the umbrella of WP FeedBack’s brand when Jan Koch the host of last’s year WP Agency Summit joined WP FeedBack as CTO.

\n\n\n\n

Koch said that it was a standalone event and not directly connected to WP Agency Summit but had the same target audience. However, the WP FeedBack Summit did use the previous WP Agency Summit’s stats and data to promote the event.

\n\n\n\n

“The WP FeedBack Summit was hosted under the WP FeedBack brand because I joined their team as CTO in March this year,” he said. “Vito [Peleg] and I had the idea to host a virtual conference around WordPress because of WordCamp Asia being canceled — we wanted to help connect the community online through our summit.

\n\n\n\n

Koch left WP FeedBack soon after the summit ended and is currently back on his own and has a goal of making WP Agency Summit a yearly event.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 17:01:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: Navigation Screen Sidelined for WordPress 5.6, Full-Site Editing Edges Closer to Public Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105839\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:247:\"https://wptavern.com/navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta?utm_source=rss&utm_medium=rss&utm_campaign=navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4676:\"

The new block-based navigation screen is once again delayed after it was originally slated for WordPress 5.5 and then put on deck for 5.6. Contributors have confirmed that it will not be landing in WordPress core until 2021 at the earliest.

\n\n\n\n

“The Navigation screen is still in experimental state in the Gutenberg plugin, so it hasn’t had any significant real-world use and testing yet,” Editor Tech Lead Isabel Brison said. She made the call to remove it from the 5.6 lineup after the feature missed the deadline for bringing it out of the experimental state. It still requires a substantial amount of development work and accessibility feedback before moving forward.

\n\n\n\n

Contributors will focus instead on making sure the Widgets screen gets out the door for 5.6 and plan to pick up again on Navigation towards the end of November.

\n\n\n\n

WordPress 5.6 lead Josepha Haden gave an update this week on the progress of all the anticipated features, including the planned public beta for full-site editing (FSE).

\n\n\n\n

“I don’t expect FSE to be feature complete by the time WP5.6 is released,” Haden said. “What I expect is that FSE will be functional for simple, routine user flows, which we can start testing and iterating on. That feedback will also help us more confidently design and build our complex user flows.”

\n\n\n\n

Frank Klein, an engineer at Human Made, asked in the comments of another update why full-site editing is being tied to 5.6 progress in the first place, since it will still only be available in the plugin at the time of release.

\n\n\n\n

“The main value is that it provides a good checkpoint along the path of FSE’s development,” Kjell Reigstad said. “Full-site editing is very much in progress. It is still experimental, but the general approach is coming into view, and becoming clearer with every plugin release.”

\n\n\n\n

Reigstad posted an update on what developers can expect regarding block-based theming and the upcoming release, since the topic is closely tied to full-site editing. He emphasized that the infrastructure is already in place and that, despite it still being experimental, future block-based themes should work in a similar way to how they are working now.

\n\n\n\n

“The focus is now shifting towards polishing the user experience: using the site editor to create templates, using the query block, iterating on the post and site blocks, and implementing the Global Styles UI,” Reigstad said.

\n\n\n\n

“The main takeaway is that when 5.6 is released, the full-site editing feature set will look similar to where it is today, with added polish to the UI, and additional features in the Query block.”

\n\n\n\n

Theme authors are entering a new time of uncertainty and transition, but Reigstad reassured the community that themes as we know them today are not on track to be phased out in the immediate future.

\n\n\n\n

“There is currently no plan to deprecate the way themes are built today,” Reigstad said. “Your existing themes will continue to work as they always have for the foreseeable future.” He also encouraged contributors to get involved in an initiative to help theme authors transition to block-based themes. (This project is not targeted for the 5.6 release.)

\n\n\n\n

Developers can follow important FSE project milestones on GitHub, and subscribe to the weekly Gutenberg + Themes updates to track progress on block-based theming. A block-based version of the Twenty Twenty-One theme is in the works and should pick up steam after 5.6 beta 1, expected on October 20.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 22:57:37 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"WPTavern: EditorPlus 1.9 Adds Animation Builder for the Block Editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105678\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:181:\"https://wptavern.com/editorplus-1-9-adds-animation-builder-for-the-block-editor?utm_source=rss&utm_medium=rss&utm_campaign=editorplus-1-9-adds-animation-builder-for-the-block-editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4535:\"

Munir Kamal shows no signs of slowing down. He continues to push forward with new features for his EditorPlus plugin, which allows end-users to customize the look of the blocks in their posts and pages. He calls it the “no-code style editor for WordPress.”

\n\n\n\n

The latest addition to his plugin? Animation styles for every core block.

\n\n\n\n

My first thought was that this would bloat the plugin with large amounts of unnecessary CSS and JavaScript for what is essentially a few bells and whistles. However, Kamal pulled it off with minimal custom CSS.

\n\n\n\n

Inspired by features from various website builders, he wanted to bring more and more of those things to the core block editor. The animations feature is just another ticked box on a seemingly never-ending checklist of features. And, so far, it’s all still free.

\n\n\n\n

Since we last covered EditorPlus in June, Kamal has added the ability to insert icons via any rich-text area (e.g., paragraphs, lists, etc.). He has also added shape divider, typography, style copying, and responsive editing options for the core WordPress blocks.

\n\n\n\n

How Do Animations Work?

\n\n\n\n

In the version 1.9 release of EditorPlus, Kamal added “entrance” animations. These types of animations happen when a visitor sees the block for the first time on the screen. For example, users could set the Image block to fade into visibility as a reader views the block.

\n\n\n\n

Currently, the plugin adds seven animations:

\n\n\n\n
  • Fade
  • Slide
  • Bounce
  • Zoom
  • Flip
  • Fold
  • Roll
\n\n\n\nAdding a Slide animation for the Cover block text.\n\n\n\n

Each animation has its own subset of options to control how it behaves on the page. The bounce animation, for example, allows users to select the bounce direction. Other options include duration, delay, speed curve, delay, and repeat. There are enough choices to spend an inordinate amount of time tinkering with the output.

\n\n\n\n

One of the best features of this new feature is that Kamal has included an Animation Player under the block options. By clicking the play button, users can view the animation in action without previewing the post.

\n\n\n\n

Watch a quick video of the Animations feature:

\n\n\n\n
\n\n
\n\n\n\n

After testing and using each animation, everything seemed to work well. The one downside — and this is not limited to animations — is that applying styles on the block level sometimes does not make sense. In many cases, it would help users to have options to style or animate the items within the block, such as the images in the Gallery block. When I broached the subject with Kamal, he was open to the idea of finding a solution to this in the future.

\n\n\n\n

What Is Next for EditorPlus?

\n\n\n\n

At a certain point, too many block options can almost feel like overkill and become unwieldy. EditorPlus does allow users to disable specific features from its settings screen, which can help get rid of some unwanted options. Kamal said he would like to continue making it more modular so that users can use only the features they need.

\n\n\n\n

“What I plan is to have micro-level feature control for this extension so that a user can switch off individual styling panels like, Typography, Background, etc.,” he said. “Even further, I plan to bring these controls based on the user role as well. So an admin can disable these features for the editor, author, etc.”

\n\n\n\n

That may be a bit down the road though. For now, he wants to focus on adding new features that he already has planned.

\n\n\n\n

“I do plan to add more animation features,” said Kamal. “I got too many ideas, such as scroll-controlled animation, hover animation, text animation, Lottie animation, background animation, animated shape dividers, and more. But, having said that, I will be careful adding only those features that don’t affect page performance much.”

\n\n\n\n

Outside of extra styles and animations for existing blocks, he plans to jump on the block-building train in future releases. EditorPlus users could see accordion, toggle, slider, star rating, and other blocks in an upcoming release.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:53:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"Donncha: Hide featured image if it’s in the post\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://odd.blog/?p=89503242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://odd.blog/2020/10/08/hide-featured-image-if-its-in-the-post/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3885:\"

I’ve been running a photoblog at inphotos.org since 2005 on WordPress. (And thanks to writing this I noticed it’s 15 years old today!)

\n\n\n\n
\n\n\n\n

In that time WordPress has changed dramatically. At first I used Flickr to host my images, but after a short time I hosted the images myself. (Good thing too since Flickr limited free user accounts to 1000 images, so I wrote a script to download the Flickr images I used in posts.)

\n\n\n\n
\n\n\n\n

For quite a long time I used the featured image instead of inserting the image into the post content, but then about two years ago I went back to inserting the photo into the post. Unfortunately that meant the photo was shown twice, once as a featured image, and once in the post content.

\n\n\n\n

The last theme I used supported custom post types, one of which was a photo type that displayed the featured image but hid the post content. It was an ok compromise, but not perfect.

\n\n\n\n
\n\n\n\n

Recently I started using Twenty Twenty, but after 15 years I had a mixture of posts with:

\n\n\n\n
  • Featured image with no image in the post.
  • Featured image with the same image in the post.
\n\n\n\n

I knew I needed something more flexible. I wanted to hide the featured image if it also appeared in the post content. I procrastinated and never got around to it until this evening when I discovered it was actually quite easy.

\n\n\n\n\n\n\n\n

Copy the following code into the function.php of your child theme and you’ll be all set! It relies on you having unique filenames for your images. If you don’t then remove the call to basename(), and that may help.

\n\n\n
\nfunction maybe_remove_featured_image( $html ) {\n        if ( $html == \'\' ) {\n                return \'\';\n        }\n        $post = get_post();\n        $post_thumbnail_id = get_post_thumbnail_id( $post );\n        if ( ! $post_thumbnail_id ) {\n                return $html;\n        }\n\n        $image_url = wp_get_attachment_image_src( $post_thumbnail_id );\n        if ( ! $image_url ) {\n                return $html;\n        }\n\n        $image_filename = basename( parse_url( $image_url[0], PHP_URL_PATH ) );\n        if ( strpos( $post->post_content, $image_filename ) ) {\n                return \'\';\n        } else {\n                return $html;\n        }\n}\nadd_filter( \'post_thumbnail_html\', \'maybe_remove_featured_image\' );\n
\n\n\n

The post_thumbnail_html filter acts on the html generated to display the featured image. My code above gets the filename of the featured image, checks if it’s in the current post and if it is returns a blank string. Feedback welcome if you have a better way of doing this!

\n\n\n\n
\n\n\n\n

\n\n

Related Posts

\n

Source

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:43:35 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Donncha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches Automatic Platform Optimization for WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105641\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-automatic-platform-optimization-for-wordpress?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-automatic-platform-optimization-for-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6128:\"

Just a day after launching its new privacy-first web analytics product last week, Cloudflare announced Automatic Platform Optimization (APO) for WordPress. The new service boasts staggering performance improvements for sites that might otherwise be slowed down by shared hosting, slow database lookups, or sluggish plugins:

\n\n\n\n

Our testing… showed a 72% reduction in Time to First Byte (TTFB), 23% reduction to First Contentful Paint, and 13% reduction in Speed Index for desktop users at the 90th percentile, by serving nearly all of your website’s content from Cloudflare’s network. 

\n\n\n\n

APO uses Cloudflare Workers to cache dynamic content and serve the website from its edge network. In most cases this eliminates origin requests and origin processing time. That means visitors requesting your website will get near instant load times. Cloudflare reports that its testing shows APO delivers consistent load times of under 400ms for HTML Time to First Byte (TTFB).

\n\n\n\n

The effects of using APO are similar to hosting static files on a CDN, but without the need to manage a complicated tech stack. Content creators retain their ability to create dynamic websites without any changes to their workflow for the sake of performance.

\n\n\n\n

Version 3.8 of Cloudflare’s official WordPress plugin was recently updated to include support for APO. It detects when users make changes to their content and purges the content stored on Cloudflare’s edge.

\n\n\n\n

The new service is available to Cloudflare users with a single click of a button. APO is included at no cost for existing Cloudflare customers on the Professional, Business, and Enterprise plans. Users on the Free plan can add it to their sites for $5/month. The service is a flat fee and is not metered.

\n\n\n\n

Cloudflare’s announcement has so far been well-received by WordPress professionals and hosting companies and many have already begun testing it.

\n\n\n\n
\n

So the week after @Cloudflare Birthday Week I try and play with as many of the new products as possible. Today was the WordPress APO on my simple demo site. You can see TTFB dropped from ~350ms to ~75ms! https://t.co/zg976EjrZI pic.twitter.com/KuaHqtHLom

— Matt Bullock (@mibullock) October 6, 2020
\n
\n\n\n\n

WordPress lead developer Mark Jaquith called APO “incredible news for the WordPress world.”

\n\n\n\n

“On sites I manage this is going to lower hosting complexity and easily save hundreds of dollars a month in hosting costs,” Jaquith said.

\n\n\n\n

After running several speed tests from six different locations around the world, early testers at Kinsta got remarkable results using APO:

\n\n\n\n

“By caching static HTML on Cloudflare’s edge network, we saw a 70-300% performance increase. As expected, the testing locations furthest away from Tokyo saw the biggest reduction in load time.

“If your WordPress site uses a traditional CDN that only caches CSS, JS, and images, upgrading to Cloudflare’s WordPress APO is a no-brainer and will help you stay competitive with modern Jamstack and static sites that live on the edge by default.”

\n\n\n\n

George Liu, a “self-confessed page speed addict” and Cloudflare Community MVP, performed a series of detailed tests on the new APO product with his blog. After many comparisons, he found that Cloudoflare’s WordPress plugin with APO turned on delivers results similar to his heavily optimized WordPress blog that uses a custom Cloudflare Worker caching configuration.

\n\n\n\n

“You’ll find that Cloudflare WordPress plugin’s one click Automatic Platform Optimization button does wonders for page speed for the average WordPress user not well versed in page speed optimizations,” Liu said.

\n\n\n\n

“Cloudflare’s WordPress plugin Automatic Platform Optimization will in theory beat all other WordPress caching solutions other than you rolling out your own Cloudflare Worker based caching like I did. So you get a good bang for your buck at US$5/month for Cloudflare’s WordPress plugin APO.”

\n\n\n\n

Liu also warned of some speed bumps with the initial rollout, as Cloudflare’s APO supports a limited set of WordPress cookies for bypassing the Cloudflare CDN cache, leaving certain use cases unsupported. APO does not seem to work on subdomains and users are also reporting that it’s not compatible with other caching plugins. It also disables real visitor IP address detection.

\n\n\n\n

Cloudflare is aware of many of these issues, which have been raised in the comments of the announcement, and is in the process of adding more cookies to the list to bypass caching. Due to some plugin conflicts, APO may not be as plug-and-play as it sounds for some users right now, but the product is very promising and should improve over time with more feedback.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 04:18:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Kick off Block-Based WordPress Theme Development With the Theme.json Creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105832\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator?utm_source=rss&utm_medium=rss&utm_campaign=kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4674:\"

Gutenberg 9.1 made a backward-incompatible change to its theme.json file (experimental-theme.json while full-site editing is under the experimental flag). This is the configuration file that theme developers will need to create as part of their block-based themes. Staying up to date with such changes can be a challenge for theme authors, but Ari Stathopoulos, a Themes Team representative, wrote a full guide for developers.

\n\n\n\n

Jon Quach, a Principal Designer at Automattic, has also been busy creating a tool to help theme authors transition to block-based themes. He recently built a UI-based project called Theme.json Creator that builds out the JSON code for theme authors. Plus, it is up to date with the most recent changes in the Gutenberg plugin.

\n\n\n\n

Tools like these will be what the development community needs as it gets over the inevitable hump of moving away from the traditional theme development paradigm and into a new era where themes are made almost entirely of blocks and a config file.

\n\n\n\n

While plugin development is becoming more complex with the addition of JavaScript, theme development is taking a sharp turn toward its roots of HTML and CSS. We are barreling toward a future in which far more people will be able to create WordPress themes. Even the possibility of sharing pieces of themes (e.g., template parts and patterns) is on the table. This could not only empower theme designers by lowering the barrier to entry, it could also empower some end-users to make the jump into theme building.

\n\n\n\n

However, the theme.json file is one aspect of future theme authorship that is extremely developer-oriented. JSON is a universal format shared between various programming languages. It is meant to be read by machines and is not quite as human-friendly as other formats. As the theme.json file grows to accommodate more configuration options over time, the less friendly it will become to simply typing keys and values in.

\n\n\n\n

It makes sense to build tools to simplify this part of the theme building process.

\n\n\n\n

That is where the Theme.json Creator tool comes in. Theme authors pick and choose the options they want to support and input custom values. Then, the tool spits out everything in properly-formatted JSON.

\n\n\n\nUsing the Theme.json Creator tool.\n\n\n\n

One big thing the tool does not yet cover is custom CSS variables. This feature is a recent addition to the theme.json specification. It allows theme authors to create any custom property that WordPress will automatically output as CSS. In his announcement post, Stathopoulos covered how to create a typographic scale with custom properties and use those variables for editor features, such as line-height and font-size values.

\n\n\n\n

Currently, Theme.json Creator’s primary focus is on global styles. However, Gutenberg allows theme authors to configure default styles on the block level. For example, theme designers can set the color or typography options for the core Heading block to be different from the default global styles. This provides theme authors with fine-tuned control over every block.

\n\n\n\n

Theme.json Creator does not yet support configuration at this level. However, it would be interesting to see if Quach adds it in the future.

\n\n\n\n

The focus on setting up global styles is a good start for now. This is still an experimental feature. The great thing about it is that it can help theme authors begin to see how one piece of the block-based themes puzzle fits in. It is a starting point for an entirely new method of adding theme support for features when most are accustomed to adding multiple add_theme_support() PHP function calls.

\n\n\n\n

With the direction that theme development seems to be heading, it is easy to imagine that it could evolve into a completely UI-based affair at some point down the line. If templates are made up of blocks and patterns, which anyone can already build with the block editor, and if styles will essentially boil down to a config file, there will be little-to-no programming required to build a basic WordPress theme.

\n\n\n\n

If someone is not already at least jotting down notes for a plugin that allows users to create and package a block-based theme, I would be surprised. For now, Theme.json Creator is removing the need to write code for at least one part of the theme design process.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 07 Oct 2020 20:53:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: Jetpack 9.0 Introduces Loom Block, Twitter Threads Feature, and Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105743\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4033:\"
\n\n\n\n

Jetpack’s highly anticipated 9.0 release has landed, introducing some of the new features the team has previewed over the past week. Users can now publish WordPress posts to Twitter as threads. This new feature is available as part of the Publicize module when you have connected a Twitter account.

\n\n\n\n

Posting Twitter threads is a feature that only works with the block editor, as it takes advantage of how content is naturally split into chunks (blocks).

\n\n\n\n

In the comments on his demo post, Automattic engineer Gary Pendergast gave a more detailed breakdown of the logic Jetpack uses to ensure full sentences aren’t broken up in the tweets.

\n\n\n\n

“With the mental model now being focused on mapping blocks to tweets, it’s much easier to make logical decisions about how to handle each block,” Pendergast said. “So, a paragraph block is the text of a tweet, if the paragraph is too long for a single tweet, it tries to split the paragraph up by sentences. If a sentence is too long, then it resorts to splitting by words. Then, if there’s an embed/image/video/gallery block following that paragraph, we can attach it to the tweet containing that paragraph. There are additional rules for other blocks, but that’s the basic process. It then just iterates over all of the supported blocks in the post.”

\n\n\n\n

Pendergast published his post as thread to demonstrate the new feature in action. The advantage of posting a thread from your WordPress site is that it doesn’t end up getting lost in Twitter’s fast-moving timeline. Most important Twitter threads evaporate from public consciousness almost as soon as they are published. Publishing threads from your website ensures they are better indexed and easier to reference in the future.

\n\n\n\n

Jetpack Adds Loom Block for Embedding Screen Recordings

\n\n\n\n

Loom was added to Jetpack as a new oEmbed provider three weeks ago. The video recording service allows for recording camera, microphone, and desktop simultaneously. The service is especially popular in educational settings. Jetpack 9.0 introduces a new Loom block for embedding recordings.

\n\n\n\n\n\n\n\n

“Loom is growing in popularity as it is being recommended more and more to assist in distance learning efforts,” Jetpack Director of Innovation Jesse Friedman said. “Now more than ever we want to be able to help those working, learning, and teaching from home. The Loom block was a natural addition to join the other Jetpack video blocks which now include YouTube, TikTok, DailyMotion, and Vimeo.”

\n\n\n\n

Loom’s free tier allows users to record up to 25 videos, but the Pro plan is free for educators. Friedman confirmed that Jetpack does not have any kind of partnership with Loom. The team decided to support the product to assist professionals, educators, and students. Having it available as a block also makes it more convenient for those using P2 for communication.

\n\n\n\n

As anticipated, Jetpack 9.0 also provides a seamless transition necessary to ensure Instagram and Facebook embeds will continue working after Facebook drops unauthenticated oEmbed support on October 24. The Jetpack team reports that it “partnered with Facebook” to make sure these embeds continue to work with the WordPress.com REST API.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 23:28:38 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:51:\"Post Status: Joost de Valk on WordPress marketshare\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=79914\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:62:\"https://poststatus.com/joost-de-valk-on-wordpress-marketshare/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1193:\"

David Bisset makes his podcast debut for Post Status, as he interviews Joost de Valk, Founder and Chief Product Officer of Yoast, and discusses all things WordPress marketshare related.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Jilt

\n\n\n\n

Jilt offers powerful email marketing built for eCommerce. From newsletters to highly segmented automations, Jilt is your one-stop show for eCommerce email. Join thousands of stores that have already earned tens of millions of dollars in extra sales using Jilt. Try Jilt for free

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 22:28:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"WPTavern: iThemes Buys WPComplete, Complementing Its Recent Restrict Content Pro Acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105631\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition?utm_source=rss&utm_medium=rss&utm_campaign=ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4395:\"

Just one month after publicly announcing its acquisition of Restrict Content Pro (RCP), iThemes purchased WPComplete for an undisclosed amount. The acquisition is for the product, website, and customers only.

\n\n\n\n

Paul Jarvis and Zack Gilbert created the WPComplete plugin in 2016. However, it has outgrown what the duo could maintain and support alone. After the transition period in which the new owners take over, the two will step away from the project.

\n\n\n\n

In essence, WPComplete is a “course completion” plugin. Site owners can create online courses while allowing students/users to mark their work as completed. It also gives students a way to track their progress through courses, which can often boost the potential for them to finish.

\n\n\n\n

“Paul and Jack believe a key to their success has been their ability to keep their team small and manageable,” wrote Matt Danner, the COO at iThemes, in the announcement. “The growth of WPComplete has presented a number of challenges for a team of two people, so the decision was made to start looking towards alternative ownership solutions that could continue to grow WPComplete and provide it with a stable team. iThemes is a perfect fit.”

\n\n\n\n

iThemes customers who have a Plugin Suite or Toolkit membership will get automatic access to the pro version of the WPComplete plugin. For current WPComplete users, Danner said everything should be “business as usual.” However, iThemes has assigned a few of its team members to work on the product and site, so customers should see some new faces.

\n\n\n\n

RCP and WPComplete are obviously complementary products. RCP is a membership plugin that allows site owners to restrict content based on that membership. WPComplete allows site members to mark lessons or coursework as completed. “We’ll be rolling out a new bundle later this month that combines both RCP and WPComplete for course and membership creators to take advantage of these two plugins,” said AJ Morris, the Product Innovation and Marketing Manager at iThemes.

\n\n\n\n

WPComplete is still a young product. The free version of the plugin currently has 2,000+ active installs and a solid 4.7 rating on WordPress.org. If marketed as an extension of the RCP plugin, it automatically puts it in front of the eyes of 1,000s of more potential customers. It should be much easier to grow the plugin as part of a membership bundle.

\n\n\n\n

iThemes is making some bold moves in the membership space. It will be interesting to see if the company makes any other acquisitions that could strengthen its product line and help it become more dominant. There is still a ton of room for growth in the membership segment of the market. There is also the potential for integrations with other major plugins.

\n\n\n\n

“Adding WPComplete to the iThemes product lineup also allows us to move more quickly on some plans we have for Restrict Content Pro,” said Danner in the initial announcement. He also vaguely mentioned a couple of ideas the team had in the works but did not go into detail.

\n\n\n\n

With a little prodding, Morris provided some insight into what they are planning for the immediate future. The biggest first step is tackling integration with the block editor. Currently, WPComplete uses shortcodes. The team’s next step is likely to begin with creating block equivalents for those shortcodes.

\n\n\n\n

“After that, we’ve touched on a few deeper integrations with Restrict Content Pro, like the possibility to restrict courses to memberships,” said Morris.

\n\n\n\n

The iThemes team does not plan to stop with WPComplete as part of its product lineup. One of the goals is to use the plugin for the iThemes website itself.

\n\n\n\n

“We always try to eat our own dogfood when we can,” said Morris. “You’ll see that with RCP and WPComplete early next year as we look to integrate them into our iThemes Training membership.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 20:59:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Exploring Full-Site Editing With the Q WordPress Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105676\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/exploring-full-site-editing-with-the-q-wordpress-theme?utm_source=rss&utm_medium=rss&utm_campaign=exploring-full-site-editing-with-the-q-wordpress-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7492:\"

I have been eagerly awaiting the moment when I could install a theme and truly test Gutenberg’s full-site editing feature. By and large, each time I have tested it over the past few months, the experience has felt utterly broken. This is why I have remained skeptical of seeing the feature land in WordPress 5.6 this December.

\n\n\n\n

The Q theme by Ari Stathopoulos is the first theme that seems to be a decent working example. Whether that is a stroke of luck with timing or that this particular theme is simply built correctly is hard to tell — Stathopoulos is a team rep for the Themes Team. Gutenberg 9.1 dropped last week with continued work toward site editing.

\n\n\n\n

Q is as experimental as it gets. The Themes Team put out an open call for experimental, block-based themes as far back as March this year. However, not many have taken the team up on this offer. If approved, Q stands to be the first block-based theme to go live in the official WordPress directory. It still has to work its way through the standard review process, awaiting its turn in the coming weeks.

\n\n\n\n

On the whole, full-site editing remains a frustrating and confusing experience. I still remain skeptical about its readiness, even in beta form, to show off to the world in WordPress 5.6.

\n\n\n\n

However, Q is an interesting theme to explore at this point for both end-users and theme developers. Users can install it and start tinkering with the site editing screen via the Gutenberg plugin. Developers can learn how global styles, templates, and template parts fit together from a working theme.

\n\n\n\n

Using the Site Editor

\n\n\n\nEditing a single post in the site editor.\n\n\n\n

The Q theme requires the Gutenberg plugin and its full-site editing mode to be enabled. Generally, requiring a plugin is not allowed for themes in the directory. However, experimental Gutenberg themes are allowed to bypass this guideline.

\n\n\n\n

Stathopoulos pointed out that the theme is highly experimental and should not be used on a production site. However, he is hopeful that it will get more eyes focused on full-site editing.

\n\n\n\n

He mentioned that several items are broken, such as category archives not showing the correct posts. This is a current limitation of the Query block in Gutenberg. However, one of the best ways to find and recognize these types of issues is to have a theme that stays up with the pace of development.

\n\n\n\n

Currently, the site editor feels like it is biting off more than it can chew. Not only can users edit the layout and design of the page, but they can also directly edit existing post content — don’t try this at home unless you are willing for your post titles to get switched to the hyphenated slug. Should the site editor be handling the double-duty of design and content editing? If so, should design and content editing be handled in separate locations in the long term or be merged into one feature?

\n\n\n\n

It feels raw. It is not geared toward users at this point.

\n\n\n\n

The bright spot with the site editor is the current progress on template parts in the editor. Template parts are essentially “modules” that handle one part of the page. For example, the typical theme will have a header and footer template part. Currently, end-users can insert custom template parts or switch one template part for another. This opens a world of possibilities, such as users choosing between multiple header designs (template parts) for their sites.

\n\n\n\nSwitching the header template part.\n\n\n\n

The downside to the entire template system is that it seems so divorced from the site editor that it is hard to believe the average user would understand what is going on. Templates and template parts reside under the Appearance menu in the admin. The Site Editor is a separate, top-level menu item. Without any preexisting knowledge of how these pieces work together, it can be confusing.

\n\n\n\n

Template parts worked for me in the site editor from the outset. However, they did not work on the front end at first. I continually received the “template part not found” message for hours. Then, at some point — whether through magic or a random save that pulled everything together — the feature began to output the previously-missing header and footer template parts.

\n\n\n\n

Glimpse Into the Future of Theme Development

\n\n\n\n

The Q theme has a scant few style rules, which it loads directly in the <head> section of the site in lieu of adding an extra stylesheet. It relies on the stock Gutenberg block styles on the front end with a few minor overrides. Most other custom styles are handled via the global styles system, which pulls from the theme’s experimental-theme.json config file (will be theme.json in the future).

\n\n\n\n

It begs the question of whether themes will necessarily need much in the way of CSS when full-site editing lands.

\n\n\n\n

If WordPress allows users to configure most styles via block options and global styles overrides, themes may not need much more than their config files. After that, it would come down to registering custom block styles and patterns.

\n\n\n\n

If this is the future that we are headed toward, anyone could essentially create a WordPress theme. And, those pieces, such as template parts and patterns, could all be shared between any site. In that future, themes may simply not matter anymore.

\n\n\n\n

Last year, Mike Schinkel proposed deprecating the theme system altogether and replacing it with web components.

\n\n\n\n

“Rather than look for a theme that has all the features one needs — which I have found always limits the choices to zero — a site owner could look for the components and modules they need and then assemble their site from those modules,” he said. “They could pick a header, a footer, a home-page hero, a set of article cards, a pricing module, and so on.”

\n\n\n\n

The more I tinker with full-site editing, the more it feels like that is the lane that it will ultimately merge into. Imagine a future where end-users could pick and choose the pieces they wanted and simply have it look right on the front end.

\n\n\n\n

It is exciting to think about that possibility. Both Schinkel and I have more of a background in programming than we do in design. It makes sense from that sort of analytical mindset to put everything into neat, reusable boxes because reuse is a cornerstone of smart programming.

\n\n\n\n

However, I worry about the state of design in such a system with so many replaceable parts. Will designers be able to take holistic approaches to theme development, creating truly intricate pieces of art? Will that system essentially create a web of cookie-cutter sites? Or, will designers simply find ways to think outside the box while within the constraints of the block system?

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 21:21:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Virtual Jamstack Conf to Feature Fireside Chat with Matt Mullenweg and Matt Biilmann, October 6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105680\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6?utm_source=rss&utm_medium=rss&utm_campaign=virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2618:\"
image credit: Jamstack Conf
\n\n\n\n

The greater Jamstack community is coming together on October 6-7, 2020, for a virtual conference. Organizers expect more than 15,000 attendees from around the globe over a two-day span that includes keynotes, sessions, interactive topic tables, workshops, speaker Q&As, and networking opportunities.

\n\n\n\n

Matt Mullenweg will be joining Netlify CEO Matt Biilmann on day 1 at 12PM PDT for a fireside chat moderated by CSS-Tricks Creator Chris Coyier. The chat will go deeper on recent topics of contention, including developer sentiment, complexity, security, and performance. Coyier also plans to discuss how the Jamstack and WordPress communities intersect through headless implementations of the CMS.

\n\n\n\n

A provocative post from TheNewStack at the end of August quoted Mullenweg as saying that “JAMstack is a regression for the vast majority of the people adopting it.” This sparked multiple heated exchanges across blogs and social media. Biilimann, who originally coined the term “Jamstack,” wrote a response to Mullenweg’s remarks, hailing “the end of the WordPress era.”

\n\n\n\n

Live conversations tend to be more cordial than shots fired across the blogosphere. It will be interesting to see if Biilimann cares to join Stackbit CEO Ohad Eder-Pressman in his wager that Jamstack will become the predominant architecture for the web by 2025. The fireside chat should be recorded, in case you cannot catch the live session. Recordings of talks from the previous virtual Jamstack event held in May are available on YouTube.

\n\n\n\n

Today is the last call for registration. Many of the workshops have already sold out, but tickets to the regular sessions on October 6 are still available. Sign up on the event website to get your free ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 20:12:50 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Gutenberg 9.1 Adds Patterns Category Dropdown and Reverts Block-Based Widgets in the Customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105629\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5615:\"

Gutenberg 9.1 was released to the public on Wednesday. The team announced over 200 commits from 77 contributors in its release post yesterday. One of the biggest changes to the interface was the addition of a new dropdown selector for block pattern categories. The team also reverted the block-based widgets section in the customizer and added an image size control to the Media & Text block.

\n\n\n\n

One of the main focuses of this release was improving the block-based widgets editor. The feature was taken out of the experimental stage in Gutenberg 8.9 and continues to improve. The widgets screen now uses the same inserter UI as the post-editing screen. However, users can currently only insert regular blocks. Patterns and reusable blocks are still not included.

\n\n\n\n

Theme authors can now control aspects of the block editor via a custom theme.json file. This is part of the ongoing Global Styles project, which will allow theme authors to configure features for their users.

\n\n\n\n

The development team has also added an explicit box-sizing style rule to the Cover and Group blocks. This is to avoid any potential issues with the new padding/spacing options. Theme authors who rely on the block editor styles should test their themes to make sure this change does not break anything.

\n\n\n\n

Better Pattern Organization

\n\n\n\nNew block patterns UI in the inserter.\n\n\n\n

I have been calling for the return of the tabbed pattern categories since Gutenberg 8.0, which was a regression from previous versions. For 11 versions, users have had to scroll and scroll and scroll through every block pattern just to find the one they wanted. The development team has sought to address this issue by using a category dropdown selector. When selecting a specific category, its patterns will appear.

\n\n\n\n

At first, I was unsure about this method over the old tabbed method. However, after some use, it feels like the right direction.

\n\n\n\n

As more and more theme and plugin authors add block pattern categories to users’ sites, the dropdown is a more sensible route. Even tabs could become unwieldy over time. The dropdown better organizes the list of categories and makes the UI cleaner. More than anything, I am enjoying the experience and look forward to this eventually landing in WordPress 5.6 later this year.

\n\n\n\n

Customizer Widgets Reverted

\n\n\n\nReverted widgets panel in the customizer.\n\n\n\n

On the subject of WordPress 5.6, one of its flagship features has been hitting some roadblocks. Block-based widgets are expected to land in core with the December release, but the team just reverted part of the feature. They had to remove the widgets block editor from the customizer they added just two major releases ago.

\n\n\n\n

It was for the best. The customizer’s block-based widgets editor was fundamentally broken. It was not ready for primetime and should have remained in the experimental stage until it was somewhat usable.

\n\n\n\n

“I will approve this since the current state of the customizer in the Gutenberg plugin is broken, and there is no clear path forward about how to fix that,” wrote Andrei Draganescu in the reversion ticket. “With this patch, the normal widgets can still be edited in the customizer and the block ones don’t break it anymore. This is NOT to mean that we won’t proceed with fixing the block editor in the customizer, that is still an ongoing discussion.”

\n\n\n\n

The current state of editing widgets via the customizer is at least workable with this change. If end-users add a block via the admin-side widgets editor, it will merely appear as an uneditable, faux widget named “Block” in the customizer. They will need to edit blocks via the normal widgets screen.

\n\n\n\n

There is no way that WordPress can ship the current solution when 5.6 rolls out. However, we are still two months out. This leaves plenty of time for a fix, but Draganescu’s note that “there is no clear path forward” may make some people a bit uneasy at this stage of development.

\n\n\n\n

Control Image Size for Media & Text

\n\n\n\nImage size dropdown selector for the Media & Text block.\n\n\n\n

One of the bright spots in this update is the addition of an image size control to the Media & Text block. Like the normal Image block, end-users can choose from any registered image size created for their uploaded image.

\n\n\n\n

This is a feature I have been looking forward to in particular. Previously, using the full-sized image often made the page weight a bit heftier than necessary. It is also nice to go along with themes that register sizes for both landscape and portrait orientations, giving users more options.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 20:56:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"WordPress.org blog: The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8711:\"

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches New Web Analytics Product Focusing on Privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105446\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-new-web-analytics-product-focusing-on-privacy?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-new-web-analytics-product-focusing-on-privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2448:\"

In pursuit of “democratizing web analytics,” Cloudflare announced it is launching privacy-first analytics as a new standalone product. The company is entering a market that has been dominated by Google Analytics for years but with a major differentiating feature – it will not track individual users by a cookie or IP address to show unique visits.

\n\n\n\n

Cloudflare Web Analytics defines a visit as “a successful page view that has an HTTP referer that doesn’t match the hostname of the request.” It’s not the same as Google’s “unique” metric, and Cloudflare says it may differ from other reporting tools. Weeding out bots from the total traffic numbers is a nascent feature that Cloudflare is improving as part of its Bot Management product.

\n\n\n\n
\n\n\n\n

Cloudflare Web Analytics is launching with features that are largely similar to Google Analytics but with some unique ways of zooming into different traffic segments and time ranges to see where traffic is originating from.

\n\n\n\n

“The most popular analytics services available were built to help ad-supported sites sell more ads,” Cloudflare product manager Jon Levine said. “But, a lot of websites don’t have ads. So if you use those services, you’re giving up the privacy of your users in order to understand how what you’ve put online is performing.

\n\n\n\n

“Cloudflare’s business has never been built around tracking users or selling advertising. We don’t want to know what you do on the Internet — it’s not our business.”

\n\n\n\n

Paying customers on the Pro, Biz, and Enterprise plans can access their analytics from their dashboards immediately. Cloudflare is also offering the product for free as JavaScript-based analytics for users who are not currently customers. Those who want access to the free plan can sign up for the waitlist.

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 04:03:01 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"WPTavern: Virtual WordPress Page Builder Summit Kicks Off October 5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105570\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:179:\"https://wptavern.com/virtual-wordpress-page-builder-summit-kicks-off-october-5?utm_source=rss&utm_medium=rss&utm_campaign=virtual-wordpress-page-builder-summit-kicks-off-october-5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6348:\"

From October 5 through October 9, the first Page Builder Summit will open its virtual doors to all attendees for free. Nathan Wrigley, the podcaster behind WP Builds, and Anchen le Roux, the founder and lead developer of Simply Digital Design, are hosting the five-day online event that focuses on the vast ecosystem of page builders for WordPress.

\n\n\n\n

The summit will include 35 sessions spread out over the event schedule. Each session will last around 30 minutes, so it will be easy to pop in and watch one in your downtime. Sessions will cover a range of builders, including the default WordPress block editor, Elementor, Beaver Builder, Oxygen, Brizy, and Divi.

\n\n\n\n

“It’s an event specifically for users of WordPress page builders, or those curious about what they can do,” said Wrigley. “I feel like a page builder style interface for creating websites is the future for our industry. WordPress itself is moving in this direction with the block editor (a.k.a. Gutenberg). With that in mind, it seemed like a good idea to create a dedicated event to share knowledge about this side of WordPress. We’ve tried to include presentations from as many page builders as we could.”

\n\n\n\n

Wrigley made sure to point out that it is not all geared toward developers, discussing the inner-workings of builders. Some of the sessions focus on marketing, optimization, and conversion, which provides a wider range of topics for potential attendees.

\n\n\n\n

The summit hosts created an online quiz for those who are unsure about which sessions to watch.

\n\n\n\n

There is a small catch. The sessions will be freely available only from the time they begin and the following 24 hours. After that, accessing the videos will come at a premium. Attendees can gain lifetime access to the PowerPack for $47 if they purchase within 15 minutes of signing up. Then, prices will rise to $97 until the event kicks off on October 5. Beyond, the price jumps to $147. The lifetime access includes access to the presentations, transcripts, a workbook, and other bonuses from the speakers.

\n\n\n\n

For those unsure about forking over the cash, they can still watch the sessions during the 24-hour window.

\n\n\n\n

The proceeds from the event will go out to paying affiliate commissions to speakers and partners. Some of it will go into planning and investing in a second summit down the road.

\n\n\n\n

“Both myself and Nathan have specific charities that we want to donate to after the event,” said le Roux. “It was part of our goals to be able to do this, but we didn’t want to make this an official contribution.”

\n\n\n\n

Why a Page Builder Summit?

\n\n\n\n

Both Wrigley and le Roux have their preferred builders. But, the goal of the summit is to offer a wide look at the tools available and help freelancers and agencies better streamline their businesses and create happier clients.

\n\n\n\n

“I’ve been a user of page builders for many years, but only at the point where they truly showed in the editing interface something that almost perfectly reflected what the end-user would see did I get really immersed,” said Wrigley. “Having come from a background in which I built entire websites from a collection of text files (HTML, CSS, PHP, etc.), I was fascinated that we’d reached a point where the learning curve for building a good website was significantly reduced.”

\n\n\n\n

He pointed out that it is not always so simple though. While the same level of coding skills may not be necessary, people must figure out how to navigate their preferred page builder, which can come with its own learning curve.

\n\n\n\n

“You need to learn their way of doing things and how to achieve your design choices,” he said. “It’s always going to work out better if you know the code, but the WordPress mission of democratizing publishing certainly seems to align quite nicely with the adoption of tools, like page builders, which mean that once-difficult tasks are now easier.”

\n\n\n\n

For le Roux, her interest in hosting the Page Builder Summit falls back to her design studio.

\n\n\n\n

“As a developer, my main reason for switching to page builders was around streamlining and creating more efficient but quality websites in the shortest amount of time,” she said. “Especially now that we focus on day rates, creating the best possible website that clients would love fast would not have been possible without page builders.”

\n\n\n\n

The Hosts’ Go-To Builders

\n\n\n\n

“We prefer using Beaver Builder with Themer at Simply Digital Design,” said le Roux. “We use Gutenberg for blog posts or where possible with custom post types or LMS software. However, we’ve also taken on a few Elementor projects where that’s the client’s preferred option.”

\n\n\n\n

Wrigley uses some of the same tools. His main work is on the WP Builds website where he hosts podcasts.

\n\n\n\n

“I have used Beaver Builder’s Themer to create templates for specific layouts, but for content creation within those layouts I’m using the block editor,” said Wrigley. “My content is mainly text and the WordPress editor is utterly remarkable in this situation. I kept the classic editor installed for a few months after WordPress 5.0 came about, but I soon realized that this was folly and that the editing interface of Gutenberg is superior. The ability to insert and move text, buttons, etc. is such a joy to work with, and the iterations that have been made in the last two years make it, in my opinion, the best text editing experience on the web.”

\n\n\n\n

Wrigley sees a future in which the WordPress block editor takes over much of the work that page builders are currently handling. However, that future is “still over the horizon.”

\n\n\n\n

“I’m excited about this future though, and we’ve got a few crystal ball-gazing presentations; trying to work out what that future might look like,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 20:31:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"WPTavern: Jetpack 9.0 to Introduce New Feature for Publishing WordPress Posts to Twitter as Threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105448\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3318:\"

Jetpack 9.0, coming on October 6, will debut a new feature that allows users to share blog posts as Twitter threads in multiples tweets. A recent version of Jetpack introduced the ability to import and unroll tweetstorms for publishing inside a post. The 9.0 release will run it back the other way so the content originates in WordPress, yet still reaps all the same benefits of circulation on Twitter as a thread.

\n\n\n\n

The new Twitter threads feature is being added as part of Jetpack’s Publicize module under the Twitter settings. After linking up a Twitter account, the Jetpack sidebar options for Publicize allow users to publish to Twitter as a link to the blog or a set of threaded tweets. It’s not just limited to text content – the threads feature will also upload and attach any images and videos included in the post.

\n\n\n\n\n\n\n\n

When first introduced to the idea of publishing a Twitter thread from WordPress, I wondered if threads might lose their trademark pithy punch, since users aren’t forced to keep each segment to the standard length of a tweet. Would each tweet be separated in an odd, unreadable way? The Jetpack team anticipated this, so the thread option adds more information to the block editor to show where the paragraphs will be split into multiple tweets.

\n\n\n\n

“Threads are wildly underused on Twitter,” Gary Pendergast said in a post introducing the feature. “I think a big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.” The tool Pendergast has been working on for Jetpack gives users the best of both worlds.

\n\n\n\n

In response to a comment requesting Automattic “concentrate on tools to get people off social media,” Pendergast said, “If we’re also able to improve the quality of conversations on social media, I think it’d be remiss of us to not do so.” He also credits IndieWeb discussions on Tweetstorms and POSSE (Publish (on your) Own Site, Syndicate Elsewhere) as inspirations for the feature.

\n\n\n\n

For years, blogging advocates have tried to convince those who post lengthy tweetstorms to switch to a publishing medium that is more suitable to the length of their thoughts. The problem is that Twitter users lose so much of the immediate feedback and momentum that their thoughts would have generated when composed as a tweetstorm.

\n\n\n\n

Instead of lecturing people about how they should really be blogging instead of tweetstorming, Jetpack is taking a fresh approach by enabling full content ownership with effortless social syndication. You can test out the experience for yourself by adding the Jetpack Beta Testers plugin and running the 9.0 RC version on your site.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 02:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: Ask the Bartender: How To WordPress in a Block World?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105491\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:167:\"https://wptavern.com/ask-the-bartender-how-to-wordpress-in-a-block-world?utm_source=rss&utm_medium=rss&utm_campaign=ask-the-bartender-how-to-wordpress-in-a-block-world\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9755:\"

I love your articles. And now, in the middle of the WordPress revolution, I realized I’m constantly searching for an answer regarding WP these days.

So many things are being said, so many previsions of the future, problems, etc., but, right now, I think I, as a designer, just want to understand one thing that seemed answered already but it’s never clear:

Is WordPress a good choice to build a client’s template where he just has to insert the info that will show in the frontend where I want to? And he doesn’t have to worry about formatting blocks? I love blocks, don’t get me wrong, but will normal templating end?

I just think that having a super CMS, HTML, CSS, and being able to play with a database with ACF is so powerful, that I’m wondering if it’s lost. After so much reading, I still don’t understand if this paradigm is going to disappear.

Right now, I don’t know if it’s best to stop making websites as I used to and adopt block patterns instead.

Ricardo
\n\n\n\n

WordPress is definitely changing. Over the past two years, we have seen much of it reshaped into something different from the previous decade and more. However, this is not new. WordPress has always been a constantly-changing platform. It just feels far too different this time around, almost foreign to many. The platform had to make a leap. Otherwise, it would have started falling behind.

\n\n\n\n

And, it is a big ask of the existing community to come along with it, to take that leap together.

\n\n\n\n

It can be scary as a developer whose livelihood has depended on things working a certain way or who has built tools and systems around pre-block WordPress. Many freelancers and agencies had their world turned upside down with the launch of the block editor. It is perfectly OK to feel a bit lost.

\n\n\n\n

Now, it is time for a little tough love. It has been two years. As a professional, you need to have a plan in place already. Whether that is an educational plan for yourself or a transitional plan for your clients, you should already be tackling projects that leverage the block editor. If you are at a point where you have not been building with blocks, you are now behind. However, you can still catch up and continue advancing in your WordPress career.

\n\n\n\n

There are so many changes coming down the pipeline that anyone who plans to develop for WordPress will be in continual education mode for years to come.

\n\n\n\n

When building for clients, the biggest thing to remember is that it is not about you. It is about getting something into the hands of your clients that addresses their specific needs. Freelancers and agencies need to often be the Jacks and Jills of all trades. Sometimes, this even means having a backup CMS or two that you can use that are not named WordPress. It helps to be well-rounded enough to jump around when needed, especially if you are not at a point in your career where you can demand specific work and pass on jobs that would put food on the table.

\n\n\n\n

It is also easy to look at every job as a nail and WordPress as the hammer. Or, even specific plugins as the tool that will always get the job done. I have seen developers in the past rely on tools like ACF, CMB2, or Meta Box but could not code a custom metadata solution when necessary to save their life. Sometimes a bigger toolbox is necessary.

\n\n\n\n

Every WordPress developer needs a solid, foundational understanding of the languages that WordPress uses. Gone are the days of skating by on HTML, CSS, and PHP knowledge. You need to learn JavaScript deeply. Matt Mullenweg, the co-founder of WordPress, was not joking around when he said this back in 2015. It holds true more and more each day. In another five years, it will tough to be a developer in the WordPress world without knowing JavaScript, at least for backend work.

\n\n\n\n

It also depends on what types of sites you are building. If you are primarily handling front-end design, you will likely be able to get by with a lower skill level. You will just need to know the “WordPress way” of building themes.

\n\n\n\n

Within the next year, you should be able to build just about any theme design with decent CSS and HTML knowledge along with an understanding of how the block system works. Full-site editing and block-based themes will change how we build the front end of the web. It is going to be a challenging transition at first, especially for those of us who are steeped in traditional theme development, but client sites will often be far easier to build. I highly recommend the twice-monthly block-based themes meetings if your focus is on the front end.

\n\n\n\n

Block Templates

\n\n\n\n

Based on your question, I am going to make some assumptions. You have a history of essentially building out meta boxes via ACF where the client just pops in their data. Then, you format that data on the front end. You are likely mixing this with custom post types (CPTs). This is a fairly common scenario.

\n\n\n\n

One of the great things about the block system is that you can lock the post editor for individual CPTs. WordPress already has you covered with its block templates feature, which allows you to define just what a post should look like. You can set up which blocks you want to appear and have the client drop their content in. At the moment, this feature is limited to the post type level. However, it should grow more robust over time, particularly when it works alongside the traditional “page templates” system.

\n\n\n\n

Block templates are a powerful tool in the ol’ toolbox that will come in handy when building client sites.

\n\n\n\n

Block Patterns

\n\n\n\n

You do not have to stop making websites as you are accustomed to at the moment. However, you should start leveraging new block features as they become available and make sense for a specific project. I am a fanatic when it comes to block patterns, so my bias will definitely show.

\n\n\n\n

The biggest thing with block patterns and clients is education. For the uninitiated, you will need to spend some time teaching them how to insert a pattern and how it can be used to their advantage. That is the hurdle you must jump.

\n\n\n\n

For many of the users that I have seen introduced to well-designed patterns, they have fallen in love with the feature. Even many who were reluctant to switch to the block editor became far more comfortable working with it after learning how patterns worked. This is not the case for every user or client, but it has been a good introduction point to the block editor for many.

\n\n\n\n

To answer your question regarding patterns: yes, you should absolutely begin to adopt them.

\n\n\n\n

ACF Is Evolving

\n\n\n\n

Because you are accustomed to ACF, you should be aware that the framework is evolving to keep up with the block editor. Version 5.8.0 introduced a PHP framework for creating custom blocks over a year ago. And, it has been improving ever since. There are even projects like ACF Blocks, which will provide even more tools for your arsenal.

\n\n\n\n

It is important to learn from what some of the larger agencies are doing. Read up on how WebDevStudios is tackling block development. The company also has an open-source block library for ACF.

\n\n\n\n

Solving Problems

\n\n\n\n

Your job as a developer is to be a problem solver. Whatever system you are building with is merely a part of your toolset. You need to be able to solve issues regardless of what tool you are using. At the end of the day, it is just code. If you can learn HTML, you can learn CSS. If you can learn those, you can learn PHP. And, if you can manage PHP, you can certainly pick up JavaScript.

\n\n\n\n

A decade or two from now, you will need to learn something else to stay relevant in your career. Web technology changes. You must change with it. Always consider yourself a student and continue your education. Surround yourself and learn from those who are more advanced than you. Emulate, borrow, and steal good ideas. Use what you have learned to make them great.

\n\n\n\n

There is no answer I can give that will be perfect for every scenario. Each client is unique, and you will need to decide the best direction for each.

\n\n\n\n

However, yes, you should already be on the path to building with a block-first mindset if you plan to continue working with WordPress for the long haul. Immerse yourself in the system. Read, study, and build something any chance you get.

\n\n\n\n

This is the first post in the Ask the Bartender series. Have a question of your own? Shoot it over.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Sep 2020 20:35:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: Supercharge the Default WordPress Theme With Twentig, a Toolbox for Twenty Twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:225:\"https://wptavern.com/supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty?utm_source=rss&utm_medium=rss&utm_campaign=supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6455:\"Custom page pattern from the Twentig plugin.\n\n\n\n

I am often on the hunt for those hidden gems when it comes to block-related plugins. I like to see the interesting places that plugin authors venture. That is why it came as a surprise when someone recommended I check out the Twentig plugin a few days ago. Somehow, it has flown under my radar for months. And, it has managed to do this while being one of the more interesting plugins for WordPress I have seen in the past year.

\n\n\n\n

Twentig is a plugin that essentially gives superpowers to the default Twenty Twenty theme. Diane and Yann Collet are the sibling co-founders and brains behind the plugin.

\n\n\n\n

While I have been generally a fan of Twenty Twenty since it was first bundled in core, it was almost a bit of a letdown in some ways. It was supposed to be the theme that truly showcased what the block editor could do — and it does a fine job of styling the default blocks — but there was a lot of potential left on the table. The Twentig plugin turns Twenty Twenty into something worthier of a showcase for the block editor. It is that missing piece, that extra mile in which WordPress should be marching its default themes.

\n\n\n\n

While the new Twenty Twenty-One default theme is just around the corner, Twentig is breathing new life into the past year’s theme. The developers behind the plugin are still fixing bugs and bringing new features users.

\n\n\n\n

Of its 34 reviews on WordPress.org, Twentig has earned a solid five-star rating. That is a nice score for a plugin with only 4,000 active installations. As I said, it has flown under the radar a bit, but the users who have found it have obviously discovered something that adds those extra touches to their sites they need.

\n\n\n\n

What Does Twentig Do?

\n\n\n\n

It is a toolbox for Twenty Twenty. The headline feature is its block editor features, such as custom patterns and page layouts. It also offers a slew of customizer options that allow end-users to put their own design spin on the default theme. However, my interest is primarily in how it extends the block editor.

\n\n\n\n

Let’s get this out of the way up front. Twentig’s one downside is that it adds a significant amount of additional CSS on top of the already-heavy Twenty Twenty and block editor styles. I will blame the current lack of a full design system from WordPress on most of this. Styling for the block editor can easily bloat a stylesheet. Adding an extra 100+ kb per page load might be a blocker for some who would like to try the plugin. Users will need to weigh the trade-offs between the additional features and the added page size.

\n\n\n\n

The thing that makes Twentig special is its extensive patterns and pages library, which offers one-click access to hundreds of layouts specifically catered to the Twenty Twenty theme.

\n\n\n\nInserting one of the hero patterns.\n\n\n\n

It took me a few minutes to figure out how to access the patterns — mainly because I did not read the manual. I expected to find them mixed in with the core patterns inserter. However, the plugin adds a new sidebar panel to the editor, which users can access by clicking the “tw” icon. After seeing the list of options, I can understand why they probably would not fit into WordPress’s limited block and patterns inserter UI.

\n\n\n\n

It would be easier to list what the plugin does not have than to go through each of the custom patterns and pages.

\n\n\n\n

The one thing that truly sets this plugin apart from the dozens of other block-library types of plugins is that there are no hiccups with the design. Almost every similar plugin or tool I have tested has had CSS conflicts with themes because they are trying to be a tool for every user. Twentig specifically targets the Twenty Twenty theme, which means it does not have to worry about whether it looks good with the other thousands of themes out there. It has one job, which is to extend its preferred theme, and it does it with well-designed block output.

\n\n\n\n

The other aspect of this is that it does not introduce new blocks. Every pattern and page layout option uses the core WordPress blocks, which includes everything from hero sections to testimonials to pricing tables to event listings. And more.

\n\n\n\n

Twentig does not stop adding features to the block editor with custom patterns. The useful and sometimes fun bits are on the individual block level, and I have yet to explore everything. I continue to discover new settings each time I open my editor.

\n\n\n\n

Whether it is custom pullquote styles, a photo image frame, or an inner border tweak to the Cover block (shown below), the plugin adds little extras that push what users can do with their content.

\n\n\n\nInner border style for the Cover block.\n\n\n\n

Each block also gets some basic top and bottom margin options, which comes in handy when laying out a page. At this point, I am simply looking forward to discovering features I have yet to find.

\n\n\n\n

Areas Themes Should Explore

\n\n\n\n

One of the things I dislike about many of these features being within the Twentig plugin is that I would like to see them within the Twenty Twenty theme instead. Obviously not every feature belongs in the theme — some features firmly land in plugin territory. The default WordPress themes should also leave some room for plugin authors to explore. But, shipping some of the more prominent patterns and styles with Twenty Twenty would make a more robust experience for the average end-user looking to get the most out of blocks.

\n\n\n\n

Block patterns were not a core WordPress feature when Twenty Twenty landed. However, for the upcoming Twenty Twenty-One theme, which is expected to bundle some unique patterns, the design team should explore what the Twentig plugin has brought to the current default. That is the direction that theme development should be heading, and theme developers can learn a lot by stealing borrowing from this plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 22:00:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Coming in Jetpack 9.0: Shortcode Embeds Module Updated to Handle Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105381\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2938:\"

Facebook and Instagram are dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers in an upcoming release. After evaluating third-party solutions, WordPress VIP is recommending its partners enable Jetpack’s Shortcode Embeds module. Jetpack will be shipping the update in its 9.0 release, which is anticipated to land prior to the October 24th deadline.

\n\n\n\n

The module is being updated to provide a seamless transition for users who might otherwise be negatively impacted by Facebook’s upcoming API change. WordPress contributors have run some simulations but are not yet sure what will happen to the display for previously embedded content.

\n\n\n\n

“It is possible that they change the contents of the JS file to manipulate cached embeds, perhaps to display a warning that the site is using an old method to embed content or that the request is not properly authenticated,” Jonathan Desrosiers commented on the trac ticket for removing the oEmbed providers.

\n\n\n\n

WordPress.com VIP roughly outlined what users can expect if they do not enable a solution to begin authenticating oEmbeds:

\n\n\n\n

By default, WordPress caches oEmbed contents in post metadata. These embeds will continue to display in previously-published content. If you edit older posts in the Block Editor, regardless of whether you update the post by saving changes, the embeds in the post will no longer be cached and will stop displaying. If you view these older posts using the Classic Editor, so long as the post is not re-saved, the embeds will continue to function and display properly. If you update the post content, the embed will cease functioning unless you have a mitigation installed.

\n\n\n\n

Although WordPress VIP recommends using the Jetpack module as the best solution, self-hosted WordPress users may want to investigate other options if they are not already using Jetpack. oEmbed Plus is a free plugin created specifically for solving the problem of WordPress dropping Facebook and Instagram as oEmbed providers but it is more work to set up and configure. It requires users to register as a Facebook developer and create an app to get API credentials.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 21:18:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"WPTavern: W3C Selects Craft CMS for Redesign Project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105265\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:149:\"https://wptavern.com/w3c-selects-craft-cms-for-redesign-project?utm_source=rss&utm_medium=rss&utm_campaign=w3c-selects-craft-cms-for-redesign-project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9407:\"

W3C has selected Craft CMS over Statamic for its redesign project, after dropping WordPress from consideration in an earlier round of elimination:

\n\n\n\n

In the end, our decision mostly came down to available resources. Craft had already committed to reach AA compliance in Craft 4 (it is currently on version 3.5, the release of version 4 is planned for April 2021). They had also arranged for an external agency to provide them with accessibility issues to tackle weekly. In the end, they decided instead to hire an in-house accessibility specialist to perform assessments and assist the development team in adopting accessibility patterns in the long run.

W3C CMS Selection Report
\n\n\n\n

Last week we published a post urging W3C to revisit Gutenberg for a fair shake against the proprietary CMS’s or consider adopting another open source option. During the selection process, Studio 24, the agency contracted for the redesign, cited its extensive experience with WordPress as the reason for not performing any accessibility testing on more recent versions of Gutenberg.

\n\n\n\n

When asked if the team contacted anyone from WordPress’ Accessibility Team during the process or put Gutenberg through the same tests as the proprietary CMS’s, Studio 24 founder Simon Jones confirmed they had not.

\n\n\n\n

“No, we only reached out to the two shortlisted CMS’s” Jones said. “I’m afraid we didn’t have time to do more. We did test GB a few months ago based on editing content – though it wasn’t the only factor in our choice. As an agency we do plan to keep reviewing GB in the future.”

\n\n\n\n

In response to our concerns regarding licensing, Jones penned an update titled “On not choosing WordPress,” which further elaborated on the reasons why the agency was not inclined towards using or evaluating the new editor:

\n\n\n\n

From a business perspective I also believe Gutenberg creates a complexity issue that makes it challenging for use by many agencies who create custom websites for clients; where we have a need to create lots of bespoke blocks and page elements for individual client projects.

The use of React complicates front-end build. We have very talented front-end developers, however, they are not React experts – nor should they need to be. I believe front-end should be built as standards-compliant HTML/CSS with JavaScript used to enrich functionality where necessary and appropriate.

As of yet, we have not found a satisfactory (and profitable) way to build custom Gutenberg blocks for commercial projects. 

\n\n\n\n

The CMS selection report also stated that W3C needs the CMS to be “usable by non-sighted users” by the launch date, since some members of the staff who contribute to the website are non-sighted.

\n\n\n\n

Since the most recent version of WordPress was not tested in comparison with the proprietary CMS’s, it’s unclear how much better they handle accessibility. Ultimately, W3C and Studio 24 were more comfortable moving forward with a proprietary vendor that was able to make certain assurances about the future accessibility of its authoring tool, despite having a smaller pool of contributors.

\n\n\n\n

“[I’m] also deeply curious since the cursory notes on accessibility for both of the reviewed CMSes seem to highlight a ton of issues like ‘Buttons and Checkboxes are built using div elements’ or most inputs lacking clear focus styles,” Gutenberg technical lead Matías Ventura said. “An element like the Calendar for choosing a post date seems entirely inoperable with keyboard on Craft, for example, while WordPress’ has had significant effort and rounds of feedback poured into that element alone to make it fully operable.”

\n\n\n\n

WordPress developer Anthony Burchell commented on how using a relatively new proprietary CMS seemed counter to W3C’s stated goal to select an option on the basis of longevity. Craft CMS’s continued success is contingent upon its business model and the company’s ability to remain profitable.

\n\n\n\n

“FOSS have the same opportunity of direct access to developers,” Burchell said. “I recognize there are many accessibility shortcomings in popular software, but I think it’s more constructive to rally behind and contribute, not use a proprietary CMS that boasts beer budget in their guidelines.”

\n\n\n\n

On the other side of the issue, accessibility advocates took the W3C’s decision as a referendum on Gutenberg’s continued struggles to meet WCAG AA standards. WordPress accessibility specialist Amanda Rush said it was “nice to see the W3C flip tables over this.”

\n\n\n\n

“Gutenberg is not mature software,” accessibility consultant and WordPress contributor Joe Dolson said in a post elaborating on his comments at WPCampus 2020 Online. He emphasized the lack of stability in the project that Studio 24 alluded to when documenting the reasons against using WordPress.

\n\n\n\n

“It is still undergoing rapid changes, and has grand goals to add a full-site editing experience for WordPress that almost guarantees that it will continue to undergo rapid changes for the next few years,” Dolson said. “Why would any organization that is investing a large amount into a site that they presumably hope will last another 10 years want to invest in something this uncertain?”

\n\n\n\n

Dolson also said the accessibility improvements he referenced regarding the audit were only a small part of the whole picture.

\n\n\n\n

“They only encompass issues that existed in the spring of 2019,” he said. “Since then, many features have been added and changed, and those features both resolve issues and have created new ones. The accessibility team is constantly playing catch up to try and provide enough support to improve Gutenberg. And even now, while it is more or less accessible, there are critical features that are not yet implemented. There are entirely new interface patterns introduced on a regular basis that break prior accessibility expectations.”

\n\n\n\n

WordPress is also being used by millions of people who are constantly reporting issues to fuel the software’s continued refinement, which increases the backlog of issues. Unfortunately, Studio 24 did not properly evaluate Gutenberg against the proprietary CMS’s in order to determine if these software projects are in any better shape.

\n\n\n\n

Instead, they decided that Craft CMS’s community was more receptive to collaborating on issues without reaching out to WordPress. Given the W3C’s stated preference for open source software, WordPress, as the only CMS under consideration with an OSD-compliant license, should have received the same accessibility evaluation.

\n\n\n\n

“I can’t make any statements that would be meaningful about the other content management systems under consideration; but if WordPress wants to be taken seriously in environments where accessibility is a legal, ethical, and mission imperative, there’s still a lot of work to be done,” Dolson said.

\n\n\n\n

Studio 24’s evaluation may not have been equitable to the only open source CMS under consideration, but the situation serves to highlight a unique quandary: when using open source software becomes the impractical choice for organizations requiring a high level of accessibility in their authoring tools.

\n\n\n\n

“Studio 24 ultimately determined that working with a CMS to make it better was more possible with a smaller, proprietary vendor than with a large open-source project,” accessibility advocate Brian DeConinck said. “Project leadership would be more receptive, and the smaller community means changes can be made more quickly. That should prompt a lot of soul-searching for…well, everyone. What does that say about the future of open source?”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 04:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"Gary: More than 280 characters\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"https://pento.net/?p=5405\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://pento.net/2020/09/29/more-than-280-characters/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5187:\"

It’s hard to be nuanced in 280 characters.

\n\n\n\n

The Twitter character limit is a major factor of what can make it so much fun to use: you can read, publish, and interact, in extremely short, digestible chunks. But, it doesn’t fit every topic, ever time. Sometimes you want to talk about complex topics, having honest, thoughtful discussions. In an environment that encourages hot takes, however, it’s often easier to just avoid having those discussions. I can’t blame people for doing that, either: I find myself taking extended breaks from Twitter, as it can easily become overwhelming.

\n\n\n\n

For me, the exception is Twitter threads.

\n\n\n\n

Twitter threads encourage nuance and creativity.

\n\n\n\n

Creative masterpieces like this Choose Your Own Adventure are not just possible, they rely on Twitter threads being the way they are.

\n\n\n\n
\n

Being Beyoncé’s assistant for the day: DONT GET FIRED THREAD pic.twitter.com/26ix05Hkhp

— green chyna (@CORNYASSBITCH) June 23, 2019
\n
\n\n\n\n

Publishing a short essay about your experiences in your job can bring attention to inequality.

\n\n\n\n
\n

DOWNTOWN BROOKLYN: I\'m working arraignments tonight, representing poor New Yorkers who were arrested yesterday on Thanksgiving.

It was the coldest Thanksgiving in more than a century. Tonight\'s also bitterly cold, even in the courtroom. I\'m wearing my scarf & coat.

— Rebecca Kavanagh (@DrRJKavanagh) November 24, 2018
\n
\n\n\n\n

And Tumblr screenshot threads are always fun to read, even when they take a turn for the epic (over 4000 tweets in this thread, and it isn’t slowing down!)

\n\n\n\n
\n

Tumblr textposts thread, probably?

— we are a family forged in bureaucracy (@ex_aItiora) August 26, 2019
\n
\n\n\n\n

Everyone can think of threads that they’ve loved reading.

\n\n\n\n

My point is, threads are wildly underused on Twitter. I think I big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.

\n\n\n\n

To help make this easier, I’ve been working on a tool that will help you publish an entire post to Twitter from your WordPress site, as a thread. It takes care of transforming your post into Twitter-friendly content, you can just… write. \"?\"

\n\n\n\n

It doesn’t just handle the tweet embeds from earlier in the thread: it handles handle uploading and attaching any images and videos you’ve included in your post.

\n\n\n\n\n\n\n\n

All sorts of embeds work, too. \"?\"

\n\n\n\n
\n
\n
\n\n\n\n

It’ll be coming in Jetpack 9.0 (due out October 6), but you can try it now in the latest Jetpack Beta! Check it out and tell me what you think. \"?\"

\n\n\n\n

This might not fix all of Twitter’s problems, but I hope it’ll help you enjoy reading and writing on Twitter a little more. \"?\"

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 02:33:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Gary\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Themes Team Releases a Web Fonts Loader, Likely To Prohibit Hotlinking Any Off-Site Assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105363\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets?utm_source=rss&utm_medium=rss&utm_campaign=themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5815:\"

Last Friday, the WordPress Themes Team announced the release of its new Webfonts Loader project. It is a drop-in script that allows theme authors to load web fonts from the user’s site instead of a third-party CDN. The secondary message included in the team’s announcement is that it no longer plans to allow themes to hotlink Google Fonts in the future.

\n\n\n\n

Throughout most of the team’s history, it has not allowed themes to hotlink or use CDNs for hosting theme assets, such as CSS, JavaScript, and fonts. The one exception to this rule was the use of Google Fonts. This allowed themes to have richer typography options at their disposal from what the team has generally declared a reliable source.

\n\n\n\n

“The exception was made because there was no practical way to not have the exception at the time,” said Aria Stathopoulos, a Themes Team representative and developer behind the Webfonts Loader project. “The exception for Google Fonts was made out of necessity. Now that there is another way, the exception will not be necessary.”

\n\n\n\n

In effect, disallowing the Google Fonts CDN would not be a new ban. It would be a removal of an exception to the existing ban.

\n\n\n\n

Google Fonts has become so embedded into the theme developer toolset over the years, there was no way the team could simply pull the plug and prohibit the use of the CDN overnight. If the Themes Team members wanted to focus more on privacy, they would need to build a tool that made it dead simple for theme authors to use.

\n\n\n\n

There is no hard deadline for when the team will remove the exception for Google Fonts, and it is not set in stone at this point. Stathopoulos said removing it has been the goal from the beginning, disallowing all CDNs. However, it took a while to find an efficient way to handle this. With a viable alternative in place, they can discuss moving forward.

\n\n\n\n

Webfonts Loader for Themes

\n\n\n\n

The Webfonts Loader project keeps it simple for theme authors. It introduces a new wptt_get_webfont_styles() function that developers can plug in a stylesheet URL. Once a page is loaded with that function call, it will download the fonts locally to a /fonts folder in the user’s /wp-content directory. This way, fonts will always be served from the user’s site.

\n\n\n\n

The system is not limited to Google Fonts either. Any URL that serves CSS with an @font-face {} rule will work. It does not currently include authentication for CDNs that require API keys, such as Adobe Fonts. However, that is something the team might add in the future.

\n\n\n\n

“For end-users, moving away from CDNs and locally hosting web fonts will improve performance (fewer handshake roundtrips for SSL), and is the privacy-conscious choice,” said Stathopoulos. “The only ‘valid privacy concern’ is that the web fonts’ CDN does not disclose information that is fundamental to the GDPR: what information gets logged, for how long these logs remain, how they are processed, if there is any cross-referencing with all the other wealth of information the company has from users, etc. The concern is a lack of disclosure and information. If a site owner doesn’t know what kind of information a third-party logs for its visitors, then they should ethically not enforce that on their visitors. With this package, the CDN is removed from the equation and the font still gets served fast — if not faster.”

\n\n\n\n

A Path to Core WordPress

\n\n\n\n

Today, there is now a broader focus on privacy concerns related to third-party resources, particularly with tech giants like Google. Such concerns extend to whether third parties are tracking users or collecting data. Additional concerns are around whether sites are disclosing the use of third-party resources, which may be required in some jurisdictions. Site owners who are often unable to work through the web of potential issues are stuck in the middle.

\n\n\n\n

Jono Alderson opened a ticket to create an API for loading web fonts locally in core WordPress in February 2019. It is a lengthy and detailed proposal, but it has yet to see much buy-in outside of a handful of developers.

\n\n\n\n

“If such a script is standardized and included in WordPress core, one of the main benefits would be more respect for the end-user’s privacy,” said Stathopoulos. “In the end, that’s all privacy is about: respecting users.”

\n\n\n\n

A standard API like Alderson proposes could solve some issues. Namely, it would virtually eliminate any privacy concerns. However, loading fonts locally could allow WordPress to optimize font loading and would create a shared system where plugins and themes do not load duplicate assets because of the current limitations of the enqueuing system. A standard API would also put the responsibility of efficiently loading fonts on WordPress’s shoulders instead of theme and plugin developers.

\n\n\n\n

The Themes Team’s new project is a solid start and strengthens the current proposal.

\n\n\n\n

“If we’re serious about WordPress becoming a fast, privacy-friendly platform, we can’t rely on theme developers to add and manage fonts without providing a framework to support them,” wrote Alderson in the ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 28 Sep 2020 20:58:48 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: Fuxia Scholz First to Pass 100K Reputation Points on WordPress Stack Exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105282\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange?utm_source=rss&utm_medium=rss&utm_campaign=fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5096:\"

Fuxia Scholz, a prolific WordPress Stack Exchange (WPSE) contributor, is the first member to reach 100,000 reputation points. The popular Q&A community site rewards expert advice by floating the highest quality answers to the top, allowing users to earn reputation points. The gamified help community has proven to be more motivating for developers than many traditional forums, since the upvotes communicate how useful their answers are to others.

\n\n\n\n
\n\n\n\n

Scholz started on Stack Overflow a few months before WordPress had its own site. She wrote around 50 answers and made connections with other WordPress developers ahead of the site’s beta phase in June 2010. Once the site graduated and got its own logo and design, Scholz started writing more.

\n\n\n\n

“One core idea for all Stack Exchange sites is gamification: You earn reputation, and you get access to certain privileges,” Scholz said.

\n\n\n\n

“You can say I got a bit addicted. My favorite questions were about problems for which I didn’t know the answer, and couldn’t find one with a search engine, because no one else had solved that before. I used my answers to teach myself, and I learned a lot this way! In May 2011 my reputation on WPSE was already higher than on Stack Overflow, and for the next years it went up in a steep curve.” Ten years after WPSE launched, Scholz has become the first to reach 100,000 reputation points.

\n\n\n\n

“What reputation and karma do is send a message that this is a community with norms, it’s not just a place to type words onto the internet. (That would be 4chan.)” Stack Overflow co-creator Joel Spolsky said. “We don’t really exist for the purpose of letting you exercise your freedom of speech. You can get your freedom of speech somewhere else. Our goal is to get the best answers to questions. All the voting makes it clear that we have standards, that some posts are better than others, and that the community itself has some norms about what’s good and bad that they express through the vote.”

\n\n\n\n

The reputation points were originally inspired by Reddit Karma. Spolsky admits that the points not a perfect system but they do tend to “drive a tremendous amount of good behavior.” Gamification can shape and encourage certain behaviors but Spolsky said it’s a weak force that cannot motivate people to do things they are not already interested in doing. For Scholz, it was the community aspect and an earned sense of ownership and responsibility that kept her hooked.

\n\n\n\n

“In 2012, the community elected me as a moderator, and that changed a lot,” she said. “Now it wasn’t just a game anymore, it was a duty. I felt responsible for the site. I still do. I also found some friends on there. We met at WordCamps and in private, and worked together on different projects.”

\n\n\n\n

Scholz no longer works in development and said she doesn’t care about WordPress anymore, but she is still a regular contributor on the WPSE.

\n\n\n\n

“I switched careers and work as a writer, translator, and community manager for Chess24.com now,” she said. “But I still care about the site WordPress Stack Exchange! I keep an eye on new tags, handle flagged posts and comments, try to make every new user feel welcome, and I search for people who are abusing the system — vote fraud and spam. And, very rarely, I even write an answer, because I still know all this stuff.

\n\n\n\n

“Checking the site has become a part of my daily routine, like feeding the cat.”

\n\n\n\n

This daily habit has snowballed into Scholz racking up more than 2,000 answers. She is getting upvotes on many of her old answers nearly every day, which is what pushed her over the 100k milestone.

\n\n\n\n

“There is a lot to say about the way our site developed over the years,” Scholz said. “I’m not happy about some things. The enthusiasm of the early days is gone. We don’t have enough regulars, there is no discussion about the site on WordPress Development Meta Stack Exchange, and our chat, once very active, funny, and friendly, is now almost dead.

\n\n\n\n

“Maybe that’s normal, I don’t know. But it’s still ‘my’ site. Reputation and badges don’t really mean anything for a long time now, but keeping the site working, useful and friendly is more important.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 26 Sep 2020 15:27:03 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: PhotoPress Plugin Seeks to Revolutionize Photography for WordPress Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=104770\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:209:\"https://wptavern.com/photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users?utm_source=rss&utm_medium=rss&utm_campaign=photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5638:\"

Peter Adams, the owner of the PhotoPress plugin, announced a couple of weeks ago that now is the time for his project to take center stage. “It’s Time for PhotoPress,” read the title of his post in which he laid out a four-phase plan for the future of his project.

\n\n\n\n

Adams is no stranger to manipulating WordPress to suit the needs of photographers. He described photography as his first love and second career. He initially found the art of taking photos in high school and set off to college to become a professional photographer in the early ’90s.

\n\n\n\n

As his university graduation loomed, he was recruited to run web development for an internet ad agency that built websites for Netscape, Bill Clinton’s White House, and dozens of Fortune 500 companies. He spent the next 15 years starting or running tech companies before returning to his roots as a photographer.

\n\n\n\n

Today, he photographs for various magazines and companies. And, that’s where his PhotoPress project comes in.

\n\n\n\n

“As far as WordPress has come, it is at risk of losing an entire generation of photographers to photo website services such as Photoshelter, SmugMug, Squarespace, and PhotoFolio,” he said. Adams wants to change that, making WordPress the go-to platform for photographers around the world.

\n\n\n\n

The Jetpack of Photography Plugins

\n\n\n\n

If you dig into the history of the PhotoPress plugin on WordPress.org, it seems to have a 15-year history. However, this is not the same plugin that was published a decade and a half ago by a different developer. The original plugin is now defunct, and Adams took over when the name was freed up on the directory.

\n\n\n\n

Adams wrote in his announcement post that WordPress has done a great job of delivering several media features over the years. “Yet despite that, there are still many rough edges and missing features that keep WordPress from being the first choice for a photographer that needs to publish a beautiful portfolio of their work, put their image catalog/archive online, or showcase a photo editorial/project.”

\n\n\n\n

He outlined a list of 10 specific problem areas that he wants to address in a “Jetpack-like” plugin for photographers. This is the bread and butter of the first of the planned four phases, which he said is about 80% finished. He had originally planned to develop PhotoPress as a series of separate plugins, each addressing a specific problem. Now, it is a single plugin with modules than can be enabled or disabled.

\n\n\n\n

When asked why the “right time” is now, Adams explained it is because the Gutenberg (block editor) project is a giant leap forward in usability in terms of creating photography blogs.

\n\n\n\nPhotoPress Gallery block in the editor.\n\n\n\n

“Photogs are a rare breed of non-technical users with high design sense,” he said. “Things that I used to have to teach photographers to do using shortcode syntax and custom CSS can now be simple controls with live feedback inside a Gutenberg block. It’s really a game-changer for getting people comfortable with customizing things like gallery styling — which is the number one thing photographers need to do.”

\n\n\n\n

The primary piece of the PhotoPress plugin is its custom PhotoPress Gallery block. It allows users to choose between a range of gallery styles, such as columns, masonry, justified, and mosaic. Each style has its own options. Images can also be launched into a slideshow when one is clicked.

\n\n\n\n

Based on some quick tests, the block’s front-end output will go farther with some themes than others. This is mainly because of conflicting CSS and issues which can be solved by testing against more themes.

\n\n\n\n

Aside from the block, the plugin can automatically extract image metadata and group that data through custom taxonomies, such as cameras, lenses, locations, keywords, and more. WordPress stores this information out of the box, but it is hidden away as post meta. The plugin uses the taxonomy system to make it manageable for end-users.

\n\n\n\n

Ultimately, Adams set out to create a photography plugin that fits in with the WordPress admin user interface and experience, which he has accomplished.

\n\n\n\n

The Future of PhotoPress

\n\n\n\n

The project is still a work in progress. Adams is still moving through Phase I of the four-phase plan. Once it is complete, he can move on to the next steps in the process.

\n\n\n\n

Phase II is to create themes that are designed specifically to work with the PhotoPress plugin. He has three planned thus far. One for handling portfolio sites. Another for creating a stock photo archive. And the last for photojournalism and exhibits. Each will be built on top of his photography theme framework.

\n\n\n\n

The themes in Phase II will likely be commercial products. Adams said he needs a way to fund the next phases of the project. He hopes to have this step underway by the end of the year.

\n\n\n\n

For 2021, he wants to begin tackling Phases III and IV. The former will be a website-as-a-service (WaaS) similar to WordPress.com but for photographers. It will begin as a paid project but could have some free options for emerging photographers and students. The final phase is to build an onboarding system.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 25 Sep 2020 19:08:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: Google Officially Releases Its Web Stories for WordPress Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105227\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:191:\"https://wptavern.com/google-officially-releases-its-web-stories-for-wordpress-plugin?utm_source=rss&utm_medium=rss&utm_campaign=google-officially-releases-its-web-stories-for-wordpress-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5593:\"Web Stories for WordPress dashboard.\n\n\n\n

Two and a half months after the launch of its public beta, Google released its Web Stories for WordPress plugin. So far, the plugin has over 10,000 active installations and has garnered a solid five-star rating from four reviews.

\n\n\n\n

Google created the Web Stories format through its AMP Project to allow publishers to create visually-rich stories. It is primarily geared toward mobile site visitors, allowing them to quickly jump through story pages with small chunks of content.

\n\n\n\n

The Web Stories plugin creates a visual interface within WordPress for creating Stories. It breaks away from the traditional WordPress interface and introduces users to an almost Photoshop-like experience for building out individual Stories. The Stories editor is completely drag-and-drop.

\n\n\n\n

The plugin also offers eight predesigned templates out of the box that cover a small range of niches. However, according to Google’s announcement, the company plans to add more templates in future updates.

\n\n\n\n

Web Stories Are for Storytelling

\n\n\n\n

“Firstly…the power of Stories,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Stories are how we (humans) see the world and share our experiences. Up to now the platforms that we have to tell stories have been limited to books/films/tv/websites/blogs/instagram stories etc.”

\n\n\n\n

“Websites are ok for telling stories but in many ways the format doesn’t really fit the linear arc of storytelling. When Marshall McLuhan said ‘the medium is the message’ in 1964 he was talking about how the medium itself has a social impact, and change the communication itself…and the possibilities for what is communicated and how it is perceived. But we should keep coming back to Stories. Stories are the key here imo. Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Marsland finished his thread by saying that using Stories as a replacement for a brochure or website is a missed opportunity. He said that it was a platform for storytelling and should be used as such.

\n\n\n\n

It is far too early to tell if Web Stories will simply be a fad or still in wide use years from now. The technology certainly lends itself well to telling stories, particularly in mobile format, but I doubt we have seen the best of what is possible on the web. The format feels too limited to be the end-all-be-all of storytelling. It is merely one medium that will live and die by its popularity with users.

\n\n\n\n

With the right design skills, some people will craft beautiful Web Stories. And, that is just what Marsland has done with the first Story he shared:

\n\n\n\nPage from the Wilson and Pootle Web Story by Jamie Marsland.\n\n\n\n

I agree with his conclusion. Web Stories should be about storytelling. When you move outside of that zone, the technology feels out of place.

\n\n\n\n

Where I disagree is that websites are not ideal for storytelling. Ultimately, the WordPress block editor will allow artistic end-users to craft intricate stories, mixing content and design in ways that we have not seen. We are just now scratching the surface. I expect our community of developers to build more intricate tools than what the Web Stories plugin currently allows, and we can do so in a way that revolutionizes storytelling on the web.

\n\n\n\n

New Features

\n\n\n\nStory editor with Unsplash photo integration.\n\n\n\n

The Web Stories plugin now adds support for Unsplash images and Coverr videos out of the box. The plugin adds a new tab with a “media” icon. For users of the first beta version of the plugin, this may be a bit confusing. The previous media icon was for a tab that displayed the user’s media. Now, the user’s media is under the tab with the “upload” icon.

\n\n\n\n

It is also not immediately clear that the Unsplash images and Coverr videos are not hosted on the site itself. There is a “powered by” notice at the bottom of the tab, but it can be easy to miss because it blends in with the media in the background.

\n\n\n\n

Media from Unsplash and Coverr is hosted off-site and not downloaded to the user’s WordPress media library. I could find no mention of this in the plugin’s documentation. Such hotlinking was a cause for debate over the recent official release of the Unsplash plugin.

\n\n\n\n

Google also announced it planned to add more “stock media integrations” in the near future. According to a document shared via a GitHub ticket, such future integrations may include Google Photos and GIF-sharing site Tenor.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 21:13:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: W3C Drops WordPress from Consideration for Redesign, Narrows CMS Shortlist to Statamic and Craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105108\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft?utm_source=rss&utm_medium=rss&utm_campaign=w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:11563:\"

The World Wide Web Consortium (W3C), the international standards organization for the web, is redesigning its website and will soon be selecting a new CMS. Although WordPress is already used to manage W3C’s blog and news sections of the website, the organization is open to adopting a new CMS to meet its list of preferences and requirements.

\n\n\n\n

Studio 24, the digital agency selected for the redesign project, narrowed their consideration to three CMS candidates:

\n\n\n\n
  1. Statamic
  2. Craft CMS
  3. WordPress
\n\n\n\n

Studio 24 was aiming to finalize their recommendations in July but found that none of them complied with the W3C’s authoring tool accessibility guidelines. The CMS’s that were better at compliance with the guidelines were not as well suited to the other project requirements.

\n\n\n\n

In the most recent project update posted to the site, Studio 24 reported they have shortlisted two CMS platforms. Coralie Mercier, Head of Marketing and Communications at W3C, confirmed that these include Statamic and Craft CMS.

\n\n\n\n

WordPress was not submitted to the same review process as the Studio 24 team claims to have extensive experience working with it. In the summary of their concerns, Studio 24 cited Gutenberg, accessibility issues, and the fact that the Classic Editor plugin will stop being officially maintained on December 31st, 2021:

\n\n\n\n

First of all, we have concerns about the longevity of WordPress as we use it. WordPress released a new version of their editor in 2018: Gutenberg. We have already rejected the use of Gutenberg in the context of this project due to accessibility issues.

If we choose to do away with Gutenberg now, we cannot go back to it at a later date. This would amount to starting from scratch with the whole CMS setup and theming.

Gutenberg is the future of WordPress. The WordPress core development team keeps pushing it forward and wants to roll it out to all areas of the content management system (navigation, sidebar, options etc.) as opposed to limiting its use to the main content editor as is currently the case.

This means that if we want to use WordPress long term, we will need to circumvent Gutenberg and keep circumventing it for a long time and in more areas of the CMS as time goes by. 

\n\n\n\n

Another major factor in the decision to remove WordPress from consideration was that they found “no elegant solution to content localization and translation.”

\n\n\n\n

Studio 24 also expressed concerns that tools like ACF, Fewbricks, and other plugins might not being maintained for the Classic Editor experience “in the context of a widespread adoption of Gutenberg by users and developers.”

\n\n\n\n

“More generally, we think this push to expand Gutenberg is an indication of WordPress focusing on the requirements of their non-technical user base as opposed to their audience of web developers building custom solutions for their clients.”

\n\n\n\n

It seems that the digital agency W3C selected for the project is less optimistic about the future of Gutenberg and may not have reviewed recent improvements to the overall editing experience since 2018, including those related to accessibility.

\n\n\n\n

Accessibility consultant and WordPress contributor Joe Dolson recently gave an update on Gutenberg accessibility audit at WPCampus 2020 Online. He reported that while there are still challenges remaining, many issues raised in the audit have been addressed across the whole interface and 2/3 of them have been solved. “Overall accessibility of Gutenberg is vastly improved today over what it was at release,” Dolson said.

\n\n\n\n

Unfortunately, Studio 24 didn’t put WordPress through the same content creation and accessibility tests that it used for Statamic and Craft CMS. This may be because they had already planned to use a Classic Editor implementation and didn’t see the necessity of putting Gutenberg through the paces.

\n\n\n\n

These tests involved creating pages with “flexible components” which they referred to as “blocks of layout,” for things like titles, WYSIWYG text input, and videos. It also involved creating a template for news items where all the content input by the user would be displayed (without formatting).

\n\n\n\n

Gutenberg would lend itself well to these uses cases but was not formally tested with the other candidates, due to the team citing their “extensive experience” with WordPress. I would like to see the W3C team revisit Gutenberg for a fair shake against the proprietary CMS’s.

\n\n\n\n

W3C Is Prioritizing Accessibility Over Its Open Source Licensing Preferences

\n\n\n\n

The document outlining the CMS requirements for the project states that “W3C has a strong preference for an open-source license for the CMS platform” as well as “a CMS that is long-lived and easy to maintain.” This preference may be due to the economic benefits of using a stable, widely adopted CMS, or it may be inspired by the undeniable symbiosis between open source and open standards.

\n\n\n\n

“The industry has learned by experience that the only software-related standards to fully achieve [their] goals are those which not only permit but encourage open source implementations. Open source implementations are a quality and honesty check for any open standard that might be implemented in software…”

Open Source Initiative
\n\n\n\n

WordPress is the only one of the three original candidates to be distributed under an OSD-compliant license. (CMS code available on GitHub isn’t the same.)

\n\n\n\n

Using proprietary software to publish the open standards that underpin the web isn’t a good look. While proprietary software makers are certainly capable of implementing open standards, regardless of licensing, there are a myriad of benefits for open standards in the context of open source usage:

\n\n\n\n

“The community of participants working with OSS may promote open debate resulting in an increased recognition of the benefits of various solutions and such debate may accelerate the adoption of solutions that are popular among the OSS participants. These characteristics of OSS support evolution of robust solutions are often a significant boost to the market adoption of open standards, in addition to the customer-driven incentives for interoperability and open standards.”

International Journal of Software Engineering & Applications
\n\n\n\n

Although both Craft CMS and Statamic have their code bases available on GitHub, they share similarly restrictive licensing models. The Craft CMS contributing document states:

\n\n\n\n

Craft isn’t FOSS
Let’s get one thing out of the way: Craft CMS is proprietary software. Everything in this repo, including community-contributed code, is the property of Pixel & Tonic.

That comes with some limitations on what you can do with the code:

– You can’t change anything related to licensing, purchasing, edition/feature-targeting, or anything else that could mess with our alcohol budget.
– You can’t publicly maintain a long-term fork of Craft. There is only One True Craft.

\n\n\n\n

Statamic’s contributing docs have similar restrictions:

\n\n\n\n

Statamic is not Free Open Source Software. It is proprietary. Everything in this and our other repos on Github — including community-contributed code — is the property of Wilderborn. For that reason there are a few limitations on how you can use the code:

\n\n\n\n

Projects with this kind of restrictive licensing often fail to attract much contribution or adoption, because the freedoms are not clear.

\n\n\n\n

In a GitHub issue requesting Craft CMS go open source, Craft founder and CEO Brandon Kelly said, “Craft isn’t closed source – all the source code is right here on GitHub,” and claims the license is relatively unrestrictive as far as proprietary software goes, that contributing functions in a similar way to FOSS projects. This rationale is not convincing enough for some developers commenting on the thread.

\n\n\n\n

“I am a little hesitant to recommend Craft with a custom open source license,” Frank Anderson said. “Even if this was a MIT+ license that added the license and payment, much like React used to have. I am hesitant because the standard open source licenses have been tested.”

\n\n\n\n

When asked about the licensing concerns of Studio 24 narrowing its candidates to two proprietary software options, Coralie Mercier told me, “we are prioritizing accessibility.” A recent project update also reports that both CMS suppliers W3C is reviewing “have engaged positively with authoring tool accessibility needs and have made progress in this area.”

\n\n\n\n

Even if you have cooperative teams at proprietary CMS’s that are working on accessibility improvements as the result of this high profile client, it cannot compare to the massive community of contributors that OSD-compliant licensing enables.

\n\n\n\n

It’s unfortunate that the state of open source CMS accessibility has forced the organization to narrow its selections to proprietary software options for its first redesign in more than a decade.

\n\n\n\n

Open standards go hand in hand with open source. There is a mutually beneficial connection between the two that has caused the web to flourish. I don’t see using a proprietary CMS as an extension of W3C values, and it’s not clear how much more benefit to accessibility the proprietary options offer in comparison. W3C may be neutral on licensing debates, but in the spirit of openness, I think the organization should adopt an open source CMS, even if it is not WordPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 20:13:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WPTavern: First Look at Twenty Twenty-One, WordPress’s Upcoming Default Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105166\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme?utm_source=rss&utm_medium=rss&utm_campaign=first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6907:\"

Fashion is ephemeral. Art is eternal. Indeed what is a fashion really? A fashion is merely a form of ugliness so absolutely unbearable that we have to alter it every six months!

\n\n\n\n

Thus wrote Oscar Wilde on Victorian-era fashion in an article titled “The Philosophy of Dress” for the New-York Tribune in 1885.

\n\n\n\n

In many ways, WordPress theming is the same as the ever-changing landscape of fashion. Rounded corners are in one day and out the next. Box shadows are in one year after being frowned up just months earlier. Perhaps web design is so intolerable that we must change it every six months. Or, at least freshen it up every year in the case of WordPress.

\n\n\n\n

If art is eternal, there are only two default, Twenty* themes that I can truly recall from past years: Twenty Ten and Twenty Fourteen — yes, Twenty Twenty is memorable, but it is also still the current default. Twenty Ten was a classic that paid homage to WordPress’s past. Twenty Fourteen was such a leap away from tradition that it is hard to forget. Everything else has seemed to fade to varying degrees.

\n\n\n\n

With WordPress 5.6 and the end of the year looming, it is time to look forward to the latest trend. As Mel Choyce-Dwan noted in the announcement of Twenty Twenty-One, the next default theme, “Pastels and muted colors are pretty in right now.”

\n\n\n\n

She is not wrong. The colors are a refreshing change of pace. Now that we are into the second day of autumn, I am getting the good kind of vibes from some of the more earthy-tones from a couple of the color palettes expected to ship with the theme.

\n\n\n\nPotential color palette options for Twenty Twenty-One.\n\n\n\n

Whether Twenty Twenty-One will be a fashionable theme for the year or art that we can remember a decade from now, only history will be able to judge. For now, let’s enjoy the creation and take a look at what we should expect from the next default WordPress theme.

\n\n\n\n

The Current Twenty Twenty-One

\n\n\n\n

The new default theme is a fork of Automattic’s Seedlet, a project in which I lauded as the next step in the evolution of theming. It is a theme that is focused on WordPress’s future of being completely comprised of blocks. It gives us an ideal insight into where theme development is heading. It makes sense as the foundation for the new default. Few other themes would make for a good starting point right now. With WordPress theme development in flux, Seedlet is simply ahead of the pack in terms of foundational elements.

\n\n\n\nSeedlet WordPress theme screenshot.\n\n\n\n

“This provides us with a thorough system of nested CSS variables to make child theming easier, and to help integrate with the global styles functionality that’s under development for full-site editing,” wrote Choyce-Dwan of using Seedlet as a starting point.

\n\n\n\n

There are no plans to spin up a Google Web Font for this theme. The design team is going native and sticking with the default system font stack. Choyce-Dwan listed several reasons for the choice, such as keeping a neutral font that allows broad use, speed, and customizability via a child theme.

\n\n\n\n

Despite the neutral font, the default pastel green is a fairly opinionated design decision. It will not be used broadly across industries. However, the team plans to create multiple color palettes that will give it more range. Presumably, these palettes can also be overwritten.

\n\n\n\nPastel green color scheme on single post view.\n\n\n\n

Other than the colors, the design is relatively simple. Choyce-Dwan said that the theme’s block patterns support is where it will be truly unique.

\n\n\n\n

I was initially unhappy with the patterns that were going to ship with WordPress 5.5. However, an 11th-hour update improved the situation so that they did not feel entirely experimental. The foundational Seedlet theme for Twenty Twenty-One has some unique patterns that begin to scratch the surface of what’s possible with this WordPress feature. My hope is that the new default theme steps it up a notch.

\n\n\n\n

Currently, the theme does not register any custom patterns. However, it has a placeholder file and a note that they are a work in progress. Choyce-Dwan shared some patterns the team has already designed in the announcement.

\n\n\n\nCurrently-designed block patterns.\n\n\n\n

“We’ll be relying on our talented community designers for more ideas,” she wrote. The team has also created a GitHub template for anyone to contribute pattern design ideas.

\n\n\n\n

Currently, the theme does not support the upcoming full-site editing feature of WordPress. After the Beta 1 release of WordPress 5.6, the team plans to begin exploring the addition of this support. WordPress is expected to ship a public beta of full-site editing in its next major release, but it is unclear whether it will be far enough along to be a headline feature for the Twenty Twenty-One theme.

\n\n\n\n

The team and volunteers have less than a month before the October 20th deadline for committing the new theme to trunk, the core WordPress development branch. At that stage, the theme should be nearly complete and ready for production. Of course, there will be several rounds of patches, bug fixes, and updates before WordPress 5.6 lands in December. Right now is the best time for anyone who wants to get involved with Twenty Twenty-One to do so.

\n\n\n\n

Useful links with more information:

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 20:01:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:37:\"HeroPress: Hello World – Hevo Nyika\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=3308\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:176:\"https://heropress.com/essays/hello-world-discovering-the-world-through-wordpress/#utm_source=rss&utm_medium=rss&utm_campaign=hello-world-discovering-the-world-through-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14438:\"\"Pull

Unokwanisa kuverenga rondedzero iyi muChiShona

\n

So I chose a career in Web Development!!

\n

To be honest it’s kind of funny when I think about it and quite surreal to be here talking about my story. It has been a journey and I would like to share my story with you.

\n

I have been lucky in the Dad department. My Dad encouraged me to work hard and dream big from a very young age. I remember occasionally having ‘when I grow up’ talks.

\n

For quite some time I wanted to be a Judge, however awesome this dream sounds it was not very inspired. After binge-watching Judge Judy for a whole weekend, I started calling myself Judge Thelma. Though I don’t remember much of this my sister says that I used to say I would arrest all the men in the World if I ever became a Judge. HAHAHA! (clearly I didn’t understand how the World works)

\n

I did not understand what being a Judge meant or what was required for me to start banging that gavel to my heart’s desire. Eventually, I learnt that I had to become a lawyer first then magistrate before I could be nominated to be a Judge and let us just say that is how I sentenced that dream to a lifetime down the drain.

\n

See what I did there? hahaha!

\nWith Daddy Dearest\n

A few years later, I was in High School and that is when I decided to pursue a career in Computer Science. I did not know what I would be doing or how I would get there but I just knew that I was going to pursue a career in ICT. I wrote my first line of code when I was 16 years old.

\n

This was after I had joined the school’s computer class, initially, I thought I would be learning about Excel Sheets and Word Documents until I was assigned to write my first program in C (talk about a double-take!!). It was not easy but it was very exciting, l remember writing up simple code for a Video Club – a simple check-in/out for VHS tapes and CDs. Dear World, thus began my fascination with computers.

\n

Seven years later, I was now in university studying ICT as I had always wanted. I was doing a Bachelors in Business Management & Information Technology. In my third year, I was interning at a local Webdesign and hosting company. This was never my plan, I only took on that job after I had failed to get a job with local banks or telecommunications companies. Before I was introduced to Website Design I envisioned myself suiting up and working in IT Audit or offering IT support. Even though things did not go as I had planned, I am glad they did not exactly go my way in that aspect. So in 2017, I was designing websites using HTML, CSS, PHP, JavaScripts and Joomla which was the prefered content management system at that company. I knew about WordPress but I was not using it for anything. People have this misconception that WordPress is not for real developers and it is not secure and at that time I was one of those people.

\n

Finding my tribe

\n

One day when I was working at the front desk Thabo Tswana came to give a colleague of mine a purple WooCommerce pen. I did not know what WooCommerce was at that time but I was taken by the purple shirt and pen he was carrying. I asked him about it and he explained what WooCommerce was and that what he was carrying was called ‘swag’. So the love of freebies led me to the WordCamp Harare website, instead of buying a ticket I applied to volunteer. I learnt more about WordPress, I was a volunteer, without any knowledge on WordPress.org or WordPress.com. I only started using WordPress because of the awesome people that l had met at that Wordcamp.

\n

Everyone was so welcoming, a week later with help from Thabo I designed my first ever WP website.

\n

Soon after I was part of the community and a bit more involved in the meetups. We had our first-ever Women Who WordPress meetup in 2018. So many ladies came on board bloggers and developers alike. We were free to talk and discuss a lot of things. We had more time to discuss the difference between WordPress.com and WordPress.org we shared views on how to handle discrimination at work, how to promote your website and a whole lot of other things.

\n

\n

Establishing roots

\n

In 2018, Harare had its first-ever female Lead Organiser Tapiwanashe Manhobo whoop whoop! I was also part of the organising team that year, I was assigned to handle Harare’s first Kids Camp. The planning process was stressful because the economic crisis in Zimbabwe was getting worse, luckily we had over 8 months to plan and with help from sponsors, we managed to pull through. In the end, everything turned out great. I wrote an article about the Kids Camp here.

\n

After the first Kids Camp, we had several WordPressors that were enthusiasts about encouraging kids to embrace ICT. In 2019 we had not planned to have a Kids Camp because of financial constraints but to our surprise, we had some anonymous donations and we managed to have a WordPress Community outreach to a youth centre a week after our WordCamp. We had the outreach at the Centre for Total Transformation which is a non-formal school that caters for underprivileged and vulnerable children. We taught them about WordPress, Computer Hardware and Software.

\n

Here is a small video I took with Ellen when we were about to leave. Did l mention that I am terrible on camera? hahaha!

\n\n

Kids Camp 2019 – Centre for Total Transformation

\n

I have fallen deeply for WordPress because of the Community, I enjoy attending WordCamps, meeting new people and just learning new stuff. I have a huge list of WordCamps I need to attend before l kick the bucket, hopefully. Last year I managed to cross WordCamp

\n

Johannesburg off my bucket list. This year I was going to attend WordCamp Capetown but unfortunately, 2020 had other plans for the whole world. Anyway when everything is back to normal my plan to travel to WordCamps will proceed. (fingers crossed)

\n

Reaping Fruits

\n

Meanwhile, my plan to improve my developing skills has not been on hold. Even though I can still cook up code in C and Java, for now, I have also included WordPress PHP functions to the mix. It was not easy to get to this point, daring myself got me to this slightly better stage. My IQ is not way up there, however, I try to do my best where I can and I am happy to say it has paid off so far.

\n

Around November last year, I was designing as a freelancer while job hunting. Out of the blue l got a call for a job offer from Trust Nhokovenzo who is big on Digital marketing and also part of the WordPress Community. He had asked someone in the community about developers and my name happened to come up. So since February, I have been part of his team at Calmlock Digital Marketing Agency.

\n

There is so much more in the world of WordPress that l am yet to tap into so even though I am ending my write up here, for now, my story is going to continue …

\n

Until next time…

\n

Hevo Nyika

\n

Saka ini ndakasarudza kugadzira mawebhusayiti.

\n

Ndakaita rombo rakanaka pana baba vandakapihwa naMwari. Baba vangu vaindikurudzira kuti ndishande nesimba. Ndinoyeuka pano neapo tichiita hurukuro dzedu dzekuti ‘kana ndakura ndoda kuveyi’.

\n

Kwenguva yakati rebei ndaida kuve Mutongi. Kunyangwe ini ndisingazvirangariri mukoma wangu anotaura kuti ndaiti ndaizosunga varume vese vari pasi rino kana ndikangoita mutongi HAHAHA zveshuwa handaiziva kuti mitemo yenyika inofambiswa seyi.
\nNdanga ndisinga nzwisisi kuti kuva mutongi kwairevei kana zvaidikanwa kwandiri kuti nditange kurova iro ghavheu kuchishuwo chemoyo wangu. Pakupedzisira, ndakadzidza kuti ndaifanirwa kuzoita gweta ipapo magistrate ndisati ndasarudzwa kuita Mutongi naizvozvo ndokupera kwakaita chiroto chekuva Mutongi.

\nNa Baba Vangu\n

Gare gare papfura makore mashoma pandakanga ndave kuHigh School ndakanga ndakuda kuita basa rema kombiyuta. Ndakanyora mutsara wekutanga wekodhi pandaive nemakore gumi nematanhatu. Izvi zvakaitika mushure mekunge ndapinda mukirasi yemakombiyuta, pakutanga ndaifunga kuti ndinenge ndichidzidza nezveExcel Sheets neWord zvisineyi ndakaona ndakunyora kodhi yangu yekutanga muC. Zvaisave nyore kunyora kodhi asi zvainakidza kwazvo, ndorangarira ndichinyora kodhi yeVhidhiyo Kirabhu.

\n

Makore manomwe apfura, ndakanga ndava kuyunivhesiti ndichidzidza ICT zvandakagara ndakaronga. Ndaiita Bachelors muBusiness Management & Information Technology. Mugore rangu rechitatu ndainge ndave kushanda kune imwe kambani yaita zvekugadzira mawebhusaiti. Ndakawana basa iri mushure mekunge ndatadza kuwana basa kumabhanga. Kunyangwe hazvo zvinhu zvisina kuenda sezvandaive ndakaronga, ndinofara kuti hazvina kunyatso enda nenzira yangu. Saka muna 2017 ndaigadzira mawebhusaiti ndichishandisa HTML, CSS, PHP, JavaScript uye Joomla iyo yaive iyo inokurudzirwa kukambani kwandaive. Panguva iyi ndaiziva nezve WordPress asi ndakanga ndisingaishandisi.

\n

Kuwanana neWordPress

\n

Rimwe zuva pandakanga ndichishanda ndakaona Thabo Tswana akauya kuzopa mumwe mukomana wandayishanda naye chinyoreso cheWooCommerce. Ndakanga ndisingazive kuti WooCommerce yaive chii asi ndakafarira chinyoreso nehembe ye WooCommerce yaanga akapfeka. Ndakamubvunza nezvazvo akatsanangura kuti WooCommerce yaive chii. Saka nekudawo zvakanaka, zvemahara ndakaenda pawebhusaiti yeWordCamp Harare ndikabata zvimbo zvegore iroro. Ndakazvipira kubatsirawo vamwe vekuWordPress kuWordCamp Harare. Nerubatsiro kubva kunaThabo ndakagadzira webhusaiti yangu yekutanga yeWordPress vhiki rakatevera .

\n

Mushure mekunge ndaitawo chipato cheavo vanoshandisa WordPress ndakanga ndakuenda kumisangano yeWordPress yaitwa muHarare. Takaita musangano wevakadzi chete muna 2018. Vakadzi vazhinji vakauya kumusangano uyu. Tainga takasununguka kukurukura zvinhu zvakawanda. Takakurukura pamusoro pemutsauko uripo pakati peWordPress.com neWordPress.org takagovana maonero ekugadzirisa rusarura kubasa nezvimwewo.

\n

\n

Nguva yandakatanga kushandisa WordPress

\n

Muna 2018, kurongwa kweWordCamp Harare kwakatungamirwa kekutanga nemusikana ainzi Tapiwanashe Manhobo (waiva mufaro mukuru). Ndakanga ndiri mumwe wevairongawo naye. Hurongwa hwekuronga WordCamp Harare mugore iri hwainetsa pamusaka pekuoma kwehupfumi wemuZimbabwe, zvisineyi takaita rombo rakanaka nokuti takawana rubatsiro kubva kunevamwewo vanhu vakatiwedzera mari. Pakupedzisira, zvese zvakabudirira zvakanaka. Takarongawo WordCamp yevana varipasi pemakore gumi nechishanu, munokwanisa kuverenga pamusoro pezuva iri pawebhisaiti yangu apa.

\n

Mushure mekuita WordCamp yevana, takave nevamwe vanhu veWordPress aifarira kukurudzira vana kuti vagamuchire ICT. Muna 2019 takanga tisina kuronga kuve neWordCamo yeVana nekuda kwezvimhingamupinyi zvemari asi chakatishamisa ndechekuti takawana mari kubvawo kune vamwe. Takaita Camp iyi paCentre for Total Transformation chinova chikoro chisiri chepamutemo chinodzidzisa vana vanotambura. Tadzidzisa vana ava pamusoro peWordPress, Computer Hardware uye Software.

\n\n

Ndofarira WordPress zvakanyanya nekuda kweavo varimu nharaunda yacho, ini ndinonakidzwa nekuenda kumaWordCampi, kusangana nevanhu vatsva uye kungo dzidza zvinhu zvitsva. Gore rakapera ndakakwanisa kuyambuka muganhu weZimbabwe ndichienda kuWordCamp Johannesburg, dai pasina kuti 2020 nyika dzepasi rino dzakawirwa nedenda reCOVID 19 zvimwe ndingadayi ndakaenda kuWordCamp Capetown. Zvisinei hazvo kana denda ranani zvimwe ndichakwanisa kufamba ndichienda kumaWordCamp edzimwe nyika.

\n

Kukowa zvandakadyara

\n

Zvichakadaro, chirongwa changu chekuvandudza hunyanzvi hwangu hachina kumira. Kunyangwe ini ndichiri kukwanisa kubika kodhi muC uye Java, ikozvino, ndasanganisirawo WordPress PHP. Zvaive zvisiri nyore kusvika apa, zvakatora kuzvishingisa nekushanda nesimba. Ndinofara mwari aiva neni pamufambo wangu uyu.

\n

Muna Mbudzi gore rakapera, ndaive ndichigadzira mawebhusayiti apo nditsvaga basa. Pasina nguva ndakataura naTrust Nhokovenzo uyo akaandipa basa mukambani yake, kambani iyi inonzi Calmlock Digital Marketing Agency.

\n

Pane zvimwe zvakawanda kuWordPress zvandisati ndapinda mazviri. Nhaizvozvo kunyangwe ndiri kupedzisa kunyora kwangu apa, nyaya yehupenyu wangu ichaenderera mberi…

\n

Kusvikira nguva inotevera …

\n

…. tsvaga chinangwa chako, chiite mushe mushe ..

\n

The post Hello World – Hevo Nyika appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 06:00:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Thelma Mutete\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: WordPress Contributors Debate Dashboard Notice for Upcoming Facebook oEmbed Provider Removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105132\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5885:\"

WordPress contributors are discussing different strategies for responding to Facebook and Instagram dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers. When a user attempts to embed content by pasting a URL as they have in the past, they may not understand why it no longer works. They may assume that WordPress broke embeds, causing an increase in the support burden for this change.

\n\n\n\n

A few participants on the trac ticket for this issue have suggested WordPress detect users who will be impacted and attempt to warn them with a notice.

\n\n\n\n

“Since this may impact users unknowingly, it is possible to push a dashboard notice to users who have Facebook/Instagram embeds in their content, showing for site admins, as a one-off that can be dismissed,” Marius Jensen said.

\n\n\n\n

“We’ve previously done post-update-processing to clean up comments, so the idea of looking over content for an embed isn’t completely outlandish, and would help with those who don’t follow WordPress’ usual channels to learn of this.”

\n\n\n\n

Others don’t see the necessity. “Why should we make exception here?” Milan Dinić said. “It’s not the first time oEmbed support was discontinued for a provider, and I don’t remember anything specific was done then.”

\n\n\n\n

There is still some uncertainty about what will happen with existing oEmbeds after Facebook updates its API. During a recent core developer meeting, Helen Helen Hou-Sandí confirmed that WordPress does not clear oEmbed caches regularly. “Technically oEmbed caches are cleared if you save and a valid response is returned, we do not do cron-based garbage collection,” Hou-Sandí said.

\n\n\n\n

In a post today on the core development blog, Jake Spurlock assured users and developers that the existing embeds added before Facebook’s API change should still work:

\n\n\n\n

Because oEmbed responses are cached in the database using the hidden oembed_cache post type, any embed added prior to the October 24th deadline will be preserved past the deprecation date. These posts are not purged by default in WordPress Core, so the contents of the embed will persist unless manually deleted.

\n\n\n\n

Marius Jensen cautioned that there is still the possibility that existing embeds may not work going, depending on what Facebook does.

\n\n\n\n

“We don’t know how they plan on implementing the use of unauthorized embed attempts,” Jensen said. “It could not return an embed code and your link would remain a plain link, or maybe they decide to return some kind of embedded ‘unauthorized’ content. I don’t think anyone has heard any specifics on how Facebook plans on doing this, so we’re all just kinda waiting to either hear more, or see what happens.”

\n\n\n\n

Jensen said WordPress doesn’t re-check the cached results except when something changes with the post, but there may be plugins that clean up temporary data that may create an unpredictable outcome.

\n\n\n\n

“The reliability of the caches are hard to determine (and being caches, it’s sort of in the term that it’s not guaranteed to always be there, but rather fetched and saved for a while when needed),” Jensen said.

\n\n\n\n

Ideally WordPress’ oEmbed caches will prevent millions of embeds from breaking, but it’s still unknown how Facebook and third party plugins could change things.

\n\n\n\n

Coming off a rocky 5.5 core update that deprecated jQuery Migrate and flooded official support forums with reports of broken sites, some contributors are wary of having another situation where users are left in the dark.

\n\n\n\n

“I think a dashboard notice is desirable,” Jon Brown said. “Otherwise we’re not preemptively warning people in a way they can prepare and transition to another solution. We’re letting them know the same instant it’s going to break (when editing a specific post). I don’t think we can safely assume cached data is going to persist forever either, plenty of routines out there purge transient data before its stated expiration date.

\n\n\n\n

“I see this as potentially being similar to the problems seen in dropping JQM. It’ll cause avoidable and silent breakage client side without even any error logging for a site developer to pick up on. In hindsight, what ideally would have happened with JQM would have been incorporating the detection code from Enable jQuery Migrate Helper into core temporarily, or simply installing that plugin automatically on behalf of users.”

\n\n\n\n

Brown suggested WordPress detect calls to the cached embeds and warn users before the calls have the chance to fail so they can consider enabling a plugin to keep their embeds working more reliably.

\n\n\n\n

The discussion remains open in the make.wordpress.org/core post and the corresponding trac ticket. Spurlock said WordPress will likely remove Facebook and Instagram oEmbed providers in the upcoming 5.6 release (scheduled for December 8) but it could also be shipped in a 5.x minor release that happens after October 24.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 04:28:56 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Gutenberg Hub Launches Landing Page Templates Directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105009\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:175:\"https://wptavern.com/gutenberg-hub-launches-landing-page-templates-directory?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-hub-launches-landing-page-templates-directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7657:\"\n\n\n\n

Munir Kamal has created copy-and-paste blocks. He has built sections or “patterns” from those blocks. He has created a plugin that allows users to completely customize the two features via block options. Yesterday, he released an initial offering of 22 landing page templates that build upon his earlier work.

\n\n\n\n

Gutenberg Hub can almost be called his magnum opus, at least at this stage of his career. It is a continually growing library of free tools for WordPress’s block editor.

\n\n\n\n

Like previous projects, Gutenberg Hub’s landing templates require the EditorPlus plugin. This plugin is essentially a suite of design controls for the core WordPress blocks. The templates make use of these options by default. Given the limitations of the block editor’s current design controls, the use of such a plugin is necessary. Otherwise, there would be few other ways to realistically create a template system like this.

\n\n\n\n

Currently, users must copy the block code — via a convenient “copy” button — from the Gutenberg Hub website and then paste it in the editor. It is not an ideal situation, and I have been asking Kamal whether he would consider building a template inserter for months now.

\n\n\n\n

This time around, he preemptively said, “And, by the way, I am already working on adding a Template Inserter in my EditorPlus plugin. That will allow users to browse and insert these templates directly from Gutenberg without leaving the website.”

\n\n\n\n

He knew the question was coming. No need for me to ask again. He was unable to share a current screenshot of what the inserter looks like, but he is asking for feedback on what people expect of the user experience and interface.

\n\n\n\n

“Earlier, I created a template inserter similar to other blocks plugins, but later I changed my mind and thought that I should integrate with the Gutenberg Patterns API and load the templates into the ‘patterns’ panel in the block inserter,” he said. “But, I am having a few issues and thinking about going back to the original idea to have a Templates button on the top toolbar that opens a popup window to browse and filter templates that users can insert on a click.”

\n\n\n\n

For now, it is still early. However, at least it is on the long-term roadmap and being worked on.

\n\n\n\n

The Landing Page Templates

\n\n\n\nTesting the photography template (with minor adjustments).\n\n\n\n

At the moment, Gutenberg Hub offers 22 landing page templates. The “page” terminology may not mean “full page.” It simply depends on the active theme. Some themes have an open-canvas type of template that allows users to create the entire page via the editor. However, that is not a common feature, so these page templates will be confined to the post content area in most cases.

\n\n\n\n

The templates also work better with themes that have at least a full-width or no-sidebar option. End-users will want a lot of breathing room to use the templates and tinker with their designs.

\n\n\n\n

Kamal has built templates that stretch across a variety of industries. From restaurants to gyms to education to fashion, there is a lot to choose from right now. He promises more are on the way and at least a 23rd template in the next few days.

\n\n\n\n

“For the niches, I did some research from the top WordPress and HTML marketplaces and found the following most common or popular niches,” he said. “I think I will stick with these niches unless I get some more recommendations.”

\n\n\n\n

In comparison, Redux Templates offers access to over 1,000 sections and templates. Of course, there are trade-offs, such as some of those being commercial and the plugin typically requiring other third-party plugins. While quantity is not the only thing to look at, it proves there are miles of landscape that Gutenberg Hub’s templates have not yet explored. But, it is merely the beginning.

\n\n\n\n

Gutenberg Hub’s full-page templates are not quite as plug-and-play as its blocks and section templates. This is not so much a fault from the developer’s end. It is an issue of the platform, which is constantly being updated, and the range of support from current themes. End-users will start seeing some of the current limitations of the system when a layout does not quite look right with one theme but does with another. Or, if their theme has not been updated to support a new feature, such as the Social Links block, the typical horizontal menu design will likely be a normal vertical list of links instead.

\n\n\n\n

These are not insurmountable issues. Gutenberg and themes need more time to mature before projects like Gutenberg Hub’s landing templates are perfect or at least as close to perfect as can be expected.

\n\n\n\n

There are some things that Gutenberg Hub could improve with its templates. With several that I tested, I needed to switch specific blocks to be full width. This should be set up as the default with templates that are clearly meant to be full width in the example screenshots available on the site. It is a minor issue, but correcting this in the editor fixed several layout issues I was having when using the templates.

\n\n\n\n

Monetization Plans

\n\n\n\n

The second question that Kamal has not been prepared to answer fully over the past several months is how he will monetize Gutenberg Hub. Eventually, developers need some return on their investment when building tons of free tools. Many would do it all for free as long as their bills somehow got paid, but the reality is that there will come a tipping point where their projects need funding for long-haul maintenance.

\n\n\n\n

Kamal said he has laid the groundwork for funding but has not finalized anything yet. Currently, he is working on three ideas:

\n\n\n\n
  • Creating a pro version of his EditorPlus plugin.
  • Offering premium templates and blocks but is looking for a talented designer to work with.
  • Using ads specific to Gutenberg users, but he is not a fan of going this route or ads in general.
\n\n\n\n

He is open to feedback on how to best monetize the website and its projects. However, he said he is unwilling to compromise on giving away current and future free templates and tools.

\n\n\n\n

Future Gutenberg Projects

\n\n\n\n

Kamal said he does not have any new Gutenberg-related projects in the pipeline. The current plan is to work on what he has already created, which is a large ecosystem of Gutenberg tools that somehow work together.

\n\n\n\n

Outside of blocks, templates, and plugins, he is beginning to write more free tutorials on the Gutenberg Hub blog and focusing on creating videos around the project, including a new tutorial series for beginners.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 22 Sep 2020 21:05:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"WPTavern: WordPress Mobile Engineers Propose Dual Licensing Gutenberg under GPL v2.0 and MPL v2.0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105025\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:239:\"https://wptavern.com/wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6556:\"

During a Q&A session at WordCamp Europe 2020 online, Matt Mullenweg mentioned that Gutenberg contributors were considering dual licensing for embedding Gutenberg in mobile apps, along with the requirement that they would need to get an agreement from all contributors. WordPress mobile engineer Maxime Biais has just published a proposal for discussion, recommending dual licensing the editor under GPL v2.0 and MPL v2.0.

\n\n\n\n

“The GPL v2.0 license is a blocker for distributing the Gutenberg library in proprietary mobile apps,” Biais said in the corresponding GitHub issue. “Currently the only known users of Gutenberg on mobile are the WordPress mobile apps which are under GPL v2.0 (WordPress for AndroidWordPress for iOS). Mobile apps under the GPL v2.0 are not common and this limits Gutenberg usage in many apps.

\n\n\n\n

“Rich text editor libraries in the mobile space are lacking. There is no well known open source rich text editor for Android or iOS. We believe that Gutenberg could be a key library for many mobile apps, but that will never happen with the GPL v2.”

\n\n\n\n

Mobile app developers are limited by the GPL, because it requires the entire app to be distributed under the same license. The team is proposing dual licensing under MPL v2.0, a weaker copyleft license that is often considered to be more “business-friendly.” It allows users to combine the software with proprietary code. MPL v2.0 requires the source code for any changes to be available under the MPL, ensuring improvements are shared back to the community. The rest of the app can be distributed under any terms with the MPL v2.0 code included as part of a “larger work.”

\n\n\n\n

“The idea here is to keep some of the WordPress-specific modules under the GPL v2.0 only; some of them are not needed and not relevant for using Gutenberg in another software. Ideally, there would be a different way of bundling the project for being used in WordPress or in a non-GPL software,” Biais said.

\n\n\n\n

The GitHub ticket has several comments from developers who hope to be able to use the editor in their own projects. Radek Pietruszewski, tech lead for a collaborative todo app called Nozbe Teams, has been requesting a relicensing of Gutenberg since October 2019.

\n\n\n\n

“Our tech stack is essentially React on web and React Native on iOS and Android,” Pietruszewski said. “We’re a tiny company, and so we share >80% of app’s codebase between these 3 platforms.

\n\n\n\n

“Our app sorely lacks a WYSIWYG editor. We had a working implementation on web, but we decided to scrap it, because there was no way to port it on iOS and Android. There are pretty much no viable rich text editors for iOS or Android, yet alone both. But even then, shipping three completely separate, but somehow compatible editors would be a vast amount of work.”

\n\n\n\n

When Peitruszewski originally made his case to the mobile team, he identified Gutenberg/Aztec as a basic infrastructure that has the potential to enable many different apps:

\n\n\n\n

And that infrastructure is sorely lacking. There are very few rich text editor libraries on both iOS and Android — and most of them suck. And if you want an editor that has a shared API for both platforms… you’re stuck. There are no options – Gutenberg is the only game in town (and it’s really good).

And it’s very hard to create this infrastructure. WYSIWYG editors are very hard, and it takes entire teams years to develop them (and they still usually suck). Almost no-one has the resources to develop it just for themselves, and if they do, they’re unwilling to open-source it.

\n\n\n\n

Automattic’s mobile app engineers have struggled to get regular contributions to the apps, despite them being open source. Dual licensing Gutenberg could open up a new world of contributors with the editor being used more widely across the industry.

\n\n\n\n

“While we might not be big enough to be able to tackle a challenge of developing a rich text editor from scratch, we’re big enough to contribute features and bug fixes to open source projects,” Pietruszewski said.

\n\n\n\n

Matt Mullenweg was the first comment on Biais’ post in favor of the change:

\n\n\n\n

I think Gutenberg has a chance to become a cross-CMS standard, giving users a familiar interface any place they currently have a rich text box. There are hundreds and hundreds of engineers at other companies solving similar problems in a proprietary way, it would be amazing to get them working together but a huge barrier now is supporting Gutenberg in mobile apps, which every modern web service or CMS has. (Hypothetically, think of Mailchimp as a possible consumer and collaborator here, but it could be any company, SaaS, or other open source CMS.)

\n\n\n\n

Unless any major blockers come up in further discussion, this dual licensing change appears to be on track to move forward. Biais noted that a similar license change has already happened on Aztec-Android and Aztec-iOS. The last hurdle is gaining the approval of all the original code contributors or rewriting the code for those who decline to give approval.

\n\n\n\n

Once Gutenberg can be used under the MPL v2.0, the editor will gain a broader reach, with people already on deck wanting to use it. Other companies and projects that are normally outside WordPress’ open source orbit will also have the opportunity to enrich Gutenberg’s ecosystem with contributions back to the project. At the same time, the MPL 2.0 protects Gutenberg from companies that would try to re-release the code as a closed-source project.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 22:59:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"WPTavern: GitHub to Use ‘Main’ Instead of ‘Master’ as the Default Branch on All New Repositories Starting Next Month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:269:\"https://wptavern.com/github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month?utm_source=rss&utm_medium=rss&utm_campaign=github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4844:\"

In August, GitHub announced that it would change the “master” branch name for all new repositories created on the platform to “main” starting October 1. The date is less than two weeks away, and WordPress developers need to be prepared for the change if they use the service for version control or project management.

\n\n\n\n

The larger tech and web development community began conversations through various venues in June, a time in which the Black Lives Matter was gaining more traction in the U.S. and worldwide. The discussion centered on removing any terminology that could be discriminatory or oppressive to specific groups of people. This ongoing discussion has shown that there is a deep division over whether such changes are necessary or even helpful.

\n\n\n\n

The WordPress community is dealing with this division itself. Aaron Jorbin proposed a change at the same time to rename the default branch name on WordPress-owned repositories. Through discussion on his post and elsewhere, the community landed on “trunk,” which keeps WordPress projects in line with its SVN roots.

\n\n\n\n

“To close the circle on this, a decision was made in June and earlier today (August 19),” wrote Helen Hou-Sandí, a lead WordPress developer, in the comments of the original proposal. “I updated the default branch name for new GitHub repositories under the WordPress organization to be trunk after GitHub enabled early access to that feature.”

\n\n\n\n

As evidenced by the comments on the Tavern’s coverage of the proposal and those on the original post, the WordPress development community as a whole did not support this decision.

\n\n\n\n

Jorbin has updated several of WordPress’s repositories and switched them to use trunk instead of master. However, there are still some lingering projects yet to be updated, including the primary WordPress and WordPress Develop repositories. He left a comment with an updated list in June. There is no public word on whether the existing, leftover projects will be changed.

\n\n\n\n

WordPress Developer Preparations

\n\n\n\nCustomizing the default branch for a user’s GitHub repositories.\n\n\n\n

GitHub is merely changing the default branch name for new repositories starting on October 1. This change does not affect existing repositories. Individual users, organization owners, and enterprise administrators can customize the default branch via their account settings now before the switch is made. Owners can also change the default branch name for individual repositories.

\n\n\n\n

The biggest thing that developers need to watch out for is their tooling or other integrations that might still require the master branch. There may be cases where an alternative default branch name will break workflows. If planning to use a different branch name, the best thing to do right now is to spin up the tools you use on a test repository. If something breaks, check to see whether the particular tool you are using will be getting an update. In most cases, this should not be a problem because customized default branch names will be an industry standard.

\n\n\n\n

The great thing about how GitHub is rolling out this feature is that it offers a choice. Those who believe that “master” is oppressive can change the branch name to something they feel is more inclusive. For those who believe otherwise, they can keep their master branch. But, everyone can use the branch name they prefer.

\n\n\n\n

For existing repositories, GitHub is asking that developers be patient for now. The company is investing in tools to make this a seamless experience later this year. There are a few technical hurdles to clear first.

\n\n\n\n

Developers should read the full GitHub guide on setting the default branch for more information.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 20:39:55 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:31 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:13:\"last-modified\";s:29:\"Thu, 22 Oct 2020 16:15:08 GMT\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 2\";s:16:\"content-encoding\";s:4:\"gzip\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(133,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(134,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603384291','no'),(135,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1603427491','no'),(136,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(139,'theme_mods_twentytwenty','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(140,'recently_activated','a:0:{}','yes'); +INSERT INTO `wp55_options` VALUES (1,'siteurl','http://localhost','yes'),(2,'home','http://localhost','yes'),(3,'blogname','Datadog Test Application','yes'),(4,'blogdescription','Just another WordPress site','yes'),(5,'users_can_register','1','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwenty','yes'),(41,'stylesheet','twentytwenty','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','48748','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:2:{i:2;a:4:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:12:\"hierarchical\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1618936275','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'initial_db_version','48748','yes'),(96,'wp55_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(97,'fresh_site','0','yes'),(98,'widget_search','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(99,'widget_recent-posts','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(100,'widget_recent-comments','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(101,'widget_archives','a:2:{i:2;a:3:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(102,'widget_meta','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(103,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:8:\"search-2\";i:1;s:14:\"recent-posts-2\";i:2;s:17:\"recent-comments-2\";}s:9:\"sidebar-2\";a:3:{i:0;s:10:\"archives-2\";i:1;s:12:\"categories-2\";i:2;s:6:\"meta-2\";}s:13:\"array_version\";i:3;}','yes'),(104,'cron','a:7:{i:1674663182;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1674695582;a:4:{s:18:\"wp_https_detection\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674695658;a:1:{s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674738782;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738858;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738859;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}s:7:\"version\";i:2;}','yes'),(105,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(106,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'_transient_doing_cron','1603384688.0394918918609619140625','yes'),(115,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-5.5.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-5.5.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"5.5.1\";s:7:\"version\";s:5:\"5.5.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"5.3\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1603384286;s:15:\"version_checked\";s:5:\"5.5.1\";s:12:\"translations\";a:0:{}}','no'),(116,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384412;s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"4.1.6\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"4.1.6\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.4.1.6.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:59:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=969272\";s:2:\"1x\";s:59:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=969272\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}}s:9:\"hello.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}}}}','no'),(117,'_site_transient_timeout_theme_roots','1603386087','no'),(118,'_site_transient_theme_roots','a:3:{s:14:\"twentynineteen\";s:7:\"/themes\";s:15:\"twentyseventeen\";s:7:\"/themes\";s:12:\"twentytwenty\";s:7:\"/themes\";}','no'),(119,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384287;s:7:\"checked\";a:3:{s:14:\"twentynineteen\";s:3:\"1.7\";s:15:\"twentyseventeen\";s:3:\"2.4\";s:12:\"twentytwenty\";s:3:\"1.5\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:14:\"twentynineteen\";a:6:{s:5:\"theme\";s:14:\"twentynineteen\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:44:\"https://wordpress.org/themes/twentynineteen/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/theme/twentynineteen.1.7.zip\";s:8:\"requires\";s:5:\"4.9.6\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:15:\"twentyseventeen\";a:6:{s:5:\"theme\";s:15:\"twentyseventeen\";s:11:\"new_version\";s:3:\"2.4\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentyseventeen/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentyseventeen.2.4.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:12:\"twentytwenty\";a:6:{s:5:\"theme\";s:12:\"twentytwenty\";s:11:\"new_version\";s:3:\"1.5\";s:3:\"url\";s:42:\"https://wordpress.org/themes/twentytwenty/\";s:7:\"package\";s:58:\"https://downloads.wordpress.org/theme/twentytwenty.1.5.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}}s:12:\"translations\";a:0:{}}','no'),(120,'_site_transient_timeout_browser_6daa110c3e56e442b403473c9591e946','1603989088','no'),(121,'_site_transient_browser_6daa110c3e56e442b403473c9591e946','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:12:\"86.0.4240.80\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(122,'_site_transient_timeout_php_check_56babb1797dd31750a342dc4c8a11025','1603989088','no'),(123,'_site_transient_php_check_56babb1797dd31750a342dc4c8a11025','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(124,'_site_transient_timeout_community-events-e0e4f94be3c2d577e126ec3b012627f2','1603427490','no'),(125,'_site_transient_community-events-e0e4f94be3c2d577e126ec3b012627f2','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:12:\"192.168.16.0\";}s:6:\"events\";a:2:{i:0;a:10:{s:4:\"type\";s:6:\"meetup\";s:5:\"title\";s:58:\"Discussion Group: WordPress Troubleshooting Basics: Part 1\";s:3:\"url\";s:68:\"https://www.meetup.com/learn-wordpress-discussions/events/273993927/\";s:6:\"meetup\";s:27:\"Learn WordPress Discussions\";s:10:\"meetup_url\";s:51:\"https://www.meetup.com/learn-wordpress-discussions/\";s:4:\"date\";s:19:\"2020-10-23 06:00:00\";s:8:\"end_date\";s:19:\"2020-10-23 07:00:00\";s:20:\"start_unix_timestamp\";i:1603458000;s:18:\"end_unix_timestamp\";i:1603461600;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"US\";s:8:\"latitude\";d:37.779998779297;s:9:\"longitude\";d:-122.41999816895;}}i:1;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:17:\"WordCamp Bulgaria\";s:3:\"url\";s:35:\"https://bulgaria.wordcamp.org/2020/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2020-10-24 10:00:00\";s:8:\"end_date\";s:19:\"2020-10-24 10:00:00\";s:20:\"start_unix_timestamp\";i:1603522800;s:18:\"end_unix_timestamp\";i:1603522800;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"BG\";s:8:\"latitude\";d:42.733883;s:9:\"longitude\";d:25.48583;}}}}','no'),(126,'can_compress_scripts','0','no'),(127,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(128,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:49:\"\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:27:\"News – – WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 20:10:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=5.6-beta1-49274\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WordPress 5.6 Beta 1 is now available for testing!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8236:\"\n

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:363:\"This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September.  WordPress 5.5.1 Launch On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8713:\"\n

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"WordPress 5.5.1 Maintenance Release\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://wordpress.org/news/2020/09/wordpress-5-5-1-maintenance-release/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 19:13:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8979\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:460:\"WordPress 5.5.1 is now available! This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade. You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process. […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9020:\"\n

WordPress 5.5.1 is now available!

\n\n\n\n

This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade.

\n\n\n\n

You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process.

\n\n\n\n

WordPress 5.5.1 is a short-cycle maintenance release. The next major release will be version 5.6.

\n\n\n\n

To see a full list of changes, you can browse the list on Trac, read the 5.5.1 RC1 and 5.5.1 RC2 posts, or visit the 5.5.1 documentation page.

\n\n\n\n

Thanks and props!

\n\n\n\n

The 5.5.1 release was led by @audrasjb, @azhiyadev, @davidbaumwald, @desrosj, @johnbillion, @planningwrite, @sergeybiryukov and @whyisjake.

\n\n\n\n

Thank you to everyone who helped make WordPress 5.5.1 happen:

\n\n\n\nAmit Dudhat, Andrea Fercia, Andrey “Rarst” Savchenko, Andy Fragen, Angel Hess, avixansa, bobbingwide, Brian Hogg, chunkysteveo, Clayton Collie, David Baumwald, David Herrera, dd32, demetris, Dominik Schilling, dushakov, Earle Davies, Enrique Sánchez, Frankie Jarrett, fullofcaffeine, Garrett Hyder, Gary Jones, gchtr, Hauwa, Herre Groen, Howdy_McGee, Ipstenu (Mika Epstein), Jb Audras, Jeremy Felt, Jeroen Rotty, Joen A., Johanna de Vos, John Blackbourn, John James Jacoby, Jonathan Bossenger, Jonathan Desrosiers, Jonathan Stegall, Joost de Valk, Jorge Costa, Justin Ahinon, Kalpesh Akabari, Kevin Hagerty, Knut Sparhell, Kyle B. Johnson, landau, Laxman Prajapati, Lester Chan, mailnew2ster, Marius L. J., Mark Jaquith, Mark Uraine, Matt Gibson, Michael Beckwith, Mikey Arce, Mohammad Jangda, Mukesh Panchal, Nabil Moqbel, net, oakesjosh, O André, Omar Reiss, Ov3rfly, Paddy, Pascal Casier, Paul Biron, Peter Wilson, rajeshsingh520, Rami Yushuvaev, rebasaurus, riaanlom, Riad Benguella, Rodrigo Arias, rtagliento, salvoaranzulla, Sanjeev Aryal, sarahricker, Sergey Biryukov, Stephen Bernhardt, Steven Stern (sterndata), Thomas M, Timothy Jacobs, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), TwentyZeroTwo, Winstina, wittich, and Yoav Farhi.\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8979\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"The Month in WordPress: August 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2020/09/the-month-in-wordpress-august-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 09:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8983\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:362:\"August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world. WordPress 5.5 Launch […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9605:\"\n

August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Launch

\n\n\n\n

The team launched WordPress 5.5 on August 11. The major release comes with a host of features like automatic updates for plugins and themes, enabling updates over uploaded ZIP files, a block directory, XML sitemaps, block patterns, inline image editing, and lazy-loading images, to name a few. WordPress 5.5 is now available in 50 languages too! You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. Subsequent to the 5.5 release, the 5.5.1 release candidate came out on August 28, which will be followed by its official launch of the minor release on September 1.

\n\n\n\n

A record 805 people contributed to WordPress 5.5, hailing from 58 different countries. @audrasjb has compiled many more stats like that and they’re well worth a read!

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.7 and 8.8

\n\n\n\n

The core team launched Gutenberg 8.7 and 8.8. Version 8.7 saw many improvements to the Post Block suite, along with other changes like adding a block example to the Buttons block, consistently autosaving edits, and updating the group block description. Version 8.8 offers updates to Global Styles, the Post Block suite, and Template management. The release significantly improves the back-compatibility of the new Widget Screen, and also includes other important accessibility and mobile improvements to user interfaces like the Toolbar, navigation menus, and Popovers. For full details on the latest versions of these Gutenberg releases, visit these posts about 8.7 and 8.8.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Check out the brand new Learn WordPress platform!

\n\n\n\n

Learn WordPress is a brand new cross-team initiative led by the WordPress Community team, with support from the training team, the TV team, and the meta team. This platform is a learning repository on learn.wordpress.org, where WordPress learning content will be made available. Video workshops published on the site will be followed up by supplementary discussion groups based on workshop content. The first of these discussion groups have been scheduled, and you can join an upcoming discussion on the dedicated meetup group. The community team invites members to contribute to the project. You can apply to present a workshop, assist with reviewing submitted workshops, and add ideas for workshops that you would like to see on the site. You can also apply to be a discussion group leader to organize discussions directly through the learn.wordpress.org platform. We are also creating a dedicated Learn WordPress working group and have posted a call for volunteers. Meetup organizers can use Learn WordPress content for their meetup events (without applying as a discussion group leader). Simply ask your meetup group to watch one of the workshops in the weeks leading up to your scheduled event, and then host a discussion group for that content as your event.

\n\n\n\n

Want to get involved with the Community team? Follow the Community blog, or join them in the #community-events channel in the Making WordPress Slack group. To organize a local WordPress community event, visit the handbook page

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8983\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n\n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"WordPress 5.5 “Eckstine”\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://wordpress.org/news/2020/08/eckstine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 11 Aug 2020 19:03:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8799\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:354:\"Version 5.5 \"Eckstine\" of WordPress is available for download or update in your WordPress dashboard. With this release, your site gets new power in three major areas: \nspeed (lazy-loading images), search (sitemaps included by default), and security (auto-updates for plugins and themes), along with many new features and improvements to the block editor.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:3:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:48:\"https://s.w.org/images/core/5.5/auto-updates.mp4\";s:6:\"length\";s:6:\"238264\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:50:\"https://s.w.org/images/core/5.5/block-patterns.mp4\";s:6:\"length\";s:7:\"3518792\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:56:\"https://s.w.org/images/core/5.5/inline-image-editing.mp4\";s:6:\"length\";s:7:\"3145140\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Matt Mullenweg\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:71062:\"\n

Here it is! Named “Eckstine” in honor of Billy Eckstine, this latest and greatest version of WordPress is available for download or update in your dashboard.

\n\n\n\n
\"\"
\n\n\n\n
\n

Welcome to WordPress 5.5.

\n\n\n\n

In WordPress 5.5, your site gets new power in three major areas:
speed, search, and security.

\n
\n\n\n\n
\n
\n\n\n\n
\n

Speed

\n\n\n\n

Posts and pages feel faster, thanks to lazy-loaded images.

\n\n\n\n

Images give your story a lot of impact, but they can sometimes make your site seem slow.

\n\n\n\n

In WordPress 5.5, images wait to load until they’re just about to scroll into view. The technical term is ‘lazy loading.’

\n\n\n\n

On mobile, lazy loading can also keep browsers from loading files meant for other devices. That can save your readers money on data — and help preserve battery life.

\n\n\n\n

Search

\n\n\n\n

Say hello to your new sitemap.

\n\n\n\n

WordPress sites work well with search engines.

\n\n\n\n

Now, by default, WordPress 5.5 includes an XML sitemap that helps search engines discover your most important pages from the very minute you go live.

\n\n\n\n

So more people will find your site sooner, giving you more time to engage, retain and convert them to subscribers, customers or whatever fits your definition of success.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Security

\n\n\n\n
Now you can choose to update plugins and themes automatically–or pick just a few–from the screens you’ve always used.
\n\n\n\n

Auto-updates for Plugins and Themes

\n\n\n\n

Now you can set plugins and themes to update automatically — or not! — in the WordPress admin. So you always know your site is running the latest code available.

\n\n\n\n

You can also turn auto-updates on or off for each plugin or theme you have installed — all on the same screens you’ve always used.

\n\n\n\n

Update by uploading ZIP files

\n\n\n\n

If updating plugins and themes manually is your thing, now that’s easier too — just upload a ZIP file.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Highlights from the block editor

\n\n\n\n

Once again, the latest WordPress release packs a long list of exciting new features for the block editor. For example:

\n\n\n\n
\n\n\n\n
\n
\n

Block patterns

\n\n\n\n

New block patterns make it simple and fun to create complex, beautiful layouts, using combinations of text and media that you can mix and match to fit your story.

\n\n\n\n

You will also find block patterns in a wide variety of plugins and themes, with more added all the time. Pick any of them from a single place — just click and go!

\n
\n\n\n\n
\n

The new block directory

\n\n\n\n

Now it’s easier than ever to find the block you need. The new block directory is built right into the block editor, so you can install new block types to your site without ever leaving the editor.

\n\n\n\n

Inline image editing

\n\n\n\n

Crop, rotate, and zoom your photos right from the image block. If you spend a lot of time on images, this could save you hours!

\n
\n
\n\n\n\n
\n\n\n\n

And so much more.

\n\n\n\n

The highlights above are a tiny fraction of the new block editor features you’ve just installed. Open the block editor and enjoy!

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Accessibility

\n\n\n\n

Every release adds improvements to the accessible publishing experience, and that remains true for WordPress 5.5.

\n\n\n\n

Now you can copy links in media screens and modal dialogs with a button, instead of trying to highlight a line of text.

\n\n\n\n

You can also move meta boxes with the keyboard, and edit images in WordPress with your assistive device, as it can read you the instructions in the image editor.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

For developers

\n\n\n\n

5.5 also brings a big box of changes just for developers.

\n\n\n\n
\n
\n

Server-side registered blocks in the REST API

\n\n\n\n

The addition of block types endpoints means that JavaScript apps (like the block editor) can retrieve definitions for any blocks registered on the server.

\n\n\n\n

Defining environments

\n\n\n\n

WordPress now has a standardized way to define a site’s environment type (staging, production, etc). Retrieve that type with wp_get_environment_type() and execute only the appropriate code.

\n\n\n\n

Dashicons

\n\n\n\n

The Dashicons library has received its final update in 5.5. It adds 39 block editor icons along with 26 others.

\n\n\n\n

Passing data to template files

\n\n\n\n

The template loading functions (get_header()get_template_part(), etc.) have a new $args argument. So now you can pass an entire array’s worth of data to those templates.

\n
\n\n\n\n
\n

More changes for developers

\n\n\n\n
  • The PHPMailer library just got a major update, going from version 5.2.27 to 6.1.6.
  • Now get more fine-grained control of redirect_guess_404_permalink().
  • Sites that use PHP’s OPcache will see more reliable cache invalidation, thanks to the new wp_opcache_invalidate() function during updates (including to plugins and themes).
  • Custom post types associated with the category taxonomy can now opt-in to supporting the default term.
  • Default terms can now be specified for custom taxonomies in register_taxonomy().
  • The REST API now officially supports specifying default metadata values through register_meta().
  • You will find updated versions of these bundled libraries: SimplePie, Twemoji, Masonry, imagesLoaded, getID3, Moment.js, and clipboard.js.
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

The Squad

\n\n\n\n

Leading this release were Matt MullenwegJake Spurlock, and David Baumwald. Supporting them was this highly enthusiastic release squad:

\n\n\n\n\n\n\n\n

Joining the squad throughout the release cycle were 805 generous volunteer contributors who collectively worked on over 523 tickets on Trac and over 1660 pull requests on GitHub.

\n\n\n\n

Put on a Billy Eckstine playlist, click that update button (or download it directly), and check the profiles of the fine folks that helped:

\n\n\nA2 Hosting, a4jp . com, a6software, Aaron D. Campbell, Aaron Jorbin, abderrahman, Abha Thakor, Achal Jain, achbed, Achyuth Ajoy, acosmin, acsnaterse, Adam Silverstein, Addie, addyosmani, adnan.limdi, adrian, airamerica, Ajay Ghaghretiya, Ajit Bohra, akbarhusen, akbarhusen429, Akhilesh Sabharwal, Akira Tachibana, Alain Schlesser, Albert Juhé Lluveras, Alex Concha, Alex Kirk, Alex Lende, Alex Shiels, Ali Shan, ali11007, Allen Snook, amaschas, Amit Dudhat, anbumz, andfinally, Andrea Fercia, Andrea Middleton, Andrea Tarantini, Andrei Draganescu, Andrew Duthie, Andrew Nacin, Andrew Nevins, Andrew Ozz, Andrey \"Rarst\" Savchenko, Andrés Maneiro, Andy Fragen, Andy Meerwaldt, Andy Peatling, Angel Hess, Angela Jin, Angelika Reisiger, Anh Tran, Ankit Gade, Ankit K Gupta, Ankit Panchal, Anne McCarthy, Anthony Burchell, Anthony Hortin, Anton Timmermans, Antonis Lilis, apedog, archon810, argentite, Arpit G Shah, Arslan Ahmed, asalce, ashiagr, ashour, Atharva Dhekne, Aurélien Joahny, aussi, automaton, avixansa, Ayesh Karunaratne, BackuPs, Barry, Barry Ceelen, Bart Czyz, bartekcholewa, bartkalisz, Bastien Ho, Bastien Martinent, bcworkz, bdbch, bdcstr, Ben Dunkle, Bence Szalai, bencroskery, Benjamin Gosset, Benoit Chantre, Bernhard Reiter, BettyJJ, bgermann, bigcloudmedia, bigdawggi, Bill Erickson, Birgir Erlendsson (birgire), Birgit Pauli-Haack, BjornW, bobbingwide, bonger, Boone Gorges, Boris Brdarić, Boy Witthaya, Brandon Kraft, Brandon Payton, Brent Swisher, Brian Hogg, Brian Krogsgard, bruandet, Bunty, Burhan Nasir, caiocrcosta, Cameron Voell, cameronamcintyre, Carike, Carl Wuensche, Carlos Galarza, Carolina Nymark, Caroline Moore, Carrigan, ceyhun, Chad, Chad Butler, Charles Fulton, Chetan Prajapati, Chintan hingrajiya, Chip Snyder, Chloé Bringmann, Chouby, Chris Van Patten, chriscct7, Christian Chung, Christian Jongeneel, Christian Sabo, Christian Wach, Christoph Herr, Christopher Churchill, chunkysteveo, cklee, clayray, Clayton Collie, Clifford Paulick, codeforest, Commeuneimage, Copons, Corey McKrill, cpasqualini, Cristovao Verstraeten, Csaba (LittleBigThings), Curtis Belt, Cyrus Collier, D.PERONNE, d6, Daniel Bachhuber, Daniel Hüsken, Daniel James, Daniel Llewellyn, Daniel Richards, Daniel Roch, Daniele Scasciafratte, Danny, Darko G., Darren Ethier (nerrad), Dave McHale, Dave Whitley, David A. Kennedy, David Aguilera, David Anderson, David Artiss, David Baumwald, David Brumbaugh, David E. Smith, David Herrera, David Ryan, David Shanske, David Smith, david.binda, davidvee, dchymko, Debabrata Karfa, Deepak Lalwani, dekervit, Delowar Hossain, demetris, Denis Yanchevskiy, derekakelly, Derrick Hammer, Derrick Tennant, Diane Co, Dilip Bheda, Dimitris Mitsis, dingo-d, Dion Hulse, Dixita Dusara, djennez, dmenard, dmethvin, doc987, Dominik Schilling, donmhico, Dono12, Doobeedoo, Dossy Shiobara, dpacks, dratwas, Drew Jaynes, DrLightman, DrProtocols, dsifford, dudo, dushakov, Dustin Bolton, dvershinin, Dylan Kuhn, Earle Davies, ecotechie, Eddie Moya, Eddy, Edi Amin, ehtis, Eileen Violini, Ekaterina, Ella van Durpe, elmastudio, Emanuel Blagonic, Emilie LEBRUN, Emmanuel Hesry, Enej Bajgoric, Enrico Sorcinelli, Enrique Piqueras, Enrique Sánchez, Eric, Eric Andrew Lewis, Eric Binnion, Erik Betshammar, Erin \'Folletto\' Casali, esemlabel, esoj, espiat, Estela Rueda, etoledom, etruel, Ev3rywh3re, Evan Mullins, Fabian Kägy, Fabian Todt, Faisal Ahmed, Felix Arntz, Felix Edelmann, ferdiesletering, finomeno, Florian Brinkmann, Florian TIAR, Florian Truchot, florianatwhodunit, FolioVision, Francesca Marano, Francois Thibaud, Frank Goossens, Frank Klein, Frank.Prendergast, Frankie Jarrett, Franz Armas, fullofcaffeine, Gabriel Koen, Gabriel Maldonado, Gabriel Mays, gadgetroid, Gal Baras, Garavani, garethgillman, Garrett Hyder, Gary Cao, Gary Jones, Gary Pendergast, gchtr, Geert De Deckere, Gemini Labs, Gennady Kovshenin, geriux, Giorgio25b, gisselfeldt, glendaviesnz, goldsounds, Goto Hayato, Govind Kumar, Grégory Viguier, gradina, Greg Ziółkowski, gregmulhauser, grierson, Grzegorz.Janoszka, gsmumbo, Guido Scialfa, guidobras, Gunther Pilz, gwwar, H-var, hakre, Halacious, hankthetank, Hapiuc Robert, Hareesh, haukep, Hauwa, Haz, Hector Farahani, Helen Hou-Sandi, Henry Wright, Herre Groen, hlanggo, hommealone, Hoover, Howdy_McGee, Hronak Nahar, huntlyc, Ian Belanger, Ian Dunn, Ian Stewart, ianjvr, ifrins, infinum, Ipstenu (Mika Epstein), Isabel Brison, ishitaka, J.D. Grimes, jackfungi, jacklinkers, Jadon N, jadpm, jagirbahesh, Jake Spurlock, Jake Whiteley, James Koster, James Nylen, Jan Koch, Jan Reilink, Jan Thiel, Janvo Aldred, Jarret, Jason Adams, Jason Coleman, Jason Cosper, Jason Crouse, Jason LeMahieu (MadtownLems), Jason Rouet, JasWSInc, Javier Casares, Jayson Basanes, jbinda, jbouganim, Jean-Baptiste Audras, Jean-David Daviet, Jeff Chandler, Jeff Farthing, Jeff Ong, Jeff Paul, Jen, Jenil Kanani, Jeremy Felt, Jeremy Herve, Jeremy Yip, Jeroen Rotty, jeryj, Jesin A, Jignesh Nakrani, Jim_Panse, Jip Moors, jivanpal, Joe Dolson, Joe Hoyle, Joe McGill, Joen Asmussen, Johanna de Vos, John Blackbourn, John Dorner, John James Jacoby, John P. Green, John Richards II, John Watkins, johnnyb, Jon Quach, Jon Surrell, Jonathan Bossenger, Jonathan Champ, Jonathan Christopher, Jonathan Desrosiers, Jonathan Stegall, jonkolbert, Jonny Harris, jonnybot, Jono Alderson, Joost de Valk, Jorge Bernal, Jorge Costa, Joseph Dickson, Josepha Haden, Josh Smith, JoshuaWold, Joy, Juanfra Aldasoro, juanlopez4691, Jules Colle, julianm, Juliette Reinders Folmer, Julio Potier, Julka Grodel, Justin Ahinon, Justin de Vesine, Justin Tadlock, justlevine, justnorris, K. Adam White, kaggdesign, Kailey (trepmal), Kaira, Kaitlin Bolling, Kalpesh Akabari, KamataRyo, Kantari Samy, Kaspars, Kavya Gokul, keesiemeijer, Kelly Dwan, kennethroberson5556, Kevin Hagerty, Kharis Sulistiyono, Khokan Sardar, kinjaldalwadi, Kiril Zhelyazkov, Kirsty Burgoine, Kishan Jasani, kitchin, Kite, Kjell Reigstad, Knut Sparhell, Konstantin Obenland, Konstantinos Xenos, ksoares, KT Cheung, Kukhyeon Heo, Kyle B. Johnson, lalitpendhare, landau, Laterna Studio, laurelfulford, Laurens Offereins, Laxman Prajapati, Lester Chan, Levdbas, Lew Ayotte, Lex Robinson, linyows, lipathor, Lisa Schuyler, liuhaibin, ljharb, logig, lucasbustamante, luiswill, Luke Cavanagh, Luke Walczak, lukestramasonder, M Asif Rahman, M.K. Safi, Maarten de Boer, Mahfoudh Arous, mailnew2ster, manojlovic, Manuel Schmalstieg, maraki, Marcin Pietrzak, Marcio Zebedeu, Marco Pereirinha, MarcoZ, Marcus, Marcus Kazmierczak, Marek Dědič, Marek Hrabe, Mario Valney, Marius Jensen, Mark Chouinard, Mark Jaquith, Mark Parnell, Mark Uraine, markdubois, markgoho, Marko Andrijasevic, Marko Heijnen, MarkRH, markshep, markusthiel, Martijn van der Kooij, martychc23, Mary Baum, Matheus Martins, Mathieu Viet, Matias Ventura, matjack1, Matt Cromwell, Matt Gibson, Matt Mullenweg, Matt Radford, Matt van Andel, mattchowning, Matthew Boynes, Matthew Eppelsheimer, Matthew Gerring, Matthias Kittsteiner, Matthias Pfefferle, Matthieu Mota, mattyrob, Maxime Culea, Maxime Pertici, maxme, Mayank Majeji, mcshane, Mel Choyce-Dwan, Menaka S., mensmaximus, metalandcoffee, Michael, Michael Arestad, Michael Arestad, Michael Beckwith, Michael Fields, Michael Nelson, Michele Butcher-Jones, Michelle, Miguel Fonseca, mihdan, Miina Sikk, Mikael Korpela, mikaumoto, Mike Crantea, Mike Glendinning, Mike Haydon, Mike Schinkel [WPLib Box project lead], Mike Schroder, Mikey Arce, Milana Cap, Milind More, mimi, mislavjuric, Mohammad Jangda, Mohammad Rockeybul Alam, Mohsin Rasool, Monika Rao, Morgan Kay, Morten Rand-Hendriksen, Morteza Geransayeh, moto hachi ( mt8.biz ), mrgrt, mrmist, mrTall, msaggiorato, Muhammad Usama Masood, Mukesh Panchal, munyagu, Nabil Moqbel, Nadir Seghir, Nahid Ferdous Mohit, Nalini Thakor, Naoko Takano, narwen, Nate Gay, Nathan Rice, Navid, neonkowy, net, netpassprodsr, Nextendweb, Ngan Tengyuen, Nick Daugherty, Nicky Lim, nicolad, Nicolas Juen, NicolasKulka, Nidhi Jain, Niels de Blaauw, Niels Lange, nigro.simone, Nik Tsekouras, Nikhil Bhansi, Nikolay Bachiyski, Nilo Velez, Niresh, nmenescardi, Noah Allen, NumidWasNotAvailable, oakesjosh, obliviousharmony, ockham, Olga Gleckler, Omar Alshaker, Omar Reiss, onokazu, Optimizing Matters, Ov3rfly, ovann86, overclokk, p_enrique, Paal Joachim Romdahl, Pablo Honey, Paddy, palmiak, Paresh Shinde, Parvand, Pascal Birchler, Pascal Casier, Paul Bearne, Paul Biron, Paul Fernhout, Paul Gibbs, Paul Ryan, Paul Schreiber, Paul Stonier, Paul Von Schrottky, pavelevap, Pedro Mendonça, pentatonicfunk, pepe, Peter \"Pessoft\" Kolínek, Peter Westwood, Peter Wilson, Phil Derksen, Phil Johnston, Philip Jackson, Pierre Gordon, pigdog234, pikamander2, pingram, Pionect, Piyush Patel, pkarjala, pkvillanueva, Prashant Baldha, pratik028, Pravin Parmar, Presskopp, Presslabs, Priyank Patel, Priyo Mukul, ProGrafika, programmin, Puneet Sahalot, pvogel2, r-a-y, Raaj Trambadia, Rachel Peter, raine, rajeshsingh520, Ramanan, Rami Yushuvaev, RavanH, Ravat Parmar, ravenswd, rawrly, rebasaurus, Red Sand Media Group, Remy Perona, Remzi Cavdar, Renatho, renggo888, retlehs, retrofox, riaanlom, Riad Benguella, Rian Rietveld, riasat, Rich Tabor, Ringisha, ritterml, Rnaby, Rob Cutmore, Rob Migchels, rob006, Robert Anderson, Robert Chapin, Robert Peake, Robert Windisch, Rodrigo Arias, Ronald Huereca, Rostislav Wolný, Roy Tanck, rtagliento, ruxandra, Ryan Boren, Ryan Fredlund, Ryan Kienstra, Ryan McCue, Ryan Welcher, Ryota Sakamoto, ryotsun, Sören Wrede, Søren Brønsted, Sachit Tandukar, Sagar Jadhav, Sajjad Hossain Sagor, Sal Ferrarello, Salvatore Formisano, salvoaranzulla, Sam Fullalove, Sam Webster, Samir Shah, Samuel Wood (Otto), samueljseay, Sander van Dragt, Sanjeev Aryal, Sanket Mehta, sarahricker, Sathiyamoorthy V, Sayed Taqui, scarolan, scholdstrom, Scott Kingsley Clark, Scott Reilly, Scott Smith, Scott Taylor, scribu, scruffian, Sean Hayes, seanpaulrasmussen, seayou, senatorman, Sergey Biryukov, Sergey Predvoditelev, Sergio de Falco, sergiomdgomes, Shannon Smith, Shantanu Desai, shaunandrews, Shawn Hooper, shawnz, Shital Marakana, shulard, siliconforks, Simon Wheatley, simonjanin, sinatrateam, sjmur, skarabeq, skorasaurus, skoskie, slushman, snapfractalpop, SpearsMarketing, sphakka, squarecandy, sreedoap, Stanimir Stoyanov, Stefano Minoia, Stefanos Togoulidis, Steph Wells, Stephen Bernhardt, Stephen Cronin, Stephen Edgar, Steve Dufresne, stevegibson12, Steven Stern (sterndata), Steven Word, stevenkussmaul, stevenlinx, Stiofan, Subrata Sarkar, SUM1, Sunny, Sunny Ratilal, Sushyant Zavarzadeh, suzylah, Sybre Waaijer, Synchro, Sérgio Estêvão, Takayuki Miyauchi, Tammie Lister, Tang Rufus, TeBenachi, Tessa Watkins LLC, Tetsuaki Hamano, theMikeD, theolg, Thierry Muller, Thimal Wickremage, Thomas M, Thorsten Frommen, Thrijith Thankachan, Tiago Hillebrandt, Till Krüss, Timothy Jacobs, Tkama, tmdesigned, tmoore41, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), Tofandel, tomdude, Tommy Ferry, Tony G, Toro_Unit (Hiroshi Urabe), torres126, Torsten Landsiedel, Toru Miki, Travis Northcutt, treecutter, truongwp, tsimmons, Tung Du, Udit Desai, Ulrich, Vagios Vlachos, valchovski, Valentin Bora, Vayu Robins, veromary, Viktor Szépe, vinkla, virginienacci, Vladimir, Vladislav Abrashev, vortfu, voyager131, vtieu, webaware, Weston Ruter, William Earnhardt, williampatton, Winstina, wittich, wpdesk, WPDO, WPMarmite, wppinar, Yahil Madakiya, yashrs, yoancutillas, Yoav Farhi, yohannp, yuhin, Yui, Yuri Salame, Yvette Sonneveld, Zack Tollman, zaheerahmad, zakkath, Zebulan Stanphill, zieladam, and Česlav Przywara.\n\n\n\n

 

\n\n\n\n

Many thanks to all of the community volunteers who contribute in the support forums. They answer questions from people across the world, whether they are using WordPress for the first time or since the first release. These releases are more successful for their efforts!

\n\n\n\n

Finally, thanks to all the community translators who worked on WordPress 5.5. Their efforts bring WordPress fully translated to 46 languages at release time, with more on the way.

\n\n\n\n

If you want to learn more about volunteering with WordPress, check out Make WordPress or the core development blog.

\n
\n\n\n\n
\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8799\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"WordPress 5.5 Release Candidate 2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2020/08/wordpress-5-5-release-candidate-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 04 Aug 2020 19:12:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8764\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:420:\"The second release candidate for WordPress 5.5 is here! WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time! You can test the WordPress 5.5 release candidate in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding edge nightlies” option) Or download the release […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2503:\"\n

The second release candidate for WordPress 5.5 is here!

\n\n\n\n

WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

For a more detailed breakdown of the changes included in WordPress 5.5, check out the WordPress 5.5 beta 1 post. The WordPress 5.5 Field Guide is also out! It’s your source for details on all the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8764\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n\n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"The Month in WordPress: July 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"https://wordpress.org/news/2020/08/the-month-in-wordpress-july-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 03 Aug 2020 13:54:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8755\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:340:\"July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11539:\"\n

July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Updates

\n\n\n\n

July was full of WordPress 5.5 updates! The WordPress 5.5 Beta 1 came out on July 7, followed by Beta 2 on July 14, Beta 3 on July 21, and Beta 4 on July 27. Subsequently, the team also published the first release candidate of WordPress 5.5 on July 28. 

\n\n\n\n

WordPress 5.5, which is slated for release on August 11, 2020, is a major update with features like automatic updates for plugins and themes, a block directory, XML sitemaps, block patterns, and lazy-loading images, among others. To learn more about the release, check out its field guide post.

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.5 and 8.6

\n\n\n\n

The core team launched Gutenberg 8.5 and 8.6. Version 8.5 – the last plugin release will be included entirely (without experimental features) in WordPress 5.5, introduced improvements to block drag-and-drop and accessibility, easier updates for external images, and support for the block directory. Version 8.6 comes with features like Cover block video position controls and block pattern updates. For full details on the latest versions on these Gutenberg releases, visit these posts about 8.5 and 8.6.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Reimagining Online WordPress Events

\n\n\n\n

The Community team made the difficult decision to suspend in-person WordPress events for the rest of 2020 in light of the COVID-19 pandemic. The team has also started working on reimagining online events. Based on feedback from the community members, the team decided to make changes to the current online WordCamp format. Key changes include wrapping up financial support for A/V vendors, ending event swag support for newer online WordCamps, and suspending the Global Community Sponsorship program for 2020. The team encourages upcoming online WordCamps to experiment with their events to facilitate an effective learning experience for attendees while avoiding online event fatigue. The team is currently working on a proposal to organize community-supported recorded workshops and synchronous discussion groups to help community members learn WordPress.

Want to get involved with the Community team? Follow the Community blog here, or join them in the #community-events channel in the Making WordPress Slack group. To organize a Meetup or WordCamp, visit the handbook page

\n\n\n\n

WordCamp US 2020 is canceled

\n\n\n\n

The organizers of WordCamp US 2020 have canceled the event in light of the continued pandemic and online event fatigue. The flagship event, which was originally scheduled for October 27-29 as an in-person event, had already planned to transition to an online event. Several WCUS Organizers will be working with the WordPress Community team to focus on other formats and ideas for online events, including a 24-hour contributor day, and contributing to the workshops initiative currently being discussed. Matt Mullenweg’s State of the Word (which typically accompanies WordCamp US) is likely to take place in a different format later in 2020.

\n\n\n\n

Plugin and theme updates are now available over zip files

\n\n\n\n

After eleven years, WordPress now allows users to update plugins and themes by uploading a ZIP file, in WordPress 5.5.  The feature, which was merged on July 7, has been one of the most requested features in WordPress. Now, when a user tries to upload a plugin or theme zip file from the WordPress dashboard by clicking the “Install Now” button, WordPress will direct users to a new screen that compares the currently-installed extension with the uploaded versions. Users can then choose between continuing with the installation or canceling. WordPress 5.5 will also offer automatic plugin and theme updates

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n
  • The Block directory is coming to WordPress with the 5.5 release. Plugin authors can now submit their Block plugins to the directory.
  • The Core team has opened up the call for features in the WordPress 5.6 release. You can comment on the post with features that you’d like to be included, current UX pain points, or maintenance tickets that need to be addressed. August 20 is the deadline for feature requests. 
  • Editor features such as the new Navigation block, the navigation screen, and the widget screen that were originally planned to be merged with WordPress 5.5 have been pushed for the next release
  • The Theme team is inviting proposals on whether to allow themes to place an additional top-level menu link in the admin.
  • BuddyPress 6.2 beta is out in the wild, and the team will soon release the stable version. The update includes changes that will make BuddyPress fully compatible with WordPress 5.5.
  • WordCamp EU 2021, which was being planned as an in-person event in Porto, Portugal, is moving online. The team is considering an in-person WordCamp EU in 2022. 
  • The Polyglots team has prepared and finalized a Translation Editor & Locale Manager Vetting Criteria to provide more clarity on how global mentors assign PTE/GTE/Locale Managers and to help locale teams set their own guidelines. The document, which was finalized after a lot of discussion, is now available in the Polyglots handbook.
  • Members of the Community team are discussing whether WordCamp volunteers, WordCamp attendees, or Meetup attendees should be awarded a WordPress.org profile badge. The ongoing discussion will be open for comments until August 13.
  • The WP Notify project, which aims to create a better way to manage and deliver notifications to the relevant audience, is on to its next steps. The team has finalized the initial requirements, and is kicking off the project build.
  • The WordPress documentation team is considering a ban on links to commercial websites in a revision to its external linking policy. The policy change does not remove external links to commercial sites from WordPress.org and only applies to documentation sites. The idea is to protect documentation from being abused, and to prevent the WordPress project from being biased. Discussion on this post is still ongoing, and a decision has not yet been made. Feel free to comment on the discussion posts, if you would like to share your thoughts on the topic.
\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8755\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"WordPress 5.5 Release Candidate\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://wordpress.org/news/2020/07/wordpress-5-5-release-candidate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 28 Jul 2020 19:08:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8732\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:370:\"The first release candidate for WordPress 5.5 is now available! This is an important milestone in the community’s progress toward the final release of WordPress 5.5. “Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2970:\"\n

The first release candidate for WordPress 5.5 is now available!

\n\n\n\n

This is an important milestone in the community’s progress toward the final release of WordPress 5.5.

\n\n\n\n

“Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

What’s in WordPress 5.5?

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developer notes tag for updates on those and other changes that could affect your products.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

The WordPress 5.5 Field Guide, due very shortly, will give you a more detailed dive into the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8732\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 4\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-4/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 27 Jul 2020 20:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8719\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:313:\"WordPress 5.5 Beta 4 is now available! This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 4 in two ways: Try the WordPress Beta Tester plugin (choose the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"David Baumwald\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3812:\"\n

WordPress 5.5 Beta 4 is now available!

\n\n\n\n

This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 4 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 3 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 3, 43 bugs have been fixed. Here are a few changes in beta 4:

\n\n\n\n
  • Add \"loading\" as an allowed kses image attribute (see #50731).
  • Add filter for the plugin/theme auto-update message in the Info tab of Site health (see #50663).
  • $_SERVER[\'SERVER_NAME\'] not a reliable when generating email host names (see #25239)
  • Several backported fixes from Gutenberg are included in WordPress 5.5 Beta 4 (See PR #24218)
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8719\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 3\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-3/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 21 Jul 2020 17:51:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8706\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:324:\"WordPress 5.5 Beta 3 is now available! This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 3 in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3876:\"\n

WordPress 5.5 Beta 3 is now available!

\n\n\n\n

This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 3 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 2 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 2, 43 bugs have been fixed. Here are a few changes in beta 3:

\n\n\n\n
  • Plugin and theme versions are now shared in the emails when automatically updated (see #50350).
  • REST API routes without a permission_callback now trigger a _doing_it_wrong() warning (see #50075).
  • Over 23 Gutenberg changes and updates (see #24068 and #50712).
  • A bug with the new import and export database Dashicons has been fixed (see #49913).
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8706\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1 \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:9:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:30 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Wed, 21 Oct 2020 20:10:31 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(129,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(130,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603384291','no'),(131,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(132,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Loginizer Plugin Gets Forced Security Update for Vulnerabilities Affecting 1 Million Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106557\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users?utm_source=rss&utm_medium=rss&utm_campaign=loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5484:\"

WordPress.org has pushed out a forced security update for the Loginizer plugin, which is active on more than 1 million websites. The plugin offers brute force protection in its free version, along with other security features like two-factor auth, reCAPTCHA, and PasswordLess login in its commercial upgrade.

\n\n\n\n

Last week security researcher Slavco Mihajloski discovered an unauthenticated SQL injection vulnerability, and an XSS vulnerability, that he disclosed to the plugin’s authors. Loginizer version 1.6.4 was released on October 16, 2020, with patches for the two issues, summarized on the plugin’s blog:

\n\n\n\n

1) [Security Fix] : A properly crafted username used to login could lead to SQL injection. This has been fixed by using the prepare function in PHP which prepares the SQL query for safe execution.

2) [Security Fix] : If the IP HTTP header was modified to have a null byte it could lead to stored XSS. This has been fixed by properly sanitizing the IP HTTP header before using the same.

\n\n\n\n

Loginizer did not disclose the vulnerability until today in order to give users the time to upgrade. Given the severity of the vulnerability, the plugins team at WordPress.org pushed out the security update to all sites running Loginizer on WordPress 3.7+.

\n\n\n\n

In July, 2020, Loginizer was acquired by Softaculous, so the company was also able to automatically upgrade any WordPress installations with Loginizer that had been created using Softaculous. This effort, combined with the updates from WordPress.org, covered a large portion of Loginizer’s user base.

\n\n\n\n
\n

Any #WordPress install with @loginizer probably isn\'t using another WAF solution. As you can notice from the graph 600k+500k active installs were updated upside down, so … Preauth SQLi in it, reported by me. Update! Crunching write up :) https://t.co/gkEVWun9wt pic.twitter.com/XWXVMYO1ED

— mslavco (@mslavco) October 19, 2020
\n
\n\n\n\n

The automatic update took some of the plugin’s users by surprise, since they had not initiated it themselves and had not activated automatic updates for plugins. After several users posted on the plugin’s support forum, plugin team member Samuel Wood said that “WordPress.org has the ability to turn on auto-updates for security issues in plugins” and has used this capability many times.

\n\n\n\n

Mihajloski published a more detailed proof-of-concept on his blog earlier today. He also highlighted some concerns regarding the systems WordPress has in place that allowed this kind of of severe vulnerability to slip through the cracks. He claims the issue could have easily been prevented by the plugin review team since the plugin wasn’t using the prepare function for safe execution of SQL queries. Mihajloski also recommended recurring code audits for plugins in the official directory.

\n\n\n\n

“When a plugin gets into the repository, it must be reviewed, but when is it reviewed again?” Mihajloski said. “Everyone starts with 0 active installs, but what happens on 1k, 10k, 50k, 100k, 500k, 1mil+ active installs?”

\n\n\n\n

Mihajloski was at puzzled how such a glaring security issue could remain in the plugin’s code so long, given that it is a security plugin with an active install count that is more than many well known CMS’s. The plugin also recently changed hands when it was acquired by Softaculus and had been audited by multiple security organizations, including WPSec and Dewhurst Security.

\n\n\n\n

Mihajloski also recommends that WordPress improve the transparency around security, as some site owners and closed communities may not be comfortable with having their assets administered by unknown people at WordPress.org.

\n\n\n\n

“WordPress.org in general is transparent, but there isn’t any statement or document about who, how and when decides about and performs automatic updates,” Mihajloski said. “It is kind of [like] holding all your eggs in one basket.

\n\n\n\n

“I think those are the crucial points that WP.org should focus on and everything will came into place in a short time: complete WordPress tech documentation for security warnings, a guide for disclosure of the bugs (from researchers, but also from a vendor aspect), and recurring code audits for popular plugins.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Oct 2020 03:47:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"Post Status: Joe Casabona on creating quality content and courses\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=80099\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://poststatus.com/joe-casabona-on-creating-quality-content-and-courses/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1407:\"

David Bisset interviews Joe Casabona, an independent creator and teacher, and discusses what it\'s like to be a creator as his job, plus some news topics.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Sandhills Development

\n\n\n\n

Sandhills Development crafts ingenuity, developed with care:

\n\n\n\n
  • Easy Digital Downloads – Sell digital products with WordPress
  • AffiliateWP – A full-featured affiliate marketing solution
  • Sugar Calendar – WordPress event management made simple
  • WP Simple Pay – A lightweight Stripe payments plugin
\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:17:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: MakeStories 2.0 Launches Editor for WordPress, Rivaling Google’s Official Web Stories Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106327\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin?utm_source=rss&utm_medium=rss&utm_campaign=makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8860:\"Recipe slide from the MakeStories WordPress plugin.\n\n\n\n

Earlier today, MakeStories launched version 2.0 of its plugin for creating Web Stories with WordPress. In many ways, this is a new plugin launch. The previous version simply allowed users to connect their WordPress installs to the MakeStories site. With the new version, users can build and edit their stories directly from the WordPress admin.

\n\n\n\n

Version 2.0 of the plugin still requires an account and a connection with the MakeStories.io website. However, it is simple to set up. Users can log in without leaving their WordPress admin interface. This API connection means that user-created Stories are stored on the MakeStories servers. If an end-user wanted to jump platforms from WordPress to something else, this would allow them to take their Stories with them.

\n\n\n\n

“One of the things we would like to assure is your content is still yours, and none of the user data is being consumed or analyzed by us,” said Pratik Ghela, the founder and product manager at MakeStories. “We only take enough data to help serve you better.”

\n\n\n\n

The plugin is a competing solution to the official Web Stories plugin by Google. While the two share similarities in the final output (they are built to utilize the same front-end format for creating Stories on the web), they take different paths to get there.

\n\n\n\n

The two share similarities on the backend too. However, MakeStories may be more polished in some areas. For example, it allows users to zoom in on the small canvas area. Having the ability to reorder slides from the grid view also feels more intuitive.

\n\n\n\n

“The main unique selling proposition of our plugin is that it comes with a guarantee of the MakeStories team,” said Ghela. “We as a team have been building this for over two years, and we are proud to be one of the tools that has stood the test of time, and competition and is still growing at a very fast pace.”

\n\n\n\n

The team also wants to make the Story-creating process faster, safer, and rewarding. The goal is to cater to designers, developers, and content creators. Ghela also feels like his team’s support turnaround time of a few hours will be the key to success and is a good reason for users to give this plugin a try before settling on something else.

\n\n\n\n

“We feel that our goal is to see Web Stories flourish,” he said. “And we may have different types of users looking out for various options. So, the official plugin from Google and the one from MakeStories at least opens up the options for users to choose from. And we feel that the folks at Google are also building a great editor, and, at the end of the day, it’s up to the user to select what they feel is the best.

\n\n\n\n

Technically, MakeStories is a SaaS (software as a service) product. Even though it is a free plugin, there will eventually be a commercial component to it. Currently, it is free at least until the first quarter of 2021, which may be extended based on various factors. There is no word on what pricing tiers may be available after that.

\n\n\n\n

“There will always be a free tier, and we have always stood for it that your data belongs to you,” said Ghela. “In case you do not like the pricing, we will personally assist you to port out from using our editor and still keep the data and everything totally intact.”

\n\n\n\n

Diving Into the Plugin

\n\n\n\nStory management screen.\n\n\n\n

MakeStories is a drag-and-drop editor for building Web Stories. It works and feels much like typical design editors like Gimp or Photoshop. It shares similarities with QuarkXPress or InDesign, for those familiar with page layout programs. In some ways, it feels a lot like a light-colored version of Google’s Web Stories plugin with more features and a slightly more intuitive interface.

\n\n\n\n

The end goal is simple: create a Story through designing slides/pages that site visitors will click through as the narrative unfolds.

\n\n\n\n

The plugin provides a plethora of shapes, textures, and animations. These features are easy to find and implement. It also includes free access to images, GIFs, and videos. These are made possible via API integrations with Unsplash, Tenor, and Pexels.

\n\n\n\n

MakeStories includes access to 10 templates at the moment. However, what makes this feature stand out is that end-users can create and save custom templates for reuse down the road.

\n\n\n\nEditing a Story from a predesigned template.\n\n\n\n

One of the more interesting, almost hidden, features is the available text patterns. The plugin allows users to insert these patterns from a couple of dozen choices. This makes it easier to visualize a design without having to build everything from scratch.

\n\n\n\nInserting a text pattern and adjusting its size.\n\n\n\n

While the editing process is a carefully-crafted experience that makes the plugin worth a look, it is the actual publishing aspect of the workflow that is a bit painful. Traditional publishing in WordPress means hitting the “publish” button to make content live. This is not the case with the MakeStories plugin. It takes you through a four-step process of entering various publisher details, setting up metadata and SEO, validating the Story content, and analytics. It is not that these steps are necessarily bad. For example, MakeStories lets you know when images are missing alt text, which is needed information screen readers. The problem is that it feels out of place to go through all of these details when I, as a user, simply want my content published. And, many of these details, such as the publisher (author), should be automatically filled in.

\n\n\n\n

Updating a Story is not as simple as hitting an “update” button either. The system needs to run through some of the same steps too.

\n\n\n\n

Ghela said the publishing process might be a bit tough but will prove fruitful in the end. The plugin takes care of the technical aspects of adding title tags, meta, and other data on the front end after the user fills in the form fields.

\n\n\n\n

“We will definitely work on improving the flow as the community evolves and improve it a lot to be easier, faster, and, most importantly, still very customizable,” he said.

\n\n\n\n

The MakeStories team has no plans of stopping at its current point on the roadmap. Ghela sounded excited about some of the upcoming additions they are planning, including features like teams, branding, easy template customization, polls, and quizzes.

\n\n\n\n

On the Web Stories Format

\n\n\n\nUN report on COVID-19 and poverty published with MakeStories.\n\n\n\n

Many will ultimately hesitate to use any plugin that implements Web Stories given Google’s history of dropping projects. There is also a feeling that the format is a bit of a fad and will not stand the test of time.

\n\n\n\n

“We greatly believe in AMP and Web Stories as a content format,” said Ghela. “We, as an agency, have been involved a lot in AMP and have done a lot of experiments with it, including a totally custom WooCommerce site in fully-native, valid AMP with support for variable products, subscriptions, and other functionalities.”

\n\n\n\n

The company is all-in on the format and feels like it will be around for the long term, particularly if there is a good ecosystem around monetization.

\n\n\n\n

“We think that the initial reactions are because there are not enough proven results and because we never imagined the story format to come to the web,” said Ghela. “There were definitely plugins that did this. Few folks tried to build stories using good ol’ HTML, CSS, and JavaScript. But, the performance and UX were not that great. On the other hand, the engineers at the AMP Team are making sure that everything is just perfect. The UX, load time, WCV Score, just everything.”

\n\n\n\n

He feels that some of the early criticisms are unwarranted and that the web development community should give the format a try and provide feedback.

\n\n\n\n

“The more data we all get, actually gives the AMP team a clear idea of what’s needed, and they can design the roadmap accordingly,” he said. “So, just giving out early reactions won’t help, but constructive criticism and getting back to the AMP team with what you are doing will.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:12:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"WordPress.org blog: WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7956:\"

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"WPTavern: WordPress 5.6 Release Team Pulls the Plug on Block-Based Widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106466\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:193:\"https://wptavern.com/wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8762:\"Current block-based widgets admin screen design.\n\n\n\n

I was wrong. I assured our readers that “the block-based widget system will be ready for prime time when WordPress 5.6 lands” in my previous post on the new feature’s readiness. I also said that was on the condition of not trying to make it work with the customizer — that experience was still broken. However, the 5.6 team pulled the plug on block-based widgets for the second time this year.

\n\n\n\n

One week ago, WordPress 5.6 release lead Josepha Haden seemed to agree that it would be ready. However, things can change quickly in a development cycle, and tough decisions have to be made with beta release deadlines.

\n\n\n\n

This is not the first feature the team has punted to a future release. Two weeks ago, they dropped block-based nav menus from the 5.6 feature list. Both features were originally planned for WordPress 5.5.

\n\n\n\n

A new Widgets admin screen has been under development since January 2019, which was not long after the initial launch of the block editor in WordPress 5.0. For now, the block-based widgets feature has been punted to WordPress 5.7. It has also been given the “early” tag, which means it should go into core WordPress soon after the 5.7 release cycle begins. This will give it more time to mature and more people an opportunity to test it.

\n\n\n\n

Helen Hou-Sandì, the core tech lead for 5.6, provided a historical account of the decision and why it was not ready for inclusion in the new ticket:

\n\n\n\n

My question for features that affect the front-end is “can I try out this new thing without the penalty of messing up my site?” — that is, user trust. At this current moment, given that widget areas are not displayed anything like what you see on your site without themes really putting effort into it and that you have to save your changes live without revisions to get an actual contextual view, widget area blocks do not allow you to try this new feature without penalizing you for experimenting.

\n\n\n\n

She went on to say that the current experience is subpar at the moment. Problems related to the customizer experience, which I covered in detail over a month ago, were also mentioned.

\n\n\n\n

“So, when we come back to this again, let’s keep sight of what it means to keep users feeling secure that they can get their site looking the way they want with WordPress, and not like they are having to work around what we’ve given them,” said Hou-Sandì.

\n\n\n\n

This is a hopeful outlook despite the tough decision. Sometimes, these types of calls need to be made for the good of the project in the long term. Pushing back a feature to a future version for a better user experience can be better than launching early with a subpar experience.

\n\n\n\n

“The good part of this is that now widgets can continue to be ‘re-imagined’ for 5.7, and get even more enhancements,” said lead WordPress developer Andrew Ozz in the ticket. “Not sure how many people have tested this for a bit longer but having blocks in the widgets areas (a.k.a. sidebars) opens up many new possibilities and makes a lot of the old, limited widgets obsolete. The ‘widget areas’ become something like ‘specialized posts with more dynamic content,’ letting users (and designers) do a lot of stuff that was either hard or impossible with the old widgets.”

\n\n\n\n

After the letdown of seeing one of my most anticipated features of 5.6 being dropped, it is encouraging to see the positive outlook from community leaders on the project.

\n\n\n\n

“You know, I was really hopeful for it too, and that last-minute call was one I labored over,” said Haden. “When I last looked, it did seem close to ready, but then more focused testing was done and there were some interactions that are a little rough for users. I’m grateful for that because the time to discover painful user experiences is before launch rather than after!”

\n\n\n\n

Despite dropping its second major feature, WordPress 5.6 still has some big highlights that will be shipping in less than two months. The new Twenty Twenty-One theme looks to be a breath of fresh air and will explore block-related features not seen in previous default themes. Haden also pointed out auto-updates for major releases, application passwords support for the REST API, and accessibility improvements as features to look forward to.

\n\n\n\n

WordPress 5.6 Beta 1 is expected to ship today.

\n\n\n\n

Adding New Features To an Old Project

\n\n\n\n

At times, it feels like the Gutenberg project has bitten off more than it can chew. Many of the big feature plans continually miss projections. Between full-site editing, global styles, widgets, nav menus, and much more, it is tough to get hyper-focused on one feature and have it ready to ship. On the other hand, too much focus one way can be to the detriment to other features in the long run. All of these pieces must eventually come together to create a more cohesive whole.

\n\n\n\n

WordPress is also 17 years old. Any new feature could affect legacy features or code. The goal for block-based widgets is to transition an existing feature to work within a new system without breaking millions of websites in the process. Twenty-one months of work on a single feature shows that it is not an easy problem to solve.

\n\n\n\n

“You are so right about complex engineering problems!” said Haden. “We are now at a point in the history of the project where connecting all of the pieces can have us facing unforeseen complications.”

\n\n\n\n

The project also needs to think about how it can address some of the issues it has faced with not quite getting major features to completion. Is the team stretched too thin to focus on all the parts? Are there areas we can improve to push features forward?

\n\n\n\n

“There will be a retrospective where we can identify what parts of our process can be improved in the future, but I also feel like setting stretch goals is good for any software project,” said Haden. “Many contributors have a sense of urgency around bringing the power of blocks to more spaces in WordPress, which I share, but when it’s time to ship, we have to balance that with our deep commitment to usability.”

\n\n\n\n

One problem that has become increasingly obvious is that front-end editing has become tougher over the years. Currently, widgets and nav menus can be edited in two places in WordPress with wildly different interfaces. Full-site editing stands to add an entirely new interface to the mix.

\n\n\n\n

“I think one of the problems that we’re trying to solve with Gutenberg has always been a more consistent experience for editing elements across the WordPress interface,” said Haden. “No user should have to learn five different workflows to make sure their page looks the way they imagined it when it’s published.”

\n\n\n\n

In the meantime, which may be numbered in years, end-users will likely have these multiple interfaces to deal with — overlap while new features are being developed. This may simply be a necessary growing pain of an aging project, one that is trying to lead the pack of hungry competitors in the CMS space.

\n\n\n\n

“There’s a lot of interest in reducing the number of workflows, and I’m hopeful that we can consolidate down to just one beautiful, intuitive interface,” said Haden.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 21:16:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce Tests New Instagram Shopping Checkout Feature, Now in Closed Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106398\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2878:\"

Instagram’s checkout feature, which allows users to purchase products without leaving the app, has become an even more important part of Facebook’s long-term investment in e-commerce now that the pandemic has so heavily skewed consumer behavior towards online shopping. When Instagram introduced checkout in 2019, it reported that 130 million users were tapping to reveal product tags in shopping posts every month.

\n\n\n\nimage credit: Instagram\n\n\n\n

Business owners who operate an existing store can extend their audience to Instagram by funneling orders from the social network into their own stores, without shoppers having to leave Instagram. Checkout supports integration with several e-commerce platform partners, including Shopify and BigCommerce, and will soon be available for WooCommerce merchants.

\n\n\n\n

WooCommerce is testing a new Instagram Shopping Checkout feature for its Facebook for WooCommerce plugin. The free extension is used on more than 900,000 websites and will provide the bridge for store owners who want to tap into Instagram’s market. The checkout capabilities are currently in closed beta. Anyone interested to test the feature can sign up for consideration. Businesses registered in the USA that meet certain other requirements may be selected to participate, and the beta is also expanding to other regions soon.

\n\n\n\n

WooCommerce currently supports shoppable posts, which are essentially products sourced from a product catalog created on Facebook that are then linked to the live store through an Instagram business account. Instagram’s checkout takes it one step further to provide a native checkout experience inside the app. Merchants pay no selling fees until December 31, 2020. After that time, the fee is 5% per shipment or a flat fee of $0.40 for shipments of $8.00 or less. 

\n\n\n\n

On the customer side, shoppers only have to enter their information once and thereafter it is stored for future Instagram purchases. Instagram also pushes shipment and delivery notifications inside the app. Store owners will need to weigh whether the convenience of the in-app checkout experience is worth forking over 5% to Facebook, or if they prefer funneling users over to the live store instead.

\n\n\n\n

Instagram Shopping Checkout is coming to WooCommerce in the near future but the company has not yet announced a launch date, as the feature is just now entering closed beta.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 04:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Past Twenty* WordPress Themes To Get New Block Patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106396\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/past-twenty-wordpress-themes-to-get-new-block-patterns?utm_source=rss&utm_medium=rss&utm_campaign=past-twenty-wordpress-themes-to-get-new-block-patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6608:\"

Mel Choyce-Dwan, the Default Theme Design Lead for WordPress 5.6, kick-started 10 tickets around two months ago that would bring new features to the old default WordPress themes. The proposal is to add unique block patterns, a feature added to WordPress 5.5, to all of the previous 10 Twenty* themes. It is a lofty goal that could breathe some new life into old work from the previous decade.

\n\n\n\n

Currently, only the last four themes are marked for an update by the time WordPress 5.6 lands. Previous themes are on the list to receive their block patterns in a future release. For developers and designers interested in getting involved, the following is a list of the Trac tickets for each theme:

\n\n\n\n\n\n\n\n

If you are wondering where Twenty Eighteen is in that list, that theme does not actually exist. It is the one missing year the WordPress community has had since the one-default-theme-per-year era began with Twenty Ten. It is easy to forget that we did not get a new theme for the 2017-2018 season. With all that has happened in the world this year, we should count ourselves fortunate to see a new default theme land for WordPress this December. WordPress updates and its upcoming default theme are at least one consistency that we have had in an otherwise chaotic time.

\n\n\n\n

More than anything, it is nice to see some work going toward older themes — not just in terms of bug fixes but feature updates. The older defaults are still a part of the face of WordPress. Twenty Twenty and Twenty Seventeen each have over one million active installs. Twenty Nineteen has over half a million. The other default themes also have significant user bases in the hundreds of thousands — still some of the most-used themes in the directory. We owe it to those themes’ users to keep them fresh, at least as long as they maintain such levels of popularity.

\n\n\n\n

This is where the massive theme development community could pitch in. Do some testing of the existing patches. Write some code for missing patterns or introduce new ideas. This is the sort of low-hanging fruit that almost anyone could take some time to help with.

\n\n\n\n

First Look at the New Patterns

\n\n\n\n

None of the proposed patterns have landed in trunk, the development version of WordPress, yet. However, several people have created mockups or added patches that could be committed soon.

\n\n\n\n

One of my favorite patterns to emerge thus far is from Beatriz Fialho for the Twenty Nineteen theme. Fialho has created many of the pattern designs proposed thus far, but this one, in particular, stands out the most. It is a simple two-column, two-row pattern with a circular image, heading, and paragraph for each section. Its simplicity fits in well with the more elegant, business-friendly look of the Twenty Nineteen theme.

\n\n\n\nServices pattern for Twenty Nineteen.\n\n\n\n

It is also fitting that Twenty Nineteen get a nice refresh with new patterns because it was the default theme to launch with the block editor. Ideally, it would continually be updated to showcase block-related features.

\n\n\n\n

While many people will focus on some of the more recent default themes, perhaps the most interesting one is a bit more dated. Twenty Thirteen was meant to showcase the new post formats feature in WordPress 3.6. According to Joen Asmussen, the theme’s primary designer, the original idea was for users to compose a ribbon of alternating colors as each post varied its colors.

\n\n\n\n

“The alternating ribbon of colors did not really come to pass because post formats were simply not used enough to create an interesting ribbon,” he wrote in the Twenty Thirteen ticket. “However, perhaps for block patterns, we have an opportunity to revisit those alternating ribbons of colors. In other words, I’d love to see those warm bold colors used in big swathes that take up the whole pattern background.”

\n\n\n\n
Patterns designed to match post formats.\n\n\n\n

This could be a fun update for end-users who are still using that feature that shall not be named post formats.

\n\n\n\n

There is a lot to like about many of the pattern mockups so far. I look forward to seeing what lands along with WordPress 5.6 and in future updates.

\n\n\n\n

Establishing Pattern Category Standard

\n\n\n\n

With the more recent Twenty Twenty-One theme’s block patterns and the new patterns being added to some of the older default themes, it looks like a specific pattern category naming scheme is starting to become a standard. Of the patches thus far, each is creating a new pattern category named after the theme itself.

\n\n\n\n

This makes sense. Allowing users to find all of their theme’s patterns in one location means that they can differentiate between them and those from core or other plugins. Third-party theme authors should follow suit and stick with this convention for the same reason.

\n\n\n\n

Developers can also define multiple categories for a single pattern. This allows theme authors to create a category that houses all of their patterns in one location. However, they can also split them into more appropriate context-specific categories for discoverability.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 19 Oct 2020 21:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"BuddyPress: BuddyPress 7.0.0-beta1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://buddypress.org/?p=315150\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://buddypress.org/2020/10/buddypress-7-0-0-beta1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4332:\"

BuddyPress 7.0.0-beta1 is now available for testing!

\n\n\n\n

Please note the plugin is still in development, so we recommend running this beta release on a testing site.

\n\n\n\n

You can test BuddyPress 7.0.0-beta1 in 4 ways :

\n\n\n\n\n\n\n\n

The 7.0.0 stable release is slated to the beginning of December, and we’d love you to give us a hand to get there!

\n\n\n\n

Please note BuddyPress 7.0.0 will require at least WordPress 4.9.

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute. Here are some of the big changes and features to pay close attention to while testing (Check out this report on Trac for the full list).

\n\n\n\n
\n\n\n\n

New Administration screens to manage BuddyPress types

\n\n\n\n

In BuddyPress 7.0.0 site administrators will be able to add, edit or delete Member & Group types using their WordPress Administration Screens just like they would do for Post tags.

\n\n\n\n

Read this development note to learn more about it.

\n\n\n\n
\n\n\n\n

Let’s welcome 3 new BP Blocks into our Block Editor

\n\n\n\n
  • The Activity Embed block let authors embed an activity into their post or page.
  • Use the BP Members block to select community users you want to feature into a post or a page.
  • Enjoy the BP Groups block to pick the groups you want to highlight into a post or a page.
\n\n\n\n

Get to know these new blocks reading this development note.

\n\n\n\n
\n\n\n\n

Improved support for WP CLI

\n\n\n\n

WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs, and much more, without using a web browser. In 7.0.0, you will be able to Enjoy new BuddyPress CLI commands to manage BuddyPress Group Meta, BuddyPress Activity Meta, activate or deactivate the BuddyPress signup feature and create BuddyPress specific testing code for plugins.

\n\n\n\n

Discover more about it from this development note.

\n\n\n\n
\n\n\n\n

And so much more such as improvements to the BP REST API, our Template pack, images and iframes lazy loading support…

\n\n\n\n
\n\n\n\n

How You Can Help

\n\n\n\n

Do you speak a language other than English? Help us translate BuddyPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, file one on BuddyPress Trac.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 22:30:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:12:\"Mathieu Viet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"WPTavern: Using the Web Stories for WordPress Plugin? You Better Play By Google’s Rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105848\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:215:\"https://wptavern.com/using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules?utm_source=rss&utm_medium=rss&utm_campaign=using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4080:\"Web Stories dashboard screen in WordPress.\n\n\n\n

What comes as a surprise to few, Google has updated its content guidelines for its Web Stories format. For users of its recently-released Web Stories for WordPress plugin, they will want to follow the extended rules for their Stories to appear in the “richer experiences” across Google’s services. This includes the grid view on Search, Google Images, and Google Discover’s carousel.

\n\n\n\n

Google released its Web Stories plugin in late September to the WordPress community. It is a drag-and-drop editor that allows end-users to create custom Stories from a custom screen in their WordPress admin.

\n\n\n\n
Visual Stories on Search.
\n\n\n\n

The plugin does not directly link to Google’s content guidelines anywhere. For users who do not do a little digging, they may be caught unaware if their stories are not surfaced in various Google services.

\n\n\n\n

On top of the Discover and Webmaster guidelines, Web Stories have six additional restrictions related to the following:

\n\n\n\n
  • Copyrighted content
  • Text-heavy Web Stories
  • Low-quality assets
  • Lack of narrative
  • Incomplete stories
  • Overly commercial
\n\n\n\n

While not using copyrighted content is one of those reasonably-obvious guidelines, the others could trip up some users. Because Stories are meant to represent bite-sized bits of information on each page, they may become ineligible if most pages have more than 180 words of text. Videos should also be limited to fewer than 60 seconds on each page.

\n\n\n\n

Low-quality media could be a flag for Stories too. Google’s guidelines point toward “stretched out or pixelated” media that negatively impacts the reader’s experience. They do not offer any specific resolution guidelines, but this should mostly be a non-issue today. The opposite issue is far more likely — users uploading media that is too large and not optimized for viewing on the web.

\n\n\n\n

The “lack of narrative” guideline is perhaps the vaguest, and it is unclear how Google will monitor or police narrative. However, the Stories format is about storytelling.

\n\n\n\n

“Stories are the key here imo,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Google specifically states that Stories need a “binding theme or narrative structure” from one page to the next. Essentially, the company is telling users to use the format for the purpose it was created for. They also do not want users to create incomplete stories where readers must click a link to finish the Story or get information.

\n\n\n\nCNN’s Web Story on Remembering John Lennon.\n\n\n\n

Overly commercial Stories are frowned upon too. While Google will allow affiliate marketing links, they should be restricted to a minor part of the experience.

\n\n\n\n

Closing his Twitter thread, Marsland seemed to hit the point. “I’ve seen some initial Google Web Stories where the platform is being used as a replacement for a brochure or website,” he wrote. “In my view that’s a huge missed opportunity. If I was advising brands I would say ‘Tell Stories’ this is a platform for Story Telling.”

\n\n\n\n

If users of the plugin follow this advice, their Stories should surface on Google’s rich search experiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 20:51:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"WPTavern: Stripe Acquires Paystack for $200M+\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106269\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"https://wptavern.com/stripe-acquires-paystack-for-200m?utm_source=rss&utm_medium=rss&utm_campaign=stripe-acquires-paystack-for-200m\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3196:\"

The big news in the world of e-commerce today is Stripe’s acquisition of Paystack, a Nigeria-based payments system that is widely used throughout African markets. The company, which became informally known as “the Stripe of Africa” picked up $8 million in Series A funding in 2018, led by Stripe, Y Combinator, and Tencent. Paystack has grown to power more than 60,000 businesses, including FedEx, UPS, MTN, the Lagos Internal Revenue Service, and AXA Mansar.

\n\n\n\n

Stripe’s acquisition of the company is rumored to be more than $200M, a small price to pay for a foothold in emerging African markets. In the company’s announcement, Stripe noted that African online commerce is growing 21% year-over-year, 75% faster than the global average. Paystack dominates among payment systems, accounting for more than half of all online transactions in Nigeria.

\n\n\n\n

“In just five years, Paystack has done what many companies could not achieve in decades,” Stripe EMEA business lead Matt Henderson said. “Their tech-first approach, values, and ambition greatly align with our own. This acquisition will give Paystack resources to develop new products, support more businesses and consolidate the hyper-fragmented African payments market.”

\n\n\n\n

Long term, Stripe plans to embed Paystack’s capabilities in its Global Payments and Treasury Network (GPTN), the company’s programmable infrastructure for global money movement.

\n\n\n\n

“Paystack merchants and partners can look forward to more payment channels, more tools, accelerated geographic expansion, and deeper integrations with global platforms,” Paystack CEO and co-founder Shola Akinlade said. He also assured customers that there’s no need to make any changes to their technical integrations, as Paystack will continue expanding and operating independently in Africa.

\n\n\n\n

Paystack is used as a payment gateway for thousands of WordPress-powered stores through plugins for WooCommerce, Easy Digital Downloads, Paid Membership Pro, Give, Contact Form 7, and an assortment of booking plugins. The company has an official WordPress plugin, Payment Forms for Paystack, which is active on more than 6,000 sites, but most users come through the Paystack WooCommerce Payment Gateway (20,000+ active installations).

\n\n\n\n

Stripe’s acquisition was a bit of positive news during what is currently a turbulent time in Nigeria, as citizens are actively engaged in peaceful protests to end police brutality. Paystack’s journey is an encouraging example of the flourishing Nigerian tech ecosystem and the possibilities available for smaller e-commerce companies that are solving problems and removing barriers for businesses in emerging markets.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 22:26:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: Diving Into the Book Review Block Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106273\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:145:\"https://wptavern.com/diving-into-the-book-review-block-plugin?utm_source=rss&utm_medium=rss&utm_campaign=diving-into-the-book-review-block-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6791:\"

Created by Donna Peplinskie, a Product Wrangler at Automattic, the Book Review Block plugin is nearly three years old. However, it only came to my attention during a recent excursion to find interesting block plugins.

\n\n\n\n

The plugin does pretty much what it says on the cover. It is designed to review books. It generally has all the fields users might need to add to their reviews, such as a title, author, image, rating, and more. The interesting thing is that it can automatically fill in those details with a simple ISBN value. Plus, it supports Schema markup, which may help with SEO.

\n\n\n\n

Rain or shine, sick or well, I read every day. I am currently a month and a half shy of a two-year reading streak. When the mood strikes, I even venture to write a book review. As much as I want to share interesting WordPress projects with the community, I sometimes have personal motives for testing and writing about plugins like Book Review Block. Anything that might help me or other avid readers share our thoughts on the world of literature with others is of interest.

\n\n\n\n

Admittedly, I was excited as I plugged in the ISBN for Rhthym of War, the upcoming fourth book of my favorite fantasy series of all time, The Stormlight Archive. I merely needed to click the “Get Book Details” button.

\n\n\n\n

Success! The plugin worked its magic and pulled in the necessary information. It had my favorite author’s name, the publisher, the upcoming release date, and the page count. It even had a long description, which I could trim down in the editor.

\n\n\n\nDefault output of the Book Review block.\n\n\n\n

There was a little work to make this happen before the success. To automatically pull in the book details, end-users must have an API Key from Google. It took me around a minute to set that up and enter it into the field available in the block options sidebar. The great thing about the plugin is that it saves this key so that users do not have to enter each time they want to review a book.

\n\n\n\n

Book Review Block a good starting point. It is straightforward and simple to use. It is not yet at a point where I would call it a great plugin. However, it could be.

\n\n\n\n

Falling Short

\n\n\n\n

The plugin’s Book Review block should be taking its cues from the core Media & Text block. When you get right down to it, the two are essentially doing the same thing visually. Both are blocks with an image and some content sitting next to each other.

\n\n\n\n

The following is a list of items where it should be following core’s lead:

\n\n\n\n
  • No way to edit alt text (book title is automatically used).
  • The image is always aligned left and the content to the right with no way to flip them.
  • The media and content are not stackable on mobile views.
  • Cannot adjust the size of the image or content columns.
  • While inline rich-text controls are supported, users cannot add Heading, List, or Paragraph blocks to the content area and use their associated block options.
\n\n\n\n

That is the shortlist that could offer some quick improvements to the user experience. Ultimately, the problems with the plugin essentially come down to not offering a way to customize the output.

\n\n\n\n

One of the other consistent problems is that the book image the plugin loads is always a bit small. This seems to be more of an issue from the Google Books API than the plugin. Each time I tested a book, I opted to add a larger image — the plugin does allow you to replace the default.

\n\n\n\n

The color settings are limited. The block only offers a background color option with no way to adjust the text color. A better option for plugin users is to wrap it in a Group block and adjust the background and text colors there.

\n\n\n\nBook Review block wrapped inside a Group block.\n\n\n\n

It would also be nice to have wide and full-alignment options, which is an often-overlooked featured from many block plugin authors.

\n\n\n\n

Using the Media & Text Block to Recreate the Book Review Block

\n\n\n\n

The Book Review Block plugin has a lot of potential, and I want to see it evolve by providing more flexibility to end-users. Because the Media & Text block is the closest core block to what the plugin offers, I decided to recreate a more visually-appealing design with it.

\n\n\n\nBook review section created with the Media & Text block.\n\n\n\n

I made some adjustments on the content side of things. I used the Heading block for the book title, a List block for the book metadata, and a Paragraph block for the description.

\n\n\n\n

The Media & Text block also provided me the freedom to adjust the alignment, stack the image and content on mobile views, and tinker with the size of the image. Plus, it has that all-important field for customizing the image alt attribute.

\n\n\n\n

The Media & Text block gave me much more design mileage.

\n\n\n\n

However, there are limitations to the core block. It does not fully capture some of the features available via the Book Review block. The most obvious are the automatic book details via an ISBN and the Schema markup. Less obvious, there is no easy way to recreate the star rating — I used emoji stars — and long description text does not wrap under the image. To recreate that, you would have to opt to use a left-aligned image followed by content.

\n\n\n\n

Overall, the Media & Text block gives me the ability to better style the output, which is what I am more interested in as a user. I want to put my unique spin on things. That is where the Book Review Plugin misfires. It is also the sort of thing that the plugin author can iterate on, offering more flexibility in the future.

\n\n\n\n

This is where many block plugins go wrong, particularly when there is more than one or two bits of data users should enter. Blocks represent freedom in many ways. However, when plugin developers stick to a rigid structure, users can sometimes lose that sense of freedom that they would otherwise have with building their pages.

\n\n\n\n

One of the best blocks, hands down, that preserves that freedom is from the Recipe Block plugin. It has structured inputs and fields. However, it allows freeform content for end-users to make it their own.

\n\n\n\n

When block authors push beyond this rigidness, users win.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 20:44:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce 4.6 Makes New Home Screen the Default for New and Existing Stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3018:\"

WooCommerce 4.6 was released today. The minor release dropped during WooSesh, a global, virtual conference dedicated to WooCommerce and e-commerce topics. It features the new home screen as the default for all stores. Previously, the screen was only the default on new stores. Existing store owners had to turn the feature on in the settings.

\n\n\n\n
\n\n\n\n

The updated home screen, originally introduced in version 4.3, helps store admins see activity across the site at a glance and includes an inbox, quick access to store management links, and an overview of stats on sales, orders, and visitors. This redesigned virtual command center arrives not a moment too soon, as anything that makes order management more efficient is a welcome improvement, due to the sheer volume of sales increases that store owners have seen over the past eight months.

\n\n\n\n

In stark contrast to industries like hospitality and entertainment that have proven to be more vulnerable during the pandemic, e-commerce has seen explosive growth. During the State of the Woo address at WooSesh 2020, the WooCommerce team shared that e-commerce is currently estimated to be a $4 trillion market that will grow to $4.5 trillion by 2021. WooCommerce accounts for a sizable chunk of that market with an estimated total payment volume for 2020 projected to reach $20.6 billion, a 74% increase compared to 2019.

\n\n\n\n

The WooCommerce community is on the forefront of that growth and is deeply invested in the products that are driving stores’ success. The WooCommerce team shared that 75% of people who build extensions also build and maintain stores for merchants, and 70% of those who build stores for merchants also build and maintain extensions or plugins. In 2021, they plan to invest heavily in unlocking more features in more countries and will make WooCommerce Payments the native payment method for the global platform.

\n\n\n\n

A new report from eMarketer shows that US e-commerce growth has jumped 32.4%, accelerating the online shopping shift by nearly two years. Experts also predict the top 10 e-commerce players will swallow up more of US retail spending to account for 63.2% of all online sales this year, up from 57.9% in 2019.

\n\n\n\n

The increase in e-commerce spending may not be entirely tied to the pandemic, as some experts believe this historic time will mark permanent changes in consumer spending habits. This is where independent stores, powered by WooCommerce and other technologies, have the opportunity to establish a strong reputation for themselves by providing quality products and reliable service, as well as by being more nimble in the face of pandemic-driven increases in volume.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 03:48:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"WPTavern: The Future of Starter Content: WordPress Themes Need a Modern Onboarding and Importing Tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106177\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool?utm_source=rss&utm_medium=rss&utm_campaign=the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7385:\"Image credit: picjumbo.com on Pexels.\n\n\n\n

Starter content. It was a grand idea, one of those big dreams of WordPress. It was the new kid on the block in late 2016. Like the introduction of post formats in 2011, the developer community was all in for at least that particular release version. Then, it was on to the next new thing, with the feature dropping off the radar for all but the most ardent evangelists.

\n\n\n\n

Some of us were burned over the years, living and dying by the progress of features that we wanted most.

\n\n\n\n

Released in WordPress 4.7, starter content has since seemed to be going the way of post formats. After four years, only 141 themes in the WordPress theme directory support the feature. There has been no movement to push it beyond its initial implementation. And, it never really covered the things that theme authors wanted in the first place. It was a start. But, themers were ultimately left to their own devices, rolling custom solutions for something that never panned out — fully-featured demo and imported content. Four years is an eternity in the web development world, and there is no sense in waiting around to see if WordPress would push forward.

\n\n\n\n

Until Helen Hou-Sandí published Revisiting Starter Content last week, most would have likely assumed the feature would be relegated to legacy code used by old-school fans of the feature and those theme authors who consider themselves completionists.

\n\n\n\n

“Starter content in 4.7 was always meant to be a step one, not the end goal or even the resting point it’s become,” wrote Hou-Sandí. “There are still two major things that need to be done: themes should have a unified way of showing users how best to put that theme to use in both the individual site and broader preview contexts, and sites with existing content should also be able to take advantage of these sort of ‘ideal content’ definitions.”

\n\n\n\n

Step two should have been this four-year-old accompanying ticket to allow users to import starter content into existing, non-fresh sites.

\n\n\n\n

Since the initial feature dropped, the theme landscape has changed. Let’s face it. WordPress might simply not be able to compete with theme companies that are pushing the limits, creating experiences that users want at much swifter speeds.

\n\n\n\n

Look at where the Brainstorm Force’s Starter Templates plugin for its Astra theme is now. Users can click a button and import a full suite of content-filled pages or even individual templates. And, the Astra theme is not alone in this. It has become an increasingly-common standard to offer some sort of onboarding to users. GoDaddy’s managed WordPress service fills a similar need on the hosting end.

\n\n\n\nAstra’s starter templates and content.\n\n\n\n

As WordPress use becomes more widespread, the more it needs a way to onboard users.

\n\n\n\n

This essentially boils down to the question: how can I make it look like the demo?

\n\n\n\n

Ah, the age-old question that theme authors have been trying to solve. Whether it has been limitations in the software or, perhaps, antiquated theme review guidelines related to demo and imported content, this has been a hurdle that has been tough to jump. But, some have sailed over it and moved on. While WordPress has seemingly been twiddling its thumbs for years, Brainstorm Force and other theme companies have solved this and continued to innovate.

\n\n\n\n

This is not necessarily a bad thing. There are plenty of ideas to steal copy and pull into the core platform.

\n\n\n\n

One of the other problems facing the WordPress starter content feature is that it is tied to the customizer. With the direction of the block system, it is easy to ask what the future holds. The customizer — originally named the theme customizer — was essentially a project to allow users to make front-end adjustments and watch those customizations happen in real time. However, new features like global styles and full-site editing are happening on their own admin screens. Most theme options will ultimately be relegated to global styles, custom templates, block styles, and block patterns. There may not be much left for the customizer to do.

\n\n\n\n

Right now, there are too many places in WordPress to edit the front-end bits of a WordPress site. My hope is that all of these things are ultimately merged into one less-confusing interface. But, I digress…

\n\n\n\n

Starter content should be rethought. Whoever takes the reins on this needs a fresh take that adopts modern methods from leading theme companies.

\n\n\n\n

The ultimate goal should be to allow theme authors to create multiple sets of templates/content that end-users can preview and import. It should not be tied to whether it is a new site. Any site owner should be able to import content and have it automagically go live. It should also be extendable to allow themes to support page builders like Elementor, Beaver Builder, and many others.

\n\n\n\n

This seems to be in line with Hou-Sandí’s thoughts. “For a future release, we should start exploring what it might look like to opt into importing starter content into existing sites, whether wholesale or piecewise,” she wrote. “Many of us who work in the WordPress development/consulting space tend not to ever deal in switching between public themes on our sites, but let’s not forget that’s a significant portion of our user audience and we want to continue to enable them to not just publish but also publish in a way that matches their vision.”

\n\n\n\n

Let’s do it right this go-round, keep a broad vision, and provide an avenue for theme authors to adopt a standardized core WordPress method instead of having everyone build in-house solutions.

\n\n\n\n

I haven’t even touched on the recent call to use starter content for WordPress.org theme previews. It will take more than ideas to excite many theme authors about the possibility. That ticket has sat for seven years with no progress, and most have had it on their wish list for much longer. It is an interesting proposal, one that has been tossed around in various team meetings for years.

\n\n\n\n

Like so many other things, theme authors have either given up hope or moved onto doing their own thing. They need to be brought into the fold, not only as third parties who are building with core WordPress tools but as developers who are contributing to those features.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 14 Oct 2020 20:07:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:116:\"WPTavern: Google Podcasts Manager Adds More Data from Search: Impressions, Top-Discovered Episodes, and Search Terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:271:\"https://wptavern.com/google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms?utm_source=rss&utm_medium=rss&utm_campaign=google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2568:\"

Google announced an expansion of listener engagement metrics today for those using its Podcast Manager. Previously, audience insights included data about the types of devices listeners are using, where listeners tune in and drop off during a given episode, total number of listens, and listening duration, but the service lacked analytics regarding how visitors were discovering the podcast.

\n\n\n\n

Google is remedying that today by expanding the dashboard to show impressions, clicks, top-discovered episodes, and search terms that brought listeners to the podcast. This information can help podcasters understand how their content is getting discovered so they can better tailor their episodes to attract more new listeners.

\n\n\n\n

The podcasting industry has seen remarkable growth over the past five years, which previously led experts to project that marketers will spend over $1 billion in advertising by 2021. After the pandemic hit, podcast listening took a downturn in the U.S. but at the same time, podcast creators have found more time to create new shows and episodes. Businesses are turning to the medium to supplement traditional marketing methods that no longer have the same impact now that consumer spending habits heavily favor online products.

\n\n\n\n

Along with the new metrics available inside Google Podcasts Manager, the company also published a guide to optimizing podcasts for Google Search. It highlights four important items for making sure a podcast can be found:

\n\n\n\n
  • Detailed show and episode metadata
  • Ensure the podcast’s webpage and RSS data match
  • Include cover art
  • Ensure Googlebot can access your audio files
\n\n\n\n

A detailed breakdown of your audience’s listening habits isn’t worth much if you’re having trouble getting your podcast discovered. Any podcasting plugin for WordPress should handle these basic optimization recommendations, but if you are still having trouble being found via Google, you can dig deeper into the podcast setup guide for more detailed recommendations.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 22:57:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Are Block-Based Widgets Ready To Land in WordPress 5.6?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/are-block-based-widgets-ready-to-land-in-wordpress-5-6?utm_source=rss&utm_medium=rss&utm_campaign=are-block-based-widgets-ready-to-land-in-wordpress-5-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8214:\"

Two weeks ago, the Gutenberg team put out an open call for block-based widgets feedback. I had already written a lengthy review of the new system earlier in September but was asked by a member of the team to share my thoughts on the most recent iteration. With the upcoming freeze for WordPress 5.6 Beta 1 just a week away, I figured it would not hurt to do another deep dive.

\n\n\n\n

For reference, my latest testing is against version 9.2.0-alpha-172f589 of the Gutenberg plugin, which was a build from earlier today. Gutenberg development moves fast, but everything should be accurate to that point.

\n\n\n\n

Ultimately, many of the problems I pointed out over a month ago still exist. However, the team has cleaned most of the minor issues, such as pointing the open/close arrows for sidebars (block areas) in the correct direction and making it more consistent with the post-editing screen. The UI is much more polished.

\n\n\n\n

Before I dive into all the problems, I want to answer the question I am proposing. Yes, the block-based widget system will be ready for prime time when WordPress 5.6 lands. It is not there yet, but it is at a point where there is a clear finish line that is reachable in the next two months.

\n\n\n\n

I will ignore the failure of block-based widgets in the customizer, which landed in Gutenberg 8.9 and was removed in 9.1. I will also look past the recent proposal to reconstruct the widgets screen to use the Customize API, at least for now. There is a boatload of problems that block-based widgets present for the customizer, and those problems are insurmountable for WordPress 5.6. Long term, WordPress needs to have a single place for editing widget/block areas. Users will likely have to live with some inconsistencies for a while.

\n\n\n\n

Assuming the team does not try to throw a last-minute Hail Mary and implement full editing of blocks in the customizer this round, it is safe to say that block-based widgets are well on their way toward a successful WordPress 5.6 debut.

\n\n\n\n

The User Experience

\n\n\n\nBlock-based widgets screen.\n\n\n\n

As a user, I genuinely enjoy using the new Widgets admin screen. The open-ended, free-form block areas create untold possibilities for designing my WordPress sites. Traditional widgets were limited in scope. Users were buckled down to a handful of core widgets, possibly some plugin widgets, and whatever their theme author offered up. However, with blocks, the pool of choices expands to at least triple the out-of-the-box options (I am not counting embed-type blocks individually). Plus, blocks provide a far more extensive set of design options than a traditional widget.

\n\n\n\n

In comparison, traditional widgets are outdated. Blocks are superior in almost every way. However, there are still problems with this new system.

\n\n\n\n

The biggest issue right now is that end-users can exit the Widgets screen without saving their changes. There is no warning to let them know that all their work is about to be lost in the ether. This is one of those OMGBBQ-level items that need to happen before WordPress 5.6 drops.

\n\n\n\n

One nice-to-have-but-not-necessary feature would be the ability to drag blocks from one block area to another. In the old widgets system, users could move widgets from sidebar to sidebar. The current alternative is to copy a widget, paste it in a new block area, and remove the original.

\n\n\n\n

I am also not a fan of not having an option for the top toolbar, which is available on the post-editing screen. One of the reasons for using this toolbar is because I dislike the default popup toolbar on individual blocks. It is distracting and often gets in the way of my work.

\n\n\n\n

Legacy widgets seem to still be a work in progress. The Legacy Widget block did not work at all for me at times. Then, it magically began to work. However, Gutenberg does now automatically add registered third-party widgets to the block inserter just as if they were blocks.

\n\n\n\nGetting a plugin’s widget to work.\n\n\n\n

This presented its own problems. The only way I managed to make third-party plugin widgets work was to insert the widget, save, and refresh the widgets screen. At that point, the widgets appeared and became editable.

\n\n\n\n

The Theme Author Experience

\n\n\n\n

One of my biggest concerns for theme authors right now is that there does not seem to be any documentation in the block editor handbook. There is plenty of time to make that happen, but there are things theme authors need to be aware of. Having a centralized location, even while the feature is under development, would help them gear up for the 5.6 release.

\n\n\n\n

Some of these questions, which may be answered in various Make blog posts, should exist on a dedicated documentation page:

\n\n\n\n
  • How can a theme opt out of block-based widgets?
  • What are the hooks to add custom styles for the Widgets screen?
  • Can themes target specific sidebar styles on the Widgets screen?
  • Is it possible to consistently style sections like traditional widgets on the front end?
  • Can themes opt into wide and full-alignment within block areas, which could essentially be used similarly to the post content area?
\n\n\n\n

These are some of the questions I would want to be answered as a former theme author. I am no longer in the thick of the theme design game and presume that those who are would have a larger list of questions.

\n\n\n\n

One less-obvious piece of documentation should center on how to handle fallbacks or default widgets. Traditionally, themes that needed to show a default set of widgets would check if the sidebar has widgets and fall back to using the_widget() to output one or more defaults. While theme authors can still do that, we should start to transition them across the board to the block system.

\n\n\n\n

Should theme authors copy/paste block HTML as a fallback? Would the starter content system be better for this, and can starter widget content handle blocks? What is the recommended method for widget fallbacks in WordPress 5.6?

\n\n\n\n

There is still the ongoing issue of how theme authors should handle the traditional widget and widget title wrapper HTML in the new block paradigm. One patch added since the Gutenberg 9.1 release wraps every top-level block with the widget wrapper. If this lands in the 9.2 release, it will likely make the issue worse.

\n\n\n\n

In the traditional system, both the widget title and content are wrapped within a container together. However, if a user adds a Heading block (widget title) and another block (widget content), each block is wrapped separately with the theme’s widget wrappers. The only way to rectify the situation as it stands is for end-users to add a Group block for each “widget” they want, which would require an extensive amount of re-education for WordPress users. It is not an ideal scenario.

\n\n\n\nEach block is wrapped as an individual section.\n\n\n\n

Instead of attempting to directly “fix” this issue, WordPress should instead do nothing to the output. Blocks and traditional widgets are fundamentally different.

\n\n\n\n

Let theme authors take the reins on this one and explore possibilities. However, give them the tools to do so, such as supporting block patterns.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 21:35:39 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: WordCamp Austin 2020 Finds Success with VR Experience for Sessions and Networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106119\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking?utm_source=rss&utm_medium=rss&utm_campaign=wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7246:\"

WordCamp Austin 2020 attendees are raving about their experiences attending the virtual event last Friday. It was no secret that the camp’s organizers planned to use Hubs Virtual Rooms by Mozilla to create a unique environment, but few could imagine how much more interactive and personalized the experience would be than a purely Zoom-based WordCamp.

\n\n\n\n

After selecting a custom avatar, attendees entered the venue using a VR headset or the browser to check out sessions or network in the hallway track.

\n\n\n\n
\n

Here’s a small taste of the experience at @WordCampATX today. #WordPress logos and no sponsor banners on any elevator doors. #WCATX pic.twitter.com/Nv2p2VchXf

— David Bisset (@dimensionmedia) October 9, 2020
\n
\n\n\n\n

Speaker and Q&A sessions were broadcast through Zoom but organizers can also embed YouTube videos and streams within the standalone VR environment.

\n\n\n\n

“The VR experience was the most life-like WordCamp experience I’ve had since the start of global lockdowns,” attendee and speaker David Vogelpohl said. “You could attend sessions in one of two virtual presentation halls depending on what track you wanted to see at that time. The speaker presented on a virtual stage and you could see the other attendees watching the presentation.”

\n\n\n\n

Vogelpohl said he enjoyed his experience getting to know others in the Slack and VR venue. Organizers preserved the general vibe of the “hallway track” to recreate what is arguably one of the most valuable aspects of in-person WordCamps.

\n\n\n\n
\n

So cool – checking out the Virtual Space of WordCamp Austin – love the background noise of people talking, ran into @ChrisWiegman and @Josh412 #WCATX pic.twitter.com/68EdgDN2Om

— Birgit Pauli-Haack (@bph) October 9, 2020
\n
\n\n\n\n

“In the hallway track between the virtual presentation halls was a large foyer where you could meet new people, spot a friend speaking with someone else, and virtually step aside from a group conversation to have a private conversation,” Vogelpohl said.

\n\n\n\n

“It was great to see folks like Josepha circling around speaking with attendees, Josh Pollock nerding out in a corner with a group of advanced WP developers, and having random friends drop into a conversation I was having with a group of others. While VR WordCamp doesn’t wholly replace the value of attending a WordCamp live, a lot of the best parts of meeting and collaborating with others was captured in the VR context.”

\n\n\n\n

The live music interludes, which showcased talents from around the community, also provided a way for virtual attendees to stay connected while waiting for the next session.

\n\n\n\n

Behind the Scenes with Anthony Burchell: Creative Director for WordCamp Austin’s Virtual World

\n\n\n\n

WordPress core contributor Anthony Burchell, who started a company dedicated to creating interactive XR sound and art experiences, was the creative director behind the WordCamp Austin’s VR backdrop.

\n\n\n\n

“For WordCamp Austin we wanted to give folks something to be excited about outside of the typical webcam and chat networking,” Burchell said. “I feel that virtual events are not utilizing the networking layer nearly enough to make folks feel like they are really at an event. I’ve seen many compelling formats for virtual events utilizing webcams and chat rooms, but in the end, it feels like there’s been a missing element of presence; something video games and virtual reality excel at.”

\n\n\n\n
\n

Virtual mission control for #WCATX pic.twitter.com/WyrFkIsW2Q

— Anthony Burchell (@antpb) October 9, 2020
\n
\n\n\n\n

Setting up the virtual world involves spinning up a self-hosted instance of Hubs Cloud, which Burchell said is very similar to the complexity of making a WordPress site.

\n\n\n\n

“The most time consuming part of creating a 3D world for an event is making the 3D assets for the space,” Burchell said. “In total I streamed 11 hours of video leading up to the event to give a glimpse into the process.”

\n\n\n\n

Burchell’s YouTube playlist documents the incredible amount of work that went into creating the WordCamp’s virtual venue for attendees to enjoy.

\n\n\n\n

“While it took quite a bit of time to prepare, the code and assets are completely reusable for another event,” Burchell said. “A lot of the time was spent trying to make the space purpose built for the goals of the camp. Much like a real WordCamp, I found the majority of folks packing into the theater rooms for presentations and dipping out a little early to network with friends in the hallway area. That was very much by design!”

\n\n\n\n

Burchell and the other organizers were careful to ensure that the Hubs space was not the primary viewing experience of the camp but rather an extension of the networking activities that attendees could drop in on. The event had nearly identical numbers of attendees joining the virtual space as it did for those joining the video channels. At the end of the afterparty, Burchell turned on flying for all attendees to conclude the successful event:

\n\n\n\n
\n\n
\n\n\n\n

“With Hubs we were able to give attendees the ability to express themselves within a venue vs within a camera and chat box,” Burchell said. “It was incredible to see characteristics of folks in the community shine through a virtual avatar! Just the simple act of seeing your WordCamp friends in the hallway joking and chatting just as they would at a real life event was enough to make me feel like I was transported to a real WordCamp.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 22:31:02 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Privacy-Conscious WordPress Plugin Caches and Serves Gravatar Images Locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105825\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally?utm_source=rss&utm_medium=rss&utm_campaign=privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5285:\"

Ari Stathopoulos released his new Local Gravatars plugin last week. The goal of the plugin is to allow site owners to take advantage of the benefits of a global avatar system while mitigating privacy concerns by hosting the images locally.

\n\n\n\n

In essence, it is a caching system that stores the images on the site owner’s server. It is an idea that Peter Shaw proposed in the comments on an earlier Tavern article covering local avatar upload. It is a middle ground that may satisfy some users’ issues with how avatars currently work in WordPress.

\n\n\n\n

“I am one of the people that blocks analytics, uses private sessions when visiting social sites, I use DuckDuckGo instead of Google, and I don’t like the ‘implied’ consents,” said Stathopoulos. “I built the plugin for my own use because I don’t know what Gravatar does, I don’t understand the privacy policies, and I am too lazy to spend two hours analyzing them. It’s faster for me to build something that is safe and doesn’t leave any room for misunderstandings.”

\n\n\n\n

He is referring to Automattic’s extensive Privacy Policy. He said it looks benign. However, he does not like the idea of any company being able to track what sites he visits without explicit consent.

\n\n\n\n

“And when I visit a site that uses Gravatar, some information is exposed to the site that serves them — including my IP,” said Stathopoulos. “Even if it’s just for analytics purposes, I don’t think the company should know that page A on site B got 1,000 visitors today with these IPs from these countries. There is absolutely no reason why any company not related to the page I’m actually visiting should have any kind of information about my visit.”

\n\n\n\n

The Local Gravatars plugin must still connect to the Gravatar service. However, the connection is made on the server rather than the client. Stathopoulos explained that the only information exposed in this case is the server’s IP and nothing from the client, which eliminates any potential privacy concerns.

\n\n\n\n

The Latest Plugin Update

\n\n\n\n

Stathopoulos updated the plugin earlier today to address some performance concerns for pages that have hundreds or more Gravatar images. In the version 1.0.1 update, he added a maximum processing time of five seconds and changed the cache cleanup process from daily to weekly. Both of these are filterable via code.

\n\n\n\n

“Now, if there are Gravatars missing in a page request, it will get as many as it can, and, after five seconds, it will stop,” said Stathopoulos. “So if there are 100 Gravatars missing and it gets the first 20, the rest will be blank (can be filtered to use a fallback URL, or even fall back to the remote URL, though that would defeat the privacy improvement). The next page request will get the next 20, and so on. At some point, all will be there, and there will be no more delays.”

\n\n\n\n

He did point out that performance could temporarily suffer when installing it on a site that has individual posts with 1,000s of comments and a lot of traffic. However, nothing would crash on the site, and the plugin should eventually lead to a performance boost in this scenario. For such large sites, owners could use the existing filter hooks to tweak the settings.

\n\n\n\n

Right now, the plugin is primarily an itch he wanted to scratch for his own purposes. However, if given enough usage and feedback, he may include a settings screen to allow users to control some of the currently-filterable defaults, such as the cleanup timeframe and the maximum process time allowed.

\n\n\n\n

The Growing List of Alternatives

\n\n\n\n

With growing concerns around privacy in the modern world, Local Gravatars is another tool that end-users can employ if they have any concerns around the Gravatar service. For those who are OK with an auto-generated avatar, Pixel Avatars may be a solution.

\n\n\n\n

“I’ve seen some of them, and they are wonderful!” Stathopoulos said of alternatives for serving avatars. “However, this plugin is slightly different in that the avatars the user already has on Gravatar.com are actually used. They can see the image they have uploaded. The user doesn’t need to upload a separate avatar, and an automatic one is not used by default.”

\n\n\n\n

He would not mind using an auto-generated avatar when commenting on blogs or news sites at times. However, Stathopoulos prefers Gravatar for community-oriented sites.

\n\n\n\n

“My Gravatar is part of my online identity, and when I see, for example, a comment from someone on WordPress.org, I know who they are by their Gravatar,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 21:06:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress 5.6 to Introduce Application Passwords for REST API Authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105997\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2604:\"

In 2015, WordPress 4.4 introduced a REST API, but one thing that has severely limited its broader use is the lack of authentication capabilities for third-party applications. After considering the benefits and drawbacks of many different types of authentication systems, George Stephanis published a proposal for integrating Application Passwords, into core.

\n\n\n\n

Stephanis highlighted a few of the major benefit that were important factors in the decision to use Application Passwords: the ease of making API requests, ease of revoking credentials, and the ease of requesting API credentials. The project is available as a standalone feature plugin, but Stephanis and his collaborators recommended WordPress merge a pull request that is based off the feature plugin’s codebase.

\n\n\n\n

After WordPress 5.6 core tech lead Helen Hou-Sandi gave the green light for Application Passwords to be merged into core, the developer community responded enthusiastically to the news.

\n\n\n\n

“I am/we are 100% in favor of this,” Joost deValk commented on the proposal. “Opening this up is like opening the dawn of a new era of WordPress based web applications. Suddenly authentication is not something you need to fix when working with the API and you can just build awesome stuff.”

\n\n\n\n

Stephanis’ proposal also mentioned how beneficial a REST API authentication system would be for the Mobile teams‘ contributors who are relying on awkward workarounds while integrating Gutenberg support.

\n\n\n\n

“This would be a first step to replace the use of XMLRPC in the mobile apps and it would allow us to add more features for self hosted users,” Automattic mobile engineer Maxime Biais said.

\n\n\n\n

After the REST API was added to WordPress five years ago, many had the expectation that WordPress-based web applications would start popping up everywhere. Without a reliable authentication system, it wasn’t easy for developers to just get inspired and build something quickly. Application Passwords in WordPress 5.6 will open up a lot of possibilities for those who were previously deterred by the lack of core methods for authenticating third-party access.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 23:01:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"WPTavern: WP Agency Summit Begins Its Second Annual Virtual Event October 12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105160\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:197:\"https://wptavern.com/wp-agency-summit-begins-its-second-annual-virtual-event-october-12?utm_source=rss&utm_medium=rss&utm_campaign=wp-agency-summit-begins-its-second-annual-virtual-event-october-12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6357:\"

Jan Koch, the founder and host of WP Agency Summit, is kicking off his second annual event on October 12. The five-day event will feature 37 speakers from a wide range of backgrounds across the WordPress industry. It is a free virtual event that anyone can attend.

\n\n\n\n

“The focus for the 2020 WP Agency Summit is showing attendees how to bring back the fun into scaling their agencies,” said Koch. “It is all about reducing the daily hustle by teaching how to successfully build and manage teams, how to work with enterprises (allowing for fewer customers but bigger projects), how to build sustainable recurring revenue, and how to position your agency to dominate your niche.”

\n\n\n\n

This year’s event includes three major changes to make the content more accessible to a larger group of people. Each session will be available between October 12 – 16 instead of the previous 48-hour window that attendees had to find time for in 2019.

\n\n\n\n

After the event has concluded, access to the content will be behind a paywall. Koch reduced the price to $77 for lifetime access for those who purchase pre-launch, which will increase to $127 during the event. Last year’s prices ballooned to $497, which meant that it was simply not affordable for many who found it too late.

\n\n\n\n

Some of the proceeds this year are going toward transcribing all the videos so that hearing-impaired users can enjoy the content.

\n\n\n\n

This year’s event will also focus on a virtual networking lounge for attendees. “I’ve seen how well it worked at the WP FeedBack Summit — we even had BobWP record a podcast episode on the fly in that lounge!” said Koch. “I’ve seen many new friendships develop, people connecting with new suppliers or getting themselves booked on podcasts, and sharing experiences about their businesses.”

\n\n\n\n

The lounge will be open during the entirety of the summit, which will allow attendees to jump into the conversation on their own time.

\n\n\n\n

A More Diverse Speaker Lineup

\n\n\n\n

Koch received some backlash for the lack of gender diversity last year. The 2019 event had over 20 speakers from a diverse male lineup. However, only four women from our industry led sessions.

\n\n\n\n

When asked about this issue in 2019, Koch responded, “I recognize this as a problem with my event. The reason I have so much more male than female speakers is quite simple, the current speaker line-up is purely based on connections I had when I started planning for the event. It was a relatively short amount of time for me, so I wasn’t able to build relationships with more female WP experts beforehand.”

\n\n\n\n

The host said he paid attention to the feedback he received. While not hitting the 50/50 split goal he had for 2020’s event, 16 of the 37 speakers are women.

\n\n\n\n

Koch said he strived to get speakers from a wider range of backgrounds. He wanted to bring in both freelancers and multi-million dollar agency owners. He also focused on getting people from multiple countries to represent WordPress agencies.

\n\n\n\n

“I did reach out to around 130 people four months before the event to make new connections,” he said. “The community around the Big Orange Heart (a non-profit for mental well-being) also helped a lot with introducing me to new members of the WP community.”

\n\n\n\n

Koch said he learned two valuable lessons when branching out beyond his existing connections for this year’s event:

\n\n\n\n

Firstly, don’t hesitate to reach out to people you think will never talk to you because they’re running such big companies. For example, I immediately got confirmations from Mario Peshev from Devrix, Brad Touesnard from Delicious Brains, or Marieke van de Rakt from Yoast. When first messaging them, I had little hope they’d set aside time to jump on an interview with me – but they were super supportive and accommodating! The WordPress community really is a welcoming environment if you approach people in a humble way.

Secondly, build connections with sincerity. Do not just focus on what you can get from that connection but how you can help the other person. I know this sounds cheesy and you’ve heard this quite often — but it is true. Once I got the first response from new contacts and explained my goal of connecting fellow WordPress community members virtually, most immediately agreed because they also benefit from new connections and being positioned as a thought-leader in this event.

\n\n\n\n

WP Agency Summit? WP FeedBack Summit?

\n\n\n\n

For readers who recall the Tavern’s coverage of the WP FeedBack Summit earlier this year, the article specifically stated that the WP FeedBack Summit was a continuation of 2019’s WP Agency Summit. The official word at the time from WP FeedBack’s public relations team was the following:

\n\n\n\n

Last year’s event, the WP Agency Summit has been rebranded under the umbrella of WP FeedBack’s brand when Jan Koch the host of last’s year WP Agency Summit joined WP FeedBack as CTO.

\n\n\n\n

Koch said that it was a standalone event and not directly connected to WP Agency Summit but had the same target audience. However, the WP FeedBack Summit did use the previous WP Agency Summit’s stats and data to promote the event.

\n\n\n\n

“The WP FeedBack Summit was hosted under the WP FeedBack brand because I joined their team as CTO in March this year,” he said. “Vito [Peleg] and I had the idea to host a virtual conference around WordPress because of WordCamp Asia being canceled — we wanted to help connect the community online through our summit.

\n\n\n\n

Koch left WP FeedBack soon after the summit ended and is currently back on his own and has a goal of making WP Agency Summit a yearly event.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 17:01:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: Navigation Screen Sidelined for WordPress 5.6, Full-Site Editing Edges Closer to Public Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105839\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:247:\"https://wptavern.com/navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta?utm_source=rss&utm_medium=rss&utm_campaign=navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4676:\"

The new block-based navigation screen is once again delayed after it was originally slated for WordPress 5.5 and then put on deck for 5.6. Contributors have confirmed that it will not be landing in WordPress core until 2021 at the earliest.

\n\n\n\n

“The Navigation screen is still in experimental state in the Gutenberg plugin, so it hasn’t had any significant real-world use and testing yet,” Editor Tech Lead Isabel Brison said. She made the call to remove it from the 5.6 lineup after the feature missed the deadline for bringing it out of the experimental state. It still requires a substantial amount of development work and accessibility feedback before moving forward.

\n\n\n\n

Contributors will focus instead on making sure the Widgets screen gets out the door for 5.6 and plan to pick up again on Navigation towards the end of November.

\n\n\n\n

WordPress 5.6 lead Josepha Haden gave an update this week on the progress of all the anticipated features, including the planned public beta for full-site editing (FSE).

\n\n\n\n

“I don’t expect FSE to be feature complete by the time WP5.6 is released,” Haden said. “What I expect is that FSE will be functional for simple, routine user flows, which we can start testing and iterating on. That feedback will also help us more confidently design and build our complex user flows.”

\n\n\n\n

Frank Klein, an engineer at Human Made, asked in the comments of another update why full-site editing is being tied to 5.6 progress in the first place, since it will still only be available in the plugin at the time of release.

\n\n\n\n

“The main value is that it provides a good checkpoint along the path of FSE’s development,” Kjell Reigstad said. “Full-site editing is very much in progress. It is still experimental, but the general approach is coming into view, and becoming clearer with every plugin release.”

\n\n\n\n

Reigstad posted an update on what developers can expect regarding block-based theming and the upcoming release, since the topic is closely tied to full-site editing. He emphasized that the infrastructure is already in place and that, despite it still being experimental, future block-based themes should work in a similar way to how they are working now.

\n\n\n\n

“The focus is now shifting towards polishing the user experience: using the site editor to create templates, using the query block, iterating on the post and site blocks, and implementing the Global Styles UI,” Reigstad said.

\n\n\n\n

“The main takeaway is that when 5.6 is released, the full-site editing feature set will look similar to where it is today, with added polish to the UI, and additional features in the Query block.”

\n\n\n\n

Theme authors are entering a new time of uncertainty and transition, but Reigstad reassured the community that themes as we know them today are not on track to be phased out in the immediate future.

\n\n\n\n

“There is currently no plan to deprecate the way themes are built today,” Reigstad said. “Your existing themes will continue to work as they always have for the foreseeable future.” He also encouraged contributors to get involved in an initiative to help theme authors transition to block-based themes. (This project is not targeted for the 5.6 release.)

\n\n\n\n

Developers can follow important FSE project milestones on GitHub, and subscribe to the weekly Gutenberg + Themes updates to track progress on block-based theming. A block-based version of the Twenty Twenty-One theme is in the works and should pick up steam after 5.6 beta 1, expected on October 20.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 22:57:37 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"WPTavern: EditorPlus 1.9 Adds Animation Builder for the Block Editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105678\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:181:\"https://wptavern.com/editorplus-1-9-adds-animation-builder-for-the-block-editor?utm_source=rss&utm_medium=rss&utm_campaign=editorplus-1-9-adds-animation-builder-for-the-block-editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4535:\"

Munir Kamal shows no signs of slowing down. He continues to push forward with new features for his EditorPlus plugin, which allows end-users to customize the look of the blocks in their posts and pages. He calls it the “no-code style editor for WordPress.”

\n\n\n\n

The latest addition to his plugin? Animation styles for every core block.

\n\n\n\n

My first thought was that this would bloat the plugin with large amounts of unnecessary CSS and JavaScript for what is essentially a few bells and whistles. However, Kamal pulled it off with minimal custom CSS.

\n\n\n\n

Inspired by features from various website builders, he wanted to bring more and more of those things to the core block editor. The animations feature is just another ticked box on a seemingly never-ending checklist of features. And, so far, it’s all still free.

\n\n\n\n

Since we last covered EditorPlus in June, Kamal has added the ability to insert icons via any rich-text area (e.g., paragraphs, lists, etc.). He has also added shape divider, typography, style copying, and responsive editing options for the core WordPress blocks.

\n\n\n\n

How Do Animations Work?

\n\n\n\n

In the version 1.9 release of EditorPlus, Kamal added “entrance” animations. These types of animations happen when a visitor sees the block for the first time on the screen. For example, users could set the Image block to fade into visibility as a reader views the block.

\n\n\n\n

Currently, the plugin adds seven animations:

\n\n\n\n
  • Fade
  • Slide
  • Bounce
  • Zoom
  • Flip
  • Fold
  • Roll
\n\n\n\nAdding a Slide animation for the Cover block text.\n\n\n\n

Each animation has its own subset of options to control how it behaves on the page. The bounce animation, for example, allows users to select the bounce direction. Other options include duration, delay, speed curve, delay, and repeat. There are enough choices to spend an inordinate amount of time tinkering with the output.

\n\n\n\n

One of the best features of this new feature is that Kamal has included an Animation Player under the block options. By clicking the play button, users can view the animation in action without previewing the post.

\n\n\n\n

Watch a quick video of the Animations feature:

\n\n\n\n
\n\n
\n\n\n\n

After testing and using each animation, everything seemed to work well. The one downside — and this is not limited to animations — is that applying styles on the block level sometimes does not make sense. In many cases, it would help users to have options to style or animate the items within the block, such as the images in the Gallery block. When I broached the subject with Kamal, he was open to the idea of finding a solution to this in the future.

\n\n\n\n

What Is Next for EditorPlus?

\n\n\n\n

At a certain point, too many block options can almost feel like overkill and become unwieldy. EditorPlus does allow users to disable specific features from its settings screen, which can help get rid of some unwanted options. Kamal said he would like to continue making it more modular so that users can use only the features they need.

\n\n\n\n

“What I plan is to have micro-level feature control for this extension so that a user can switch off individual styling panels like, Typography, Background, etc.,” he said. “Even further, I plan to bring these controls based on the user role as well. So an admin can disable these features for the editor, author, etc.”

\n\n\n\n

That may be a bit down the road though. For now, he wants to focus on adding new features that he already has planned.

\n\n\n\n

“I do plan to add more animation features,” said Kamal. “I got too many ideas, such as scroll-controlled animation, hover animation, text animation, Lottie animation, background animation, animated shape dividers, and more. But, having said that, I will be careful adding only those features that don’t affect page performance much.”

\n\n\n\n

Outside of extra styles and animations for existing blocks, he plans to jump on the block-building train in future releases. EditorPlus users could see accordion, toggle, slider, star rating, and other blocks in an upcoming release.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:53:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"Donncha: Hide featured image if it’s in the post\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://odd.blog/?p=89503242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://odd.blog/2020/10/08/hide-featured-image-if-its-in-the-post/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3885:\"

I’ve been running a photoblog at inphotos.org since 2005 on WordPress. (And thanks to writing this I noticed it’s 15 years old today!)

\n\n\n\n
\n\n\n\n

In that time WordPress has changed dramatically. At first I used Flickr to host my images, but after a short time I hosted the images myself. (Good thing too since Flickr limited free user accounts to 1000 images, so I wrote a script to download the Flickr images I used in posts.)

\n\n\n\n
\n\n\n\n

For quite a long time I used the featured image instead of inserting the image into the post content, but then about two years ago I went back to inserting the photo into the post. Unfortunately that meant the photo was shown twice, once as a featured image, and once in the post content.

\n\n\n\n

The last theme I used supported custom post types, one of which was a photo type that displayed the featured image but hid the post content. It was an ok compromise, but not perfect.

\n\n\n\n
\n\n\n\n

Recently I started using Twenty Twenty, but after 15 years I had a mixture of posts with:

\n\n\n\n
  • Featured image with no image in the post.
  • Featured image with the same image in the post.
\n\n\n\n

I knew I needed something more flexible. I wanted to hide the featured image if it also appeared in the post content. I procrastinated and never got around to it until this evening when I discovered it was actually quite easy.

\n\n\n\n\n\n\n\n

Copy the following code into the function.php of your child theme and you’ll be all set! It relies on you having unique filenames for your images. If you don’t then remove the call to basename(), and that may help.

\n\n\n
\nfunction maybe_remove_featured_image( $html ) {\n        if ( $html == \'\' ) {\n                return \'\';\n        }\n        $post = get_post();\n        $post_thumbnail_id = get_post_thumbnail_id( $post );\n        if ( ! $post_thumbnail_id ) {\n                return $html;\n        }\n\n        $image_url = wp_get_attachment_image_src( $post_thumbnail_id );\n        if ( ! $image_url ) {\n                return $html;\n        }\n\n        $image_filename = basename( parse_url( $image_url[0], PHP_URL_PATH ) );\n        if ( strpos( $post->post_content, $image_filename ) ) {\n                return \'\';\n        } else {\n                return $html;\n        }\n}\nadd_filter( \'post_thumbnail_html\', \'maybe_remove_featured_image\' );\n
\n\n\n

The post_thumbnail_html filter acts on the html generated to display the featured image. My code above gets the filename of the featured image, checks if it’s in the current post and if it is returns a blank string. Feedback welcome if you have a better way of doing this!

\n\n\n\n
\n\n\n\n

\n\n

Related Posts

\n

Source

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:43:35 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Donncha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches Automatic Platform Optimization for WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105641\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-automatic-platform-optimization-for-wordpress?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-automatic-platform-optimization-for-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6128:\"

Just a day after launching its new privacy-first web analytics product last week, Cloudflare announced Automatic Platform Optimization (APO) for WordPress. The new service boasts staggering performance improvements for sites that might otherwise be slowed down by shared hosting, slow database lookups, or sluggish plugins:

\n\n\n\n

Our testing… showed a 72% reduction in Time to First Byte (TTFB), 23% reduction to First Contentful Paint, and 13% reduction in Speed Index for desktop users at the 90th percentile, by serving nearly all of your website’s content from Cloudflare’s network. 

\n\n\n\n

APO uses Cloudflare Workers to cache dynamic content and serve the website from its edge network. In most cases this eliminates origin requests and origin processing time. That means visitors requesting your website will get near instant load times. Cloudflare reports that its testing shows APO delivers consistent load times of under 400ms for HTML Time to First Byte (TTFB).

\n\n\n\n

The effects of using APO are similar to hosting static files on a CDN, but without the need to manage a complicated tech stack. Content creators retain their ability to create dynamic websites without any changes to their workflow for the sake of performance.

\n\n\n\n

Version 3.8 of Cloudflare’s official WordPress plugin was recently updated to include support for APO. It detects when users make changes to their content and purges the content stored on Cloudflare’s edge.

\n\n\n\n

The new service is available to Cloudflare users with a single click of a button. APO is included at no cost for existing Cloudflare customers on the Professional, Business, and Enterprise plans. Users on the Free plan can add it to their sites for $5/month. The service is a flat fee and is not metered.

\n\n\n\n

Cloudflare’s announcement has so far been well-received by WordPress professionals and hosting companies and many have already begun testing it.

\n\n\n\n
\n

So the week after @Cloudflare Birthday Week I try and play with as many of the new products as possible. Today was the WordPress APO on my simple demo site. You can see TTFB dropped from ~350ms to ~75ms! https://t.co/zg976EjrZI pic.twitter.com/KuaHqtHLom

— Matt Bullock (@mibullock) October 6, 2020
\n
\n\n\n\n

WordPress lead developer Mark Jaquith called APO “incredible news for the WordPress world.”

\n\n\n\n

“On sites I manage this is going to lower hosting complexity and easily save hundreds of dollars a month in hosting costs,” Jaquith said.

\n\n\n\n

After running several speed tests from six different locations around the world, early testers at Kinsta got remarkable results using APO:

\n\n\n\n

“By caching static HTML on Cloudflare’s edge network, we saw a 70-300% performance increase. As expected, the testing locations furthest away from Tokyo saw the biggest reduction in load time.

“If your WordPress site uses a traditional CDN that only caches CSS, JS, and images, upgrading to Cloudflare’s WordPress APO is a no-brainer and will help you stay competitive with modern Jamstack and static sites that live on the edge by default.”

\n\n\n\n

George Liu, a “self-confessed page speed addict” and Cloudflare Community MVP, performed a series of detailed tests on the new APO product with his blog. After many comparisons, he found that Cloudoflare’s WordPress plugin with APO turned on delivers results similar to his heavily optimized WordPress blog that uses a custom Cloudflare Worker caching configuration.

\n\n\n\n

“You’ll find that Cloudflare WordPress plugin’s one click Automatic Platform Optimization button does wonders for page speed for the average WordPress user not well versed in page speed optimizations,” Liu said.

\n\n\n\n

“Cloudflare’s WordPress plugin Automatic Platform Optimization will in theory beat all other WordPress caching solutions other than you rolling out your own Cloudflare Worker based caching like I did. So you get a good bang for your buck at US$5/month for Cloudflare’s WordPress plugin APO.”

\n\n\n\n

Liu also warned of some speed bumps with the initial rollout, as Cloudflare’s APO supports a limited set of WordPress cookies for bypassing the Cloudflare CDN cache, leaving certain use cases unsupported. APO does not seem to work on subdomains and users are also reporting that it’s not compatible with other caching plugins. It also disables real visitor IP address detection.

\n\n\n\n

Cloudflare is aware of many of these issues, which have been raised in the comments of the announcement, and is in the process of adding more cookies to the list to bypass caching. Due to some plugin conflicts, APO may not be as plug-and-play as it sounds for some users right now, but the product is very promising and should improve over time with more feedback.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 04:18:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Kick off Block-Based WordPress Theme Development With the Theme.json Creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105832\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator?utm_source=rss&utm_medium=rss&utm_campaign=kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4674:\"

Gutenberg 9.1 made a backward-incompatible change to its theme.json file (experimental-theme.json while full-site editing is under the experimental flag). This is the configuration file that theme developers will need to create as part of their block-based themes. Staying up to date with such changes can be a challenge for theme authors, but Ari Stathopoulos, a Themes Team representative, wrote a full guide for developers.

\n\n\n\n

Jon Quach, a Principal Designer at Automattic, has also been busy creating a tool to help theme authors transition to block-based themes. He recently built a UI-based project called Theme.json Creator that builds out the JSON code for theme authors. Plus, it is up to date with the most recent changes in the Gutenberg plugin.

\n\n\n\n

Tools like these will be what the development community needs as it gets over the inevitable hump of moving away from the traditional theme development paradigm and into a new era where themes are made almost entirely of blocks and a config file.

\n\n\n\n

While plugin development is becoming more complex with the addition of JavaScript, theme development is taking a sharp turn toward its roots of HTML and CSS. We are barreling toward a future in which far more people will be able to create WordPress themes. Even the possibility of sharing pieces of themes (e.g., template parts and patterns) is on the table. This could not only empower theme designers by lowering the barrier to entry, it could also empower some end-users to make the jump into theme building.

\n\n\n\n

However, the theme.json file is one aspect of future theme authorship that is extremely developer-oriented. JSON is a universal format shared between various programming languages. It is meant to be read by machines and is not quite as human-friendly as other formats. As the theme.json file grows to accommodate more configuration options over time, the less friendly it will become to simply typing keys and values in.

\n\n\n\n

It makes sense to build tools to simplify this part of the theme building process.

\n\n\n\n

That is where the Theme.json Creator tool comes in. Theme authors pick and choose the options they want to support and input custom values. Then, the tool spits out everything in properly-formatted JSON.

\n\n\n\nUsing the Theme.json Creator tool.\n\n\n\n

One big thing the tool does not yet cover is custom CSS variables. This feature is a recent addition to the theme.json specification. It allows theme authors to create any custom property that WordPress will automatically output as CSS. In his announcement post, Stathopoulos covered how to create a typographic scale with custom properties and use those variables for editor features, such as line-height and font-size values.

\n\n\n\n

Currently, Theme.json Creator’s primary focus is on global styles. However, Gutenberg allows theme authors to configure default styles on the block level. For example, theme designers can set the color or typography options for the core Heading block to be different from the default global styles. This provides theme authors with fine-tuned control over every block.

\n\n\n\n

Theme.json Creator does not yet support configuration at this level. However, it would be interesting to see if Quach adds it in the future.

\n\n\n\n

The focus on setting up global styles is a good start for now. This is still an experimental feature. The great thing about it is that it can help theme authors begin to see how one piece of the block-based themes puzzle fits in. It is a starting point for an entirely new method of adding theme support for features when most are accustomed to adding multiple add_theme_support() PHP function calls.

\n\n\n\n

With the direction that theme development seems to be heading, it is easy to imagine that it could evolve into a completely UI-based affair at some point down the line. If templates are made up of blocks and patterns, which anyone can already build with the block editor, and if styles will essentially boil down to a config file, there will be little-to-no programming required to build a basic WordPress theme.

\n\n\n\n

If someone is not already at least jotting down notes for a plugin that allows users to create and package a block-based theme, I would be surprised. For now, Theme.json Creator is removing the need to write code for at least one part of the theme design process.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 07 Oct 2020 20:53:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: Jetpack 9.0 Introduces Loom Block, Twitter Threads Feature, and Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105743\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4033:\"
\n\n\n\n

Jetpack’s highly anticipated 9.0 release has landed, introducing some of the new features the team has previewed over the past week. Users can now publish WordPress posts to Twitter as threads. This new feature is available as part of the Publicize module when you have connected a Twitter account.

\n\n\n\n

Posting Twitter threads is a feature that only works with the block editor, as it takes advantage of how content is naturally split into chunks (blocks).

\n\n\n\n

In the comments on his demo post, Automattic engineer Gary Pendergast gave a more detailed breakdown of the logic Jetpack uses to ensure full sentences aren’t broken up in the tweets.

\n\n\n\n

“With the mental model now being focused on mapping blocks to tweets, it’s much easier to make logical decisions about how to handle each block,” Pendergast said. “So, a paragraph block is the text of a tweet, if the paragraph is too long for a single tweet, it tries to split the paragraph up by sentences. If a sentence is too long, then it resorts to splitting by words. Then, if there’s an embed/image/video/gallery block following that paragraph, we can attach it to the tweet containing that paragraph. There are additional rules for other blocks, but that’s the basic process. It then just iterates over all of the supported blocks in the post.”

\n\n\n\n

Pendergast published his post as thread to demonstrate the new feature in action. The advantage of posting a thread from your WordPress site is that it doesn’t end up getting lost in Twitter’s fast-moving timeline. Most important Twitter threads evaporate from public consciousness almost as soon as they are published. Publishing threads from your website ensures they are better indexed and easier to reference in the future.

\n\n\n\n

Jetpack Adds Loom Block for Embedding Screen Recordings

\n\n\n\n

Loom was added to Jetpack as a new oEmbed provider three weeks ago. The video recording service allows for recording camera, microphone, and desktop simultaneously. The service is especially popular in educational settings. Jetpack 9.0 introduces a new Loom block for embedding recordings.

\n\n\n\n\n\n\n\n

“Loom is growing in popularity as it is being recommended more and more to assist in distance learning efforts,” Jetpack Director of Innovation Jesse Friedman said. “Now more than ever we want to be able to help those working, learning, and teaching from home. The Loom block was a natural addition to join the other Jetpack video blocks which now include YouTube, TikTok, DailyMotion, and Vimeo.”

\n\n\n\n

Loom’s free tier allows users to record up to 25 videos, but the Pro plan is free for educators. Friedman confirmed that Jetpack does not have any kind of partnership with Loom. The team decided to support the product to assist professionals, educators, and students. Having it available as a block also makes it more convenient for those using P2 for communication.

\n\n\n\n

As anticipated, Jetpack 9.0 also provides a seamless transition necessary to ensure Instagram and Facebook embeds will continue working after Facebook drops unauthenticated oEmbed support on October 24. The Jetpack team reports that it “partnered with Facebook” to make sure these embeds continue to work with the WordPress.com REST API.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 23:28:38 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:51:\"Post Status: Joost de Valk on WordPress marketshare\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=79914\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:62:\"https://poststatus.com/joost-de-valk-on-wordpress-marketshare/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1193:\"

David Bisset makes his podcast debut for Post Status, as he interviews Joost de Valk, Founder and Chief Product Officer of Yoast, and discusses all things WordPress marketshare related.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Jilt

\n\n\n\n

Jilt offers powerful email marketing built for eCommerce. From newsletters to highly segmented automations, Jilt is your one-stop show for eCommerce email. Join thousands of stores that have already earned tens of millions of dollars in extra sales using Jilt. Try Jilt for free

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 22:28:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"WPTavern: iThemes Buys WPComplete, Complementing Its Recent Restrict Content Pro Acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105631\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition?utm_source=rss&utm_medium=rss&utm_campaign=ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4395:\"

Just one month after publicly announcing its acquisition of Restrict Content Pro (RCP), iThemes purchased WPComplete for an undisclosed amount. The acquisition is for the product, website, and customers only.

\n\n\n\n

Paul Jarvis and Zack Gilbert created the WPComplete plugin in 2016. However, it has outgrown what the duo could maintain and support alone. After the transition period in which the new owners take over, the two will step away from the project.

\n\n\n\n

In essence, WPComplete is a “course completion” plugin. Site owners can create online courses while allowing students/users to mark their work as completed. It also gives students a way to track their progress through courses, which can often boost the potential for them to finish.

\n\n\n\n

“Paul and Jack believe a key to their success has been their ability to keep their team small and manageable,” wrote Matt Danner, the COO at iThemes, in the announcement. “The growth of WPComplete has presented a number of challenges for a team of two people, so the decision was made to start looking towards alternative ownership solutions that could continue to grow WPComplete and provide it with a stable team. iThemes is a perfect fit.”

\n\n\n\n

iThemes customers who have a Plugin Suite or Toolkit membership will get automatic access to the pro version of the WPComplete plugin. For current WPComplete users, Danner said everything should be “business as usual.” However, iThemes has assigned a few of its team members to work on the product and site, so customers should see some new faces.

\n\n\n\n

RCP and WPComplete are obviously complementary products. RCP is a membership plugin that allows site owners to restrict content based on that membership. WPComplete allows site members to mark lessons or coursework as completed. “We’ll be rolling out a new bundle later this month that combines both RCP and WPComplete for course and membership creators to take advantage of these two plugins,” said AJ Morris, the Product Innovation and Marketing Manager at iThemes.

\n\n\n\n

WPComplete is still a young product. The free version of the plugin currently has 2,000+ active installs and a solid 4.7 rating on WordPress.org. If marketed as an extension of the RCP plugin, it automatically puts it in front of the eyes of 1,000s of more potential customers. It should be much easier to grow the plugin as part of a membership bundle.

\n\n\n\n

iThemes is making some bold moves in the membership space. It will be interesting to see if the company makes any other acquisitions that could strengthen its product line and help it become more dominant. There is still a ton of room for growth in the membership segment of the market. There is also the potential for integrations with other major plugins.

\n\n\n\n

“Adding WPComplete to the iThemes product lineup also allows us to move more quickly on some plans we have for Restrict Content Pro,” said Danner in the initial announcement. He also vaguely mentioned a couple of ideas the team had in the works but did not go into detail.

\n\n\n\n

With a little prodding, Morris provided some insight into what they are planning for the immediate future. The biggest first step is tackling integration with the block editor. Currently, WPComplete uses shortcodes. The team’s next step is likely to begin with creating block equivalents for those shortcodes.

\n\n\n\n

“After that, we’ve touched on a few deeper integrations with Restrict Content Pro, like the possibility to restrict courses to memberships,” said Morris.

\n\n\n\n

The iThemes team does not plan to stop with WPComplete as part of its product lineup. One of the goals is to use the plugin for the iThemes website itself.

\n\n\n\n

“We always try to eat our own dogfood when we can,” said Morris. “You’ll see that with RCP and WPComplete early next year as we look to integrate them into our iThemes Training membership.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 20:59:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Exploring Full-Site Editing With the Q WordPress Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105676\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/exploring-full-site-editing-with-the-q-wordpress-theme?utm_source=rss&utm_medium=rss&utm_campaign=exploring-full-site-editing-with-the-q-wordpress-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7492:\"

I have been eagerly awaiting the moment when I could install a theme and truly test Gutenberg’s full-site editing feature. By and large, each time I have tested it over the past few months, the experience has felt utterly broken. This is why I have remained skeptical of seeing the feature land in WordPress 5.6 this December.

\n\n\n\n

The Q theme by Ari Stathopoulos is the first theme that seems to be a decent working example. Whether that is a stroke of luck with timing or that this particular theme is simply built correctly is hard to tell — Stathopoulos is a team rep for the Themes Team. Gutenberg 9.1 dropped last week with continued work toward site editing.

\n\n\n\n

Q is as experimental as it gets. The Themes Team put out an open call for experimental, block-based themes as far back as March this year. However, not many have taken the team up on this offer. If approved, Q stands to be the first block-based theme to go live in the official WordPress directory. It still has to work its way through the standard review process, awaiting its turn in the coming weeks.

\n\n\n\n

On the whole, full-site editing remains a frustrating and confusing experience. I still remain skeptical about its readiness, even in beta form, to show off to the world in WordPress 5.6.

\n\n\n\n

However, Q is an interesting theme to explore at this point for both end-users and theme developers. Users can install it and start tinkering with the site editing screen via the Gutenberg plugin. Developers can learn how global styles, templates, and template parts fit together from a working theme.

\n\n\n\n

Using the Site Editor

\n\n\n\nEditing a single post in the site editor.\n\n\n\n

The Q theme requires the Gutenberg plugin and its full-site editing mode to be enabled. Generally, requiring a plugin is not allowed for themes in the directory. However, experimental Gutenberg themes are allowed to bypass this guideline.

\n\n\n\n

Stathopoulos pointed out that the theme is highly experimental and should not be used on a production site. However, he is hopeful that it will get more eyes focused on full-site editing.

\n\n\n\n

He mentioned that several items are broken, such as category archives not showing the correct posts. This is a current limitation of the Query block in Gutenberg. However, one of the best ways to find and recognize these types of issues is to have a theme that stays up with the pace of development.

\n\n\n\n

Currently, the site editor feels like it is biting off more than it can chew. Not only can users edit the layout and design of the page, but they can also directly edit existing post content — don’t try this at home unless you are willing for your post titles to get switched to the hyphenated slug. Should the site editor be handling the double-duty of design and content editing? If so, should design and content editing be handled in separate locations in the long term or be merged into one feature?

\n\n\n\n

It feels raw. It is not geared toward users at this point.

\n\n\n\n

The bright spot with the site editor is the current progress on template parts in the editor. Template parts are essentially “modules” that handle one part of the page. For example, the typical theme will have a header and footer template part. Currently, end-users can insert custom template parts or switch one template part for another. This opens a world of possibilities, such as users choosing between multiple header designs (template parts) for their sites.

\n\n\n\nSwitching the header template part.\n\n\n\n

The downside to the entire template system is that it seems so divorced from the site editor that it is hard to believe the average user would understand what is going on. Templates and template parts reside under the Appearance menu in the admin. The Site Editor is a separate, top-level menu item. Without any preexisting knowledge of how these pieces work together, it can be confusing.

\n\n\n\n

Template parts worked for me in the site editor from the outset. However, they did not work on the front end at first. I continually received the “template part not found” message for hours. Then, at some point — whether through magic or a random save that pulled everything together — the feature began to output the previously-missing header and footer template parts.

\n\n\n\n

Glimpse Into the Future of Theme Development

\n\n\n\n

The Q theme has a scant few style rules, which it loads directly in the <head> section of the site in lieu of adding an extra stylesheet. It relies on the stock Gutenberg block styles on the front end with a few minor overrides. Most other custom styles are handled via the global styles system, which pulls from the theme’s experimental-theme.json config file (will be theme.json in the future).

\n\n\n\n

It begs the question of whether themes will necessarily need much in the way of CSS when full-site editing lands.

\n\n\n\n

If WordPress allows users to configure most styles via block options and global styles overrides, themes may not need much more than their config files. After that, it would come down to registering custom block styles and patterns.

\n\n\n\n

If this is the future that we are headed toward, anyone could essentially create a WordPress theme. And, those pieces, such as template parts and patterns, could all be shared between any site. In that future, themes may simply not matter anymore.

\n\n\n\n

Last year, Mike Schinkel proposed deprecating the theme system altogether and replacing it with web components.

\n\n\n\n

“Rather than look for a theme that has all the features one needs — which I have found always limits the choices to zero — a site owner could look for the components and modules they need and then assemble their site from those modules,” he said. “They could pick a header, a footer, a home-page hero, a set of article cards, a pricing module, and so on.”

\n\n\n\n

The more I tinker with full-site editing, the more it feels like that is the lane that it will ultimately merge into. Imagine a future where end-users could pick and choose the pieces they wanted and simply have it look right on the front end.

\n\n\n\n

It is exciting to think about that possibility. Both Schinkel and I have more of a background in programming than we do in design. It makes sense from that sort of analytical mindset to put everything into neat, reusable boxes because reuse is a cornerstone of smart programming.

\n\n\n\n

However, I worry about the state of design in such a system with so many replaceable parts. Will designers be able to take holistic approaches to theme development, creating truly intricate pieces of art? Will that system essentially create a web of cookie-cutter sites? Or, will designers simply find ways to think outside the box while within the constraints of the block system?

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 21:21:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Virtual Jamstack Conf to Feature Fireside Chat with Matt Mullenweg and Matt Biilmann, October 6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105680\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6?utm_source=rss&utm_medium=rss&utm_campaign=virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2618:\"
image credit: Jamstack Conf
\n\n\n\n

The greater Jamstack community is coming together on October 6-7, 2020, for a virtual conference. Organizers expect more than 15,000 attendees from around the globe over a two-day span that includes keynotes, sessions, interactive topic tables, workshops, speaker Q&As, and networking opportunities.

\n\n\n\n

Matt Mullenweg will be joining Netlify CEO Matt Biilmann on day 1 at 12PM PDT for a fireside chat moderated by CSS-Tricks Creator Chris Coyier. The chat will go deeper on recent topics of contention, including developer sentiment, complexity, security, and performance. Coyier also plans to discuss how the Jamstack and WordPress communities intersect through headless implementations of the CMS.

\n\n\n\n

A provocative post from TheNewStack at the end of August quoted Mullenweg as saying that “JAMstack is a regression for the vast majority of the people adopting it.” This sparked multiple heated exchanges across blogs and social media. Biilimann, who originally coined the term “Jamstack,” wrote a response to Mullenweg’s remarks, hailing “the end of the WordPress era.”

\n\n\n\n

Live conversations tend to be more cordial than shots fired across the blogosphere. It will be interesting to see if Biilimann cares to join Stackbit CEO Ohad Eder-Pressman in his wager that Jamstack will become the predominant architecture for the web by 2025. The fireside chat should be recorded, in case you cannot catch the live session. Recordings of talks from the previous virtual Jamstack event held in May are available on YouTube.

\n\n\n\n

Today is the last call for registration. Many of the workshops have already sold out, but tickets to the regular sessions on October 6 are still available. Sign up on the event website to get your free ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 20:12:50 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Gutenberg 9.1 Adds Patterns Category Dropdown and Reverts Block-Based Widgets in the Customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105629\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5615:\"

Gutenberg 9.1 was released to the public on Wednesday. The team announced over 200 commits from 77 contributors in its release post yesterday. One of the biggest changes to the interface was the addition of a new dropdown selector for block pattern categories. The team also reverted the block-based widgets section in the customizer and added an image size control to the Media & Text block.

\n\n\n\n

One of the main focuses of this release was improving the block-based widgets editor. The feature was taken out of the experimental stage in Gutenberg 8.9 and continues to improve. The widgets screen now uses the same inserter UI as the post-editing screen. However, users can currently only insert regular blocks. Patterns and reusable blocks are still not included.

\n\n\n\n

Theme authors can now control aspects of the block editor via a custom theme.json file. This is part of the ongoing Global Styles project, which will allow theme authors to configure features for their users.

\n\n\n\n

The development team has also added an explicit box-sizing style rule to the Cover and Group blocks. This is to avoid any potential issues with the new padding/spacing options. Theme authors who rely on the block editor styles should test their themes to make sure this change does not break anything.

\n\n\n\n

Better Pattern Organization

\n\n\n\nNew block patterns UI in the inserter.\n\n\n\n

I have been calling for the return of the tabbed pattern categories since Gutenberg 8.0, which was a regression from previous versions. For 11 versions, users have had to scroll and scroll and scroll through every block pattern just to find the one they wanted. The development team has sought to address this issue by using a category dropdown selector. When selecting a specific category, its patterns will appear.

\n\n\n\n

At first, I was unsure about this method over the old tabbed method. However, after some use, it feels like the right direction.

\n\n\n\n

As more and more theme and plugin authors add block pattern categories to users’ sites, the dropdown is a more sensible route. Even tabs could become unwieldy over time. The dropdown better organizes the list of categories and makes the UI cleaner. More than anything, I am enjoying the experience and look forward to this eventually landing in WordPress 5.6 later this year.

\n\n\n\n

Customizer Widgets Reverted

\n\n\n\nReverted widgets panel in the customizer.\n\n\n\n

On the subject of WordPress 5.6, one of its flagship features has been hitting some roadblocks. Block-based widgets are expected to land in core with the December release, but the team just reverted part of the feature. They had to remove the widgets block editor from the customizer they added just two major releases ago.

\n\n\n\n

It was for the best. The customizer’s block-based widgets editor was fundamentally broken. It was not ready for primetime and should have remained in the experimental stage until it was somewhat usable.

\n\n\n\n

“I will approve this since the current state of the customizer in the Gutenberg plugin is broken, and there is no clear path forward about how to fix that,” wrote Andrei Draganescu in the reversion ticket. “With this patch, the normal widgets can still be edited in the customizer and the block ones don’t break it anymore. This is NOT to mean that we won’t proceed with fixing the block editor in the customizer, that is still an ongoing discussion.”

\n\n\n\n

The current state of editing widgets via the customizer is at least workable with this change. If end-users add a block via the admin-side widgets editor, it will merely appear as an uneditable, faux widget named “Block” in the customizer. They will need to edit blocks via the normal widgets screen.

\n\n\n\n

There is no way that WordPress can ship the current solution when 5.6 rolls out. However, we are still two months out. This leaves plenty of time for a fix, but Draganescu’s note that “there is no clear path forward” may make some people a bit uneasy at this stage of development.

\n\n\n\n

Control Image Size for Media & Text

\n\n\n\nImage size dropdown selector for the Media & Text block.\n\n\n\n

One of the bright spots in this update is the addition of an image size control to the Media & Text block. Like the normal Image block, end-users can choose from any registered image size created for their uploaded image.

\n\n\n\n

This is a feature I have been looking forward to in particular. Previously, using the full-sized image often made the page weight a bit heftier than necessary. It is also nice to go along with themes that register sizes for both landscape and portrait orientations, giving users more options.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 20:56:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"WordPress.org blog: The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8711:\"

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches New Web Analytics Product Focusing on Privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105446\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-new-web-analytics-product-focusing-on-privacy?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-new-web-analytics-product-focusing-on-privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2448:\"

In pursuit of “democratizing web analytics,” Cloudflare announced it is launching privacy-first analytics as a new standalone product. The company is entering a market that has been dominated by Google Analytics for years but with a major differentiating feature – it will not track individual users by a cookie or IP address to show unique visits.

\n\n\n\n

Cloudflare Web Analytics defines a visit as “a successful page view that has an HTTP referer that doesn’t match the hostname of the request.” It’s not the same as Google’s “unique” metric, and Cloudflare says it may differ from other reporting tools. Weeding out bots from the total traffic numbers is a nascent feature that Cloudflare is improving as part of its Bot Management product.

\n\n\n\n
\n\n\n\n

Cloudflare Web Analytics is launching with features that are largely similar to Google Analytics but with some unique ways of zooming into different traffic segments and time ranges to see where traffic is originating from.

\n\n\n\n

“The most popular analytics services available were built to help ad-supported sites sell more ads,” Cloudflare product manager Jon Levine said. “But, a lot of websites don’t have ads. So if you use those services, you’re giving up the privacy of your users in order to understand how what you’ve put online is performing.

\n\n\n\n

“Cloudflare’s business has never been built around tracking users or selling advertising. We don’t want to know what you do on the Internet — it’s not our business.”

\n\n\n\n

Paying customers on the Pro, Biz, and Enterprise plans can access their analytics from their dashboards immediately. Cloudflare is also offering the product for free as JavaScript-based analytics for users who are not currently customers. Those who want access to the free plan can sign up for the waitlist.

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 04:03:01 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"WPTavern: Virtual WordPress Page Builder Summit Kicks Off October 5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105570\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:179:\"https://wptavern.com/virtual-wordpress-page-builder-summit-kicks-off-october-5?utm_source=rss&utm_medium=rss&utm_campaign=virtual-wordpress-page-builder-summit-kicks-off-october-5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6348:\"

From October 5 through October 9, the first Page Builder Summit will open its virtual doors to all attendees for free. Nathan Wrigley, the podcaster behind WP Builds, and Anchen le Roux, the founder and lead developer of Simply Digital Design, are hosting the five-day online event that focuses on the vast ecosystem of page builders for WordPress.

\n\n\n\n

The summit will include 35 sessions spread out over the event schedule. Each session will last around 30 minutes, so it will be easy to pop in and watch one in your downtime. Sessions will cover a range of builders, including the default WordPress block editor, Elementor, Beaver Builder, Oxygen, Brizy, and Divi.

\n\n\n\n

“It’s an event specifically for users of WordPress page builders, or those curious about what they can do,” said Wrigley. “I feel like a page builder style interface for creating websites is the future for our industry. WordPress itself is moving in this direction with the block editor (a.k.a. Gutenberg). With that in mind, it seemed like a good idea to create a dedicated event to share knowledge about this side of WordPress. We’ve tried to include presentations from as many page builders as we could.”

\n\n\n\n

Wrigley made sure to point out that it is not all geared toward developers, discussing the inner-workings of builders. Some of the sessions focus on marketing, optimization, and conversion, which provides a wider range of topics for potential attendees.

\n\n\n\n

The summit hosts created an online quiz for those who are unsure about which sessions to watch.

\n\n\n\n

There is a small catch. The sessions will be freely available only from the time they begin and the following 24 hours. After that, accessing the videos will come at a premium. Attendees can gain lifetime access to the PowerPack for $47 if they purchase within 15 minutes of signing up. Then, prices will rise to $97 until the event kicks off on October 5. Beyond, the price jumps to $147. The lifetime access includes access to the presentations, transcripts, a workbook, and other bonuses from the speakers.

\n\n\n\n

For those unsure about forking over the cash, they can still watch the sessions during the 24-hour window.

\n\n\n\n

The proceeds from the event will go out to paying affiliate commissions to speakers and partners. Some of it will go into planning and investing in a second summit down the road.

\n\n\n\n

“Both myself and Nathan have specific charities that we want to donate to after the event,” said le Roux. “It was part of our goals to be able to do this, but we didn’t want to make this an official contribution.”

\n\n\n\n

Why a Page Builder Summit?

\n\n\n\n

Both Wrigley and le Roux have their preferred builders. But, the goal of the summit is to offer a wide look at the tools available and help freelancers and agencies better streamline their businesses and create happier clients.

\n\n\n\n

“I’ve been a user of page builders for many years, but only at the point where they truly showed in the editing interface something that almost perfectly reflected what the end-user would see did I get really immersed,” said Wrigley. “Having come from a background in which I built entire websites from a collection of text files (HTML, CSS, PHP, etc.), I was fascinated that we’d reached a point where the learning curve for building a good website was significantly reduced.”

\n\n\n\n

He pointed out that it is not always so simple though. While the same level of coding skills may not be necessary, people must figure out how to navigate their preferred page builder, which can come with its own learning curve.

\n\n\n\n

“You need to learn their way of doing things and how to achieve your design choices,” he said. “It’s always going to work out better if you know the code, but the WordPress mission of democratizing publishing certainly seems to align quite nicely with the adoption of tools, like page builders, which mean that once-difficult tasks are now easier.”

\n\n\n\n

For le Roux, her interest in hosting the Page Builder Summit falls back to her design studio.

\n\n\n\n

“As a developer, my main reason for switching to page builders was around streamlining and creating more efficient but quality websites in the shortest amount of time,” she said. “Especially now that we focus on day rates, creating the best possible website that clients would love fast would not have been possible without page builders.”

\n\n\n\n

The Hosts’ Go-To Builders

\n\n\n\n

“We prefer using Beaver Builder with Themer at Simply Digital Design,” said le Roux. “We use Gutenberg for blog posts or where possible with custom post types or LMS software. However, we’ve also taken on a few Elementor projects where that’s the client’s preferred option.”

\n\n\n\n

Wrigley uses some of the same tools. His main work is on the WP Builds website where he hosts podcasts.

\n\n\n\n

“I have used Beaver Builder’s Themer to create templates for specific layouts, but for content creation within those layouts I’m using the block editor,” said Wrigley. “My content is mainly text and the WordPress editor is utterly remarkable in this situation. I kept the classic editor installed for a few months after WordPress 5.0 came about, but I soon realized that this was folly and that the editing interface of Gutenberg is superior. The ability to insert and move text, buttons, etc. is such a joy to work with, and the iterations that have been made in the last two years make it, in my opinion, the best text editing experience on the web.”

\n\n\n\n

Wrigley sees a future in which the WordPress block editor takes over much of the work that page builders are currently handling. However, that future is “still over the horizon.”

\n\n\n\n

“I’m excited about this future though, and we’ve got a few crystal ball-gazing presentations; trying to work out what that future might look like,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 20:31:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"WPTavern: Jetpack 9.0 to Introduce New Feature for Publishing WordPress Posts to Twitter as Threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105448\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3318:\"

Jetpack 9.0, coming on October 6, will debut a new feature that allows users to share blog posts as Twitter threads in multiples tweets. A recent version of Jetpack introduced the ability to import and unroll tweetstorms for publishing inside a post. The 9.0 release will run it back the other way so the content originates in WordPress, yet still reaps all the same benefits of circulation on Twitter as a thread.

\n\n\n\n

The new Twitter threads feature is being added as part of Jetpack’s Publicize module under the Twitter settings. After linking up a Twitter account, the Jetpack sidebar options for Publicize allow users to publish to Twitter as a link to the blog or a set of threaded tweets. It’s not just limited to text content – the threads feature will also upload and attach any images and videos included in the post.

\n\n\n\n\n\n\n\n

When first introduced to the idea of publishing a Twitter thread from WordPress, I wondered if threads might lose their trademark pithy punch, since users aren’t forced to keep each segment to the standard length of a tweet. Would each tweet be separated in an odd, unreadable way? The Jetpack team anticipated this, so the thread option adds more information to the block editor to show where the paragraphs will be split into multiple tweets.

\n\n\n\n

“Threads are wildly underused on Twitter,” Gary Pendergast said in a post introducing the feature. “I think a big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.” The tool Pendergast has been working on for Jetpack gives users the best of both worlds.

\n\n\n\n

In response to a comment requesting Automattic “concentrate on tools to get people off social media,” Pendergast said, “If we’re also able to improve the quality of conversations on social media, I think it’d be remiss of us to not do so.” He also credits IndieWeb discussions on Tweetstorms and POSSE (Publish (on your) Own Site, Syndicate Elsewhere) as inspirations for the feature.

\n\n\n\n

For years, blogging advocates have tried to convince those who post lengthy tweetstorms to switch to a publishing medium that is more suitable to the length of their thoughts. The problem is that Twitter users lose so much of the immediate feedback and momentum that their thoughts would have generated when composed as a tweetstorm.

\n\n\n\n

Instead of lecturing people about how they should really be blogging instead of tweetstorming, Jetpack is taking a fresh approach by enabling full content ownership with effortless social syndication. You can test out the experience for yourself by adding the Jetpack Beta Testers plugin and running the 9.0 RC version on your site.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 02:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: Ask the Bartender: How To WordPress in a Block World?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105491\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:167:\"https://wptavern.com/ask-the-bartender-how-to-wordpress-in-a-block-world?utm_source=rss&utm_medium=rss&utm_campaign=ask-the-bartender-how-to-wordpress-in-a-block-world\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9755:\"

I love your articles. And now, in the middle of the WordPress revolution, I realized I’m constantly searching for an answer regarding WP these days.

So many things are being said, so many previsions of the future, problems, etc., but, right now, I think I, as a designer, just want to understand one thing that seemed answered already but it’s never clear:

Is WordPress a good choice to build a client’s template where he just has to insert the info that will show in the frontend where I want to? And he doesn’t have to worry about formatting blocks? I love blocks, don’t get me wrong, but will normal templating end?

I just think that having a super CMS, HTML, CSS, and being able to play with a database with ACF is so powerful, that I’m wondering if it’s lost. After so much reading, I still don’t understand if this paradigm is going to disappear.

Right now, I don’t know if it’s best to stop making websites as I used to and adopt block patterns instead.

Ricardo
\n\n\n\n

WordPress is definitely changing. Over the past two years, we have seen much of it reshaped into something different from the previous decade and more. However, this is not new. WordPress has always been a constantly-changing platform. It just feels far too different this time around, almost foreign to many. The platform had to make a leap. Otherwise, it would have started falling behind.

\n\n\n\n

And, it is a big ask of the existing community to come along with it, to take that leap together.

\n\n\n\n

It can be scary as a developer whose livelihood has depended on things working a certain way or who has built tools and systems around pre-block WordPress. Many freelancers and agencies had their world turned upside down with the launch of the block editor. It is perfectly OK to feel a bit lost.

\n\n\n\n

Now, it is time for a little tough love. It has been two years. As a professional, you need to have a plan in place already. Whether that is an educational plan for yourself or a transitional plan for your clients, you should already be tackling projects that leverage the block editor. If you are at a point where you have not been building with blocks, you are now behind. However, you can still catch up and continue advancing in your WordPress career.

\n\n\n\n

There are so many changes coming down the pipeline that anyone who plans to develop for WordPress will be in continual education mode for years to come.

\n\n\n\n

When building for clients, the biggest thing to remember is that it is not about you. It is about getting something into the hands of your clients that addresses their specific needs. Freelancers and agencies need to often be the Jacks and Jills of all trades. Sometimes, this even means having a backup CMS or two that you can use that are not named WordPress. It helps to be well-rounded enough to jump around when needed, especially if you are not at a point in your career where you can demand specific work and pass on jobs that would put food on the table.

\n\n\n\n

It is also easy to look at every job as a nail and WordPress as the hammer. Or, even specific plugins as the tool that will always get the job done. I have seen developers in the past rely on tools like ACF, CMB2, or Meta Box but could not code a custom metadata solution when necessary to save their life. Sometimes a bigger toolbox is necessary.

\n\n\n\n

Every WordPress developer needs a solid, foundational understanding of the languages that WordPress uses. Gone are the days of skating by on HTML, CSS, and PHP knowledge. You need to learn JavaScript deeply. Matt Mullenweg, the co-founder of WordPress, was not joking around when he said this back in 2015. It holds true more and more each day. In another five years, it will tough to be a developer in the WordPress world without knowing JavaScript, at least for backend work.

\n\n\n\n

It also depends on what types of sites you are building. If you are primarily handling front-end design, you will likely be able to get by with a lower skill level. You will just need to know the “WordPress way” of building themes.

\n\n\n\n

Within the next year, you should be able to build just about any theme design with decent CSS and HTML knowledge along with an understanding of how the block system works. Full-site editing and block-based themes will change how we build the front end of the web. It is going to be a challenging transition at first, especially for those of us who are steeped in traditional theme development, but client sites will often be far easier to build. I highly recommend the twice-monthly block-based themes meetings if your focus is on the front end.

\n\n\n\n

Block Templates

\n\n\n\n

Based on your question, I am going to make some assumptions. You have a history of essentially building out meta boxes via ACF where the client just pops in their data. Then, you format that data on the front end. You are likely mixing this with custom post types (CPTs). This is a fairly common scenario.

\n\n\n\n

One of the great things about the block system is that you can lock the post editor for individual CPTs. WordPress already has you covered with its block templates feature, which allows you to define just what a post should look like. You can set up which blocks you want to appear and have the client drop their content in. At the moment, this feature is limited to the post type level. However, it should grow more robust over time, particularly when it works alongside the traditional “page templates” system.

\n\n\n\n

Block templates are a powerful tool in the ol’ toolbox that will come in handy when building client sites.

\n\n\n\n

Block Patterns

\n\n\n\n

You do not have to stop making websites as you are accustomed to at the moment. However, you should start leveraging new block features as they become available and make sense for a specific project. I am a fanatic when it comes to block patterns, so my bias will definitely show.

\n\n\n\n

The biggest thing with block patterns and clients is education. For the uninitiated, you will need to spend some time teaching them how to insert a pattern and how it can be used to their advantage. That is the hurdle you must jump.

\n\n\n\n

For many of the users that I have seen introduced to well-designed patterns, they have fallen in love with the feature. Even many who were reluctant to switch to the block editor became far more comfortable working with it after learning how patterns worked. This is not the case for every user or client, but it has been a good introduction point to the block editor for many.

\n\n\n\n

To answer your question regarding patterns: yes, you should absolutely begin to adopt them.

\n\n\n\n

ACF Is Evolving

\n\n\n\n

Because you are accustomed to ACF, you should be aware that the framework is evolving to keep up with the block editor. Version 5.8.0 introduced a PHP framework for creating custom blocks over a year ago. And, it has been improving ever since. There are even projects like ACF Blocks, which will provide even more tools for your arsenal.

\n\n\n\n

It is important to learn from what some of the larger agencies are doing. Read up on how WebDevStudios is tackling block development. The company also has an open-source block library for ACF.

\n\n\n\n

Solving Problems

\n\n\n\n

Your job as a developer is to be a problem solver. Whatever system you are building with is merely a part of your toolset. You need to be able to solve issues regardless of what tool you are using. At the end of the day, it is just code. If you can learn HTML, you can learn CSS. If you can learn those, you can learn PHP. And, if you can manage PHP, you can certainly pick up JavaScript.

\n\n\n\n

A decade or two from now, you will need to learn something else to stay relevant in your career. Web technology changes. You must change with it. Always consider yourself a student and continue your education. Surround yourself and learn from those who are more advanced than you. Emulate, borrow, and steal good ideas. Use what you have learned to make them great.

\n\n\n\n

There is no answer I can give that will be perfect for every scenario. Each client is unique, and you will need to decide the best direction for each.

\n\n\n\n

However, yes, you should already be on the path to building with a block-first mindset if you plan to continue working with WordPress for the long haul. Immerse yourself in the system. Read, study, and build something any chance you get.

\n\n\n\n

This is the first post in the Ask the Bartender series. Have a question of your own? Shoot it over.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Sep 2020 20:35:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: Supercharge the Default WordPress Theme With Twentig, a Toolbox for Twenty Twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:225:\"https://wptavern.com/supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty?utm_source=rss&utm_medium=rss&utm_campaign=supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6455:\"Custom page pattern from the Twentig plugin.\n\n\n\n

I am often on the hunt for those hidden gems when it comes to block-related plugins. I like to see the interesting places that plugin authors venture. That is why it came as a surprise when someone recommended I check out the Twentig plugin a few days ago. Somehow, it has flown under my radar for months. And, it has managed to do this while being one of the more interesting plugins for WordPress I have seen in the past year.

\n\n\n\n

Twentig is a plugin that essentially gives superpowers to the default Twenty Twenty theme. Diane and Yann Collet are the sibling co-founders and brains behind the plugin.

\n\n\n\n

While I have been generally a fan of Twenty Twenty since it was first bundled in core, it was almost a bit of a letdown in some ways. It was supposed to be the theme that truly showcased what the block editor could do — and it does a fine job of styling the default blocks — but there was a lot of potential left on the table. The Twentig plugin turns Twenty Twenty into something worthier of a showcase for the block editor. It is that missing piece, that extra mile in which WordPress should be marching its default themes.

\n\n\n\n

While the new Twenty Twenty-One default theme is just around the corner, Twentig is breathing new life into the past year’s theme. The developers behind the plugin are still fixing bugs and bringing new features users.

\n\n\n\n

Of its 34 reviews on WordPress.org, Twentig has earned a solid five-star rating. That is a nice score for a plugin with only 4,000 active installations. As I said, it has flown under the radar a bit, but the users who have found it have obviously discovered something that adds those extra touches to their sites they need.

\n\n\n\n

What Does Twentig Do?

\n\n\n\n

It is a toolbox for Twenty Twenty. The headline feature is its block editor features, such as custom patterns and page layouts. It also offers a slew of customizer options that allow end-users to put their own design spin on the default theme. However, my interest is primarily in how it extends the block editor.

\n\n\n\n

Let’s get this out of the way up front. Twentig’s one downside is that it adds a significant amount of additional CSS on top of the already-heavy Twenty Twenty and block editor styles. I will blame the current lack of a full design system from WordPress on most of this. Styling for the block editor can easily bloat a stylesheet. Adding an extra 100+ kb per page load might be a blocker for some who would like to try the plugin. Users will need to weigh the trade-offs between the additional features and the added page size.

\n\n\n\n

The thing that makes Twentig special is its extensive patterns and pages library, which offers one-click access to hundreds of layouts specifically catered to the Twenty Twenty theme.

\n\n\n\nInserting one of the hero patterns.\n\n\n\n

It took me a few minutes to figure out how to access the patterns — mainly because I did not read the manual. I expected to find them mixed in with the core patterns inserter. However, the plugin adds a new sidebar panel to the editor, which users can access by clicking the “tw” icon. After seeing the list of options, I can understand why they probably would not fit into WordPress’s limited block and patterns inserter UI.

\n\n\n\n

It would be easier to list what the plugin does not have than to go through each of the custom patterns and pages.

\n\n\n\n

The one thing that truly sets this plugin apart from the dozens of other block-library types of plugins is that there are no hiccups with the design. Almost every similar plugin or tool I have tested has had CSS conflicts with themes because they are trying to be a tool for every user. Twentig specifically targets the Twenty Twenty theme, which means it does not have to worry about whether it looks good with the other thousands of themes out there. It has one job, which is to extend its preferred theme, and it does it with well-designed block output.

\n\n\n\n

The other aspect of this is that it does not introduce new blocks. Every pattern and page layout option uses the core WordPress blocks, which includes everything from hero sections to testimonials to pricing tables to event listings. And more.

\n\n\n\n

Twentig does not stop adding features to the block editor with custom patterns. The useful and sometimes fun bits are on the individual block level, and I have yet to explore everything. I continue to discover new settings each time I open my editor.

\n\n\n\n

Whether it is custom pullquote styles, a photo image frame, or an inner border tweak to the Cover block (shown below), the plugin adds little extras that push what users can do with their content.

\n\n\n\nInner border style for the Cover block.\n\n\n\n

Each block also gets some basic top and bottom margin options, which comes in handy when laying out a page. At this point, I am simply looking forward to discovering features I have yet to find.

\n\n\n\n

Areas Themes Should Explore

\n\n\n\n

One of the things I dislike about many of these features being within the Twentig plugin is that I would like to see them within the Twenty Twenty theme instead. Obviously not every feature belongs in the theme — some features firmly land in plugin territory. The default WordPress themes should also leave some room for plugin authors to explore. But, shipping some of the more prominent patterns and styles with Twenty Twenty would make a more robust experience for the average end-user looking to get the most out of blocks.

\n\n\n\n

Block patterns were not a core WordPress feature when Twenty Twenty landed. However, for the upcoming Twenty Twenty-One theme, which is expected to bundle some unique patterns, the design team should explore what the Twentig plugin has brought to the current default. That is the direction that theme development should be heading, and theme developers can learn a lot by stealing borrowing from this plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 22:00:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Coming in Jetpack 9.0: Shortcode Embeds Module Updated to Handle Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105381\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2938:\"

Facebook and Instagram are dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers in an upcoming release. After evaluating third-party solutions, WordPress VIP is recommending its partners enable Jetpack’s Shortcode Embeds module. Jetpack will be shipping the update in its 9.0 release, which is anticipated to land prior to the October 24th deadline.

\n\n\n\n

The module is being updated to provide a seamless transition for users who might otherwise be negatively impacted by Facebook’s upcoming API change. WordPress contributors have run some simulations but are not yet sure what will happen to the display for previously embedded content.

\n\n\n\n

“It is possible that they change the contents of the JS file to manipulate cached embeds, perhaps to display a warning that the site is using an old method to embed content or that the request is not properly authenticated,” Jonathan Desrosiers commented on the trac ticket for removing the oEmbed providers.

\n\n\n\n

WordPress.com VIP roughly outlined what users can expect if they do not enable a solution to begin authenticating oEmbeds:

\n\n\n\n

By default, WordPress caches oEmbed contents in post metadata. These embeds will continue to display in previously-published content. If you edit older posts in the Block Editor, regardless of whether you update the post by saving changes, the embeds in the post will no longer be cached and will stop displaying. If you view these older posts using the Classic Editor, so long as the post is not re-saved, the embeds will continue to function and display properly. If you update the post content, the embed will cease functioning unless you have a mitigation installed.

\n\n\n\n

Although WordPress VIP recommends using the Jetpack module as the best solution, self-hosted WordPress users may want to investigate other options if they are not already using Jetpack. oEmbed Plus is a free plugin created specifically for solving the problem of WordPress dropping Facebook and Instagram as oEmbed providers but it is more work to set up and configure. It requires users to register as a Facebook developer and create an app to get API credentials.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 21:18:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"WPTavern: W3C Selects Craft CMS for Redesign Project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105265\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:149:\"https://wptavern.com/w3c-selects-craft-cms-for-redesign-project?utm_source=rss&utm_medium=rss&utm_campaign=w3c-selects-craft-cms-for-redesign-project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9407:\"

W3C has selected Craft CMS over Statamic for its redesign project, after dropping WordPress from consideration in an earlier round of elimination:

\n\n\n\n

In the end, our decision mostly came down to available resources. Craft had already committed to reach AA compliance in Craft 4 (it is currently on version 3.5, the release of version 4 is planned for April 2021). They had also arranged for an external agency to provide them with accessibility issues to tackle weekly. In the end, they decided instead to hire an in-house accessibility specialist to perform assessments and assist the development team in adopting accessibility patterns in the long run.

W3C CMS Selection Report
\n\n\n\n

Last week we published a post urging W3C to revisit Gutenberg for a fair shake against the proprietary CMS’s or consider adopting another open source option. During the selection process, Studio 24, the agency contracted for the redesign, cited its extensive experience with WordPress as the reason for not performing any accessibility testing on more recent versions of Gutenberg.

\n\n\n\n

When asked if the team contacted anyone from WordPress’ Accessibility Team during the process or put Gutenberg through the same tests as the proprietary CMS’s, Studio 24 founder Simon Jones confirmed they had not.

\n\n\n\n

“No, we only reached out to the two shortlisted CMS’s” Jones said. “I’m afraid we didn’t have time to do more. We did test GB a few months ago based on editing content – though it wasn’t the only factor in our choice. As an agency we do plan to keep reviewing GB in the future.”

\n\n\n\n

In response to our concerns regarding licensing, Jones penned an update titled “On not choosing WordPress,” which further elaborated on the reasons why the agency was not inclined towards using or evaluating the new editor:

\n\n\n\n

From a business perspective I also believe Gutenberg creates a complexity issue that makes it challenging for use by many agencies who create custom websites for clients; where we have a need to create lots of bespoke blocks and page elements for individual client projects.

The use of React complicates front-end build. We have very talented front-end developers, however, they are not React experts – nor should they need to be. I believe front-end should be built as standards-compliant HTML/CSS with JavaScript used to enrich functionality where necessary and appropriate.

As of yet, we have not found a satisfactory (and profitable) way to build custom Gutenberg blocks for commercial projects. 

\n\n\n\n

The CMS selection report also stated that W3C needs the CMS to be “usable by non-sighted users” by the launch date, since some members of the staff who contribute to the website are non-sighted.

\n\n\n\n

Since the most recent version of WordPress was not tested in comparison with the proprietary CMS’s, it’s unclear how much better they handle accessibility. Ultimately, W3C and Studio 24 were more comfortable moving forward with a proprietary vendor that was able to make certain assurances about the future accessibility of its authoring tool, despite having a smaller pool of contributors.

\n\n\n\n

“[I’m] also deeply curious since the cursory notes on accessibility for both of the reviewed CMSes seem to highlight a ton of issues like ‘Buttons and Checkboxes are built using div elements’ or most inputs lacking clear focus styles,” Gutenberg technical lead Matías Ventura said. “An element like the Calendar for choosing a post date seems entirely inoperable with keyboard on Craft, for example, while WordPress’ has had significant effort and rounds of feedback poured into that element alone to make it fully operable.”

\n\n\n\n

WordPress developer Anthony Burchell commented on how using a relatively new proprietary CMS seemed counter to W3C’s stated goal to select an option on the basis of longevity. Craft CMS’s continued success is contingent upon its business model and the company’s ability to remain profitable.

\n\n\n\n

“FOSS have the same opportunity of direct access to developers,” Burchell said. “I recognize there are many accessibility shortcomings in popular software, but I think it’s more constructive to rally behind and contribute, not use a proprietary CMS that boasts beer budget in their guidelines.”

\n\n\n\n

On the other side of the issue, accessibility advocates took the W3C’s decision as a referendum on Gutenberg’s continued struggles to meet WCAG AA standards. WordPress accessibility specialist Amanda Rush said it was “nice to see the W3C flip tables over this.”

\n\n\n\n

“Gutenberg is not mature software,” accessibility consultant and WordPress contributor Joe Dolson said in a post elaborating on his comments at WPCampus 2020 Online. He emphasized the lack of stability in the project that Studio 24 alluded to when documenting the reasons against using WordPress.

\n\n\n\n

“It is still undergoing rapid changes, and has grand goals to add a full-site editing experience for WordPress that almost guarantees that it will continue to undergo rapid changes for the next few years,” Dolson said. “Why would any organization that is investing a large amount into a site that they presumably hope will last another 10 years want to invest in something this uncertain?”

\n\n\n\n

Dolson also said the accessibility improvements he referenced regarding the audit were only a small part of the whole picture.

\n\n\n\n

“They only encompass issues that existed in the spring of 2019,” he said. “Since then, many features have been added and changed, and those features both resolve issues and have created new ones. The accessibility team is constantly playing catch up to try and provide enough support to improve Gutenberg. And even now, while it is more or less accessible, there are critical features that are not yet implemented. There are entirely new interface patterns introduced on a regular basis that break prior accessibility expectations.”

\n\n\n\n

WordPress is also being used by millions of people who are constantly reporting issues to fuel the software’s continued refinement, which increases the backlog of issues. Unfortunately, Studio 24 did not properly evaluate Gutenberg against the proprietary CMS’s in order to determine if these software projects are in any better shape.

\n\n\n\n

Instead, they decided that Craft CMS’s community was more receptive to collaborating on issues without reaching out to WordPress. Given the W3C’s stated preference for open source software, WordPress, as the only CMS under consideration with an OSD-compliant license, should have received the same accessibility evaluation.

\n\n\n\n

“I can’t make any statements that would be meaningful about the other content management systems under consideration; but if WordPress wants to be taken seriously in environments where accessibility is a legal, ethical, and mission imperative, there’s still a lot of work to be done,” Dolson said.

\n\n\n\n

Studio 24’s evaluation may not have been equitable to the only open source CMS under consideration, but the situation serves to highlight a unique quandary: when using open source software becomes the impractical choice for organizations requiring a high level of accessibility in their authoring tools.

\n\n\n\n

“Studio 24 ultimately determined that working with a CMS to make it better was more possible with a smaller, proprietary vendor than with a large open-source project,” accessibility advocate Brian DeConinck said. “Project leadership would be more receptive, and the smaller community means changes can be made more quickly. That should prompt a lot of soul-searching for…well, everyone. What does that say about the future of open source?”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 04:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"Gary: More than 280 characters\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"https://pento.net/?p=5405\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://pento.net/2020/09/29/more-than-280-characters/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5187:\"

It’s hard to be nuanced in 280 characters.

\n\n\n\n

The Twitter character limit is a major factor of what can make it so much fun to use: you can read, publish, and interact, in extremely short, digestible chunks. But, it doesn’t fit every topic, ever time. Sometimes you want to talk about complex topics, having honest, thoughtful discussions. In an environment that encourages hot takes, however, it’s often easier to just avoid having those discussions. I can’t blame people for doing that, either: I find myself taking extended breaks from Twitter, as it can easily become overwhelming.

\n\n\n\n

For me, the exception is Twitter threads.

\n\n\n\n

Twitter threads encourage nuance and creativity.

\n\n\n\n

Creative masterpieces like this Choose Your Own Adventure are not just possible, they rely on Twitter threads being the way they are.

\n\n\n\n
\n

Being Beyoncé’s assistant for the day: DONT GET FIRED THREAD pic.twitter.com/26ix05Hkhp

— green chyna (@CORNYASSBITCH) June 23, 2019
\n
\n\n\n\n

Publishing a short essay about your experiences in your job can bring attention to inequality.

\n\n\n\n
\n

DOWNTOWN BROOKLYN: I\'m working arraignments tonight, representing poor New Yorkers who were arrested yesterday on Thanksgiving.

It was the coldest Thanksgiving in more than a century. Tonight\'s also bitterly cold, even in the courtroom. I\'m wearing my scarf & coat.

— Rebecca Kavanagh (@DrRJKavanagh) November 24, 2018
\n
\n\n\n\n

And Tumblr screenshot threads are always fun to read, even when they take a turn for the epic (over 4000 tweets in this thread, and it isn’t slowing down!)

\n\n\n\n
\n

Tumblr textposts thread, probably?

— we are a family forged in bureaucracy (@ex_aItiora) August 26, 2019
\n
\n\n\n\n

Everyone can think of threads that they’ve loved reading.

\n\n\n\n

My point is, threads are wildly underused on Twitter. I think I big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.

\n\n\n\n

To help make this easier, I’ve been working on a tool that will help you publish an entire post to Twitter from your WordPress site, as a thread. It takes care of transforming your post into Twitter-friendly content, you can just… write. \"?\"

\n\n\n\n

It doesn’t just handle the tweet embeds from earlier in the thread: it handles handle uploading and attaching any images and videos you’ve included in your post.

\n\n\n\n\n\n\n\n

All sorts of embeds work, too. \"?\"

\n\n\n\n
\n
\n
\n\n\n\n

It’ll be coming in Jetpack 9.0 (due out October 6), but you can try it now in the latest Jetpack Beta! Check it out and tell me what you think. \"?\"

\n\n\n\n

This might not fix all of Twitter’s problems, but I hope it’ll help you enjoy reading and writing on Twitter a little more. \"?\"

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 02:33:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Gary\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Themes Team Releases a Web Fonts Loader, Likely To Prohibit Hotlinking Any Off-Site Assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105363\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets?utm_source=rss&utm_medium=rss&utm_campaign=themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5815:\"

Last Friday, the WordPress Themes Team announced the release of its new Webfonts Loader project. It is a drop-in script that allows theme authors to load web fonts from the user’s site instead of a third-party CDN. The secondary message included in the team’s announcement is that it no longer plans to allow themes to hotlink Google Fonts in the future.

\n\n\n\n

Throughout most of the team’s history, it has not allowed themes to hotlink or use CDNs for hosting theme assets, such as CSS, JavaScript, and fonts. The one exception to this rule was the use of Google Fonts. This allowed themes to have richer typography options at their disposal from what the team has generally declared a reliable source.

\n\n\n\n

“The exception was made because there was no practical way to not have the exception at the time,” said Aria Stathopoulos, a Themes Team representative and developer behind the Webfonts Loader project. “The exception for Google Fonts was made out of necessity. Now that there is another way, the exception will not be necessary.”

\n\n\n\n

In effect, disallowing the Google Fonts CDN would not be a new ban. It would be a removal of an exception to the existing ban.

\n\n\n\n

Google Fonts has become so embedded into the theme developer toolset over the years, there was no way the team could simply pull the plug and prohibit the use of the CDN overnight. If the Themes Team members wanted to focus more on privacy, they would need to build a tool that made it dead simple for theme authors to use.

\n\n\n\n

There is no hard deadline for when the team will remove the exception for Google Fonts, and it is not set in stone at this point. Stathopoulos said removing it has been the goal from the beginning, disallowing all CDNs. However, it took a while to find an efficient way to handle this. With a viable alternative in place, they can discuss moving forward.

\n\n\n\n

Webfonts Loader for Themes

\n\n\n\n

The Webfonts Loader project keeps it simple for theme authors. It introduces a new wptt_get_webfont_styles() function that developers can plug in a stylesheet URL. Once a page is loaded with that function call, it will download the fonts locally to a /fonts folder in the user’s /wp-content directory. This way, fonts will always be served from the user’s site.

\n\n\n\n

The system is not limited to Google Fonts either. Any URL that serves CSS with an @font-face {} rule will work. It does not currently include authentication for CDNs that require API keys, such as Adobe Fonts. However, that is something the team might add in the future.

\n\n\n\n

“For end-users, moving away from CDNs and locally hosting web fonts will improve performance (fewer handshake roundtrips for SSL), and is the privacy-conscious choice,” said Stathopoulos. “The only ‘valid privacy concern’ is that the web fonts’ CDN does not disclose information that is fundamental to the GDPR: what information gets logged, for how long these logs remain, how they are processed, if there is any cross-referencing with all the other wealth of information the company has from users, etc. The concern is a lack of disclosure and information. If a site owner doesn’t know what kind of information a third-party logs for its visitors, then they should ethically not enforce that on their visitors. With this package, the CDN is removed from the equation and the font still gets served fast — if not faster.”

\n\n\n\n

A Path to Core WordPress

\n\n\n\n

Today, there is now a broader focus on privacy concerns related to third-party resources, particularly with tech giants like Google. Such concerns extend to whether third parties are tracking users or collecting data. Additional concerns are around whether sites are disclosing the use of third-party resources, which may be required in some jurisdictions. Site owners who are often unable to work through the web of potential issues are stuck in the middle.

\n\n\n\n

Jono Alderson opened a ticket to create an API for loading web fonts locally in core WordPress in February 2019. It is a lengthy and detailed proposal, but it has yet to see much buy-in outside of a handful of developers.

\n\n\n\n

“If such a script is standardized and included in WordPress core, one of the main benefits would be more respect for the end-user’s privacy,” said Stathopoulos. “In the end, that’s all privacy is about: respecting users.”

\n\n\n\n

A standard API like Alderson proposes could solve some issues. Namely, it would virtually eliminate any privacy concerns. However, loading fonts locally could allow WordPress to optimize font loading and would create a shared system where plugins and themes do not load duplicate assets because of the current limitations of the enqueuing system. A standard API would also put the responsibility of efficiently loading fonts on WordPress’s shoulders instead of theme and plugin developers.

\n\n\n\n

The Themes Team’s new project is a solid start and strengthens the current proposal.

\n\n\n\n

“If we’re serious about WordPress becoming a fast, privacy-friendly platform, we can’t rely on theme developers to add and manage fonts without providing a framework to support them,” wrote Alderson in the ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 28 Sep 2020 20:58:48 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: Fuxia Scholz First to Pass 100K Reputation Points on WordPress Stack Exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105282\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange?utm_source=rss&utm_medium=rss&utm_campaign=fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5096:\"

Fuxia Scholz, a prolific WordPress Stack Exchange (WPSE) contributor, is the first member to reach 100,000 reputation points. The popular Q&A community site rewards expert advice by floating the highest quality answers to the top, allowing users to earn reputation points. The gamified help community has proven to be more motivating for developers than many traditional forums, since the upvotes communicate how useful their answers are to others.

\n\n\n\n
\n\n\n\n

Scholz started on Stack Overflow a few months before WordPress had its own site. She wrote around 50 answers and made connections with other WordPress developers ahead of the site’s beta phase in June 2010. Once the site graduated and got its own logo and design, Scholz started writing more.

\n\n\n\n

“One core idea for all Stack Exchange sites is gamification: You earn reputation, and you get access to certain privileges,” Scholz said.

\n\n\n\n

“You can say I got a bit addicted. My favorite questions were about problems for which I didn’t know the answer, and couldn’t find one with a search engine, because no one else had solved that before. I used my answers to teach myself, and I learned a lot this way! In May 2011 my reputation on WPSE was already higher than on Stack Overflow, and for the next years it went up in a steep curve.” Ten years after WPSE launched, Scholz has become the first to reach 100,000 reputation points.

\n\n\n\n

“What reputation and karma do is send a message that this is a community with norms, it’s not just a place to type words onto the internet. (That would be 4chan.)” Stack Overflow co-creator Joel Spolsky said. “We don’t really exist for the purpose of letting you exercise your freedom of speech. You can get your freedom of speech somewhere else. Our goal is to get the best answers to questions. All the voting makes it clear that we have standards, that some posts are better than others, and that the community itself has some norms about what’s good and bad that they express through the vote.”

\n\n\n\n

The reputation points were originally inspired by Reddit Karma. Spolsky admits that the points not a perfect system but they do tend to “drive a tremendous amount of good behavior.” Gamification can shape and encourage certain behaviors but Spolsky said it’s a weak force that cannot motivate people to do things they are not already interested in doing. For Scholz, it was the community aspect and an earned sense of ownership and responsibility that kept her hooked.

\n\n\n\n

“In 2012, the community elected me as a moderator, and that changed a lot,” she said. “Now it wasn’t just a game anymore, it was a duty. I felt responsible for the site. I still do. I also found some friends on there. We met at WordCamps and in private, and worked together on different projects.”

\n\n\n\n

Scholz no longer works in development and said she doesn’t care about WordPress anymore, but she is still a regular contributor on the WPSE.

\n\n\n\n

“I switched careers and work as a writer, translator, and community manager for Chess24.com now,” she said. “But I still care about the site WordPress Stack Exchange! I keep an eye on new tags, handle flagged posts and comments, try to make every new user feel welcome, and I search for people who are abusing the system — vote fraud and spam. And, very rarely, I even write an answer, because I still know all this stuff.

\n\n\n\n

“Checking the site has become a part of my daily routine, like feeding the cat.”

\n\n\n\n

This daily habit has snowballed into Scholz racking up more than 2,000 answers. She is getting upvotes on many of her old answers nearly every day, which is what pushed her over the 100k milestone.

\n\n\n\n

“There is a lot to say about the way our site developed over the years,” Scholz said. “I’m not happy about some things. The enthusiasm of the early days is gone. We don’t have enough regulars, there is no discussion about the site on WordPress Development Meta Stack Exchange, and our chat, once very active, funny, and friendly, is now almost dead.

\n\n\n\n

“Maybe that’s normal, I don’t know. But it’s still ‘my’ site. Reputation and badges don’t really mean anything for a long time now, but keeping the site working, useful and friendly is more important.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 26 Sep 2020 15:27:03 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: PhotoPress Plugin Seeks to Revolutionize Photography for WordPress Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=104770\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:209:\"https://wptavern.com/photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users?utm_source=rss&utm_medium=rss&utm_campaign=photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5638:\"

Peter Adams, the owner of the PhotoPress plugin, announced a couple of weeks ago that now is the time for his project to take center stage. “It’s Time for PhotoPress,” read the title of his post in which he laid out a four-phase plan for the future of his project.

\n\n\n\n

Adams is no stranger to manipulating WordPress to suit the needs of photographers. He described photography as his first love and second career. He initially found the art of taking photos in high school and set off to college to become a professional photographer in the early ’90s.

\n\n\n\n

As his university graduation loomed, he was recruited to run web development for an internet ad agency that built websites for Netscape, Bill Clinton’s White House, and dozens of Fortune 500 companies. He spent the next 15 years starting or running tech companies before returning to his roots as a photographer.

\n\n\n\n

Today, he photographs for various magazines and companies. And, that’s where his PhotoPress project comes in.

\n\n\n\n

“As far as WordPress has come, it is at risk of losing an entire generation of photographers to photo website services such as Photoshelter, SmugMug, Squarespace, and PhotoFolio,” he said. Adams wants to change that, making WordPress the go-to platform for photographers around the world.

\n\n\n\n

The Jetpack of Photography Plugins

\n\n\n\n

If you dig into the history of the PhotoPress plugin on WordPress.org, it seems to have a 15-year history. However, this is not the same plugin that was published a decade and a half ago by a different developer. The original plugin is now defunct, and Adams took over when the name was freed up on the directory.

\n\n\n\n

Adams wrote in his announcement post that WordPress has done a great job of delivering several media features over the years. “Yet despite that, there are still many rough edges and missing features that keep WordPress from being the first choice for a photographer that needs to publish a beautiful portfolio of their work, put their image catalog/archive online, or showcase a photo editorial/project.”

\n\n\n\n

He outlined a list of 10 specific problem areas that he wants to address in a “Jetpack-like” plugin for photographers. This is the bread and butter of the first of the planned four phases, which he said is about 80% finished. He had originally planned to develop PhotoPress as a series of separate plugins, each addressing a specific problem. Now, it is a single plugin with modules than can be enabled or disabled.

\n\n\n\n

When asked why the “right time” is now, Adams explained it is because the Gutenberg (block editor) project is a giant leap forward in usability in terms of creating photography blogs.

\n\n\n\nPhotoPress Gallery block in the editor.\n\n\n\n

“Photogs are a rare breed of non-technical users with high design sense,” he said. “Things that I used to have to teach photographers to do using shortcode syntax and custom CSS can now be simple controls with live feedback inside a Gutenberg block. It’s really a game-changer for getting people comfortable with customizing things like gallery styling — which is the number one thing photographers need to do.”

\n\n\n\n

The primary piece of the PhotoPress plugin is its custom PhotoPress Gallery block. It allows users to choose between a range of gallery styles, such as columns, masonry, justified, and mosaic. Each style has its own options. Images can also be launched into a slideshow when one is clicked.

\n\n\n\n

Based on some quick tests, the block’s front-end output will go farther with some themes than others. This is mainly because of conflicting CSS and issues which can be solved by testing against more themes.

\n\n\n\n

Aside from the block, the plugin can automatically extract image metadata and group that data through custom taxonomies, such as cameras, lenses, locations, keywords, and more. WordPress stores this information out of the box, but it is hidden away as post meta. The plugin uses the taxonomy system to make it manageable for end-users.

\n\n\n\n

Ultimately, Adams set out to create a photography plugin that fits in with the WordPress admin user interface and experience, which he has accomplished.

\n\n\n\n

The Future of PhotoPress

\n\n\n\n

The project is still a work in progress. Adams is still moving through Phase I of the four-phase plan. Once it is complete, he can move on to the next steps in the process.

\n\n\n\n

Phase II is to create themes that are designed specifically to work with the PhotoPress plugin. He has three planned thus far. One for handling portfolio sites. Another for creating a stock photo archive. And the last for photojournalism and exhibits. Each will be built on top of his photography theme framework.

\n\n\n\n

The themes in Phase II will likely be commercial products. Adams said he needs a way to fund the next phases of the project. He hopes to have this step underway by the end of the year.

\n\n\n\n

For 2021, he wants to begin tackling Phases III and IV. The former will be a website-as-a-service (WaaS) similar to WordPress.com but for photographers. It will begin as a paid project but could have some free options for emerging photographers and students. The final phase is to build an onboarding system.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 25 Sep 2020 19:08:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: Google Officially Releases Its Web Stories for WordPress Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105227\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:191:\"https://wptavern.com/google-officially-releases-its-web-stories-for-wordpress-plugin?utm_source=rss&utm_medium=rss&utm_campaign=google-officially-releases-its-web-stories-for-wordpress-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5593:\"Web Stories for WordPress dashboard.\n\n\n\n

Two and a half months after the launch of its public beta, Google released its Web Stories for WordPress plugin. So far, the plugin has over 10,000 active installations and has garnered a solid five-star rating from four reviews.

\n\n\n\n

Google created the Web Stories format through its AMP Project to allow publishers to create visually-rich stories. It is primarily geared toward mobile site visitors, allowing them to quickly jump through story pages with small chunks of content.

\n\n\n\n

The Web Stories plugin creates a visual interface within WordPress for creating Stories. It breaks away from the traditional WordPress interface and introduces users to an almost Photoshop-like experience for building out individual Stories. The Stories editor is completely drag-and-drop.

\n\n\n\n

The plugin also offers eight predesigned templates out of the box that cover a small range of niches. However, according to Google’s announcement, the company plans to add more templates in future updates.

\n\n\n\n

Web Stories Are for Storytelling

\n\n\n\n

“Firstly…the power of Stories,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Stories are how we (humans) see the world and share our experiences. Up to now the platforms that we have to tell stories have been limited to books/films/tv/websites/blogs/instagram stories etc.”

\n\n\n\n

“Websites are ok for telling stories but in many ways the format doesn’t really fit the linear arc of storytelling. When Marshall McLuhan said ‘the medium is the message’ in 1964 he was talking about how the medium itself has a social impact, and change the communication itself…and the possibilities for what is communicated and how it is perceived. But we should keep coming back to Stories. Stories are the key here imo. Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Marsland finished his thread by saying that using Stories as a replacement for a brochure or website is a missed opportunity. He said that it was a platform for storytelling and should be used as such.

\n\n\n\n

It is far too early to tell if Web Stories will simply be a fad or still in wide use years from now. The technology certainly lends itself well to telling stories, particularly in mobile format, but I doubt we have seen the best of what is possible on the web. The format feels too limited to be the end-all-be-all of storytelling. It is merely one medium that will live and die by its popularity with users.

\n\n\n\n

With the right design skills, some people will craft beautiful Web Stories. And, that is just what Marsland has done with the first Story he shared:

\n\n\n\nPage from the Wilson and Pootle Web Story by Jamie Marsland.\n\n\n\n

I agree with his conclusion. Web Stories should be about storytelling. When you move outside of that zone, the technology feels out of place.

\n\n\n\n

Where I disagree is that websites are not ideal for storytelling. Ultimately, the WordPress block editor will allow artistic end-users to craft intricate stories, mixing content and design in ways that we have not seen. We are just now scratching the surface. I expect our community of developers to build more intricate tools than what the Web Stories plugin currently allows, and we can do so in a way that revolutionizes storytelling on the web.

\n\n\n\n

New Features

\n\n\n\nStory editor with Unsplash photo integration.\n\n\n\n

The Web Stories plugin now adds support for Unsplash images and Coverr videos out of the box. The plugin adds a new tab with a “media” icon. For users of the first beta version of the plugin, this may be a bit confusing. The previous media icon was for a tab that displayed the user’s media. Now, the user’s media is under the tab with the “upload” icon.

\n\n\n\n

It is also not immediately clear that the Unsplash images and Coverr videos are not hosted on the site itself. There is a “powered by” notice at the bottom of the tab, but it can be easy to miss because it blends in with the media in the background.

\n\n\n\n

Media from Unsplash and Coverr is hosted off-site and not downloaded to the user’s WordPress media library. I could find no mention of this in the plugin’s documentation. Such hotlinking was a cause for debate over the recent official release of the Unsplash plugin.

\n\n\n\n

Google also announced it planned to add more “stock media integrations” in the near future. According to a document shared via a GitHub ticket, such future integrations may include Google Photos and GIF-sharing site Tenor.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 21:13:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: W3C Drops WordPress from Consideration for Redesign, Narrows CMS Shortlist to Statamic and Craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105108\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft?utm_source=rss&utm_medium=rss&utm_campaign=w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:11563:\"

The World Wide Web Consortium (W3C), the international standards organization for the web, is redesigning its website and will soon be selecting a new CMS. Although WordPress is already used to manage W3C’s blog and news sections of the website, the organization is open to adopting a new CMS to meet its list of preferences and requirements.

\n\n\n\n

Studio 24, the digital agency selected for the redesign project, narrowed their consideration to three CMS candidates:

\n\n\n\n
  1. Statamic
  2. Craft CMS
  3. WordPress
\n\n\n\n

Studio 24 was aiming to finalize their recommendations in July but found that none of them complied with the W3C’s authoring tool accessibility guidelines. The CMS’s that were better at compliance with the guidelines were not as well suited to the other project requirements.

\n\n\n\n

In the most recent project update posted to the site, Studio 24 reported they have shortlisted two CMS platforms. Coralie Mercier, Head of Marketing and Communications at W3C, confirmed that these include Statamic and Craft CMS.

\n\n\n\n

WordPress was not submitted to the same review process as the Studio 24 team claims to have extensive experience working with it. In the summary of their concerns, Studio 24 cited Gutenberg, accessibility issues, and the fact that the Classic Editor plugin will stop being officially maintained on December 31st, 2021:

\n\n\n\n

First of all, we have concerns about the longevity of WordPress as we use it. WordPress released a new version of their editor in 2018: Gutenberg. We have already rejected the use of Gutenberg in the context of this project due to accessibility issues.

If we choose to do away with Gutenberg now, we cannot go back to it at a later date. This would amount to starting from scratch with the whole CMS setup and theming.

Gutenberg is the future of WordPress. The WordPress core development team keeps pushing it forward and wants to roll it out to all areas of the content management system (navigation, sidebar, options etc.) as opposed to limiting its use to the main content editor as is currently the case.

This means that if we want to use WordPress long term, we will need to circumvent Gutenberg and keep circumventing it for a long time and in more areas of the CMS as time goes by. 

\n\n\n\n

Another major factor in the decision to remove WordPress from consideration was that they found “no elegant solution to content localization and translation.”

\n\n\n\n

Studio 24 also expressed concerns that tools like ACF, Fewbricks, and other plugins might not being maintained for the Classic Editor experience “in the context of a widespread adoption of Gutenberg by users and developers.”

\n\n\n\n

“More generally, we think this push to expand Gutenberg is an indication of WordPress focusing on the requirements of their non-technical user base as opposed to their audience of web developers building custom solutions for their clients.”

\n\n\n\n

It seems that the digital agency W3C selected for the project is less optimistic about the future of Gutenberg and may not have reviewed recent improvements to the overall editing experience since 2018, including those related to accessibility.

\n\n\n\n

Accessibility consultant and WordPress contributor Joe Dolson recently gave an update on Gutenberg accessibility audit at WPCampus 2020 Online. He reported that while there are still challenges remaining, many issues raised in the audit have been addressed across the whole interface and 2/3 of them have been solved. “Overall accessibility of Gutenberg is vastly improved today over what it was at release,” Dolson said.

\n\n\n\n

Unfortunately, Studio 24 didn’t put WordPress through the same content creation and accessibility tests that it used for Statamic and Craft CMS. This may be because they had already planned to use a Classic Editor implementation and didn’t see the necessity of putting Gutenberg through the paces.

\n\n\n\n

These tests involved creating pages with “flexible components” which they referred to as “blocks of layout,” for things like titles, WYSIWYG text input, and videos. It also involved creating a template for news items where all the content input by the user would be displayed (without formatting).

\n\n\n\n

Gutenberg would lend itself well to these uses cases but was not formally tested with the other candidates, due to the team citing their “extensive experience” with WordPress. I would like to see the W3C team revisit Gutenberg for a fair shake against the proprietary CMS’s.

\n\n\n\n

W3C Is Prioritizing Accessibility Over Its Open Source Licensing Preferences

\n\n\n\n

The document outlining the CMS requirements for the project states that “W3C has a strong preference for an open-source license for the CMS platform” as well as “a CMS that is long-lived and easy to maintain.” This preference may be due to the economic benefits of using a stable, widely adopted CMS, or it may be inspired by the undeniable symbiosis between open source and open standards.

\n\n\n\n

“The industry has learned by experience that the only software-related standards to fully achieve [their] goals are those which not only permit but encourage open source implementations. Open source implementations are a quality and honesty check for any open standard that might be implemented in software…”

Open Source Initiative
\n\n\n\n

WordPress is the only one of the three original candidates to be distributed under an OSD-compliant license. (CMS code available on GitHub isn’t the same.)

\n\n\n\n

Using proprietary software to publish the open standards that underpin the web isn’t a good look. While proprietary software makers are certainly capable of implementing open standards, regardless of licensing, there are a myriad of benefits for open standards in the context of open source usage:

\n\n\n\n

“The community of participants working with OSS may promote open debate resulting in an increased recognition of the benefits of various solutions and such debate may accelerate the adoption of solutions that are popular among the OSS participants. These characteristics of OSS support evolution of robust solutions are often a significant boost to the market adoption of open standards, in addition to the customer-driven incentives for interoperability and open standards.”

International Journal of Software Engineering & Applications
\n\n\n\n

Although both Craft CMS and Statamic have their code bases available on GitHub, they share similarly restrictive licensing models. The Craft CMS contributing document states:

\n\n\n\n

Craft isn’t FOSS
Let’s get one thing out of the way: Craft CMS is proprietary software. Everything in this repo, including community-contributed code, is the property of Pixel & Tonic.

That comes with some limitations on what you can do with the code:

– You can’t change anything related to licensing, purchasing, edition/feature-targeting, or anything else that could mess with our alcohol budget.
– You can’t publicly maintain a long-term fork of Craft. There is only One True Craft.

\n\n\n\n

Statamic’s contributing docs have similar restrictions:

\n\n\n\n

Statamic is not Free Open Source Software. It is proprietary. Everything in this and our other repos on Github — including community-contributed code — is the property of Wilderborn. For that reason there are a few limitations on how you can use the code:

\n\n\n\n

Projects with this kind of restrictive licensing often fail to attract much contribution or adoption, because the freedoms are not clear.

\n\n\n\n

In a GitHub issue requesting Craft CMS go open source, Craft founder and CEO Brandon Kelly said, “Craft isn’t closed source – all the source code is right here on GitHub,” and claims the license is relatively unrestrictive as far as proprietary software goes, that contributing functions in a similar way to FOSS projects. This rationale is not convincing enough for some developers commenting on the thread.

\n\n\n\n

“I am a little hesitant to recommend Craft with a custom open source license,” Frank Anderson said. “Even if this was a MIT+ license that added the license and payment, much like React used to have. I am hesitant because the standard open source licenses have been tested.”

\n\n\n\n

When asked about the licensing concerns of Studio 24 narrowing its candidates to two proprietary software options, Coralie Mercier told me, “we are prioritizing accessibility.” A recent project update also reports that both CMS suppliers W3C is reviewing “have engaged positively with authoring tool accessibility needs and have made progress in this area.”

\n\n\n\n

Even if you have cooperative teams at proprietary CMS’s that are working on accessibility improvements as the result of this high profile client, it cannot compare to the massive community of contributors that OSD-compliant licensing enables.

\n\n\n\n

It’s unfortunate that the state of open source CMS accessibility has forced the organization to narrow its selections to proprietary software options for its first redesign in more than a decade.

\n\n\n\n

Open standards go hand in hand with open source. There is a mutually beneficial connection between the two that has caused the web to flourish. I don’t see using a proprietary CMS as an extension of W3C values, and it’s not clear how much more benefit to accessibility the proprietary options offer in comparison. W3C may be neutral on licensing debates, but in the spirit of openness, I think the organization should adopt an open source CMS, even if it is not WordPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 20:13:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WPTavern: First Look at Twenty Twenty-One, WordPress’s Upcoming Default Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105166\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme?utm_source=rss&utm_medium=rss&utm_campaign=first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6907:\"

Fashion is ephemeral. Art is eternal. Indeed what is a fashion really? A fashion is merely a form of ugliness so absolutely unbearable that we have to alter it every six months!

\n\n\n\n

Thus wrote Oscar Wilde on Victorian-era fashion in an article titled “The Philosophy of Dress” for the New-York Tribune in 1885.

\n\n\n\n

In many ways, WordPress theming is the same as the ever-changing landscape of fashion. Rounded corners are in one day and out the next. Box shadows are in one year after being frowned up just months earlier. Perhaps web design is so intolerable that we must change it every six months. Or, at least freshen it up every year in the case of WordPress.

\n\n\n\n

If art is eternal, there are only two default, Twenty* themes that I can truly recall from past years: Twenty Ten and Twenty Fourteen — yes, Twenty Twenty is memorable, but it is also still the current default. Twenty Ten was a classic that paid homage to WordPress’s past. Twenty Fourteen was such a leap away from tradition that it is hard to forget. Everything else has seemed to fade to varying degrees.

\n\n\n\n

With WordPress 5.6 and the end of the year looming, it is time to look forward to the latest trend. As Mel Choyce-Dwan noted in the announcement of Twenty Twenty-One, the next default theme, “Pastels and muted colors are pretty in right now.”

\n\n\n\n

She is not wrong. The colors are a refreshing change of pace. Now that we are into the second day of autumn, I am getting the good kind of vibes from some of the more earthy-tones from a couple of the color palettes expected to ship with the theme.

\n\n\n\nPotential color palette options for Twenty Twenty-One.\n\n\n\n

Whether Twenty Twenty-One will be a fashionable theme for the year or art that we can remember a decade from now, only history will be able to judge. For now, let’s enjoy the creation and take a look at what we should expect from the next default WordPress theme.

\n\n\n\n

The Current Twenty Twenty-One

\n\n\n\n

The new default theme is a fork of Automattic’s Seedlet, a project in which I lauded as the next step in the evolution of theming. It is a theme that is focused on WordPress’s future of being completely comprised of blocks. It gives us an ideal insight into where theme development is heading. It makes sense as the foundation for the new default. Few other themes would make for a good starting point right now. With WordPress theme development in flux, Seedlet is simply ahead of the pack in terms of foundational elements.

\n\n\n\nSeedlet WordPress theme screenshot.\n\n\n\n

“This provides us with a thorough system of nested CSS variables to make child theming easier, and to help integrate with the global styles functionality that’s under development for full-site editing,” wrote Choyce-Dwan of using Seedlet as a starting point.

\n\n\n\n

There are no plans to spin up a Google Web Font for this theme. The design team is going native and sticking with the default system font stack. Choyce-Dwan listed several reasons for the choice, such as keeping a neutral font that allows broad use, speed, and customizability via a child theme.

\n\n\n\n

Despite the neutral font, the default pastel green is a fairly opinionated design decision. It will not be used broadly across industries. However, the team plans to create multiple color palettes that will give it more range. Presumably, these palettes can also be overwritten.

\n\n\n\nPastel green color scheme on single post view.\n\n\n\n

Other than the colors, the design is relatively simple. Choyce-Dwan said that the theme’s block patterns support is where it will be truly unique.

\n\n\n\n

I was initially unhappy with the patterns that were going to ship with WordPress 5.5. However, an 11th-hour update improved the situation so that they did not feel entirely experimental. The foundational Seedlet theme for Twenty Twenty-One has some unique patterns that begin to scratch the surface of what’s possible with this WordPress feature. My hope is that the new default theme steps it up a notch.

\n\n\n\n

Currently, the theme does not register any custom patterns. However, it has a placeholder file and a note that they are a work in progress. Choyce-Dwan shared some patterns the team has already designed in the announcement.

\n\n\n\nCurrently-designed block patterns.\n\n\n\n

“We’ll be relying on our talented community designers for more ideas,” she wrote. The team has also created a GitHub template for anyone to contribute pattern design ideas.

\n\n\n\n

Currently, the theme does not support the upcoming full-site editing feature of WordPress. After the Beta 1 release of WordPress 5.6, the team plans to begin exploring the addition of this support. WordPress is expected to ship a public beta of full-site editing in its next major release, but it is unclear whether it will be far enough along to be a headline feature for the Twenty Twenty-One theme.

\n\n\n\n

The team and volunteers have less than a month before the October 20th deadline for committing the new theme to trunk, the core WordPress development branch. At that stage, the theme should be nearly complete and ready for production. Of course, there will be several rounds of patches, bug fixes, and updates before WordPress 5.6 lands in December. Right now is the best time for anyone who wants to get involved with Twenty Twenty-One to do so.

\n\n\n\n

Useful links with more information:

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 20:01:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:37:\"HeroPress: Hello World – Hevo Nyika\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=3308\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:176:\"https://heropress.com/essays/hello-world-discovering-the-world-through-wordpress/#utm_source=rss&utm_medium=rss&utm_campaign=hello-world-discovering-the-world-through-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14438:\"\"Pull

Unokwanisa kuverenga rondedzero iyi muChiShona

\n

So I chose a career in Web Development!!

\n

To be honest it’s kind of funny when I think about it and quite surreal to be here talking about my story. It has been a journey and I would like to share my story with you.

\n

I have been lucky in the Dad department. My Dad encouraged me to work hard and dream big from a very young age. I remember occasionally having ‘when I grow up’ talks.

\n

For quite some time I wanted to be a Judge, however awesome this dream sounds it was not very inspired. After binge-watching Judge Judy for a whole weekend, I started calling myself Judge Thelma. Though I don’t remember much of this my sister says that I used to say I would arrest all the men in the World if I ever became a Judge. HAHAHA! (clearly I didn’t understand how the World works)

\n

I did not understand what being a Judge meant or what was required for me to start banging that gavel to my heart’s desire. Eventually, I learnt that I had to become a lawyer first then magistrate before I could be nominated to be a Judge and let us just say that is how I sentenced that dream to a lifetime down the drain.

\n

See what I did there? hahaha!

\nWith Daddy Dearest\n

A few years later, I was in High School and that is when I decided to pursue a career in Computer Science. I did not know what I would be doing or how I would get there but I just knew that I was going to pursue a career in ICT. I wrote my first line of code when I was 16 years old.

\n

This was after I had joined the school’s computer class, initially, I thought I would be learning about Excel Sheets and Word Documents until I was assigned to write my first program in C (talk about a double-take!!). It was not easy but it was very exciting, l remember writing up simple code for a Video Club – a simple check-in/out for VHS tapes and CDs. Dear World, thus began my fascination with computers.

\n

Seven years later, I was now in university studying ICT as I had always wanted. I was doing a Bachelors in Business Management & Information Technology. In my third year, I was interning at a local Webdesign and hosting company. This was never my plan, I only took on that job after I had failed to get a job with local banks or telecommunications companies. Before I was introduced to Website Design I envisioned myself suiting up and working in IT Audit or offering IT support. Even though things did not go as I had planned, I am glad they did not exactly go my way in that aspect. So in 2017, I was designing websites using HTML, CSS, PHP, JavaScripts and Joomla which was the prefered content management system at that company. I knew about WordPress but I was not using it for anything. People have this misconception that WordPress is not for real developers and it is not secure and at that time I was one of those people.

\n

Finding my tribe

\n

One day when I was working at the front desk Thabo Tswana came to give a colleague of mine a purple WooCommerce pen. I did not know what WooCommerce was at that time but I was taken by the purple shirt and pen he was carrying. I asked him about it and he explained what WooCommerce was and that what he was carrying was called ‘swag’. So the love of freebies led me to the WordCamp Harare website, instead of buying a ticket I applied to volunteer. I learnt more about WordPress, I was a volunteer, without any knowledge on WordPress.org or WordPress.com. I only started using WordPress because of the awesome people that l had met at that Wordcamp.

\n

Everyone was so welcoming, a week later with help from Thabo I designed my first ever WP website.

\n

Soon after I was part of the community and a bit more involved in the meetups. We had our first-ever Women Who WordPress meetup in 2018. So many ladies came on board bloggers and developers alike. We were free to talk and discuss a lot of things. We had more time to discuss the difference between WordPress.com and WordPress.org we shared views on how to handle discrimination at work, how to promote your website and a whole lot of other things.

\n

\n

Establishing roots

\n

In 2018, Harare had its first-ever female Lead Organiser Tapiwanashe Manhobo whoop whoop! I was also part of the organising team that year, I was assigned to handle Harare’s first Kids Camp. The planning process was stressful because the economic crisis in Zimbabwe was getting worse, luckily we had over 8 months to plan and with help from sponsors, we managed to pull through. In the end, everything turned out great. I wrote an article about the Kids Camp here.

\n

After the first Kids Camp, we had several WordPressors that were enthusiasts about encouraging kids to embrace ICT. In 2019 we had not planned to have a Kids Camp because of financial constraints but to our surprise, we had some anonymous donations and we managed to have a WordPress Community outreach to a youth centre a week after our WordCamp. We had the outreach at the Centre for Total Transformation which is a non-formal school that caters for underprivileged and vulnerable children. We taught them about WordPress, Computer Hardware and Software.

\n

Here is a small video I took with Ellen when we were about to leave. Did l mention that I am terrible on camera? hahaha!

\n\n

Kids Camp 2019 – Centre for Total Transformation

\n

I have fallen deeply for WordPress because of the Community, I enjoy attending WordCamps, meeting new people and just learning new stuff. I have a huge list of WordCamps I need to attend before l kick the bucket, hopefully. Last year I managed to cross WordCamp

\n

Johannesburg off my bucket list. This year I was going to attend WordCamp Capetown but unfortunately, 2020 had other plans for the whole world. Anyway when everything is back to normal my plan to travel to WordCamps will proceed. (fingers crossed)

\n

Reaping Fruits

\n

Meanwhile, my plan to improve my developing skills has not been on hold. Even though I can still cook up code in C and Java, for now, I have also included WordPress PHP functions to the mix. It was not easy to get to this point, daring myself got me to this slightly better stage. My IQ is not way up there, however, I try to do my best where I can and I am happy to say it has paid off so far.

\n

Around November last year, I was designing as a freelancer while job hunting. Out of the blue l got a call for a job offer from Trust Nhokovenzo who is big on Digital marketing and also part of the WordPress Community. He had asked someone in the community about developers and my name happened to come up. So since February, I have been part of his team at Calmlock Digital Marketing Agency.

\n

There is so much more in the world of WordPress that l am yet to tap into so even though I am ending my write up here, for now, my story is going to continue …

\n

Until next time…

\n

Hevo Nyika

\n

Saka ini ndakasarudza kugadzira mawebhusayiti.

\n

Ndakaita rombo rakanaka pana baba vandakapihwa naMwari. Baba vangu vaindikurudzira kuti ndishande nesimba. Ndinoyeuka pano neapo tichiita hurukuro dzedu dzekuti ‘kana ndakura ndoda kuveyi’.

\n

Kwenguva yakati rebei ndaida kuve Mutongi. Kunyangwe ini ndisingazvirangariri mukoma wangu anotaura kuti ndaiti ndaizosunga varume vese vari pasi rino kana ndikangoita mutongi HAHAHA zveshuwa handaiziva kuti mitemo yenyika inofambiswa seyi.
\nNdanga ndisinga nzwisisi kuti kuva mutongi kwairevei kana zvaidikanwa kwandiri kuti nditange kurova iro ghavheu kuchishuwo chemoyo wangu. Pakupedzisira, ndakadzidza kuti ndaifanirwa kuzoita gweta ipapo magistrate ndisati ndasarudzwa kuita Mutongi naizvozvo ndokupera kwakaita chiroto chekuva Mutongi.

\nNa Baba Vangu\n

Gare gare papfura makore mashoma pandakanga ndave kuHigh School ndakanga ndakuda kuita basa rema kombiyuta. Ndakanyora mutsara wekutanga wekodhi pandaive nemakore gumi nematanhatu. Izvi zvakaitika mushure mekunge ndapinda mukirasi yemakombiyuta, pakutanga ndaifunga kuti ndinenge ndichidzidza nezveExcel Sheets neWord zvisineyi ndakaona ndakunyora kodhi yangu yekutanga muC. Zvaisave nyore kunyora kodhi asi zvainakidza kwazvo, ndorangarira ndichinyora kodhi yeVhidhiyo Kirabhu.

\n

Makore manomwe apfura, ndakanga ndava kuyunivhesiti ndichidzidza ICT zvandakagara ndakaronga. Ndaiita Bachelors muBusiness Management & Information Technology. Mugore rangu rechitatu ndainge ndave kushanda kune imwe kambani yaita zvekugadzira mawebhusaiti. Ndakawana basa iri mushure mekunge ndatadza kuwana basa kumabhanga. Kunyangwe hazvo zvinhu zvisina kuenda sezvandaive ndakaronga, ndinofara kuti hazvina kunyatso enda nenzira yangu. Saka muna 2017 ndaigadzira mawebhusaiti ndichishandisa HTML, CSS, PHP, JavaScript uye Joomla iyo yaive iyo inokurudzirwa kukambani kwandaive. Panguva iyi ndaiziva nezve WordPress asi ndakanga ndisingaishandisi.

\n

Kuwanana neWordPress

\n

Rimwe zuva pandakanga ndichishanda ndakaona Thabo Tswana akauya kuzopa mumwe mukomana wandayishanda naye chinyoreso cheWooCommerce. Ndakanga ndisingazive kuti WooCommerce yaive chii asi ndakafarira chinyoreso nehembe ye WooCommerce yaanga akapfeka. Ndakamubvunza nezvazvo akatsanangura kuti WooCommerce yaive chii. Saka nekudawo zvakanaka, zvemahara ndakaenda pawebhusaiti yeWordCamp Harare ndikabata zvimbo zvegore iroro. Ndakazvipira kubatsirawo vamwe vekuWordPress kuWordCamp Harare. Nerubatsiro kubva kunaThabo ndakagadzira webhusaiti yangu yekutanga yeWordPress vhiki rakatevera .

\n

Mushure mekunge ndaitawo chipato cheavo vanoshandisa WordPress ndakanga ndakuenda kumisangano yeWordPress yaitwa muHarare. Takaita musangano wevakadzi chete muna 2018. Vakadzi vazhinji vakauya kumusangano uyu. Tainga takasununguka kukurukura zvinhu zvakawanda. Takakurukura pamusoro pemutsauko uripo pakati peWordPress.com neWordPress.org takagovana maonero ekugadzirisa rusarura kubasa nezvimwewo.

\n

\n

Nguva yandakatanga kushandisa WordPress

\n

Muna 2018, kurongwa kweWordCamp Harare kwakatungamirwa kekutanga nemusikana ainzi Tapiwanashe Manhobo (waiva mufaro mukuru). Ndakanga ndiri mumwe wevairongawo naye. Hurongwa hwekuronga WordCamp Harare mugore iri hwainetsa pamusaka pekuoma kwehupfumi wemuZimbabwe, zvisineyi takaita rombo rakanaka nokuti takawana rubatsiro kubva kunevamwewo vanhu vakatiwedzera mari. Pakupedzisira, zvese zvakabudirira zvakanaka. Takarongawo WordCamp yevana varipasi pemakore gumi nechishanu, munokwanisa kuverenga pamusoro pezuva iri pawebhisaiti yangu apa.

\n

Mushure mekuita WordCamp yevana, takave nevamwe vanhu veWordPress aifarira kukurudzira vana kuti vagamuchire ICT. Muna 2019 takanga tisina kuronga kuve neWordCamo yeVana nekuda kwezvimhingamupinyi zvemari asi chakatishamisa ndechekuti takawana mari kubvawo kune vamwe. Takaita Camp iyi paCentre for Total Transformation chinova chikoro chisiri chepamutemo chinodzidzisa vana vanotambura. Tadzidzisa vana ava pamusoro peWordPress, Computer Hardware uye Software.

\n\n

Ndofarira WordPress zvakanyanya nekuda kweavo varimu nharaunda yacho, ini ndinonakidzwa nekuenda kumaWordCampi, kusangana nevanhu vatsva uye kungo dzidza zvinhu zvitsva. Gore rakapera ndakakwanisa kuyambuka muganhu weZimbabwe ndichienda kuWordCamp Johannesburg, dai pasina kuti 2020 nyika dzepasi rino dzakawirwa nedenda reCOVID 19 zvimwe ndingadayi ndakaenda kuWordCamp Capetown. Zvisinei hazvo kana denda ranani zvimwe ndichakwanisa kufamba ndichienda kumaWordCamp edzimwe nyika.

\n

Kukowa zvandakadyara

\n

Zvichakadaro, chirongwa changu chekuvandudza hunyanzvi hwangu hachina kumira. Kunyangwe ini ndichiri kukwanisa kubika kodhi muC uye Java, ikozvino, ndasanganisirawo WordPress PHP. Zvaive zvisiri nyore kusvika apa, zvakatora kuzvishingisa nekushanda nesimba. Ndinofara mwari aiva neni pamufambo wangu uyu.

\n

Muna Mbudzi gore rakapera, ndaive ndichigadzira mawebhusayiti apo nditsvaga basa. Pasina nguva ndakataura naTrust Nhokovenzo uyo akaandipa basa mukambani yake, kambani iyi inonzi Calmlock Digital Marketing Agency.

\n

Pane zvimwe zvakawanda kuWordPress zvandisati ndapinda mazviri. Nhaizvozvo kunyangwe ndiri kupedzisa kunyora kwangu apa, nyaya yehupenyu wangu ichaenderera mberi…

\n

Kusvikira nguva inotevera …

\n

…. tsvaga chinangwa chako, chiite mushe mushe ..

\n

The post Hello World – Hevo Nyika appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 06:00:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Thelma Mutete\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: WordPress Contributors Debate Dashboard Notice for Upcoming Facebook oEmbed Provider Removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105132\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5885:\"

WordPress contributors are discussing different strategies for responding to Facebook and Instagram dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers. When a user attempts to embed content by pasting a URL as they have in the past, they may not understand why it no longer works. They may assume that WordPress broke embeds, causing an increase in the support burden for this change.

\n\n\n\n

A few participants on the trac ticket for this issue have suggested WordPress detect users who will be impacted and attempt to warn them with a notice.

\n\n\n\n

“Since this may impact users unknowingly, it is possible to push a dashboard notice to users who have Facebook/Instagram embeds in their content, showing for site admins, as a one-off that can be dismissed,” Marius Jensen said.

\n\n\n\n

“We’ve previously done post-update-processing to clean up comments, so the idea of looking over content for an embed isn’t completely outlandish, and would help with those who don’t follow WordPress’ usual channels to learn of this.”

\n\n\n\n

Others don’t see the necessity. “Why should we make exception here?” Milan Dinić said. “It’s not the first time oEmbed support was discontinued for a provider, and I don’t remember anything specific was done then.”

\n\n\n\n

There is still some uncertainty about what will happen with existing oEmbeds after Facebook updates its API. During a recent core developer meeting, Helen Helen Hou-Sandí confirmed that WordPress does not clear oEmbed caches regularly. “Technically oEmbed caches are cleared if you save and a valid response is returned, we do not do cron-based garbage collection,” Hou-Sandí said.

\n\n\n\n

In a post today on the core development blog, Jake Spurlock assured users and developers that the existing embeds added before Facebook’s API change should still work:

\n\n\n\n

Because oEmbed responses are cached in the database using the hidden oembed_cache post type, any embed added prior to the October 24th deadline will be preserved past the deprecation date. These posts are not purged by default in WordPress Core, so the contents of the embed will persist unless manually deleted.

\n\n\n\n

Marius Jensen cautioned that there is still the possibility that existing embeds may not work going, depending on what Facebook does.

\n\n\n\n

“We don’t know how they plan on implementing the use of unauthorized embed attempts,” Jensen said. “It could not return an embed code and your link would remain a plain link, or maybe they decide to return some kind of embedded ‘unauthorized’ content. I don’t think anyone has heard any specifics on how Facebook plans on doing this, so we’re all just kinda waiting to either hear more, or see what happens.”

\n\n\n\n

Jensen said WordPress doesn’t re-check the cached results except when something changes with the post, but there may be plugins that clean up temporary data that may create an unpredictable outcome.

\n\n\n\n

“The reliability of the caches are hard to determine (and being caches, it’s sort of in the term that it’s not guaranteed to always be there, but rather fetched and saved for a while when needed),” Jensen said.

\n\n\n\n

Ideally WordPress’ oEmbed caches will prevent millions of embeds from breaking, but it’s still unknown how Facebook and third party plugins could change things.

\n\n\n\n

Coming off a rocky 5.5 core update that deprecated jQuery Migrate and flooded official support forums with reports of broken sites, some contributors are wary of having another situation where users are left in the dark.

\n\n\n\n

“I think a dashboard notice is desirable,” Jon Brown said. “Otherwise we’re not preemptively warning people in a way they can prepare and transition to another solution. We’re letting them know the same instant it’s going to break (when editing a specific post). I don’t think we can safely assume cached data is going to persist forever either, plenty of routines out there purge transient data before its stated expiration date.

\n\n\n\n

“I see this as potentially being similar to the problems seen in dropping JQM. It’ll cause avoidable and silent breakage client side without even any error logging for a site developer to pick up on. In hindsight, what ideally would have happened with JQM would have been incorporating the detection code from Enable jQuery Migrate Helper into core temporarily, or simply installing that plugin automatically on behalf of users.”

\n\n\n\n

Brown suggested WordPress detect calls to the cached embeds and warn users before the calls have the chance to fail so they can consider enabling a plugin to keep their embeds working more reliably.

\n\n\n\n

The discussion remains open in the make.wordpress.org/core post and the corresponding trac ticket. Spurlock said WordPress will likely remove Facebook and Instagram oEmbed providers in the upcoming 5.6 release (scheduled for December 8) but it could also be shipped in a 5.x minor release that happens after October 24.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 04:28:56 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Gutenberg Hub Launches Landing Page Templates Directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105009\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:175:\"https://wptavern.com/gutenberg-hub-launches-landing-page-templates-directory?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-hub-launches-landing-page-templates-directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7657:\"\n\n\n\n

Munir Kamal has created copy-and-paste blocks. He has built sections or “patterns” from those blocks. He has created a plugin that allows users to completely customize the two features via block options. Yesterday, he released an initial offering of 22 landing page templates that build upon his earlier work.

\n\n\n\n

Gutenberg Hub can almost be called his magnum opus, at least at this stage of his career. It is a continually growing library of free tools for WordPress’s block editor.

\n\n\n\n

Like previous projects, Gutenberg Hub’s landing templates require the EditorPlus plugin. This plugin is essentially a suite of design controls for the core WordPress blocks. The templates make use of these options by default. Given the limitations of the block editor’s current design controls, the use of such a plugin is necessary. Otherwise, there would be few other ways to realistically create a template system like this.

\n\n\n\n

Currently, users must copy the block code — via a convenient “copy” button — from the Gutenberg Hub website and then paste it in the editor. It is not an ideal situation, and I have been asking Kamal whether he would consider building a template inserter for months now.

\n\n\n\n

This time around, he preemptively said, “And, by the way, I am already working on adding a Template Inserter in my EditorPlus plugin. That will allow users to browse and insert these templates directly from Gutenberg without leaving the website.”

\n\n\n\n

He knew the question was coming. No need for me to ask again. He was unable to share a current screenshot of what the inserter looks like, but he is asking for feedback on what people expect of the user experience and interface.

\n\n\n\n

“Earlier, I created a template inserter similar to other blocks plugins, but later I changed my mind and thought that I should integrate with the Gutenberg Patterns API and load the templates into the ‘patterns’ panel in the block inserter,” he said. “But, I am having a few issues and thinking about going back to the original idea to have a Templates button on the top toolbar that opens a popup window to browse and filter templates that users can insert on a click.”

\n\n\n\n

For now, it is still early. However, at least it is on the long-term roadmap and being worked on.

\n\n\n\n

The Landing Page Templates

\n\n\n\nTesting the photography template (with minor adjustments).\n\n\n\n

At the moment, Gutenberg Hub offers 22 landing page templates. The “page” terminology may not mean “full page.” It simply depends on the active theme. Some themes have an open-canvas type of template that allows users to create the entire page via the editor. However, that is not a common feature, so these page templates will be confined to the post content area in most cases.

\n\n\n\n

The templates also work better with themes that have at least a full-width or no-sidebar option. End-users will want a lot of breathing room to use the templates and tinker with their designs.

\n\n\n\n

Kamal has built templates that stretch across a variety of industries. From restaurants to gyms to education to fashion, there is a lot to choose from right now. He promises more are on the way and at least a 23rd template in the next few days.

\n\n\n\n

“For the niches, I did some research from the top WordPress and HTML marketplaces and found the following most common or popular niches,” he said. “I think I will stick with these niches unless I get some more recommendations.”

\n\n\n\n

In comparison, Redux Templates offers access to over 1,000 sections and templates. Of course, there are trade-offs, such as some of those being commercial and the plugin typically requiring other third-party plugins. While quantity is not the only thing to look at, it proves there are miles of landscape that Gutenberg Hub’s templates have not yet explored. But, it is merely the beginning.

\n\n\n\n

Gutenberg Hub’s full-page templates are not quite as plug-and-play as its blocks and section templates. This is not so much a fault from the developer’s end. It is an issue of the platform, which is constantly being updated, and the range of support from current themes. End-users will start seeing some of the current limitations of the system when a layout does not quite look right with one theme but does with another. Or, if their theme has not been updated to support a new feature, such as the Social Links block, the typical horizontal menu design will likely be a normal vertical list of links instead.

\n\n\n\n

These are not insurmountable issues. Gutenberg and themes need more time to mature before projects like Gutenberg Hub’s landing templates are perfect or at least as close to perfect as can be expected.

\n\n\n\n

There are some things that Gutenberg Hub could improve with its templates. With several that I tested, I needed to switch specific blocks to be full width. This should be set up as the default with templates that are clearly meant to be full width in the example screenshots available on the site. It is a minor issue, but correcting this in the editor fixed several layout issues I was having when using the templates.

\n\n\n\n

Monetization Plans

\n\n\n\n

The second question that Kamal has not been prepared to answer fully over the past several months is how he will monetize Gutenberg Hub. Eventually, developers need some return on their investment when building tons of free tools. Many would do it all for free as long as their bills somehow got paid, but the reality is that there will come a tipping point where their projects need funding for long-haul maintenance.

\n\n\n\n

Kamal said he has laid the groundwork for funding but has not finalized anything yet. Currently, he is working on three ideas:

\n\n\n\n
  • Creating a pro version of his EditorPlus plugin.
  • Offering premium templates and blocks but is looking for a talented designer to work with.
  • Using ads specific to Gutenberg users, but he is not a fan of going this route or ads in general.
\n\n\n\n

He is open to feedback on how to best monetize the website and its projects. However, he said he is unwilling to compromise on giving away current and future free templates and tools.

\n\n\n\n

Future Gutenberg Projects

\n\n\n\n

Kamal said he does not have any new Gutenberg-related projects in the pipeline. The current plan is to work on what he has already created, which is a large ecosystem of Gutenberg tools that somehow work together.

\n\n\n\n

Outside of blocks, templates, and plugins, he is beginning to write more free tutorials on the Gutenberg Hub blog and focusing on creating videos around the project, including a new tutorial series for beginners.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 22 Sep 2020 21:05:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"WPTavern: WordPress Mobile Engineers Propose Dual Licensing Gutenberg under GPL v2.0 and MPL v2.0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105025\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:239:\"https://wptavern.com/wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6556:\"

During a Q&A session at WordCamp Europe 2020 online, Matt Mullenweg mentioned that Gutenberg contributors were considering dual licensing for embedding Gutenberg in mobile apps, along with the requirement that they would need to get an agreement from all contributors. WordPress mobile engineer Maxime Biais has just published a proposal for discussion, recommending dual licensing the editor under GPL v2.0 and MPL v2.0.

\n\n\n\n

“The GPL v2.0 license is a blocker for distributing the Gutenberg library in proprietary mobile apps,” Biais said in the corresponding GitHub issue. “Currently the only known users of Gutenberg on mobile are the WordPress mobile apps which are under GPL v2.0 (WordPress for AndroidWordPress for iOS). Mobile apps under the GPL v2.0 are not common and this limits Gutenberg usage in many apps.

\n\n\n\n

“Rich text editor libraries in the mobile space are lacking. There is no well known open source rich text editor for Android or iOS. We believe that Gutenberg could be a key library for many mobile apps, but that will never happen with the GPL v2.”

\n\n\n\n

Mobile app developers are limited by the GPL, because it requires the entire app to be distributed under the same license. The team is proposing dual licensing under MPL v2.0, a weaker copyleft license that is often considered to be more “business-friendly.” It allows users to combine the software with proprietary code. MPL v2.0 requires the source code for any changes to be available under the MPL, ensuring improvements are shared back to the community. The rest of the app can be distributed under any terms with the MPL v2.0 code included as part of a “larger work.”

\n\n\n\n

“The idea here is to keep some of the WordPress-specific modules under the GPL v2.0 only; some of them are not needed and not relevant for using Gutenberg in another software. Ideally, there would be a different way of bundling the project for being used in WordPress or in a non-GPL software,” Biais said.

\n\n\n\n

The GitHub ticket has several comments from developers who hope to be able to use the editor in their own projects. Radek Pietruszewski, tech lead for a collaborative todo app called Nozbe Teams, has been requesting a relicensing of Gutenberg since October 2019.

\n\n\n\n

“Our tech stack is essentially React on web and React Native on iOS and Android,” Pietruszewski said. “We’re a tiny company, and so we share >80% of app’s codebase between these 3 platforms.

\n\n\n\n

“Our app sorely lacks a WYSIWYG editor. We had a working implementation on web, but we decided to scrap it, because there was no way to port it on iOS and Android. There are pretty much no viable rich text editors for iOS or Android, yet alone both. But even then, shipping three completely separate, but somehow compatible editors would be a vast amount of work.”

\n\n\n\n

When Peitruszewski originally made his case to the mobile team, he identified Gutenberg/Aztec as a basic infrastructure that has the potential to enable many different apps:

\n\n\n\n

And that infrastructure is sorely lacking. There are very few rich text editor libraries on both iOS and Android — and most of them suck. And if you want an editor that has a shared API for both platforms… you’re stuck. There are no options – Gutenberg is the only game in town (and it’s really good).

And it’s very hard to create this infrastructure. WYSIWYG editors are very hard, and it takes entire teams years to develop them (and they still usually suck). Almost no-one has the resources to develop it just for themselves, and if they do, they’re unwilling to open-source it.

\n\n\n\n

Automattic’s mobile app engineers have struggled to get regular contributions to the apps, despite them being open source. Dual licensing Gutenberg could open up a new world of contributors with the editor being used more widely across the industry.

\n\n\n\n

“While we might not be big enough to be able to tackle a challenge of developing a rich text editor from scratch, we’re big enough to contribute features and bug fixes to open source projects,” Pietruszewski said.

\n\n\n\n

Matt Mullenweg was the first comment on Biais’ post in favor of the change:

\n\n\n\n

I think Gutenberg has a chance to become a cross-CMS standard, giving users a familiar interface any place they currently have a rich text box. There are hundreds and hundreds of engineers at other companies solving similar problems in a proprietary way, it would be amazing to get them working together but a huge barrier now is supporting Gutenberg in mobile apps, which every modern web service or CMS has. (Hypothetically, think of Mailchimp as a possible consumer and collaborator here, but it could be any company, SaaS, or other open source CMS.)

\n\n\n\n

Unless any major blockers come up in further discussion, this dual licensing change appears to be on track to move forward. Biais noted that a similar license change has already happened on Aztec-Android and Aztec-iOS. The last hurdle is gaining the approval of all the original code contributors or rewriting the code for those who decline to give approval.

\n\n\n\n

Once Gutenberg can be used under the MPL v2.0, the editor will gain a broader reach, with people already on deck wanting to use it. Other companies and projects that are normally outside WordPress’ open source orbit will also have the opportunity to enrich Gutenberg’s ecosystem with contributions back to the project. At the same time, the MPL 2.0 protects Gutenberg from companies that would try to re-release the code as a closed-source project.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 22:59:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"WPTavern: GitHub to Use ‘Main’ Instead of ‘Master’ as the Default Branch on All New Repositories Starting Next Month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:269:\"https://wptavern.com/github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month?utm_source=rss&utm_medium=rss&utm_campaign=github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4844:\"

In August, GitHub announced that it would change the “master” branch name for all new repositories created on the platform to “main” starting October 1. The date is less than two weeks away, and WordPress developers need to be prepared for the change if they use the service for version control or project management.

\n\n\n\n

The larger tech and web development community began conversations through various venues in June, a time in which the Black Lives Matter was gaining more traction in the U.S. and worldwide. The discussion centered on removing any terminology that could be discriminatory or oppressive to specific groups of people. This ongoing discussion has shown that there is a deep division over whether such changes are necessary or even helpful.

\n\n\n\n

The WordPress community is dealing with this division itself. Aaron Jorbin proposed a change at the same time to rename the default branch name on WordPress-owned repositories. Through discussion on his post and elsewhere, the community landed on “trunk,” which keeps WordPress projects in line with its SVN roots.

\n\n\n\n

“To close the circle on this, a decision was made in June and earlier today (August 19),” wrote Helen Hou-Sandí, a lead WordPress developer, in the comments of the original proposal. “I updated the default branch name for new GitHub repositories under the WordPress organization to be trunk after GitHub enabled early access to that feature.”

\n\n\n\n

As evidenced by the comments on the Tavern’s coverage of the proposal and those on the original post, the WordPress development community as a whole did not support this decision.

\n\n\n\n

Jorbin has updated several of WordPress’s repositories and switched them to use trunk instead of master. However, there are still some lingering projects yet to be updated, including the primary WordPress and WordPress Develop repositories. He left a comment with an updated list in June. There is no public word on whether the existing, leftover projects will be changed.

\n\n\n\n

WordPress Developer Preparations

\n\n\n\nCustomizing the default branch for a user’s GitHub repositories.\n\n\n\n

GitHub is merely changing the default branch name for new repositories starting on October 1. This change does not affect existing repositories. Individual users, organization owners, and enterprise administrators can customize the default branch via their account settings now before the switch is made. Owners can also change the default branch name for individual repositories.

\n\n\n\n

The biggest thing that developers need to watch out for is their tooling or other integrations that might still require the master branch. There may be cases where an alternative default branch name will break workflows. If planning to use a different branch name, the best thing to do right now is to spin up the tools you use on a test repository. If something breaks, check to see whether the particular tool you are using will be getting an update. In most cases, this should not be a problem because customized default branch names will be an industry standard.

\n\n\n\n

The great thing about how GitHub is rolling out this feature is that it offers a choice. Those who believe that “master” is oppressive can change the branch name to something they feel is more inclusive. For those who believe otherwise, they can keep their master branch. But, everyone can use the branch name they prefer.

\n\n\n\n

For existing repositories, GitHub is asking that developers be patient for now. The company is investing in tools to make this a seamless experience later this year. There are a few technical hurdles to clear first.

\n\n\n\n

Developers should read the full GitHub guide on setting the default branch for more information.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 20:39:55 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:31 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:13:\"last-modified\";s:29:\"Thu, 22 Oct 2020 16:15:08 GMT\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 2\";s:16:\"content-encoding\";s:4:\"gzip\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(133,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(134,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603384291','no'),(135,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1603427491','no'),(136,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(139,'theme_mods_twentytwenty','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(140,'recently_activated','a:0:{}','yes'); /*!40000 ALTER TABLE `wp55_options` ENABLE KEYS */; UNLOCK TABLES; @@ -249,7 +249,7 @@ CREATE TABLE `wp55_posts` ( LOCK TABLES `wp55_posts` WRITE; /*!40000 ALTER TABLE `wp55_posts` DISABLE KEYS */; -INSERT INTO `wp55_posts` VALUES (1,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?p=1',0,'post','',1),(2,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?page_id=2',0,'page','',0),(3,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','

Who we are

Our website address is: http://localhost:9999.

What personal data we collect and why we collect it

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Contact forms

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Analytics

Who we share your data with

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.

Your contact information

Additional information

How we protect your data

What data breach procedures we have in place

What third parties we receive data from

What automated decision making and/or profiling we do with user data

Industry regulatory disclosure requirements

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?page_id=3',0,'page','',0),(4,1,'2020-10-22 16:31:28','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2020-10-22 16:31:28','0000-00-00 00:00:00','',0,'http://localhost:9999/?p=4',0,'post','',0),(5,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','publish','open','open','','simple_view','','','2020-10-22 16:32:47','2020-10-22 16:32:47','',0,'http://localhost:9999/?p=5',0,'post','',0),(6,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','inherit','closed','closed','','5-revision-v1','','','2020-10-22 16:32:26','2020-10-22 16:32:26','',5,'http://localhost:9999/5-revision-v1',0,'revision','',0); +INSERT INTO `wp55_posts` VALUES (1,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?p=1',0,'post','',1),(2,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?page_id=2',0,'page','',0),(3,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','

Who we are

Our website address is: http://localhost.

What personal data we collect and why we collect it

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Contact forms

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Analytics

Who we share your data with

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.

Your contact information

Additional information

How we protect your data

What data breach procedures we have in place

What third parties we receive data from

What automated decision making and/or profiling we do with user data

Industry regulatory disclosure requirements

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?page_id=3',0,'page','',0),(4,1,'2020-10-22 16:31:28','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2020-10-22 16:31:28','0000-00-00 00:00:00','',0,'http://localhost/?p=4',0,'post','',0),(5,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','publish','open','open','','simple_view','','','2020-10-22 16:32:47','2020-10-22 16:32:47','',0,'http://localhost/?p=5',0,'post','',0),(6,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','inherit','closed','closed','','5-revision-v1','','','2020-10-22 16:32:26','2020-10-22 16:32:26','',5,'http://localhost/5-revision-v1',0,'revision','',0); /*!40000 ALTER TABLE `wp55_posts` ENABLE KEYS */; UNLOCK TABLES; @@ -423,7 +423,7 @@ CREATE TABLE `wp55_users` ( LOCK TABLES `wp55_users` WRITE; /*!40000 ALTER TABLE `wp55_users` DISABLE KEYS */; -INSERT INTO `wp55_users` VALUES (1,'test','$P$BDzpK1XXL9P2cYWggPMUbN87GQSiI80','test','test@gmail.com','http://localhost:9999','2020-10-22 16:31:15','',0,'test'); +INSERT INTO `wp55_users` VALUES (1,'test','$P$BDzpK1XXL9P2cYWggPMUbN87GQSiI80','test','test@gmail.com','http://localhost','2020-10-22 16:31:15','',0,'test'); /*!40000 ALTER TABLE `wp55_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -436,4 +436,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-10-22 16:38:54 \ No newline at end of file +-- Dump completed on 2020-10-22 16:38:54 diff --git a/tests/Frameworks/WordPress/Version_5_9/wp-config.php b/tests/Frameworks/WordPress/Version_5_9/wp-config.php index caaa747600..69d2da664a 100644 --- a/tests/Frameworks/WordPress/Version_5_9/wp-config.php +++ b/tests/Frameworks/WordPress/Version_5_9/wp-config.php @@ -20,7 +20,7 @@ // ** Database settings - You can get this info from your web host ** // /** The name of the database for WordPress */ -define( 'DB_NAME', 'test' ); +define( 'DB_NAME', 'wp59' ); /** Database username */ define( 'DB_USER', 'test' ); diff --git a/tests/Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql b/tests/Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql index 840355354c..367f6e7127 100644 --- a/tests/Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql +++ b/tests/Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql @@ -15,6 +15,8 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +USE wp61; + -- -- Table structure for table `users` -- @@ -172,7 +174,7 @@ CREATE TABLE `wp55_options` ( LOCK TABLES `wp55_options` WRITE; /*!40000 ALTER TABLE `wp55_options` DISABLE KEYS */; -INSERT INTO `wp55_options` VALUES (1,'siteurl','http://localhost:9999','yes'),(2,'home','http://localhost:9999','yes'),(3,'blogname','Datadog Test Application','yes'),(4,'blogdescription','Just another WordPress site','yes'),(5,'users_can_register','0','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwenty','yes'),(41,'stylesheet','twentytwenty','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','48748','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:2:{i:2;a:4:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:12:\"hierarchical\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1618936275','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'initial_db_version','48748','yes'),(96,'wp55_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(97,'fresh_site','0','yes'),(98,'widget_search','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(99,'widget_recent-posts','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(100,'widget_recent-comments','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(101,'widget_archives','a:2:{i:2;a:3:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(102,'widget_meta','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(103,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:8:\"search-2\";i:1;s:14:\"recent-posts-2\";i:2;s:17:\"recent-comments-2\";}s:9:\"sidebar-2\";a:3:{i:0;s:10:\"archives-2\";i:1;s:12:\"categories-2\";i:2;s:6:\"meta-2\";}s:13:\"array_version\";i:3;}','yes'),(104,'cron','a:7:{i:1603384279;a:5:{s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1603384285;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1603384288;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1603384345;a:1:{s:28:\"wp_update_comment_type_batch\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1603384346;a:1:{s:8:\"do_pings\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1603470679;a:1:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}}s:7:\"version\";i:2;}','yes'),(105,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(106,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'_transient_doing_cron','1603384688.0394918918609619140625','yes'),(115,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-5.5.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-5.5.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"5.5.1\";s:7:\"version\";s:5:\"5.5.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"5.3\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1603384286;s:15:\"version_checked\";s:5:\"5.5.1\";s:12:\"translations\";a:0:{}}','no'),(116,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384412;s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"4.1.6\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"4.1.6\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.4.1.6.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:59:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=969272\";s:2:\"1x\";s:59:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=969272\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}}s:9:\"hello.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}}}}','no'),(117,'_site_transient_timeout_theme_roots','1603386087','no'),(118,'_site_transient_theme_roots','a:3:{s:14:\"twentynineteen\";s:7:\"/themes\";s:15:\"twentyseventeen\";s:7:\"/themes\";s:12:\"twentytwenty\";s:7:\"/themes\";}','no'),(119,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384287;s:7:\"checked\";a:3:{s:14:\"twentynineteen\";s:3:\"1.7\";s:15:\"twentyseventeen\";s:3:\"2.4\";s:12:\"twentytwenty\";s:3:\"1.5\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:14:\"twentynineteen\";a:6:{s:5:\"theme\";s:14:\"twentynineteen\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:44:\"https://wordpress.org/themes/twentynineteen/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/theme/twentynineteen.1.7.zip\";s:8:\"requires\";s:5:\"4.9.6\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:15:\"twentyseventeen\";a:6:{s:5:\"theme\";s:15:\"twentyseventeen\";s:11:\"new_version\";s:3:\"2.4\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentyseventeen/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentyseventeen.2.4.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:12:\"twentytwenty\";a:6:{s:5:\"theme\";s:12:\"twentytwenty\";s:11:\"new_version\";s:3:\"1.5\";s:3:\"url\";s:42:\"https://wordpress.org/themes/twentytwenty/\";s:7:\"package\";s:58:\"https://downloads.wordpress.org/theme/twentytwenty.1.5.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}}s:12:\"translations\";a:0:{}}','no'),(120,'_site_transient_timeout_browser_6daa110c3e56e442b403473c9591e946','1603989088','no'),(121,'_site_transient_browser_6daa110c3e56e442b403473c9591e946','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:12:\"86.0.4240.80\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(122,'_site_transient_timeout_php_check_56babb1797dd31750a342dc4c8a11025','1603989088','no'),(123,'_site_transient_php_check_56babb1797dd31750a342dc4c8a11025','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(124,'_site_transient_timeout_community-events-e0e4f94be3c2d577e126ec3b012627f2','1603427490','no'),(125,'_site_transient_community-events-e0e4f94be3c2d577e126ec3b012627f2','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:12:\"192.168.16.0\";}s:6:\"events\";a:2:{i:0;a:10:{s:4:\"type\";s:6:\"meetup\";s:5:\"title\";s:58:\"Discussion Group: WordPress Troubleshooting Basics: Part 1\";s:3:\"url\";s:68:\"https://www.meetup.com/learn-wordpress-discussions/events/273993927/\";s:6:\"meetup\";s:27:\"Learn WordPress Discussions\";s:10:\"meetup_url\";s:51:\"https://www.meetup.com/learn-wordpress-discussions/\";s:4:\"date\";s:19:\"2020-10-23 06:00:00\";s:8:\"end_date\";s:19:\"2020-10-23 07:00:00\";s:20:\"start_unix_timestamp\";i:1603458000;s:18:\"end_unix_timestamp\";i:1603461600;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"US\";s:8:\"latitude\";d:37.779998779297;s:9:\"longitude\";d:-122.41999816895;}}i:1;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:17:\"WordCamp Bulgaria\";s:3:\"url\";s:35:\"https://bulgaria.wordcamp.org/2020/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2020-10-24 10:00:00\";s:8:\"end_date\";s:19:\"2020-10-24 10:00:00\";s:20:\"start_unix_timestamp\";i:1603522800;s:18:\"end_unix_timestamp\";i:1603522800;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"BG\";s:8:\"latitude\";d:42.733883;s:9:\"longitude\";d:25.48583;}}}}','no'),(126,'can_compress_scripts','0','no'),(127,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(128,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:49:\"\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:27:\"News – – WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 20:10:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=5.6-beta1-49274\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WordPress 5.6 Beta 1 is now available for testing!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8236:\"\n

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:363:\"This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September.  WordPress 5.5.1 Launch On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8713:\"\n

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"WordPress 5.5.1 Maintenance Release\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://wordpress.org/news/2020/09/wordpress-5-5-1-maintenance-release/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 19:13:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8979\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:460:\"WordPress 5.5.1 is now available! This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade. You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process. […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9020:\"\n

WordPress 5.5.1 is now available!

\n\n\n\n

This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade.

\n\n\n\n

You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process.

\n\n\n\n

WordPress 5.5.1 is a short-cycle maintenance release. The next major release will be version 5.6.

\n\n\n\n

To see a full list of changes, you can browse the list on Trac, read the 5.5.1 RC1 and 5.5.1 RC2 posts, or visit the 5.5.1 documentation page.

\n\n\n\n

Thanks and props!

\n\n\n\n

The 5.5.1 release was led by @audrasjb, @azhiyadev, @davidbaumwald, @desrosj, @johnbillion, @planningwrite, @sergeybiryukov and @whyisjake.

\n\n\n\n

Thank you to everyone who helped make WordPress 5.5.1 happen:

\n\n\n\nAmit Dudhat, Andrea Fercia, Andrey “Rarst” Savchenko, Andy Fragen, Angel Hess, avixansa, bobbingwide, Brian Hogg, chunkysteveo, Clayton Collie, David Baumwald, David Herrera, dd32, demetris, Dominik Schilling, dushakov, Earle Davies, Enrique Sánchez, Frankie Jarrett, fullofcaffeine, Garrett Hyder, Gary Jones, gchtr, Hauwa, Herre Groen, Howdy_McGee, Ipstenu (Mika Epstein), Jb Audras, Jeremy Felt, Jeroen Rotty, Joen A., Johanna de Vos, John Blackbourn, John James Jacoby, Jonathan Bossenger, Jonathan Desrosiers, Jonathan Stegall, Joost de Valk, Jorge Costa, Justin Ahinon, Kalpesh Akabari, Kevin Hagerty, Knut Sparhell, Kyle B. Johnson, landau, Laxman Prajapati, Lester Chan, mailnew2ster, Marius L. J., Mark Jaquith, Mark Uraine, Matt Gibson, Michael Beckwith, Mikey Arce, Mohammad Jangda, Mukesh Panchal, Nabil Moqbel, net, oakesjosh, O André, Omar Reiss, Ov3rfly, Paddy, Pascal Casier, Paul Biron, Peter Wilson, rajeshsingh520, Rami Yushuvaev, rebasaurus, riaanlom, Riad Benguella, Rodrigo Arias, rtagliento, salvoaranzulla, Sanjeev Aryal, sarahricker, Sergey Biryukov, Stephen Bernhardt, Steven Stern (sterndata), Thomas M, Timothy Jacobs, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), TwentyZeroTwo, Winstina, wittich, and Yoav Farhi.\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8979\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"The Month in WordPress: August 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2020/09/the-month-in-wordpress-august-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 09:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8983\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:362:\"August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world. WordPress 5.5 Launch […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9605:\"\n

August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Launch

\n\n\n\n

The team launched WordPress 5.5 on August 11. The major release comes with a host of features like automatic updates for plugins and themes, enabling updates over uploaded ZIP files, a block directory, XML sitemaps, block patterns, inline image editing, and lazy-loading images, to name a few. WordPress 5.5 is now available in 50 languages too! You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. Subsequent to the 5.5 release, the 5.5.1 release candidate came out on August 28, which will be followed by its official launch of the minor release on September 1.

\n\n\n\n

A record 805 people contributed to WordPress 5.5, hailing from 58 different countries. @audrasjb has compiled many more stats like that and they’re well worth a read!

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.7 and 8.8

\n\n\n\n

The core team launched Gutenberg 8.7 and 8.8. Version 8.7 saw many improvements to the Post Block suite, along with other changes like adding a block example to the Buttons block, consistently autosaving edits, and updating the group block description. Version 8.8 offers updates to Global Styles, the Post Block suite, and Template management. The release significantly improves the back-compatibility of the new Widget Screen, and also includes other important accessibility and mobile improvements to user interfaces like the Toolbar, navigation menus, and Popovers. For full details on the latest versions of these Gutenberg releases, visit these posts about 8.7 and 8.8.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Check out the brand new Learn WordPress platform!

\n\n\n\n

Learn WordPress is a brand new cross-team initiative led by the WordPress Community team, with support from the training team, the TV team, and the meta team. This platform is a learning repository on learn.wordpress.org, where WordPress learning content will be made available. Video workshops published on the site will be followed up by supplementary discussion groups based on workshop content. The first of these discussion groups have been scheduled, and you can join an upcoming discussion on the dedicated meetup group. The community team invites members to contribute to the project. You can apply to present a workshop, assist with reviewing submitted workshops, and add ideas for workshops that you would like to see on the site. You can also apply to be a discussion group leader to organize discussions directly through the learn.wordpress.org platform. We are also creating a dedicated Learn WordPress working group and have posted a call for volunteers. Meetup organizers can use Learn WordPress content for their meetup events (without applying as a discussion group leader). Simply ask your meetup group to watch one of the workshops in the weeks leading up to your scheduled event, and then host a discussion group for that content as your event.

\n\n\n\n

Want to get involved with the Community team? Follow the Community blog, or join them in the #community-events channel in the Making WordPress Slack group. To organize a local WordPress community event, visit the handbook page

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8983\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n\n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"WordPress 5.5 “Eckstine”\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://wordpress.org/news/2020/08/eckstine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 11 Aug 2020 19:03:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8799\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:354:\"Version 5.5 \"Eckstine\" of WordPress is available for download or update in your WordPress dashboard. With this release, your site gets new power in three major areas: \nspeed (lazy-loading images), search (sitemaps included by default), and security (auto-updates for plugins and themes), along with many new features and improvements to the block editor.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:3:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:48:\"https://s.w.org/images/core/5.5/auto-updates.mp4\";s:6:\"length\";s:6:\"238264\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:50:\"https://s.w.org/images/core/5.5/block-patterns.mp4\";s:6:\"length\";s:7:\"3518792\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:56:\"https://s.w.org/images/core/5.5/inline-image-editing.mp4\";s:6:\"length\";s:7:\"3145140\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Matt Mullenweg\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:71062:\"\n

Here it is! Named “Eckstine” in honor of Billy Eckstine, this latest and greatest version of WordPress is available for download or update in your dashboard.

\n\n\n\n
\"\"
\n\n\n\n
\n

Welcome to WordPress 5.5.

\n\n\n\n

In WordPress 5.5, your site gets new power in three major areas:
speed, search, and security.

\n
\n\n\n\n
\n
\n\n\n\n
\n

Speed

\n\n\n\n

Posts and pages feel faster, thanks to lazy-loaded images.

\n\n\n\n

Images give your story a lot of impact, but they can sometimes make your site seem slow.

\n\n\n\n

In WordPress 5.5, images wait to load until they’re just about to scroll into view. The technical term is ‘lazy loading.’

\n\n\n\n

On mobile, lazy loading can also keep browsers from loading files meant for other devices. That can save your readers money on data — and help preserve battery life.

\n\n\n\n

Search

\n\n\n\n

Say hello to your new sitemap.

\n\n\n\n

WordPress sites work well with search engines.

\n\n\n\n

Now, by default, WordPress 5.5 includes an XML sitemap that helps search engines discover your most important pages from the very minute you go live.

\n\n\n\n

So more people will find your site sooner, giving you more time to engage, retain and convert them to subscribers, customers or whatever fits your definition of success.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Security

\n\n\n\n
Now you can choose to update plugins and themes automatically–or pick just a few–from the screens you’ve always used.
\n\n\n\n

Auto-updates for Plugins and Themes

\n\n\n\n

Now you can set plugins and themes to update automatically — or not! — in the WordPress admin. So you always know your site is running the latest code available.

\n\n\n\n

You can also turn auto-updates on or off for each plugin or theme you have installed — all on the same screens you’ve always used.

\n\n\n\n

Update by uploading ZIP files

\n\n\n\n

If updating plugins and themes manually is your thing, now that’s easier too — just upload a ZIP file.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Highlights from the block editor

\n\n\n\n

Once again, the latest WordPress release packs a long list of exciting new features for the block editor. For example:

\n\n\n\n
\n\n\n\n
\n
\n

Block patterns

\n\n\n\n

New block patterns make it simple and fun to create complex, beautiful layouts, using combinations of text and media that you can mix and match to fit your story.

\n\n\n\n

You will also find block patterns in a wide variety of plugins and themes, with more added all the time. Pick any of them from a single place — just click and go!

\n
\n\n\n\n
\n

The new block directory

\n\n\n\n

Now it’s easier than ever to find the block you need. The new block directory is built right into the block editor, so you can install new block types to your site without ever leaving the editor.

\n\n\n\n

Inline image editing

\n\n\n\n

Crop, rotate, and zoom your photos right from the image block. If you spend a lot of time on images, this could save you hours!

\n
\n
\n\n\n\n
\n\n\n\n

And so much more.

\n\n\n\n

The highlights above are a tiny fraction of the new block editor features you’ve just installed. Open the block editor and enjoy!

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Accessibility

\n\n\n\n

Every release adds improvements to the accessible publishing experience, and that remains true for WordPress 5.5.

\n\n\n\n

Now you can copy links in media screens and modal dialogs with a button, instead of trying to highlight a line of text.

\n\n\n\n

You can also move meta boxes with the keyboard, and edit images in WordPress with your assistive device, as it can read you the instructions in the image editor.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

For developers

\n\n\n\n

5.5 also brings a big box of changes just for developers.

\n\n\n\n
\n
\n

Server-side registered blocks in the REST API

\n\n\n\n

The addition of block types endpoints means that JavaScript apps (like the block editor) can retrieve definitions for any blocks registered on the server.

\n\n\n\n

Defining environments

\n\n\n\n

WordPress now has a standardized way to define a site’s environment type (staging, production, etc). Retrieve that type with wp_get_environment_type() and execute only the appropriate code.

\n\n\n\n

Dashicons

\n\n\n\n

The Dashicons library has received its final update in 5.5. It adds 39 block editor icons along with 26 others.

\n\n\n\n

Passing data to template files

\n\n\n\n

The template loading functions (get_header()get_template_part(), etc.) have a new $args argument. So now you can pass an entire array’s worth of data to those templates.

\n
\n\n\n\n
\n

More changes for developers

\n\n\n\n
  • The PHPMailer library just got a major update, going from version 5.2.27 to 6.1.6.
  • Now get more fine-grained control of redirect_guess_404_permalink().
  • Sites that use PHP’s OPcache will see more reliable cache invalidation, thanks to the new wp_opcache_invalidate() function during updates (including to plugins and themes).
  • Custom post types associated with the category taxonomy can now opt-in to supporting the default term.
  • Default terms can now be specified for custom taxonomies in register_taxonomy().
  • The REST API now officially supports specifying default metadata values through register_meta().
  • You will find updated versions of these bundled libraries: SimplePie, Twemoji, Masonry, imagesLoaded, getID3, Moment.js, and clipboard.js.
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

The Squad

\n\n\n\n

Leading this release were Matt MullenwegJake Spurlock, and David Baumwald. Supporting them was this highly enthusiastic release squad:

\n\n\n\n\n\n\n\n

Joining the squad throughout the release cycle were 805 generous volunteer contributors who collectively worked on over 523 tickets on Trac and over 1660 pull requests on GitHub.

\n\n\n\n

Put on a Billy Eckstine playlist, click that update button (or download it directly), and check the profiles of the fine folks that helped:

\n\n\nA2 Hosting, a4jp . com, a6software, Aaron D. Campbell, Aaron Jorbin, abderrahman, Abha Thakor, Achal Jain, achbed, Achyuth Ajoy, acosmin, acsnaterse, Adam Silverstein, Addie, addyosmani, adnan.limdi, adrian, airamerica, Ajay Ghaghretiya, Ajit Bohra, akbarhusen, akbarhusen429, Akhilesh Sabharwal, Akira Tachibana, Alain Schlesser, Albert Juhé Lluveras, Alex Concha, Alex Kirk, Alex Lende, Alex Shiels, Ali Shan, ali11007, Allen Snook, amaschas, Amit Dudhat, anbumz, andfinally, Andrea Fercia, Andrea Middleton, Andrea Tarantini, Andrei Draganescu, Andrew Duthie, Andrew Nacin, Andrew Nevins, Andrew Ozz, Andrey \"Rarst\" Savchenko, Andrés Maneiro, Andy Fragen, Andy Meerwaldt, Andy Peatling, Angel Hess, Angela Jin, Angelika Reisiger, Anh Tran, Ankit Gade, Ankit K Gupta, Ankit Panchal, Anne McCarthy, Anthony Burchell, Anthony Hortin, Anton Timmermans, Antonis Lilis, apedog, archon810, argentite, Arpit G Shah, Arslan Ahmed, asalce, ashiagr, ashour, Atharva Dhekne, Aurélien Joahny, aussi, automaton, avixansa, Ayesh Karunaratne, BackuPs, Barry, Barry Ceelen, Bart Czyz, bartekcholewa, bartkalisz, Bastien Ho, Bastien Martinent, bcworkz, bdbch, bdcstr, Ben Dunkle, Bence Szalai, bencroskery, Benjamin Gosset, Benoit Chantre, Bernhard Reiter, BettyJJ, bgermann, bigcloudmedia, bigdawggi, Bill Erickson, Birgir Erlendsson (birgire), Birgit Pauli-Haack, BjornW, bobbingwide, bonger, Boone Gorges, Boris Brdarić, Boy Witthaya, Brandon Kraft, Brandon Payton, Brent Swisher, Brian Hogg, Brian Krogsgard, bruandet, Bunty, Burhan Nasir, caiocrcosta, Cameron Voell, cameronamcintyre, Carike, Carl Wuensche, Carlos Galarza, Carolina Nymark, Caroline Moore, Carrigan, ceyhun, Chad, Chad Butler, Charles Fulton, Chetan Prajapati, Chintan hingrajiya, Chip Snyder, Chloé Bringmann, Chouby, Chris Van Patten, chriscct7, Christian Chung, Christian Jongeneel, Christian Sabo, Christian Wach, Christoph Herr, Christopher Churchill, chunkysteveo, cklee, clayray, Clayton Collie, Clifford Paulick, codeforest, Commeuneimage, Copons, Corey McKrill, cpasqualini, Cristovao Verstraeten, Csaba (LittleBigThings), Curtis Belt, Cyrus Collier, D.PERONNE, d6, Daniel Bachhuber, Daniel Hüsken, Daniel James, Daniel Llewellyn, Daniel Richards, Daniel Roch, Daniele Scasciafratte, Danny, Darko G., Darren Ethier (nerrad), Dave McHale, Dave Whitley, David A. Kennedy, David Aguilera, David Anderson, David Artiss, David Baumwald, David Brumbaugh, David E. Smith, David Herrera, David Ryan, David Shanske, David Smith, david.binda, davidvee, dchymko, Debabrata Karfa, Deepak Lalwani, dekervit, Delowar Hossain, demetris, Denis Yanchevskiy, derekakelly, Derrick Hammer, Derrick Tennant, Diane Co, Dilip Bheda, Dimitris Mitsis, dingo-d, Dion Hulse, Dixita Dusara, djennez, dmenard, dmethvin, doc987, Dominik Schilling, donmhico, Dono12, Doobeedoo, Dossy Shiobara, dpacks, dratwas, Drew Jaynes, DrLightman, DrProtocols, dsifford, dudo, dushakov, Dustin Bolton, dvershinin, Dylan Kuhn, Earle Davies, ecotechie, Eddie Moya, Eddy, Edi Amin, ehtis, Eileen Violini, Ekaterina, Ella van Durpe, elmastudio, Emanuel Blagonic, Emilie LEBRUN, Emmanuel Hesry, Enej Bajgoric, Enrico Sorcinelli, Enrique Piqueras, Enrique Sánchez, Eric, Eric Andrew Lewis, Eric Binnion, Erik Betshammar, Erin \'Folletto\' Casali, esemlabel, esoj, espiat, Estela Rueda, etoledom, etruel, Ev3rywh3re, Evan Mullins, Fabian Kägy, Fabian Todt, Faisal Ahmed, Felix Arntz, Felix Edelmann, ferdiesletering, finomeno, Florian Brinkmann, Florian TIAR, Florian Truchot, florianatwhodunit, FolioVision, Francesca Marano, Francois Thibaud, Frank Goossens, Frank Klein, Frank.Prendergast, Frankie Jarrett, Franz Armas, fullofcaffeine, Gabriel Koen, Gabriel Maldonado, Gabriel Mays, gadgetroid, Gal Baras, Garavani, garethgillman, Garrett Hyder, Gary Cao, Gary Jones, Gary Pendergast, gchtr, Geert De Deckere, Gemini Labs, Gennady Kovshenin, geriux, Giorgio25b, gisselfeldt, glendaviesnz, goldsounds, Goto Hayato, Govind Kumar, Grégory Viguier, gradina, Greg Ziółkowski, gregmulhauser, grierson, Grzegorz.Janoszka, gsmumbo, Guido Scialfa, guidobras, Gunther Pilz, gwwar, H-var, hakre, Halacious, hankthetank, Hapiuc Robert, Hareesh, haukep, Hauwa, Haz, Hector Farahani, Helen Hou-Sandi, Henry Wright, Herre Groen, hlanggo, hommealone, Hoover, Howdy_McGee, Hronak Nahar, huntlyc, Ian Belanger, Ian Dunn, Ian Stewart, ianjvr, ifrins, infinum, Ipstenu (Mika Epstein), Isabel Brison, ishitaka, J.D. Grimes, jackfungi, jacklinkers, Jadon N, jadpm, jagirbahesh, Jake Spurlock, Jake Whiteley, James Koster, James Nylen, Jan Koch, Jan Reilink, Jan Thiel, Janvo Aldred, Jarret, Jason Adams, Jason Coleman, Jason Cosper, Jason Crouse, Jason LeMahieu (MadtownLems), Jason Rouet, JasWSInc, Javier Casares, Jayson Basanes, jbinda, jbouganim, Jean-Baptiste Audras, Jean-David Daviet, Jeff Chandler, Jeff Farthing, Jeff Ong, Jeff Paul, Jen, Jenil Kanani, Jeremy Felt, Jeremy Herve, Jeremy Yip, Jeroen Rotty, jeryj, Jesin A, Jignesh Nakrani, Jim_Panse, Jip Moors, jivanpal, Joe Dolson, Joe Hoyle, Joe McGill, Joen Asmussen, Johanna de Vos, John Blackbourn, John Dorner, John James Jacoby, John P. Green, John Richards II, John Watkins, johnnyb, Jon Quach, Jon Surrell, Jonathan Bossenger, Jonathan Champ, Jonathan Christopher, Jonathan Desrosiers, Jonathan Stegall, jonkolbert, Jonny Harris, jonnybot, Jono Alderson, Joost de Valk, Jorge Bernal, Jorge Costa, Joseph Dickson, Josepha Haden, Josh Smith, JoshuaWold, Joy, Juanfra Aldasoro, juanlopez4691, Jules Colle, julianm, Juliette Reinders Folmer, Julio Potier, Julka Grodel, Justin Ahinon, Justin de Vesine, Justin Tadlock, justlevine, justnorris, K. Adam White, kaggdesign, Kailey (trepmal), Kaira, Kaitlin Bolling, Kalpesh Akabari, KamataRyo, Kantari Samy, Kaspars, Kavya Gokul, keesiemeijer, Kelly Dwan, kennethroberson5556, Kevin Hagerty, Kharis Sulistiyono, Khokan Sardar, kinjaldalwadi, Kiril Zhelyazkov, Kirsty Burgoine, Kishan Jasani, kitchin, Kite, Kjell Reigstad, Knut Sparhell, Konstantin Obenland, Konstantinos Xenos, ksoares, KT Cheung, Kukhyeon Heo, Kyle B. Johnson, lalitpendhare, landau, Laterna Studio, laurelfulford, Laurens Offereins, Laxman Prajapati, Lester Chan, Levdbas, Lew Ayotte, Lex Robinson, linyows, lipathor, Lisa Schuyler, liuhaibin, ljharb, logig, lucasbustamante, luiswill, Luke Cavanagh, Luke Walczak, lukestramasonder, M Asif Rahman, M.K. Safi, Maarten de Boer, Mahfoudh Arous, mailnew2ster, manojlovic, Manuel Schmalstieg, maraki, Marcin Pietrzak, Marcio Zebedeu, Marco Pereirinha, MarcoZ, Marcus, Marcus Kazmierczak, Marek Dědič, Marek Hrabe, Mario Valney, Marius Jensen, Mark Chouinard, Mark Jaquith, Mark Parnell, Mark Uraine, markdubois, markgoho, Marko Andrijasevic, Marko Heijnen, MarkRH, markshep, markusthiel, Martijn van der Kooij, martychc23, Mary Baum, Matheus Martins, Mathieu Viet, Matias Ventura, matjack1, Matt Cromwell, Matt Gibson, Matt Mullenweg, Matt Radford, Matt van Andel, mattchowning, Matthew Boynes, Matthew Eppelsheimer, Matthew Gerring, Matthias Kittsteiner, Matthias Pfefferle, Matthieu Mota, mattyrob, Maxime Culea, Maxime Pertici, maxme, Mayank Majeji, mcshane, Mel Choyce-Dwan, Menaka S., mensmaximus, metalandcoffee, Michael, Michael Arestad, Michael Arestad, Michael Beckwith, Michael Fields, Michael Nelson, Michele Butcher-Jones, Michelle, Miguel Fonseca, mihdan, Miina Sikk, Mikael Korpela, mikaumoto, Mike Crantea, Mike Glendinning, Mike Haydon, Mike Schinkel [WPLib Box project lead], Mike Schroder, Mikey Arce, Milana Cap, Milind More, mimi, mislavjuric, Mohammad Jangda, Mohammad Rockeybul Alam, Mohsin Rasool, Monika Rao, Morgan Kay, Morten Rand-Hendriksen, Morteza Geransayeh, moto hachi ( mt8.biz ), mrgrt, mrmist, mrTall, msaggiorato, Muhammad Usama Masood, Mukesh Panchal, munyagu, Nabil Moqbel, Nadir Seghir, Nahid Ferdous Mohit, Nalini Thakor, Naoko Takano, narwen, Nate Gay, Nathan Rice, Navid, neonkowy, net, netpassprodsr, Nextendweb, Ngan Tengyuen, Nick Daugherty, Nicky Lim, nicolad, Nicolas Juen, NicolasKulka, Nidhi Jain, Niels de Blaauw, Niels Lange, nigro.simone, Nik Tsekouras, Nikhil Bhansi, Nikolay Bachiyski, Nilo Velez, Niresh, nmenescardi, Noah Allen, NumidWasNotAvailable, oakesjosh, obliviousharmony, ockham, Olga Gleckler, Omar Alshaker, Omar Reiss, onokazu, Optimizing Matters, Ov3rfly, ovann86, overclokk, p_enrique, Paal Joachim Romdahl, Pablo Honey, Paddy, palmiak, Paresh Shinde, Parvand, Pascal Birchler, Pascal Casier, Paul Bearne, Paul Biron, Paul Fernhout, Paul Gibbs, Paul Ryan, Paul Schreiber, Paul Stonier, Paul Von Schrottky, pavelevap, Pedro Mendonça, pentatonicfunk, pepe, Peter \"Pessoft\" Kolínek, Peter Westwood, Peter Wilson, Phil Derksen, Phil Johnston, Philip Jackson, Pierre Gordon, pigdog234, pikamander2, pingram, Pionect, Piyush Patel, pkarjala, pkvillanueva, Prashant Baldha, pratik028, Pravin Parmar, Presskopp, Presslabs, Priyank Patel, Priyo Mukul, ProGrafika, programmin, Puneet Sahalot, pvogel2, r-a-y, Raaj Trambadia, Rachel Peter, raine, rajeshsingh520, Ramanan, Rami Yushuvaev, RavanH, Ravat Parmar, ravenswd, rawrly, rebasaurus, Red Sand Media Group, Remy Perona, Remzi Cavdar, Renatho, renggo888, retlehs, retrofox, riaanlom, Riad Benguella, Rian Rietveld, riasat, Rich Tabor, Ringisha, ritterml, Rnaby, Rob Cutmore, Rob Migchels, rob006, Robert Anderson, Robert Chapin, Robert Peake, Robert Windisch, Rodrigo Arias, Ronald Huereca, Rostislav Wolný, Roy Tanck, rtagliento, ruxandra, Ryan Boren, Ryan Fredlund, Ryan Kienstra, Ryan McCue, Ryan Welcher, Ryota Sakamoto, ryotsun, Sören Wrede, Søren Brønsted, Sachit Tandukar, Sagar Jadhav, Sajjad Hossain Sagor, Sal Ferrarello, Salvatore Formisano, salvoaranzulla, Sam Fullalove, Sam Webster, Samir Shah, Samuel Wood (Otto), samueljseay, Sander van Dragt, Sanjeev Aryal, Sanket Mehta, sarahricker, Sathiyamoorthy V, Sayed Taqui, scarolan, scholdstrom, Scott Kingsley Clark, Scott Reilly, Scott Smith, Scott Taylor, scribu, scruffian, Sean Hayes, seanpaulrasmussen, seayou, senatorman, Sergey Biryukov, Sergey Predvoditelev, Sergio de Falco, sergiomdgomes, Shannon Smith, Shantanu Desai, shaunandrews, Shawn Hooper, shawnz, Shital Marakana, shulard, siliconforks, Simon Wheatley, simonjanin, sinatrateam, sjmur, skarabeq, skorasaurus, skoskie, slushman, snapfractalpop, SpearsMarketing, sphakka, squarecandy, sreedoap, Stanimir Stoyanov, Stefano Minoia, Stefanos Togoulidis, Steph Wells, Stephen Bernhardt, Stephen Cronin, Stephen Edgar, Steve Dufresne, stevegibson12, Steven Stern (sterndata), Steven Word, stevenkussmaul, stevenlinx, Stiofan, Subrata Sarkar, SUM1, Sunny, Sunny Ratilal, Sushyant Zavarzadeh, suzylah, Sybre Waaijer, Synchro, Sérgio Estêvão, Takayuki Miyauchi, Tammie Lister, Tang Rufus, TeBenachi, Tessa Watkins LLC, Tetsuaki Hamano, theMikeD, theolg, Thierry Muller, Thimal Wickremage, Thomas M, Thorsten Frommen, Thrijith Thankachan, Tiago Hillebrandt, Till Krüss, Timothy Jacobs, Tkama, tmdesigned, tmoore41, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), Tofandel, tomdude, Tommy Ferry, Tony G, Toro_Unit (Hiroshi Urabe), torres126, Torsten Landsiedel, Toru Miki, Travis Northcutt, treecutter, truongwp, tsimmons, Tung Du, Udit Desai, Ulrich, Vagios Vlachos, valchovski, Valentin Bora, Vayu Robins, veromary, Viktor Szépe, vinkla, virginienacci, Vladimir, Vladislav Abrashev, vortfu, voyager131, vtieu, webaware, Weston Ruter, William Earnhardt, williampatton, Winstina, wittich, wpdesk, WPDO, WPMarmite, wppinar, Yahil Madakiya, yashrs, yoancutillas, Yoav Farhi, yohannp, yuhin, Yui, Yuri Salame, Yvette Sonneveld, Zack Tollman, zaheerahmad, zakkath, Zebulan Stanphill, zieladam, and Česlav Przywara.\n\n\n\n

 

\n\n\n\n

Many thanks to all of the community volunteers who contribute in the support forums. They answer questions from people across the world, whether they are using WordPress for the first time or since the first release. These releases are more successful for their efforts!

\n\n\n\n

Finally, thanks to all the community translators who worked on WordPress 5.5. Their efforts bring WordPress fully translated to 46 languages at release time, with more on the way.

\n\n\n\n

If you want to learn more about volunteering with WordPress, check out Make WordPress or the core development blog.

\n
\n\n\n\n
\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8799\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"WordPress 5.5 Release Candidate 2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2020/08/wordpress-5-5-release-candidate-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 04 Aug 2020 19:12:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8764\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:420:\"The second release candidate for WordPress 5.5 is here! WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time! You can test the WordPress 5.5 release candidate in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding edge nightlies” option) Or download the release […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2503:\"\n

The second release candidate for WordPress 5.5 is here!

\n\n\n\n

WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

For a more detailed breakdown of the changes included in WordPress 5.5, check out the WordPress 5.5 beta 1 post. The WordPress 5.5 Field Guide is also out! It’s your source for details on all the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8764\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n\n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"The Month in WordPress: July 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"https://wordpress.org/news/2020/08/the-month-in-wordpress-july-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 03 Aug 2020 13:54:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8755\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:340:\"July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11539:\"\n

July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Updates

\n\n\n\n

July was full of WordPress 5.5 updates! The WordPress 5.5 Beta 1 came out on July 7, followed by Beta 2 on July 14, Beta 3 on July 21, and Beta 4 on July 27. Subsequently, the team also published the first release candidate of WordPress 5.5 on July 28. 

\n\n\n\n

WordPress 5.5, which is slated for release on August 11, 2020, is a major update with features like automatic updates for plugins and themes, a block directory, XML sitemaps, block patterns, and lazy-loading images, among others. To learn more about the release, check out its field guide post.

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.5 and 8.6

\n\n\n\n

The core team launched Gutenberg 8.5 and 8.6. Version 8.5 – the last plugin release will be included entirely (without experimental features) in WordPress 5.5, introduced improvements to block drag-and-drop and accessibility, easier updates for external images, and support for the block directory. Version 8.6 comes with features like Cover block video position controls and block pattern updates. For full details on the latest versions on these Gutenberg releases, visit these posts about 8.5 and 8.6.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Reimagining Online WordPress Events

\n\n\n\n

The Community team made the difficult decision to suspend in-person WordPress events for the rest of 2020 in light of the COVID-19 pandemic. The team has also started working on reimagining online events. Based on feedback from the community members, the team decided to make changes to the current online WordCamp format. Key changes include wrapping up financial support for A/V vendors, ending event swag support for newer online WordCamps, and suspending the Global Community Sponsorship program for 2020. The team encourages upcoming online WordCamps to experiment with their events to facilitate an effective learning experience for attendees while avoiding online event fatigue. The team is currently working on a proposal to organize community-supported recorded workshops and synchronous discussion groups to help community members learn WordPress.

Want to get involved with the Community team? Follow the Community blog here, or join them in the #community-events channel in the Making WordPress Slack group. To organize a Meetup or WordCamp, visit the handbook page

\n\n\n\n

WordCamp US 2020 is canceled

\n\n\n\n

The organizers of WordCamp US 2020 have canceled the event in light of the continued pandemic and online event fatigue. The flagship event, which was originally scheduled for October 27-29 as an in-person event, had already planned to transition to an online event. Several WCUS Organizers will be working with the WordPress Community team to focus on other formats and ideas for online events, including a 24-hour contributor day, and contributing to the workshops initiative currently being discussed. Matt Mullenweg’s State of the Word (which typically accompanies WordCamp US) is likely to take place in a different format later in 2020.

\n\n\n\n

Plugin and theme updates are now available over zip files

\n\n\n\n

After eleven years, WordPress now allows users to update plugins and themes by uploading a ZIP file, in WordPress 5.5.  The feature, which was merged on July 7, has been one of the most requested features in WordPress. Now, when a user tries to upload a plugin or theme zip file from the WordPress dashboard by clicking the “Install Now” button, WordPress will direct users to a new screen that compares the currently-installed extension with the uploaded versions. Users can then choose between continuing with the installation or canceling. WordPress 5.5 will also offer automatic plugin and theme updates

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n
  • The Block directory is coming to WordPress with the 5.5 release. Plugin authors can now submit their Block plugins to the directory.
  • The Core team has opened up the call for features in the WordPress 5.6 release. You can comment on the post with features that you’d like to be included, current UX pain points, or maintenance tickets that need to be addressed. August 20 is the deadline for feature requests. 
  • Editor features such as the new Navigation block, the navigation screen, and the widget screen that were originally planned to be merged with WordPress 5.5 have been pushed for the next release
  • The Theme team is inviting proposals on whether to allow themes to place an additional top-level menu link in the admin.
  • BuddyPress 6.2 beta is out in the wild, and the team will soon release the stable version. The update includes changes that will make BuddyPress fully compatible with WordPress 5.5.
  • WordCamp EU 2021, which was being planned as an in-person event in Porto, Portugal, is moving online. The team is considering an in-person WordCamp EU in 2022. 
  • The Polyglots team has prepared and finalized a Translation Editor & Locale Manager Vetting Criteria to provide more clarity on how global mentors assign PTE/GTE/Locale Managers and to help locale teams set their own guidelines. The document, which was finalized after a lot of discussion, is now available in the Polyglots handbook.
  • Members of the Community team are discussing whether WordCamp volunteers, WordCamp attendees, or Meetup attendees should be awarded a WordPress.org profile badge. The ongoing discussion will be open for comments until August 13.
  • The WP Notify project, which aims to create a better way to manage and deliver notifications to the relevant audience, is on to its next steps. The team has finalized the initial requirements, and is kicking off the project build.
  • The WordPress documentation team is considering a ban on links to commercial websites in a revision to its external linking policy. The policy change does not remove external links to commercial sites from WordPress.org and only applies to documentation sites. The idea is to protect documentation from being abused, and to prevent the WordPress project from being biased. Discussion on this post is still ongoing, and a decision has not yet been made. Feel free to comment on the discussion posts, if you would like to share your thoughts on the topic.
\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8755\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"WordPress 5.5 Release Candidate\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://wordpress.org/news/2020/07/wordpress-5-5-release-candidate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 28 Jul 2020 19:08:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8732\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:370:\"The first release candidate for WordPress 5.5 is now available! This is an important milestone in the community’s progress toward the final release of WordPress 5.5. “Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2970:\"\n

The first release candidate for WordPress 5.5 is now available!

\n\n\n\n

This is an important milestone in the community’s progress toward the final release of WordPress 5.5.

\n\n\n\n

“Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

What’s in WordPress 5.5?

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developer notes tag for updates on those and other changes that could affect your products.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

The WordPress 5.5 Field Guide, due very shortly, will give you a more detailed dive into the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8732\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 4\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-4/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 27 Jul 2020 20:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8719\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:313:\"WordPress 5.5 Beta 4 is now available! This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 4 in two ways: Try the WordPress Beta Tester plugin (choose the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"David Baumwald\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3812:\"\n

WordPress 5.5 Beta 4 is now available!

\n\n\n\n

This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 4 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 3 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 3, 43 bugs have been fixed. Here are a few changes in beta 4:

\n\n\n\n
  • Add \"loading\" as an allowed kses image attribute (see #50731).
  • Add filter for the plugin/theme auto-update message in the Info tab of Site health (see #50663).
  • $_SERVER[\'SERVER_NAME\'] not a reliable when generating email host names (see #25239)
  • Several backported fixes from Gutenberg are included in WordPress 5.5 Beta 4 (See PR #24218)
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8719\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 3\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-3/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 21 Jul 2020 17:51:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8706\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:324:\"WordPress 5.5 Beta 3 is now available! This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 3 in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3876:\"\n

WordPress 5.5 Beta 3 is now available!

\n\n\n\n

This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 3 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 2 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 2, 43 bugs have been fixed. Here are a few changes in beta 3:

\n\n\n\n
  • Plugin and theme versions are now shared in the emails when automatically updated (see #50350).
  • REST API routes without a permission_callback now trigger a _doing_it_wrong() warning (see #50075).
  • Over 23 Gutenberg changes and updates (see #24068 and #50712).
  • A bug with the new import and export database Dashicons has been fixed (see #49913).
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8706\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1 \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:9:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:30 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Wed, 21 Oct 2020 20:10:31 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(129,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(130,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603384291','no'),(131,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(132,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Loginizer Plugin Gets Forced Security Update for Vulnerabilities Affecting 1 Million Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106557\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users?utm_source=rss&utm_medium=rss&utm_campaign=loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5484:\"

WordPress.org has pushed out a forced security update for the Loginizer plugin, which is active on more than 1 million websites. The plugin offers brute force protection in its free version, along with other security features like two-factor auth, reCAPTCHA, and PasswordLess login in its commercial upgrade.

\n\n\n\n

Last week security researcher Slavco Mihajloski discovered an unauthenticated SQL injection vulnerability, and an XSS vulnerability, that he disclosed to the plugin’s authors. Loginizer version 1.6.4 was released on October 16, 2020, with patches for the two issues, summarized on the plugin’s blog:

\n\n\n\n

1) [Security Fix] : A properly crafted username used to login could lead to SQL injection. This has been fixed by using the prepare function in PHP which prepares the SQL query for safe execution.

2) [Security Fix] : If the IP HTTP header was modified to have a null byte it could lead to stored XSS. This has been fixed by properly sanitizing the IP HTTP header before using the same.

\n\n\n\n

Loginizer did not disclose the vulnerability until today in order to give users the time to upgrade. Given the severity of the vulnerability, the plugins team at WordPress.org pushed out the security update to all sites running Loginizer on WordPress 3.7+.

\n\n\n\n

In July, 2020, Loginizer was acquired by Softaculous, so the company was also able to automatically upgrade any WordPress installations with Loginizer that had been created using Softaculous. This effort, combined with the updates from WordPress.org, covered a large portion of Loginizer’s user base.

\n\n\n\n
\n

Any #WordPress install with @loginizer probably isn\'t using another WAF solution. As you can notice from the graph 600k+500k active installs were updated upside down, so … Preauth SQLi in it, reported by me. Update! Crunching write up :) https://t.co/gkEVWun9wt pic.twitter.com/XWXVMYO1ED

— mslavco (@mslavco) October 19, 2020
\n
\n\n\n\n

The automatic update took some of the plugin’s users by surprise, since they had not initiated it themselves and had not activated automatic updates for plugins. After several users posted on the plugin’s support forum, plugin team member Samuel Wood said that “WordPress.org has the ability to turn on auto-updates for security issues in plugins” and has used this capability many times.

\n\n\n\n

Mihajloski published a more detailed proof-of-concept on his blog earlier today. He also highlighted some concerns regarding the systems WordPress has in place that allowed this kind of of severe vulnerability to slip through the cracks. He claims the issue could have easily been prevented by the plugin review team since the plugin wasn’t using the prepare function for safe execution of SQL queries. Mihajloski also recommended recurring code audits for plugins in the official directory.

\n\n\n\n

“When a plugin gets into the repository, it must be reviewed, but when is it reviewed again?” Mihajloski said. “Everyone starts with 0 active installs, but what happens on 1k, 10k, 50k, 100k, 500k, 1mil+ active installs?”

\n\n\n\n

Mihajloski was at puzzled how such a glaring security issue could remain in the plugin’s code so long, given that it is a security plugin with an active install count that is more than many well known CMS’s. The plugin also recently changed hands when it was acquired by Softaculus and had been audited by multiple security organizations, including WPSec and Dewhurst Security.

\n\n\n\n

Mihajloski also recommends that WordPress improve the transparency around security, as some site owners and closed communities may not be comfortable with having their assets administered by unknown people at WordPress.org.

\n\n\n\n

“WordPress.org in general is transparent, but there isn’t any statement or document about who, how and when decides about and performs automatic updates,” Mihajloski said. “It is kind of [like] holding all your eggs in one basket.

\n\n\n\n

“I think those are the crucial points that WP.org should focus on and everything will came into place in a short time: complete WordPress tech documentation for security warnings, a guide for disclosure of the bugs (from researchers, but also from a vendor aspect), and recurring code audits for popular plugins.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Oct 2020 03:47:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"Post Status: Joe Casabona on creating quality content and courses\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=80099\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://poststatus.com/joe-casabona-on-creating-quality-content-and-courses/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1407:\"

David Bisset interviews Joe Casabona, an independent creator and teacher, and discusses what it\'s like to be a creator as his job, plus some news topics.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Sandhills Development

\n\n\n\n

Sandhills Development crafts ingenuity, developed with care:

\n\n\n\n
  • Easy Digital Downloads – Sell digital products with WordPress
  • AffiliateWP – A full-featured affiliate marketing solution
  • Sugar Calendar – WordPress event management made simple
  • WP Simple Pay – A lightweight Stripe payments plugin
\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:17:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: MakeStories 2.0 Launches Editor for WordPress, Rivaling Google’s Official Web Stories Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106327\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin?utm_source=rss&utm_medium=rss&utm_campaign=makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8860:\"Recipe slide from the MakeStories WordPress plugin.\n\n\n\n

Earlier today, MakeStories launched version 2.0 of its plugin for creating Web Stories with WordPress. In many ways, this is a new plugin launch. The previous version simply allowed users to connect their WordPress installs to the MakeStories site. With the new version, users can build and edit their stories directly from the WordPress admin.

\n\n\n\n

Version 2.0 of the plugin still requires an account and a connection with the MakeStories.io website. However, it is simple to set up. Users can log in without leaving their WordPress admin interface. This API connection means that user-created Stories are stored on the MakeStories servers. If an end-user wanted to jump platforms from WordPress to something else, this would allow them to take their Stories with them.

\n\n\n\n

“One of the things we would like to assure is your content is still yours, and none of the user data is being consumed or analyzed by us,” said Pratik Ghela, the founder and product manager at MakeStories. “We only take enough data to help serve you better.”

\n\n\n\n

The plugin is a competing solution to the official Web Stories plugin by Google. While the two share similarities in the final output (they are built to utilize the same front-end format for creating Stories on the web), they take different paths to get there.

\n\n\n\n

The two share similarities on the backend too. However, MakeStories may be more polished in some areas. For example, it allows users to zoom in on the small canvas area. Having the ability to reorder slides from the grid view also feels more intuitive.

\n\n\n\n

“The main unique selling proposition of our plugin is that it comes with a guarantee of the MakeStories team,” said Ghela. “We as a team have been building this for over two years, and we are proud to be one of the tools that has stood the test of time, and competition and is still growing at a very fast pace.”

\n\n\n\n

The team also wants to make the Story-creating process faster, safer, and rewarding. The goal is to cater to designers, developers, and content creators. Ghela also feels like his team’s support turnaround time of a few hours will be the key to success and is a good reason for users to give this plugin a try before settling on something else.

\n\n\n\n

“We feel that our goal is to see Web Stories flourish,” he said. “And we may have different types of users looking out for various options. So, the official plugin from Google and the one from MakeStories at least opens up the options for users to choose from. And we feel that the folks at Google are also building a great editor, and, at the end of the day, it’s up to the user to select what they feel is the best.

\n\n\n\n

Technically, MakeStories is a SaaS (software as a service) product. Even though it is a free plugin, there will eventually be a commercial component to it. Currently, it is free at least until the first quarter of 2021, which may be extended based on various factors. There is no word on what pricing tiers may be available after that.

\n\n\n\n

“There will always be a free tier, and we have always stood for it that your data belongs to you,” said Ghela. “In case you do not like the pricing, we will personally assist you to port out from using our editor and still keep the data and everything totally intact.”

\n\n\n\n

Diving Into the Plugin

\n\n\n\nStory management screen.\n\n\n\n

MakeStories is a drag-and-drop editor for building Web Stories. It works and feels much like typical design editors like Gimp or Photoshop. It shares similarities with QuarkXPress or InDesign, for those familiar with page layout programs. In some ways, it feels a lot like a light-colored version of Google’s Web Stories plugin with more features and a slightly more intuitive interface.

\n\n\n\n

The end goal is simple: create a Story through designing slides/pages that site visitors will click through as the narrative unfolds.

\n\n\n\n

The plugin provides a plethora of shapes, textures, and animations. These features are easy to find and implement. It also includes free access to images, GIFs, and videos. These are made possible via API integrations with Unsplash, Tenor, and Pexels.

\n\n\n\n

MakeStories includes access to 10 templates at the moment. However, what makes this feature stand out is that end-users can create and save custom templates for reuse down the road.

\n\n\n\nEditing a Story from a predesigned template.\n\n\n\n

One of the more interesting, almost hidden, features is the available text patterns. The plugin allows users to insert these patterns from a couple of dozen choices. This makes it easier to visualize a design without having to build everything from scratch.

\n\n\n\nInserting a text pattern and adjusting its size.\n\n\n\n

While the editing process is a carefully-crafted experience that makes the plugin worth a look, it is the actual publishing aspect of the workflow that is a bit painful. Traditional publishing in WordPress means hitting the “publish” button to make content live. This is not the case with the MakeStories plugin. It takes you through a four-step process of entering various publisher details, setting up metadata and SEO, validating the Story content, and analytics. It is not that these steps are necessarily bad. For example, MakeStories lets you know when images are missing alt text, which is needed information screen readers. The problem is that it feels out of place to go through all of these details when I, as a user, simply want my content published. And, many of these details, such as the publisher (author), should be automatically filled in.

\n\n\n\n

Updating a Story is not as simple as hitting an “update” button either. The system needs to run through some of the same steps too.

\n\n\n\n

Ghela said the publishing process might be a bit tough but will prove fruitful in the end. The plugin takes care of the technical aspects of adding title tags, meta, and other data on the front end after the user fills in the form fields.

\n\n\n\n

“We will definitely work on improving the flow as the community evolves and improve it a lot to be easier, faster, and, most importantly, still very customizable,” he said.

\n\n\n\n

The MakeStories team has no plans of stopping at its current point on the roadmap. Ghela sounded excited about some of the upcoming additions they are planning, including features like teams, branding, easy template customization, polls, and quizzes.

\n\n\n\n

On the Web Stories Format

\n\n\n\nUN report on COVID-19 and poverty published with MakeStories.\n\n\n\n

Many will ultimately hesitate to use any plugin that implements Web Stories given Google’s history of dropping projects. There is also a feeling that the format is a bit of a fad and will not stand the test of time.

\n\n\n\n

“We greatly believe in AMP and Web Stories as a content format,” said Ghela. “We, as an agency, have been involved a lot in AMP and have done a lot of experiments with it, including a totally custom WooCommerce site in fully-native, valid AMP with support for variable products, subscriptions, and other functionalities.”

\n\n\n\n

The company is all-in on the format and feels like it will be around for the long term, particularly if there is a good ecosystem around monetization.

\n\n\n\n

“We think that the initial reactions are because there are not enough proven results and because we never imagined the story format to come to the web,” said Ghela. “There were definitely plugins that did this. Few folks tried to build stories using good ol’ HTML, CSS, and JavaScript. But, the performance and UX were not that great. On the other hand, the engineers at the AMP Team are making sure that everything is just perfect. The UX, load time, WCV Score, just everything.”

\n\n\n\n

He feels that some of the early criticisms are unwarranted and that the web development community should give the format a try and provide feedback.

\n\n\n\n

“The more data we all get, actually gives the AMP team a clear idea of what’s needed, and they can design the roadmap accordingly,” he said. “So, just giving out early reactions won’t help, but constructive criticism and getting back to the AMP team with what you are doing will.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:12:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"WordPress.org blog: WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7956:\"

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"WPTavern: WordPress 5.6 Release Team Pulls the Plug on Block-Based Widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106466\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:193:\"https://wptavern.com/wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8762:\"Current block-based widgets admin screen design.\n\n\n\n

I was wrong. I assured our readers that “the block-based widget system will be ready for prime time when WordPress 5.6 lands” in my previous post on the new feature’s readiness. I also said that was on the condition of not trying to make it work with the customizer — that experience was still broken. However, the 5.6 team pulled the plug on block-based widgets for the second time this year.

\n\n\n\n

One week ago, WordPress 5.6 release lead Josepha Haden seemed to agree that it would be ready. However, things can change quickly in a development cycle, and tough decisions have to be made with beta release deadlines.

\n\n\n\n

This is not the first feature the team has punted to a future release. Two weeks ago, they dropped block-based nav menus from the 5.6 feature list. Both features were originally planned for WordPress 5.5.

\n\n\n\n

A new Widgets admin screen has been under development since January 2019, which was not long after the initial launch of the block editor in WordPress 5.0. For now, the block-based widgets feature has been punted to WordPress 5.7. It has also been given the “early” tag, which means it should go into core WordPress soon after the 5.7 release cycle begins. This will give it more time to mature and more people an opportunity to test it.

\n\n\n\n

Helen Hou-Sandì, the core tech lead for 5.6, provided a historical account of the decision and why it was not ready for inclusion in the new ticket:

\n\n\n\n

My question for features that affect the front-end is “can I try out this new thing without the penalty of messing up my site?” — that is, user trust. At this current moment, given that widget areas are not displayed anything like what you see on your site without themes really putting effort into it and that you have to save your changes live without revisions to get an actual contextual view, widget area blocks do not allow you to try this new feature without penalizing you for experimenting.

\n\n\n\n

She went on to say that the current experience is subpar at the moment. Problems related to the customizer experience, which I covered in detail over a month ago, were also mentioned.

\n\n\n\n

“So, when we come back to this again, let’s keep sight of what it means to keep users feeling secure that they can get their site looking the way they want with WordPress, and not like they are having to work around what we’ve given them,” said Hou-Sandì.

\n\n\n\n

This is a hopeful outlook despite the tough decision. Sometimes, these types of calls need to be made for the good of the project in the long term. Pushing back a feature to a future version for a better user experience can be better than launching early with a subpar experience.

\n\n\n\n

“The good part of this is that now widgets can continue to be ‘re-imagined’ for 5.7, and get even more enhancements,” said lead WordPress developer Andrew Ozz in the ticket. “Not sure how many people have tested this for a bit longer but having blocks in the widgets areas (a.k.a. sidebars) opens up many new possibilities and makes a lot of the old, limited widgets obsolete. The ‘widget areas’ become something like ‘specialized posts with more dynamic content,’ letting users (and designers) do a lot of stuff that was either hard or impossible with the old widgets.”

\n\n\n\n

After the letdown of seeing one of my most anticipated features of 5.6 being dropped, it is encouraging to see the positive outlook from community leaders on the project.

\n\n\n\n

“You know, I was really hopeful for it too, and that last-minute call was one I labored over,” said Haden. “When I last looked, it did seem close to ready, but then more focused testing was done and there were some interactions that are a little rough for users. I’m grateful for that because the time to discover painful user experiences is before launch rather than after!”

\n\n\n\n

Despite dropping its second major feature, WordPress 5.6 still has some big highlights that will be shipping in less than two months. The new Twenty Twenty-One theme looks to be a breath of fresh air and will explore block-related features not seen in previous default themes. Haden also pointed out auto-updates for major releases, application passwords support for the REST API, and accessibility improvements as features to look forward to.

\n\n\n\n

WordPress 5.6 Beta 1 is expected to ship today.

\n\n\n\n

Adding New Features To an Old Project

\n\n\n\n

At times, it feels like the Gutenberg project has bitten off more than it can chew. Many of the big feature plans continually miss projections. Between full-site editing, global styles, widgets, nav menus, and much more, it is tough to get hyper-focused on one feature and have it ready to ship. On the other hand, too much focus one way can be to the detriment to other features in the long run. All of these pieces must eventually come together to create a more cohesive whole.

\n\n\n\n

WordPress is also 17 years old. Any new feature could affect legacy features or code. The goal for block-based widgets is to transition an existing feature to work within a new system without breaking millions of websites in the process. Twenty-one months of work on a single feature shows that it is not an easy problem to solve.

\n\n\n\n

“You are so right about complex engineering problems!” said Haden. “We are now at a point in the history of the project where connecting all of the pieces can have us facing unforeseen complications.”

\n\n\n\n

The project also needs to think about how it can address some of the issues it has faced with not quite getting major features to completion. Is the team stretched too thin to focus on all the parts? Are there areas we can improve to push features forward?

\n\n\n\n

“There will be a retrospective where we can identify what parts of our process can be improved in the future, but I also feel like setting stretch goals is good for any software project,” said Haden. “Many contributors have a sense of urgency around bringing the power of blocks to more spaces in WordPress, which I share, but when it’s time to ship, we have to balance that with our deep commitment to usability.”

\n\n\n\n

One problem that has become increasingly obvious is that front-end editing has become tougher over the years. Currently, widgets and nav menus can be edited in two places in WordPress with wildly different interfaces. Full-site editing stands to add an entirely new interface to the mix.

\n\n\n\n

“I think one of the problems that we’re trying to solve with Gutenberg has always been a more consistent experience for editing elements across the WordPress interface,” said Haden. “No user should have to learn five different workflows to make sure their page looks the way they imagined it when it’s published.”

\n\n\n\n

In the meantime, which may be numbered in years, end-users will likely have these multiple interfaces to deal with — overlap while new features are being developed. This may simply be a necessary growing pain of an aging project, one that is trying to lead the pack of hungry competitors in the CMS space.

\n\n\n\n

“There’s a lot of interest in reducing the number of workflows, and I’m hopeful that we can consolidate down to just one beautiful, intuitive interface,” said Haden.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 21:16:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce Tests New Instagram Shopping Checkout Feature, Now in Closed Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106398\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2878:\"

Instagram’s checkout feature, which allows users to purchase products without leaving the app, has become an even more important part of Facebook’s long-term investment in e-commerce now that the pandemic has so heavily skewed consumer behavior towards online shopping. When Instagram introduced checkout in 2019, it reported that 130 million users were tapping to reveal product tags in shopping posts every month.

\n\n\n\nimage credit: Instagram\n\n\n\n

Business owners who operate an existing store can extend their audience to Instagram by funneling orders from the social network into their own stores, without shoppers having to leave Instagram. Checkout supports integration with several e-commerce platform partners, including Shopify and BigCommerce, and will soon be available for WooCommerce merchants.

\n\n\n\n

WooCommerce is testing a new Instagram Shopping Checkout feature for its Facebook for WooCommerce plugin. The free extension is used on more than 900,000 websites and will provide the bridge for store owners who want to tap into Instagram’s market. The checkout capabilities are currently in closed beta. Anyone interested to test the feature can sign up for consideration. Businesses registered in the USA that meet certain other requirements may be selected to participate, and the beta is also expanding to other regions soon.

\n\n\n\n

WooCommerce currently supports shoppable posts, which are essentially products sourced from a product catalog created on Facebook that are then linked to the live store through an Instagram business account. Instagram’s checkout takes it one step further to provide a native checkout experience inside the app. Merchants pay no selling fees until December 31, 2020. After that time, the fee is 5% per shipment or a flat fee of $0.40 for shipments of $8.00 or less. 

\n\n\n\n

On the customer side, shoppers only have to enter their information once and thereafter it is stored for future Instagram purchases. Instagram also pushes shipment and delivery notifications inside the app. Store owners will need to weigh whether the convenience of the in-app checkout experience is worth forking over 5% to Facebook, or if they prefer funneling users over to the live store instead.

\n\n\n\n

Instagram Shopping Checkout is coming to WooCommerce in the near future but the company has not yet announced a launch date, as the feature is just now entering closed beta.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 04:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Past Twenty* WordPress Themes To Get New Block Patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106396\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/past-twenty-wordpress-themes-to-get-new-block-patterns?utm_source=rss&utm_medium=rss&utm_campaign=past-twenty-wordpress-themes-to-get-new-block-patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6608:\"

Mel Choyce-Dwan, the Default Theme Design Lead for WordPress 5.6, kick-started 10 tickets around two months ago that would bring new features to the old default WordPress themes. The proposal is to add unique block patterns, a feature added to WordPress 5.5, to all of the previous 10 Twenty* themes. It is a lofty goal that could breathe some new life into old work from the previous decade.

\n\n\n\n

Currently, only the last four themes are marked for an update by the time WordPress 5.6 lands. Previous themes are on the list to receive their block patterns in a future release. For developers and designers interested in getting involved, the following is a list of the Trac tickets for each theme:

\n\n\n\n\n\n\n\n

If you are wondering where Twenty Eighteen is in that list, that theme does not actually exist. It is the one missing year the WordPress community has had since the one-default-theme-per-year era began with Twenty Ten. It is easy to forget that we did not get a new theme for the 2017-2018 season. With all that has happened in the world this year, we should count ourselves fortunate to see a new default theme land for WordPress this December. WordPress updates and its upcoming default theme are at least one consistency that we have had in an otherwise chaotic time.

\n\n\n\n

More than anything, it is nice to see some work going toward older themes — not just in terms of bug fixes but feature updates. The older defaults are still a part of the face of WordPress. Twenty Twenty and Twenty Seventeen each have over one million active installs. Twenty Nineteen has over half a million. The other default themes also have significant user bases in the hundreds of thousands — still some of the most-used themes in the directory. We owe it to those themes’ users to keep them fresh, at least as long as they maintain such levels of popularity.

\n\n\n\n

This is where the massive theme development community could pitch in. Do some testing of the existing patches. Write some code for missing patterns or introduce new ideas. This is the sort of low-hanging fruit that almost anyone could take some time to help with.

\n\n\n\n

First Look at the New Patterns

\n\n\n\n

None of the proposed patterns have landed in trunk, the development version of WordPress, yet. However, several people have created mockups or added patches that could be committed soon.

\n\n\n\n

One of my favorite patterns to emerge thus far is from Beatriz Fialho for the Twenty Nineteen theme. Fialho has created many of the pattern designs proposed thus far, but this one, in particular, stands out the most. It is a simple two-column, two-row pattern with a circular image, heading, and paragraph for each section. Its simplicity fits in well with the more elegant, business-friendly look of the Twenty Nineteen theme.

\n\n\n\nServices pattern for Twenty Nineteen.\n\n\n\n

It is also fitting that Twenty Nineteen get a nice refresh with new patterns because it was the default theme to launch with the block editor. Ideally, it would continually be updated to showcase block-related features.

\n\n\n\n

While many people will focus on some of the more recent default themes, perhaps the most interesting one is a bit more dated. Twenty Thirteen was meant to showcase the new post formats feature in WordPress 3.6. According to Joen Asmussen, the theme’s primary designer, the original idea was for users to compose a ribbon of alternating colors as each post varied its colors.

\n\n\n\n

“The alternating ribbon of colors did not really come to pass because post formats were simply not used enough to create an interesting ribbon,” he wrote in the Twenty Thirteen ticket. “However, perhaps for block patterns, we have an opportunity to revisit those alternating ribbons of colors. In other words, I’d love to see those warm bold colors used in big swathes that take up the whole pattern background.”

\n\n\n\n
Patterns designed to match post formats.\n\n\n\n

This could be a fun update for end-users who are still using that feature that shall not be named post formats.

\n\n\n\n

There is a lot to like about many of the pattern mockups so far. I look forward to seeing what lands along with WordPress 5.6 and in future updates.

\n\n\n\n

Establishing Pattern Category Standard

\n\n\n\n

With the more recent Twenty Twenty-One theme’s block patterns and the new patterns being added to some of the older default themes, it looks like a specific pattern category naming scheme is starting to become a standard. Of the patches thus far, each is creating a new pattern category named after the theme itself.

\n\n\n\n

This makes sense. Allowing users to find all of their theme’s patterns in one location means that they can differentiate between them and those from core or other plugins. Third-party theme authors should follow suit and stick with this convention for the same reason.

\n\n\n\n

Developers can also define multiple categories for a single pattern. This allows theme authors to create a category that houses all of their patterns in one location. However, they can also split them into more appropriate context-specific categories for discoverability.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 19 Oct 2020 21:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"BuddyPress: BuddyPress 7.0.0-beta1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://buddypress.org/?p=315150\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://buddypress.org/2020/10/buddypress-7-0-0-beta1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4332:\"

BuddyPress 7.0.0-beta1 is now available for testing!

\n\n\n\n

Please note the plugin is still in development, so we recommend running this beta release on a testing site.

\n\n\n\n

You can test BuddyPress 7.0.0-beta1 in 4 ways :

\n\n\n\n\n\n\n\n

The 7.0.0 stable release is slated to the beginning of December, and we’d love you to give us a hand to get there!

\n\n\n\n

Please note BuddyPress 7.0.0 will require at least WordPress 4.9.

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute. Here are some of the big changes and features to pay close attention to while testing (Check out this report on Trac for the full list).

\n\n\n\n
\n\n\n\n

New Administration screens to manage BuddyPress types

\n\n\n\n

In BuddyPress 7.0.0 site administrators will be able to add, edit or delete Member & Group types using their WordPress Administration Screens just like they would do for Post tags.

\n\n\n\n

Read this development note to learn more about it.

\n\n\n\n
\n\n\n\n

Let’s welcome 3 new BP Blocks into our Block Editor

\n\n\n\n
  • The Activity Embed block let authors embed an activity into their post or page.
  • Use the BP Members block to select community users you want to feature into a post or a page.
  • Enjoy the BP Groups block to pick the groups you want to highlight into a post or a page.
\n\n\n\n

Get to know these new blocks reading this development note.

\n\n\n\n
\n\n\n\n

Improved support for WP CLI

\n\n\n\n

WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs, and much more, without using a web browser. In 7.0.0, you will be able to Enjoy new BuddyPress CLI commands to manage BuddyPress Group Meta, BuddyPress Activity Meta, activate or deactivate the BuddyPress signup feature and create BuddyPress specific testing code for plugins.

\n\n\n\n

Discover more about it from this development note.

\n\n\n\n
\n\n\n\n

And so much more such as improvements to the BP REST API, our Template pack, images and iframes lazy loading support…

\n\n\n\n
\n\n\n\n

How You Can Help

\n\n\n\n

Do you speak a language other than English? Help us translate BuddyPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, file one on BuddyPress Trac.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 22:30:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:12:\"Mathieu Viet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"WPTavern: Using the Web Stories for WordPress Plugin? You Better Play By Google’s Rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105848\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:215:\"https://wptavern.com/using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules?utm_source=rss&utm_medium=rss&utm_campaign=using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4080:\"Web Stories dashboard screen in WordPress.\n\n\n\n

What comes as a surprise to few, Google has updated its content guidelines for its Web Stories format. For users of its recently-released Web Stories for WordPress plugin, they will want to follow the extended rules for their Stories to appear in the “richer experiences” across Google’s services. This includes the grid view on Search, Google Images, and Google Discover’s carousel.

\n\n\n\n

Google released its Web Stories plugin in late September to the WordPress community. It is a drag-and-drop editor that allows end-users to create custom Stories from a custom screen in their WordPress admin.

\n\n\n\n
Visual Stories on Search.
\n\n\n\n

The plugin does not directly link to Google’s content guidelines anywhere. For users who do not do a little digging, they may be caught unaware if their stories are not surfaced in various Google services.

\n\n\n\n

On top of the Discover and Webmaster guidelines, Web Stories have six additional restrictions related to the following:

\n\n\n\n
  • Copyrighted content
  • Text-heavy Web Stories
  • Low-quality assets
  • Lack of narrative
  • Incomplete stories
  • Overly commercial
\n\n\n\n

While not using copyrighted content is one of those reasonably-obvious guidelines, the others could trip up some users. Because Stories are meant to represent bite-sized bits of information on each page, they may become ineligible if most pages have more than 180 words of text. Videos should also be limited to fewer than 60 seconds on each page.

\n\n\n\n

Low-quality media could be a flag for Stories too. Google’s guidelines point toward “stretched out or pixelated” media that negatively impacts the reader’s experience. They do not offer any specific resolution guidelines, but this should mostly be a non-issue today. The opposite issue is far more likely — users uploading media that is too large and not optimized for viewing on the web.

\n\n\n\n

The “lack of narrative” guideline is perhaps the vaguest, and it is unclear how Google will monitor or police narrative. However, the Stories format is about storytelling.

\n\n\n\n

“Stories are the key here imo,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Google specifically states that Stories need a “binding theme or narrative structure” from one page to the next. Essentially, the company is telling users to use the format for the purpose it was created for. They also do not want users to create incomplete stories where readers must click a link to finish the Story or get information.

\n\n\n\nCNN’s Web Story on Remembering John Lennon.\n\n\n\n

Overly commercial Stories are frowned upon too. While Google will allow affiliate marketing links, they should be restricted to a minor part of the experience.

\n\n\n\n

Closing his Twitter thread, Marsland seemed to hit the point. “I’ve seen some initial Google Web Stories where the platform is being used as a replacement for a brochure or website,” he wrote. “In my view that’s a huge missed opportunity. If I was advising brands I would say ‘Tell Stories’ this is a platform for Story Telling.”

\n\n\n\n

If users of the plugin follow this advice, their Stories should surface on Google’s rich search experiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 20:51:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"WPTavern: Stripe Acquires Paystack for $200M+\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106269\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"https://wptavern.com/stripe-acquires-paystack-for-200m?utm_source=rss&utm_medium=rss&utm_campaign=stripe-acquires-paystack-for-200m\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3196:\"

The big news in the world of e-commerce today is Stripe’s acquisition of Paystack, a Nigeria-based payments system that is widely used throughout African markets. The company, which became informally known as “the Stripe of Africa” picked up $8 million in Series A funding in 2018, led by Stripe, Y Combinator, and Tencent. Paystack has grown to power more than 60,000 businesses, including FedEx, UPS, MTN, the Lagos Internal Revenue Service, and AXA Mansar.

\n\n\n\n

Stripe’s acquisition of the company is rumored to be more than $200M, a small price to pay for a foothold in emerging African markets. In the company’s announcement, Stripe noted that African online commerce is growing 21% year-over-year, 75% faster than the global average. Paystack dominates among payment systems, accounting for more than half of all online transactions in Nigeria.

\n\n\n\n

“In just five years, Paystack has done what many companies could not achieve in decades,” Stripe EMEA business lead Matt Henderson said. “Their tech-first approach, values, and ambition greatly align with our own. This acquisition will give Paystack resources to develop new products, support more businesses and consolidate the hyper-fragmented African payments market.”

\n\n\n\n

Long term, Stripe plans to embed Paystack’s capabilities in its Global Payments and Treasury Network (GPTN), the company’s programmable infrastructure for global money movement.

\n\n\n\n

“Paystack merchants and partners can look forward to more payment channels, more tools, accelerated geographic expansion, and deeper integrations with global platforms,” Paystack CEO and co-founder Shola Akinlade said. He also assured customers that there’s no need to make any changes to their technical integrations, as Paystack will continue expanding and operating independently in Africa.

\n\n\n\n

Paystack is used as a payment gateway for thousands of WordPress-powered stores through plugins for WooCommerce, Easy Digital Downloads, Paid Membership Pro, Give, Contact Form 7, and an assortment of booking plugins. The company has an official WordPress plugin, Payment Forms for Paystack, which is active on more than 6,000 sites, but most users come through the Paystack WooCommerce Payment Gateway (20,000+ active installations).

\n\n\n\n

Stripe’s acquisition was a bit of positive news during what is currently a turbulent time in Nigeria, as citizens are actively engaged in peaceful protests to end police brutality. Paystack’s journey is an encouraging example of the flourishing Nigerian tech ecosystem and the possibilities available for smaller e-commerce companies that are solving problems and removing barriers for businesses in emerging markets.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 22:26:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: Diving Into the Book Review Block Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106273\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:145:\"https://wptavern.com/diving-into-the-book-review-block-plugin?utm_source=rss&utm_medium=rss&utm_campaign=diving-into-the-book-review-block-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6791:\"

Created by Donna Peplinskie, a Product Wrangler at Automattic, the Book Review Block plugin is nearly three years old. However, it only came to my attention during a recent excursion to find interesting block plugins.

\n\n\n\n

The plugin does pretty much what it says on the cover. It is designed to review books. It generally has all the fields users might need to add to their reviews, such as a title, author, image, rating, and more. The interesting thing is that it can automatically fill in those details with a simple ISBN value. Plus, it supports Schema markup, which may help with SEO.

\n\n\n\n

Rain or shine, sick or well, I read every day. I am currently a month and a half shy of a two-year reading streak. When the mood strikes, I even venture to write a book review. As much as I want to share interesting WordPress projects with the community, I sometimes have personal motives for testing and writing about plugins like Book Review Block. Anything that might help me or other avid readers share our thoughts on the world of literature with others is of interest.

\n\n\n\n

Admittedly, I was excited as I plugged in the ISBN for Rhthym of War, the upcoming fourth book of my favorite fantasy series of all time, The Stormlight Archive. I merely needed to click the “Get Book Details” button.

\n\n\n\n

Success! The plugin worked its magic and pulled in the necessary information. It had my favorite author’s name, the publisher, the upcoming release date, and the page count. It even had a long description, which I could trim down in the editor.

\n\n\n\nDefault output of the Book Review block.\n\n\n\n

There was a little work to make this happen before the success. To automatically pull in the book details, end-users must have an API Key from Google. It took me around a minute to set that up and enter it into the field available in the block options sidebar. The great thing about the plugin is that it saves this key so that users do not have to enter each time they want to review a book.

\n\n\n\n

Book Review Block a good starting point. It is straightforward and simple to use. It is not yet at a point where I would call it a great plugin. However, it could be.

\n\n\n\n

Falling Short

\n\n\n\n

The plugin’s Book Review block should be taking its cues from the core Media & Text block. When you get right down to it, the two are essentially doing the same thing visually. Both are blocks with an image and some content sitting next to each other.

\n\n\n\n

The following is a list of items where it should be following core’s lead:

\n\n\n\n
  • No way to edit alt text (book title is automatically used).
  • The image is always aligned left and the content to the right with no way to flip them.
  • The media and content are not stackable on mobile views.
  • Cannot adjust the size of the image or content columns.
  • While inline rich-text controls are supported, users cannot add Heading, List, or Paragraph blocks to the content area and use their associated block options.
\n\n\n\n

That is the shortlist that could offer some quick improvements to the user experience. Ultimately, the problems with the plugin essentially come down to not offering a way to customize the output.

\n\n\n\n

One of the other consistent problems is that the book image the plugin loads is always a bit small. This seems to be more of an issue from the Google Books API than the plugin. Each time I tested a book, I opted to add a larger image — the plugin does allow you to replace the default.

\n\n\n\n

The color settings are limited. The block only offers a background color option with no way to adjust the text color. A better option for plugin users is to wrap it in a Group block and adjust the background and text colors there.

\n\n\n\nBook Review block wrapped inside a Group block.\n\n\n\n

It would also be nice to have wide and full-alignment options, which is an often-overlooked featured from many block plugin authors.

\n\n\n\n

Using the Media & Text Block to Recreate the Book Review Block

\n\n\n\n

The Book Review Block plugin has a lot of potential, and I want to see it evolve by providing more flexibility to end-users. Because the Media & Text block is the closest core block to what the plugin offers, I decided to recreate a more visually-appealing design with it.

\n\n\n\nBook review section created with the Media & Text block.\n\n\n\n

I made some adjustments on the content side of things. I used the Heading block for the book title, a List block for the book metadata, and a Paragraph block for the description.

\n\n\n\n

The Media & Text block also provided me the freedom to adjust the alignment, stack the image and content on mobile views, and tinker with the size of the image. Plus, it has that all-important field for customizing the image alt attribute.

\n\n\n\n

The Media & Text block gave me much more design mileage.

\n\n\n\n

However, there are limitations to the core block. It does not fully capture some of the features available via the Book Review block. The most obvious are the automatic book details via an ISBN and the Schema markup. Less obvious, there is no easy way to recreate the star rating — I used emoji stars — and long description text does not wrap under the image. To recreate that, you would have to opt to use a left-aligned image followed by content.

\n\n\n\n

Overall, the Media & Text block gives me the ability to better style the output, which is what I am more interested in as a user. I want to put my unique spin on things. That is where the Book Review Plugin misfires. It is also the sort of thing that the plugin author can iterate on, offering more flexibility in the future.

\n\n\n\n

This is where many block plugins go wrong, particularly when there is more than one or two bits of data users should enter. Blocks represent freedom in many ways. However, when plugin developers stick to a rigid structure, users can sometimes lose that sense of freedom that they would otherwise have with building their pages.

\n\n\n\n

One of the best blocks, hands down, that preserves that freedom is from the Recipe Block plugin. It has structured inputs and fields. However, it allows freeform content for end-users to make it their own.

\n\n\n\n

When block authors push beyond this rigidness, users win.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 20:44:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce 4.6 Makes New Home Screen the Default for New and Existing Stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3018:\"

WooCommerce 4.6 was released today. The minor release dropped during WooSesh, a global, virtual conference dedicated to WooCommerce and e-commerce topics. It features the new home screen as the default for all stores. Previously, the screen was only the default on new stores. Existing store owners had to turn the feature on in the settings.

\n\n\n\n
\n\n\n\n

The updated home screen, originally introduced in version 4.3, helps store admins see activity across the site at a glance and includes an inbox, quick access to store management links, and an overview of stats on sales, orders, and visitors. This redesigned virtual command center arrives not a moment too soon, as anything that makes order management more efficient is a welcome improvement, due to the sheer volume of sales increases that store owners have seen over the past eight months.

\n\n\n\n

In stark contrast to industries like hospitality and entertainment that have proven to be more vulnerable during the pandemic, e-commerce has seen explosive growth. During the State of the Woo address at WooSesh 2020, the WooCommerce team shared that e-commerce is currently estimated to be a $4 trillion market that will grow to $4.5 trillion by 2021. WooCommerce accounts for a sizable chunk of that market with an estimated total payment volume for 2020 projected to reach $20.6 billion, a 74% increase compared to 2019.

\n\n\n\n

The WooCommerce community is on the forefront of that growth and is deeply invested in the products that are driving stores’ success. The WooCommerce team shared that 75% of people who build extensions also build and maintain stores for merchants, and 70% of those who build stores for merchants also build and maintain extensions or plugins. In 2021, they plan to invest heavily in unlocking more features in more countries and will make WooCommerce Payments the native payment method for the global platform.

\n\n\n\n

A new report from eMarketer shows that US e-commerce growth has jumped 32.4%, accelerating the online shopping shift by nearly two years. Experts also predict the top 10 e-commerce players will swallow up more of US retail spending to account for 63.2% of all online sales this year, up from 57.9% in 2019.

\n\n\n\n

The increase in e-commerce spending may not be entirely tied to the pandemic, as some experts believe this historic time will mark permanent changes in consumer spending habits. This is where independent stores, powered by WooCommerce and other technologies, have the opportunity to establish a strong reputation for themselves by providing quality products and reliable service, as well as by being more nimble in the face of pandemic-driven increases in volume.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 03:48:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"WPTavern: The Future of Starter Content: WordPress Themes Need a Modern Onboarding and Importing Tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106177\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool?utm_source=rss&utm_medium=rss&utm_campaign=the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7385:\"Image credit: picjumbo.com on Pexels.\n\n\n\n

Starter content. It was a grand idea, one of those big dreams of WordPress. It was the new kid on the block in late 2016. Like the introduction of post formats in 2011, the developer community was all in for at least that particular release version. Then, it was on to the next new thing, with the feature dropping off the radar for all but the most ardent evangelists.

\n\n\n\n

Some of us were burned over the years, living and dying by the progress of features that we wanted most.

\n\n\n\n

Released in WordPress 4.7, starter content has since seemed to be going the way of post formats. After four years, only 141 themes in the WordPress theme directory support the feature. There has been no movement to push it beyond its initial implementation. And, it never really covered the things that theme authors wanted in the first place. It was a start. But, themers were ultimately left to their own devices, rolling custom solutions for something that never panned out — fully-featured demo and imported content. Four years is an eternity in the web development world, and there is no sense in waiting around to see if WordPress would push forward.

\n\n\n\n

Until Helen Hou-Sandí published Revisiting Starter Content last week, most would have likely assumed the feature would be relegated to legacy code used by old-school fans of the feature and those theme authors who consider themselves completionists.

\n\n\n\n

“Starter content in 4.7 was always meant to be a step one, not the end goal or even the resting point it’s become,” wrote Hou-Sandí. “There are still two major things that need to be done: themes should have a unified way of showing users how best to put that theme to use in both the individual site and broader preview contexts, and sites with existing content should also be able to take advantage of these sort of ‘ideal content’ definitions.”

\n\n\n\n

Step two should have been this four-year-old accompanying ticket to allow users to import starter content into existing, non-fresh sites.

\n\n\n\n

Since the initial feature dropped, the theme landscape has changed. Let’s face it. WordPress might simply not be able to compete with theme companies that are pushing the limits, creating experiences that users want at much swifter speeds.

\n\n\n\n

Look at where the Brainstorm Force’s Starter Templates plugin for its Astra theme is now. Users can click a button and import a full suite of content-filled pages or even individual templates. And, the Astra theme is not alone in this. It has become an increasingly-common standard to offer some sort of onboarding to users. GoDaddy’s managed WordPress service fills a similar need on the hosting end.

\n\n\n\nAstra’s starter templates and content.\n\n\n\n

As WordPress use becomes more widespread, the more it needs a way to onboard users.

\n\n\n\n

This essentially boils down to the question: how can I make it look like the demo?

\n\n\n\n

Ah, the age-old question that theme authors have been trying to solve. Whether it has been limitations in the software or, perhaps, antiquated theme review guidelines related to demo and imported content, this has been a hurdle that has been tough to jump. But, some have sailed over it and moved on. While WordPress has seemingly been twiddling its thumbs for years, Brainstorm Force and other theme companies have solved this and continued to innovate.

\n\n\n\n

This is not necessarily a bad thing. There are plenty of ideas to steal copy and pull into the core platform.

\n\n\n\n

One of the other problems facing the WordPress starter content feature is that it is tied to the customizer. With the direction of the block system, it is easy to ask what the future holds. The customizer — originally named the theme customizer — was essentially a project to allow users to make front-end adjustments and watch those customizations happen in real time. However, new features like global styles and full-site editing are happening on their own admin screens. Most theme options will ultimately be relegated to global styles, custom templates, block styles, and block patterns. There may not be much left for the customizer to do.

\n\n\n\n

Right now, there are too many places in WordPress to edit the front-end bits of a WordPress site. My hope is that all of these things are ultimately merged into one less-confusing interface. But, I digress…

\n\n\n\n

Starter content should be rethought. Whoever takes the reins on this needs a fresh take that adopts modern methods from leading theme companies.

\n\n\n\n

The ultimate goal should be to allow theme authors to create multiple sets of templates/content that end-users can preview and import. It should not be tied to whether it is a new site. Any site owner should be able to import content and have it automagically go live. It should also be extendable to allow themes to support page builders like Elementor, Beaver Builder, and many others.

\n\n\n\n

This seems to be in line with Hou-Sandí’s thoughts. “For a future release, we should start exploring what it might look like to opt into importing starter content into existing sites, whether wholesale or piecewise,” she wrote. “Many of us who work in the WordPress development/consulting space tend not to ever deal in switching between public themes on our sites, but let’s not forget that’s a significant portion of our user audience and we want to continue to enable them to not just publish but also publish in a way that matches their vision.”

\n\n\n\n

Let’s do it right this go-round, keep a broad vision, and provide an avenue for theme authors to adopt a standardized core WordPress method instead of having everyone build in-house solutions.

\n\n\n\n

I haven’t even touched on the recent call to use starter content for WordPress.org theme previews. It will take more than ideas to excite many theme authors about the possibility. That ticket has sat for seven years with no progress, and most have had it on their wish list for much longer. It is an interesting proposal, one that has been tossed around in various team meetings for years.

\n\n\n\n

Like so many other things, theme authors have either given up hope or moved onto doing their own thing. They need to be brought into the fold, not only as third parties who are building with core WordPress tools but as developers who are contributing to those features.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 14 Oct 2020 20:07:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:116:\"WPTavern: Google Podcasts Manager Adds More Data from Search: Impressions, Top-Discovered Episodes, and Search Terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:271:\"https://wptavern.com/google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms?utm_source=rss&utm_medium=rss&utm_campaign=google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2568:\"

Google announced an expansion of listener engagement metrics today for those using its Podcast Manager. Previously, audience insights included data about the types of devices listeners are using, where listeners tune in and drop off during a given episode, total number of listens, and listening duration, but the service lacked analytics regarding how visitors were discovering the podcast.

\n\n\n\n

Google is remedying that today by expanding the dashboard to show impressions, clicks, top-discovered episodes, and search terms that brought listeners to the podcast. This information can help podcasters understand how their content is getting discovered so they can better tailor their episodes to attract more new listeners.

\n\n\n\n

The podcasting industry has seen remarkable growth over the past five years, which previously led experts to project that marketers will spend over $1 billion in advertising by 2021. After the pandemic hit, podcast listening took a downturn in the U.S. but at the same time, podcast creators have found more time to create new shows and episodes. Businesses are turning to the medium to supplement traditional marketing methods that no longer have the same impact now that consumer spending habits heavily favor online products.

\n\n\n\n

Along with the new metrics available inside Google Podcasts Manager, the company also published a guide to optimizing podcasts for Google Search. It highlights four important items for making sure a podcast can be found:

\n\n\n\n
  • Detailed show and episode metadata
  • Ensure the podcast’s webpage and RSS data match
  • Include cover art
  • Ensure Googlebot can access your audio files
\n\n\n\n

A detailed breakdown of your audience’s listening habits isn’t worth much if you’re having trouble getting your podcast discovered. Any podcasting plugin for WordPress should handle these basic optimization recommendations, but if you are still having trouble being found via Google, you can dig deeper into the podcast setup guide for more detailed recommendations.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 22:57:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Are Block-Based Widgets Ready To Land in WordPress 5.6?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/are-block-based-widgets-ready-to-land-in-wordpress-5-6?utm_source=rss&utm_medium=rss&utm_campaign=are-block-based-widgets-ready-to-land-in-wordpress-5-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8214:\"

Two weeks ago, the Gutenberg team put out an open call for block-based widgets feedback. I had already written a lengthy review of the new system earlier in September but was asked by a member of the team to share my thoughts on the most recent iteration. With the upcoming freeze for WordPress 5.6 Beta 1 just a week away, I figured it would not hurt to do another deep dive.

\n\n\n\n

For reference, my latest testing is against version 9.2.0-alpha-172f589 of the Gutenberg plugin, which was a build from earlier today. Gutenberg development moves fast, but everything should be accurate to that point.

\n\n\n\n

Ultimately, many of the problems I pointed out over a month ago still exist. However, the team has cleaned most of the minor issues, such as pointing the open/close arrows for sidebars (block areas) in the correct direction and making it more consistent with the post-editing screen. The UI is much more polished.

\n\n\n\n

Before I dive into all the problems, I want to answer the question I am proposing. Yes, the block-based widget system will be ready for prime time when WordPress 5.6 lands. It is not there yet, but it is at a point where there is a clear finish line that is reachable in the next two months.

\n\n\n\n

I will ignore the failure of block-based widgets in the customizer, which landed in Gutenberg 8.9 and was removed in 9.1. I will also look past the recent proposal to reconstruct the widgets screen to use the Customize API, at least for now. There is a boatload of problems that block-based widgets present for the customizer, and those problems are insurmountable for WordPress 5.6. Long term, WordPress needs to have a single place for editing widget/block areas. Users will likely have to live with some inconsistencies for a while.

\n\n\n\n

Assuming the team does not try to throw a last-minute Hail Mary and implement full editing of blocks in the customizer this round, it is safe to say that block-based widgets are well on their way toward a successful WordPress 5.6 debut.

\n\n\n\n

The User Experience

\n\n\n\nBlock-based widgets screen.\n\n\n\n

As a user, I genuinely enjoy using the new Widgets admin screen. The open-ended, free-form block areas create untold possibilities for designing my WordPress sites. Traditional widgets were limited in scope. Users were buckled down to a handful of core widgets, possibly some plugin widgets, and whatever their theme author offered up. However, with blocks, the pool of choices expands to at least triple the out-of-the-box options (I am not counting embed-type blocks individually). Plus, blocks provide a far more extensive set of design options than a traditional widget.

\n\n\n\n

In comparison, traditional widgets are outdated. Blocks are superior in almost every way. However, there are still problems with this new system.

\n\n\n\n

The biggest issue right now is that end-users can exit the Widgets screen without saving their changes. There is no warning to let them know that all their work is about to be lost in the ether. This is one of those OMGBBQ-level items that need to happen before WordPress 5.6 drops.

\n\n\n\n

One nice-to-have-but-not-necessary feature would be the ability to drag blocks from one block area to another. In the old widgets system, users could move widgets from sidebar to sidebar. The current alternative is to copy a widget, paste it in a new block area, and remove the original.

\n\n\n\n

I am also not a fan of not having an option for the top toolbar, which is available on the post-editing screen. One of the reasons for using this toolbar is because I dislike the default popup toolbar on individual blocks. It is distracting and often gets in the way of my work.

\n\n\n\n

Legacy widgets seem to still be a work in progress. The Legacy Widget block did not work at all for me at times. Then, it magically began to work. However, Gutenberg does now automatically add registered third-party widgets to the block inserter just as if they were blocks.

\n\n\n\nGetting a plugin’s widget to work.\n\n\n\n

This presented its own problems. The only way I managed to make third-party plugin widgets work was to insert the widget, save, and refresh the widgets screen. At that point, the widgets appeared and became editable.

\n\n\n\n

The Theme Author Experience

\n\n\n\n

One of my biggest concerns for theme authors right now is that there does not seem to be any documentation in the block editor handbook. There is plenty of time to make that happen, but there are things theme authors need to be aware of. Having a centralized location, even while the feature is under development, would help them gear up for the 5.6 release.

\n\n\n\n

Some of these questions, which may be answered in various Make blog posts, should exist on a dedicated documentation page:

\n\n\n\n
  • How can a theme opt out of block-based widgets?
  • What are the hooks to add custom styles for the Widgets screen?
  • Can themes target specific sidebar styles on the Widgets screen?
  • Is it possible to consistently style sections like traditional widgets on the front end?
  • Can themes opt into wide and full-alignment within block areas, which could essentially be used similarly to the post content area?
\n\n\n\n

These are some of the questions I would want to be answered as a former theme author. I am no longer in the thick of the theme design game and presume that those who are would have a larger list of questions.

\n\n\n\n

One less-obvious piece of documentation should center on how to handle fallbacks or default widgets. Traditionally, themes that needed to show a default set of widgets would check if the sidebar has widgets and fall back to using the_widget() to output one or more defaults. While theme authors can still do that, we should start to transition them across the board to the block system.

\n\n\n\n

Should theme authors copy/paste block HTML as a fallback? Would the starter content system be better for this, and can starter widget content handle blocks? What is the recommended method for widget fallbacks in WordPress 5.6?

\n\n\n\n

There is still the ongoing issue of how theme authors should handle the traditional widget and widget title wrapper HTML in the new block paradigm. One patch added since the Gutenberg 9.1 release wraps every top-level block with the widget wrapper. If this lands in the 9.2 release, it will likely make the issue worse.

\n\n\n\n

In the traditional system, both the widget title and content are wrapped within a container together. However, if a user adds a Heading block (widget title) and another block (widget content), each block is wrapped separately with the theme’s widget wrappers. The only way to rectify the situation as it stands is for end-users to add a Group block for each “widget” they want, which would require an extensive amount of re-education for WordPress users. It is not an ideal scenario.

\n\n\n\nEach block is wrapped as an individual section.\n\n\n\n

Instead of attempting to directly “fix” this issue, WordPress should instead do nothing to the output. Blocks and traditional widgets are fundamentally different.

\n\n\n\n

Let theme authors take the reins on this one and explore possibilities. However, give them the tools to do so, such as supporting block patterns.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 21:35:39 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: WordCamp Austin 2020 Finds Success with VR Experience for Sessions and Networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106119\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking?utm_source=rss&utm_medium=rss&utm_campaign=wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7246:\"

WordCamp Austin 2020 attendees are raving about their experiences attending the virtual event last Friday. It was no secret that the camp’s organizers planned to use Hubs Virtual Rooms by Mozilla to create a unique environment, but few could imagine how much more interactive and personalized the experience would be than a purely Zoom-based WordCamp.

\n\n\n\n

After selecting a custom avatar, attendees entered the venue using a VR headset or the browser to check out sessions or network in the hallway track.

\n\n\n\n
\n

Here’s a small taste of the experience at @WordCampATX today. #WordPress logos and no sponsor banners on any elevator doors. #WCATX pic.twitter.com/Nv2p2VchXf

— David Bisset (@dimensionmedia) October 9, 2020
\n
\n\n\n\n

Speaker and Q&A sessions were broadcast through Zoom but organizers can also embed YouTube videos and streams within the standalone VR environment.

\n\n\n\n

“The VR experience was the most life-like WordCamp experience I’ve had since the start of global lockdowns,” attendee and speaker David Vogelpohl said. “You could attend sessions in one of two virtual presentation halls depending on what track you wanted to see at that time. The speaker presented on a virtual stage and you could see the other attendees watching the presentation.”

\n\n\n\n

Vogelpohl said he enjoyed his experience getting to know others in the Slack and VR venue. Organizers preserved the general vibe of the “hallway track” to recreate what is arguably one of the most valuable aspects of in-person WordCamps.

\n\n\n\n
\n

So cool – checking out the Virtual Space of WordCamp Austin – love the background noise of people talking, ran into @ChrisWiegman and @Josh412 #WCATX pic.twitter.com/68EdgDN2Om

— Birgit Pauli-Haack (@bph) October 9, 2020
\n
\n\n\n\n

“In the hallway track between the virtual presentation halls was a large foyer where you could meet new people, spot a friend speaking with someone else, and virtually step aside from a group conversation to have a private conversation,” Vogelpohl said.

\n\n\n\n

“It was great to see folks like Josepha circling around speaking with attendees, Josh Pollock nerding out in a corner with a group of advanced WP developers, and having random friends drop into a conversation I was having with a group of others. While VR WordCamp doesn’t wholly replace the value of attending a WordCamp live, a lot of the best parts of meeting and collaborating with others was captured in the VR context.”

\n\n\n\n

The live music interludes, which showcased talents from around the community, also provided a way for virtual attendees to stay connected while waiting for the next session.

\n\n\n\n

Behind the Scenes with Anthony Burchell: Creative Director for WordCamp Austin’s Virtual World

\n\n\n\n

WordPress core contributor Anthony Burchell, who started a company dedicated to creating interactive XR sound and art experiences, was the creative director behind the WordCamp Austin’s VR backdrop.

\n\n\n\n

“For WordCamp Austin we wanted to give folks something to be excited about outside of the typical webcam and chat networking,” Burchell said. “I feel that virtual events are not utilizing the networking layer nearly enough to make folks feel like they are really at an event. I’ve seen many compelling formats for virtual events utilizing webcams and chat rooms, but in the end, it feels like there’s been a missing element of presence; something video games and virtual reality excel at.”

\n\n\n\n
\n

Virtual mission control for #WCATX pic.twitter.com/WyrFkIsW2Q

— Anthony Burchell (@antpb) October 9, 2020
\n
\n\n\n\n

Setting up the virtual world involves spinning up a self-hosted instance of Hubs Cloud, which Burchell said is very similar to the complexity of making a WordPress site.

\n\n\n\n

“The most time consuming part of creating a 3D world for an event is making the 3D assets for the space,” Burchell said. “In total I streamed 11 hours of video leading up to the event to give a glimpse into the process.”

\n\n\n\n

Burchell’s YouTube playlist documents the incredible amount of work that went into creating the WordCamp’s virtual venue for attendees to enjoy.

\n\n\n\n

“While it took quite a bit of time to prepare, the code and assets are completely reusable for another event,” Burchell said. “A lot of the time was spent trying to make the space purpose built for the goals of the camp. Much like a real WordCamp, I found the majority of folks packing into the theater rooms for presentations and dipping out a little early to network with friends in the hallway area. That was very much by design!”

\n\n\n\n

Burchell and the other organizers were careful to ensure that the Hubs space was not the primary viewing experience of the camp but rather an extension of the networking activities that attendees could drop in on. The event had nearly identical numbers of attendees joining the virtual space as it did for those joining the video channels. At the end of the afterparty, Burchell turned on flying for all attendees to conclude the successful event:

\n\n\n\n
\n\n
\n\n\n\n

“With Hubs we were able to give attendees the ability to express themselves within a venue vs within a camera and chat box,” Burchell said. “It was incredible to see characteristics of folks in the community shine through a virtual avatar! Just the simple act of seeing your WordCamp friends in the hallway joking and chatting just as they would at a real life event was enough to make me feel like I was transported to a real WordCamp.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 22:31:02 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Privacy-Conscious WordPress Plugin Caches and Serves Gravatar Images Locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105825\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally?utm_source=rss&utm_medium=rss&utm_campaign=privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5285:\"

Ari Stathopoulos released his new Local Gravatars plugin last week. The goal of the plugin is to allow site owners to take advantage of the benefits of a global avatar system while mitigating privacy concerns by hosting the images locally.

\n\n\n\n

In essence, it is a caching system that stores the images on the site owner’s server. It is an idea that Peter Shaw proposed in the comments on an earlier Tavern article covering local avatar upload. It is a middle ground that may satisfy some users’ issues with how avatars currently work in WordPress.

\n\n\n\n

“I am one of the people that blocks analytics, uses private sessions when visiting social sites, I use DuckDuckGo instead of Google, and I don’t like the ‘implied’ consents,” said Stathopoulos. “I built the plugin for my own use because I don’t know what Gravatar does, I don’t understand the privacy policies, and I am too lazy to spend two hours analyzing them. It’s faster for me to build something that is safe and doesn’t leave any room for misunderstandings.”

\n\n\n\n

He is referring to Automattic’s extensive Privacy Policy. He said it looks benign. However, he does not like the idea of any company being able to track what sites he visits without explicit consent.

\n\n\n\n

“And when I visit a site that uses Gravatar, some information is exposed to the site that serves them — including my IP,” said Stathopoulos. “Even if it’s just for analytics purposes, I don’t think the company should know that page A on site B got 1,000 visitors today with these IPs from these countries. There is absolutely no reason why any company not related to the page I’m actually visiting should have any kind of information about my visit.”

\n\n\n\n

The Local Gravatars plugin must still connect to the Gravatar service. However, the connection is made on the server rather than the client. Stathopoulos explained that the only information exposed in this case is the server’s IP and nothing from the client, which eliminates any potential privacy concerns.

\n\n\n\n

The Latest Plugin Update

\n\n\n\n

Stathopoulos updated the plugin earlier today to address some performance concerns for pages that have hundreds or more Gravatar images. In the version 1.0.1 update, he added a maximum processing time of five seconds and changed the cache cleanup process from daily to weekly. Both of these are filterable via code.

\n\n\n\n

“Now, if there are Gravatars missing in a page request, it will get as many as it can, and, after five seconds, it will stop,” said Stathopoulos. “So if there are 100 Gravatars missing and it gets the first 20, the rest will be blank (can be filtered to use a fallback URL, or even fall back to the remote URL, though that would defeat the privacy improvement). The next page request will get the next 20, and so on. At some point, all will be there, and there will be no more delays.”

\n\n\n\n

He did point out that performance could temporarily suffer when installing it on a site that has individual posts with 1,000s of comments and a lot of traffic. However, nothing would crash on the site, and the plugin should eventually lead to a performance boost in this scenario. For such large sites, owners could use the existing filter hooks to tweak the settings.

\n\n\n\n

Right now, the plugin is primarily an itch he wanted to scratch for his own purposes. However, if given enough usage and feedback, he may include a settings screen to allow users to control some of the currently-filterable defaults, such as the cleanup timeframe and the maximum process time allowed.

\n\n\n\n

The Growing List of Alternatives

\n\n\n\n

With growing concerns around privacy in the modern world, Local Gravatars is another tool that end-users can employ if they have any concerns around the Gravatar service. For those who are OK with an auto-generated avatar, Pixel Avatars may be a solution.

\n\n\n\n

“I’ve seen some of them, and they are wonderful!” Stathopoulos said of alternatives for serving avatars. “However, this plugin is slightly different in that the avatars the user already has on Gravatar.com are actually used. They can see the image they have uploaded. The user doesn’t need to upload a separate avatar, and an automatic one is not used by default.”

\n\n\n\n

He would not mind using an auto-generated avatar when commenting on blogs or news sites at times. However, Stathopoulos prefers Gravatar for community-oriented sites.

\n\n\n\n

“My Gravatar is part of my online identity, and when I see, for example, a comment from someone on WordPress.org, I know who they are by their Gravatar,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 21:06:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress 5.6 to Introduce Application Passwords for REST API Authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105997\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2604:\"

In 2015, WordPress 4.4 introduced a REST API, but one thing that has severely limited its broader use is the lack of authentication capabilities for third-party applications. After considering the benefits and drawbacks of many different types of authentication systems, George Stephanis published a proposal for integrating Application Passwords, into core.

\n\n\n\n

Stephanis highlighted a few of the major benefit that were important factors in the decision to use Application Passwords: the ease of making API requests, ease of revoking credentials, and the ease of requesting API credentials. The project is available as a standalone feature plugin, but Stephanis and his collaborators recommended WordPress merge a pull request that is based off the feature plugin’s codebase.

\n\n\n\n

After WordPress 5.6 core tech lead Helen Hou-Sandi gave the green light for Application Passwords to be merged into core, the developer community responded enthusiastically to the news.

\n\n\n\n

“I am/we are 100% in favor of this,” Joost deValk commented on the proposal. “Opening this up is like opening the dawn of a new era of WordPress based web applications. Suddenly authentication is not something you need to fix when working with the API and you can just build awesome stuff.”

\n\n\n\n

Stephanis’ proposal also mentioned how beneficial a REST API authentication system would be for the Mobile teams‘ contributors who are relying on awkward workarounds while integrating Gutenberg support.

\n\n\n\n

“This would be a first step to replace the use of XMLRPC in the mobile apps and it would allow us to add more features for self hosted users,” Automattic mobile engineer Maxime Biais said.

\n\n\n\n

After the REST API was added to WordPress five years ago, many had the expectation that WordPress-based web applications would start popping up everywhere. Without a reliable authentication system, it wasn’t easy for developers to just get inspired and build something quickly. Application Passwords in WordPress 5.6 will open up a lot of possibilities for those who were previously deterred by the lack of core methods for authenticating third-party access.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 23:01:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"WPTavern: WP Agency Summit Begins Its Second Annual Virtual Event October 12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105160\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:197:\"https://wptavern.com/wp-agency-summit-begins-its-second-annual-virtual-event-october-12?utm_source=rss&utm_medium=rss&utm_campaign=wp-agency-summit-begins-its-second-annual-virtual-event-october-12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6357:\"

Jan Koch, the founder and host of WP Agency Summit, is kicking off his second annual event on October 12. The five-day event will feature 37 speakers from a wide range of backgrounds across the WordPress industry. It is a free virtual event that anyone can attend.

\n\n\n\n

“The focus for the 2020 WP Agency Summit is showing attendees how to bring back the fun into scaling their agencies,” said Koch. “It is all about reducing the daily hustle by teaching how to successfully build and manage teams, how to work with enterprises (allowing for fewer customers but bigger projects), how to build sustainable recurring revenue, and how to position your agency to dominate your niche.”

\n\n\n\n

This year’s event includes three major changes to make the content more accessible to a larger group of people. Each session will be available between October 12 – 16 instead of the previous 48-hour window that attendees had to find time for in 2019.

\n\n\n\n

After the event has concluded, access to the content will be behind a paywall. Koch reduced the price to $77 for lifetime access for those who purchase pre-launch, which will increase to $127 during the event. Last year’s prices ballooned to $497, which meant that it was simply not affordable for many who found it too late.

\n\n\n\n

Some of the proceeds this year are going toward transcribing all the videos so that hearing-impaired users can enjoy the content.

\n\n\n\n

This year’s event will also focus on a virtual networking lounge for attendees. “I’ve seen how well it worked at the WP FeedBack Summit — we even had BobWP record a podcast episode on the fly in that lounge!” said Koch. “I’ve seen many new friendships develop, people connecting with new suppliers or getting themselves booked on podcasts, and sharing experiences about their businesses.”

\n\n\n\n

The lounge will be open during the entirety of the summit, which will allow attendees to jump into the conversation on their own time.

\n\n\n\n

A More Diverse Speaker Lineup

\n\n\n\n

Koch received some backlash for the lack of gender diversity last year. The 2019 event had over 20 speakers from a diverse male lineup. However, only four women from our industry led sessions.

\n\n\n\n

When asked about this issue in 2019, Koch responded, “I recognize this as a problem with my event. The reason I have so much more male than female speakers is quite simple, the current speaker line-up is purely based on connections I had when I started planning for the event. It was a relatively short amount of time for me, so I wasn’t able to build relationships with more female WP experts beforehand.”

\n\n\n\n

The host said he paid attention to the feedback he received. While not hitting the 50/50 split goal he had for 2020’s event, 16 of the 37 speakers are women.

\n\n\n\n

Koch said he strived to get speakers from a wider range of backgrounds. He wanted to bring in both freelancers and multi-million dollar agency owners. He also focused on getting people from multiple countries to represent WordPress agencies.

\n\n\n\n

“I did reach out to around 130 people four months before the event to make new connections,” he said. “The community around the Big Orange Heart (a non-profit for mental well-being) also helped a lot with introducing me to new members of the WP community.”

\n\n\n\n

Koch said he learned two valuable lessons when branching out beyond his existing connections for this year’s event:

\n\n\n\n

Firstly, don’t hesitate to reach out to people you think will never talk to you because they’re running such big companies. For example, I immediately got confirmations from Mario Peshev from Devrix, Brad Touesnard from Delicious Brains, or Marieke van de Rakt from Yoast. When first messaging them, I had little hope they’d set aside time to jump on an interview with me – but they were super supportive and accommodating! The WordPress community really is a welcoming environment if you approach people in a humble way.

Secondly, build connections with sincerity. Do not just focus on what you can get from that connection but how you can help the other person. I know this sounds cheesy and you’ve heard this quite often — but it is true. Once I got the first response from new contacts and explained my goal of connecting fellow WordPress community members virtually, most immediately agreed because they also benefit from new connections and being positioned as a thought-leader in this event.

\n\n\n\n

WP Agency Summit? WP FeedBack Summit?

\n\n\n\n

For readers who recall the Tavern’s coverage of the WP FeedBack Summit earlier this year, the article specifically stated that the WP FeedBack Summit was a continuation of 2019’s WP Agency Summit. The official word at the time from WP FeedBack’s public relations team was the following:

\n\n\n\n

Last year’s event, the WP Agency Summit has been rebranded under the umbrella of WP FeedBack’s brand when Jan Koch the host of last’s year WP Agency Summit joined WP FeedBack as CTO.

\n\n\n\n

Koch said that it was a standalone event and not directly connected to WP Agency Summit but had the same target audience. However, the WP FeedBack Summit did use the previous WP Agency Summit’s stats and data to promote the event.

\n\n\n\n

“The WP FeedBack Summit was hosted under the WP FeedBack brand because I joined their team as CTO in March this year,” he said. “Vito [Peleg] and I had the idea to host a virtual conference around WordPress because of WordCamp Asia being canceled — we wanted to help connect the community online through our summit.

\n\n\n\n

Koch left WP FeedBack soon after the summit ended and is currently back on his own and has a goal of making WP Agency Summit a yearly event.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 17:01:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: Navigation Screen Sidelined for WordPress 5.6, Full-Site Editing Edges Closer to Public Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105839\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:247:\"https://wptavern.com/navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta?utm_source=rss&utm_medium=rss&utm_campaign=navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4676:\"

The new block-based navigation screen is once again delayed after it was originally slated for WordPress 5.5 and then put on deck for 5.6. Contributors have confirmed that it will not be landing in WordPress core until 2021 at the earliest.

\n\n\n\n

“The Navigation screen is still in experimental state in the Gutenberg plugin, so it hasn’t had any significant real-world use and testing yet,” Editor Tech Lead Isabel Brison said. She made the call to remove it from the 5.6 lineup after the feature missed the deadline for bringing it out of the experimental state. It still requires a substantial amount of development work and accessibility feedback before moving forward.

\n\n\n\n

Contributors will focus instead on making sure the Widgets screen gets out the door for 5.6 and plan to pick up again on Navigation towards the end of November.

\n\n\n\n

WordPress 5.6 lead Josepha Haden gave an update this week on the progress of all the anticipated features, including the planned public beta for full-site editing (FSE).

\n\n\n\n

“I don’t expect FSE to be feature complete by the time WP5.6 is released,” Haden said. “What I expect is that FSE will be functional for simple, routine user flows, which we can start testing and iterating on. That feedback will also help us more confidently design and build our complex user flows.”

\n\n\n\n

Frank Klein, an engineer at Human Made, asked in the comments of another update why full-site editing is being tied to 5.6 progress in the first place, since it will still only be available in the plugin at the time of release.

\n\n\n\n

“The main value is that it provides a good checkpoint along the path of FSE’s development,” Kjell Reigstad said. “Full-site editing is very much in progress. It is still experimental, but the general approach is coming into view, and becoming clearer with every plugin release.”

\n\n\n\n

Reigstad posted an update on what developers can expect regarding block-based theming and the upcoming release, since the topic is closely tied to full-site editing. He emphasized that the infrastructure is already in place and that, despite it still being experimental, future block-based themes should work in a similar way to how they are working now.

\n\n\n\n

“The focus is now shifting towards polishing the user experience: using the site editor to create templates, using the query block, iterating on the post and site blocks, and implementing the Global Styles UI,” Reigstad said.

\n\n\n\n

“The main takeaway is that when 5.6 is released, the full-site editing feature set will look similar to where it is today, with added polish to the UI, and additional features in the Query block.”

\n\n\n\n

Theme authors are entering a new time of uncertainty and transition, but Reigstad reassured the community that themes as we know them today are not on track to be phased out in the immediate future.

\n\n\n\n

“There is currently no plan to deprecate the way themes are built today,” Reigstad said. “Your existing themes will continue to work as they always have for the foreseeable future.” He also encouraged contributors to get involved in an initiative to help theme authors transition to block-based themes. (This project is not targeted for the 5.6 release.)

\n\n\n\n

Developers can follow important FSE project milestones on GitHub, and subscribe to the weekly Gutenberg + Themes updates to track progress on block-based theming. A block-based version of the Twenty Twenty-One theme is in the works and should pick up steam after 5.6 beta 1, expected on October 20.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 22:57:37 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"WPTavern: EditorPlus 1.9 Adds Animation Builder for the Block Editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105678\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:181:\"https://wptavern.com/editorplus-1-9-adds-animation-builder-for-the-block-editor?utm_source=rss&utm_medium=rss&utm_campaign=editorplus-1-9-adds-animation-builder-for-the-block-editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4535:\"

Munir Kamal shows no signs of slowing down. He continues to push forward with new features for his EditorPlus plugin, which allows end-users to customize the look of the blocks in their posts and pages. He calls it the “no-code style editor for WordPress.”

\n\n\n\n

The latest addition to his plugin? Animation styles for every core block.

\n\n\n\n

My first thought was that this would bloat the plugin with large amounts of unnecessary CSS and JavaScript for what is essentially a few bells and whistles. However, Kamal pulled it off with minimal custom CSS.

\n\n\n\n

Inspired by features from various website builders, he wanted to bring more and more of those things to the core block editor. The animations feature is just another ticked box on a seemingly never-ending checklist of features. And, so far, it’s all still free.

\n\n\n\n

Since we last covered EditorPlus in June, Kamal has added the ability to insert icons via any rich-text area (e.g., paragraphs, lists, etc.). He has also added shape divider, typography, style copying, and responsive editing options for the core WordPress blocks.

\n\n\n\n

How Do Animations Work?

\n\n\n\n

In the version 1.9 release of EditorPlus, Kamal added “entrance” animations. These types of animations happen when a visitor sees the block for the first time on the screen. For example, users could set the Image block to fade into visibility as a reader views the block.

\n\n\n\n

Currently, the plugin adds seven animations:

\n\n\n\n
  • Fade
  • Slide
  • Bounce
  • Zoom
  • Flip
  • Fold
  • Roll
\n\n\n\nAdding a Slide animation for the Cover block text.\n\n\n\n

Each animation has its own subset of options to control how it behaves on the page. The bounce animation, for example, allows users to select the bounce direction. Other options include duration, delay, speed curve, delay, and repeat. There are enough choices to spend an inordinate amount of time tinkering with the output.

\n\n\n\n

One of the best features of this new feature is that Kamal has included an Animation Player under the block options. By clicking the play button, users can view the animation in action without previewing the post.

\n\n\n\n

Watch a quick video of the Animations feature:

\n\n\n\n
\n\n
\n\n\n\n

After testing and using each animation, everything seemed to work well. The one downside — and this is not limited to animations — is that applying styles on the block level sometimes does not make sense. In many cases, it would help users to have options to style or animate the items within the block, such as the images in the Gallery block. When I broached the subject with Kamal, he was open to the idea of finding a solution to this in the future.

\n\n\n\n

What Is Next for EditorPlus?

\n\n\n\n

At a certain point, too many block options can almost feel like overkill and become unwieldy. EditorPlus does allow users to disable specific features from its settings screen, which can help get rid of some unwanted options. Kamal said he would like to continue making it more modular so that users can use only the features they need.

\n\n\n\n

“What I plan is to have micro-level feature control for this extension so that a user can switch off individual styling panels like, Typography, Background, etc.,” he said. “Even further, I plan to bring these controls based on the user role as well. So an admin can disable these features for the editor, author, etc.”

\n\n\n\n

That may be a bit down the road though. For now, he wants to focus on adding new features that he already has planned.

\n\n\n\n

“I do plan to add more animation features,” said Kamal. “I got too many ideas, such as scroll-controlled animation, hover animation, text animation, Lottie animation, background animation, animated shape dividers, and more. But, having said that, I will be careful adding only those features that don’t affect page performance much.”

\n\n\n\n

Outside of extra styles and animations for existing blocks, he plans to jump on the block-building train in future releases. EditorPlus users could see accordion, toggle, slider, star rating, and other blocks in an upcoming release.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:53:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"Donncha: Hide featured image if it’s in the post\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://odd.blog/?p=89503242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://odd.blog/2020/10/08/hide-featured-image-if-its-in-the-post/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3885:\"

I’ve been running a photoblog at inphotos.org since 2005 on WordPress. (And thanks to writing this I noticed it’s 15 years old today!)

\n\n\n\n
\n\n\n\n

In that time WordPress has changed dramatically. At first I used Flickr to host my images, but after a short time I hosted the images myself. (Good thing too since Flickr limited free user accounts to 1000 images, so I wrote a script to download the Flickr images I used in posts.)

\n\n\n\n
\n\n\n\n

For quite a long time I used the featured image instead of inserting the image into the post content, but then about two years ago I went back to inserting the photo into the post. Unfortunately that meant the photo was shown twice, once as a featured image, and once in the post content.

\n\n\n\n

The last theme I used supported custom post types, one of which was a photo type that displayed the featured image but hid the post content. It was an ok compromise, but not perfect.

\n\n\n\n
\n\n\n\n

Recently I started using Twenty Twenty, but after 15 years I had a mixture of posts with:

\n\n\n\n
  • Featured image with no image in the post.
  • Featured image with the same image in the post.
\n\n\n\n

I knew I needed something more flexible. I wanted to hide the featured image if it also appeared in the post content. I procrastinated and never got around to it until this evening when I discovered it was actually quite easy.

\n\n\n\n\n\n\n\n

Copy the following code into the function.php of your child theme and you’ll be all set! It relies on you having unique filenames for your images. If you don’t then remove the call to basename(), and that may help.

\n\n\n
\nfunction maybe_remove_featured_image( $html ) {\n        if ( $html == \'\' ) {\n                return \'\';\n        }\n        $post = get_post();\n        $post_thumbnail_id = get_post_thumbnail_id( $post );\n        if ( ! $post_thumbnail_id ) {\n                return $html;\n        }\n\n        $image_url = wp_get_attachment_image_src( $post_thumbnail_id );\n        if ( ! $image_url ) {\n                return $html;\n        }\n\n        $image_filename = basename( parse_url( $image_url[0], PHP_URL_PATH ) );\n        if ( strpos( $post->post_content, $image_filename ) ) {\n                return \'\';\n        } else {\n                return $html;\n        }\n}\nadd_filter( \'post_thumbnail_html\', \'maybe_remove_featured_image\' );\n
\n\n\n

The post_thumbnail_html filter acts on the html generated to display the featured image. My code above gets the filename of the featured image, checks if it’s in the current post and if it is returns a blank string. Feedback welcome if you have a better way of doing this!

\n\n\n\n
\n\n\n\n

\n\n

Related Posts

\n

Source

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:43:35 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Donncha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches Automatic Platform Optimization for WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105641\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-automatic-platform-optimization-for-wordpress?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-automatic-platform-optimization-for-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6128:\"

Just a day after launching its new privacy-first web analytics product last week, Cloudflare announced Automatic Platform Optimization (APO) for WordPress. The new service boasts staggering performance improvements for sites that might otherwise be slowed down by shared hosting, slow database lookups, or sluggish plugins:

\n\n\n\n

Our testing… showed a 72% reduction in Time to First Byte (TTFB), 23% reduction to First Contentful Paint, and 13% reduction in Speed Index for desktop users at the 90th percentile, by serving nearly all of your website’s content from Cloudflare’s network. 

\n\n\n\n

APO uses Cloudflare Workers to cache dynamic content and serve the website from its edge network. In most cases this eliminates origin requests and origin processing time. That means visitors requesting your website will get near instant load times. Cloudflare reports that its testing shows APO delivers consistent load times of under 400ms for HTML Time to First Byte (TTFB).

\n\n\n\n

The effects of using APO are similar to hosting static files on a CDN, but without the need to manage a complicated tech stack. Content creators retain their ability to create dynamic websites without any changes to their workflow for the sake of performance.

\n\n\n\n

Version 3.8 of Cloudflare’s official WordPress plugin was recently updated to include support for APO. It detects when users make changes to their content and purges the content stored on Cloudflare’s edge.

\n\n\n\n

The new service is available to Cloudflare users with a single click of a button. APO is included at no cost for existing Cloudflare customers on the Professional, Business, and Enterprise plans. Users on the Free plan can add it to their sites for $5/month. The service is a flat fee and is not metered.

\n\n\n\n

Cloudflare’s announcement has so far been well-received by WordPress professionals and hosting companies and many have already begun testing it.

\n\n\n\n
\n

So the week after @Cloudflare Birthday Week I try and play with as many of the new products as possible. Today was the WordPress APO on my simple demo site. You can see TTFB dropped from ~350ms to ~75ms! https://t.co/zg976EjrZI pic.twitter.com/KuaHqtHLom

— Matt Bullock (@mibullock) October 6, 2020
\n
\n\n\n\n

WordPress lead developer Mark Jaquith called APO “incredible news for the WordPress world.”

\n\n\n\n

“On sites I manage this is going to lower hosting complexity and easily save hundreds of dollars a month in hosting costs,” Jaquith said.

\n\n\n\n

After running several speed tests from six different locations around the world, early testers at Kinsta got remarkable results using APO:

\n\n\n\n

“By caching static HTML on Cloudflare’s edge network, we saw a 70-300% performance increase. As expected, the testing locations furthest away from Tokyo saw the biggest reduction in load time.

“If your WordPress site uses a traditional CDN that only caches CSS, JS, and images, upgrading to Cloudflare’s WordPress APO is a no-brainer and will help you stay competitive with modern Jamstack and static sites that live on the edge by default.”

\n\n\n\n

George Liu, a “self-confessed page speed addict” and Cloudflare Community MVP, performed a series of detailed tests on the new APO product with his blog. After many comparisons, he found that Cloudoflare’s WordPress plugin with APO turned on delivers results similar to his heavily optimized WordPress blog that uses a custom Cloudflare Worker caching configuration.

\n\n\n\n

“You’ll find that Cloudflare WordPress plugin’s one click Automatic Platform Optimization button does wonders for page speed for the average WordPress user not well versed in page speed optimizations,” Liu said.

\n\n\n\n

“Cloudflare’s WordPress plugin Automatic Platform Optimization will in theory beat all other WordPress caching solutions other than you rolling out your own Cloudflare Worker based caching like I did. So you get a good bang for your buck at US$5/month for Cloudflare’s WordPress plugin APO.”

\n\n\n\n

Liu also warned of some speed bumps with the initial rollout, as Cloudflare’s APO supports a limited set of WordPress cookies for bypassing the Cloudflare CDN cache, leaving certain use cases unsupported. APO does not seem to work on subdomains and users are also reporting that it’s not compatible with other caching plugins. It also disables real visitor IP address detection.

\n\n\n\n

Cloudflare is aware of many of these issues, which have been raised in the comments of the announcement, and is in the process of adding more cookies to the list to bypass caching. Due to some plugin conflicts, APO may not be as plug-and-play as it sounds for some users right now, but the product is very promising and should improve over time with more feedback.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 04:18:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Kick off Block-Based WordPress Theme Development With the Theme.json Creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105832\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator?utm_source=rss&utm_medium=rss&utm_campaign=kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4674:\"

Gutenberg 9.1 made a backward-incompatible change to its theme.json file (experimental-theme.json while full-site editing is under the experimental flag). This is the configuration file that theme developers will need to create as part of their block-based themes. Staying up to date with such changes can be a challenge for theme authors, but Ari Stathopoulos, a Themes Team representative, wrote a full guide for developers.

\n\n\n\n

Jon Quach, a Principal Designer at Automattic, has also been busy creating a tool to help theme authors transition to block-based themes. He recently built a UI-based project called Theme.json Creator that builds out the JSON code for theme authors. Plus, it is up to date with the most recent changes in the Gutenberg plugin.

\n\n\n\n

Tools like these will be what the development community needs as it gets over the inevitable hump of moving away from the traditional theme development paradigm and into a new era where themes are made almost entirely of blocks and a config file.

\n\n\n\n

While plugin development is becoming more complex with the addition of JavaScript, theme development is taking a sharp turn toward its roots of HTML and CSS. We are barreling toward a future in which far more people will be able to create WordPress themes. Even the possibility of sharing pieces of themes (e.g., template parts and patterns) is on the table. This could not only empower theme designers by lowering the barrier to entry, it could also empower some end-users to make the jump into theme building.

\n\n\n\n

However, the theme.json file is one aspect of future theme authorship that is extremely developer-oriented. JSON is a universal format shared between various programming languages. It is meant to be read by machines and is not quite as human-friendly as other formats. As the theme.json file grows to accommodate more configuration options over time, the less friendly it will become to simply typing keys and values in.

\n\n\n\n

It makes sense to build tools to simplify this part of the theme building process.

\n\n\n\n

That is where the Theme.json Creator tool comes in. Theme authors pick and choose the options they want to support and input custom values. Then, the tool spits out everything in properly-formatted JSON.

\n\n\n\nUsing the Theme.json Creator tool.\n\n\n\n

One big thing the tool does not yet cover is custom CSS variables. This feature is a recent addition to the theme.json specification. It allows theme authors to create any custom property that WordPress will automatically output as CSS. In his announcement post, Stathopoulos covered how to create a typographic scale with custom properties and use those variables for editor features, such as line-height and font-size values.

\n\n\n\n

Currently, Theme.json Creator’s primary focus is on global styles. However, Gutenberg allows theme authors to configure default styles on the block level. For example, theme designers can set the color or typography options for the core Heading block to be different from the default global styles. This provides theme authors with fine-tuned control over every block.

\n\n\n\n

Theme.json Creator does not yet support configuration at this level. However, it would be interesting to see if Quach adds it in the future.

\n\n\n\n

The focus on setting up global styles is a good start for now. This is still an experimental feature. The great thing about it is that it can help theme authors begin to see how one piece of the block-based themes puzzle fits in. It is a starting point for an entirely new method of adding theme support for features when most are accustomed to adding multiple add_theme_support() PHP function calls.

\n\n\n\n

With the direction that theme development seems to be heading, it is easy to imagine that it could evolve into a completely UI-based affair at some point down the line. If templates are made up of blocks and patterns, which anyone can already build with the block editor, and if styles will essentially boil down to a config file, there will be little-to-no programming required to build a basic WordPress theme.

\n\n\n\n

If someone is not already at least jotting down notes for a plugin that allows users to create and package a block-based theme, I would be surprised. For now, Theme.json Creator is removing the need to write code for at least one part of the theme design process.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 07 Oct 2020 20:53:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: Jetpack 9.0 Introduces Loom Block, Twitter Threads Feature, and Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105743\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4033:\"
\n\n\n\n

Jetpack’s highly anticipated 9.0 release has landed, introducing some of the new features the team has previewed over the past week. Users can now publish WordPress posts to Twitter as threads. This new feature is available as part of the Publicize module when you have connected a Twitter account.

\n\n\n\n

Posting Twitter threads is a feature that only works with the block editor, as it takes advantage of how content is naturally split into chunks (blocks).

\n\n\n\n

In the comments on his demo post, Automattic engineer Gary Pendergast gave a more detailed breakdown of the logic Jetpack uses to ensure full sentences aren’t broken up in the tweets.

\n\n\n\n

“With the mental model now being focused on mapping blocks to tweets, it’s much easier to make logical decisions about how to handle each block,” Pendergast said. “So, a paragraph block is the text of a tweet, if the paragraph is too long for a single tweet, it tries to split the paragraph up by sentences. If a sentence is too long, then it resorts to splitting by words. Then, if there’s an embed/image/video/gallery block following that paragraph, we can attach it to the tweet containing that paragraph. There are additional rules for other blocks, but that’s the basic process. It then just iterates over all of the supported blocks in the post.”

\n\n\n\n

Pendergast published his post as thread to demonstrate the new feature in action. The advantage of posting a thread from your WordPress site is that it doesn’t end up getting lost in Twitter’s fast-moving timeline. Most important Twitter threads evaporate from public consciousness almost as soon as they are published. Publishing threads from your website ensures they are better indexed and easier to reference in the future.

\n\n\n\n

Jetpack Adds Loom Block for Embedding Screen Recordings

\n\n\n\n

Loom was added to Jetpack as a new oEmbed provider three weeks ago. The video recording service allows for recording camera, microphone, and desktop simultaneously. The service is especially popular in educational settings. Jetpack 9.0 introduces a new Loom block for embedding recordings.

\n\n\n\n\n\n\n\n

“Loom is growing in popularity as it is being recommended more and more to assist in distance learning efforts,” Jetpack Director of Innovation Jesse Friedman said. “Now more than ever we want to be able to help those working, learning, and teaching from home. The Loom block was a natural addition to join the other Jetpack video blocks which now include YouTube, TikTok, DailyMotion, and Vimeo.”

\n\n\n\n

Loom’s free tier allows users to record up to 25 videos, but the Pro plan is free for educators. Friedman confirmed that Jetpack does not have any kind of partnership with Loom. The team decided to support the product to assist professionals, educators, and students. Having it available as a block also makes it more convenient for those using P2 for communication.

\n\n\n\n

As anticipated, Jetpack 9.0 also provides a seamless transition necessary to ensure Instagram and Facebook embeds will continue working after Facebook drops unauthenticated oEmbed support on October 24. The Jetpack team reports that it “partnered with Facebook” to make sure these embeds continue to work with the WordPress.com REST API.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 23:28:38 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:51:\"Post Status: Joost de Valk on WordPress marketshare\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=79914\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:62:\"https://poststatus.com/joost-de-valk-on-wordpress-marketshare/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1193:\"

David Bisset makes his podcast debut for Post Status, as he interviews Joost de Valk, Founder and Chief Product Officer of Yoast, and discusses all things WordPress marketshare related.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Jilt

\n\n\n\n

Jilt offers powerful email marketing built for eCommerce. From newsletters to highly segmented automations, Jilt is your one-stop show for eCommerce email. Join thousands of stores that have already earned tens of millions of dollars in extra sales using Jilt. Try Jilt for free

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 22:28:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"WPTavern: iThemes Buys WPComplete, Complementing Its Recent Restrict Content Pro Acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105631\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition?utm_source=rss&utm_medium=rss&utm_campaign=ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4395:\"

Just one month after publicly announcing its acquisition of Restrict Content Pro (RCP), iThemes purchased WPComplete for an undisclosed amount. The acquisition is for the product, website, and customers only.

\n\n\n\n

Paul Jarvis and Zack Gilbert created the WPComplete plugin in 2016. However, it has outgrown what the duo could maintain and support alone. After the transition period in which the new owners take over, the two will step away from the project.

\n\n\n\n

In essence, WPComplete is a “course completion” plugin. Site owners can create online courses while allowing students/users to mark their work as completed. It also gives students a way to track their progress through courses, which can often boost the potential for them to finish.

\n\n\n\n

“Paul and Jack believe a key to their success has been their ability to keep their team small and manageable,” wrote Matt Danner, the COO at iThemes, in the announcement. “The growth of WPComplete has presented a number of challenges for a team of two people, so the decision was made to start looking towards alternative ownership solutions that could continue to grow WPComplete and provide it with a stable team. iThemes is a perfect fit.”

\n\n\n\n

iThemes customers who have a Plugin Suite or Toolkit membership will get automatic access to the pro version of the WPComplete plugin. For current WPComplete users, Danner said everything should be “business as usual.” However, iThemes has assigned a few of its team members to work on the product and site, so customers should see some new faces.

\n\n\n\n

RCP and WPComplete are obviously complementary products. RCP is a membership plugin that allows site owners to restrict content based on that membership. WPComplete allows site members to mark lessons or coursework as completed. “We’ll be rolling out a new bundle later this month that combines both RCP and WPComplete for course and membership creators to take advantage of these two plugins,” said AJ Morris, the Product Innovation and Marketing Manager at iThemes.

\n\n\n\n

WPComplete is still a young product. The free version of the plugin currently has 2,000+ active installs and a solid 4.7 rating on WordPress.org. If marketed as an extension of the RCP plugin, it automatically puts it in front of the eyes of 1,000s of more potential customers. It should be much easier to grow the plugin as part of a membership bundle.

\n\n\n\n

iThemes is making some bold moves in the membership space. It will be interesting to see if the company makes any other acquisitions that could strengthen its product line and help it become more dominant. There is still a ton of room for growth in the membership segment of the market. There is also the potential for integrations with other major plugins.

\n\n\n\n

“Adding WPComplete to the iThemes product lineup also allows us to move more quickly on some plans we have for Restrict Content Pro,” said Danner in the initial announcement. He also vaguely mentioned a couple of ideas the team had in the works but did not go into detail.

\n\n\n\n

With a little prodding, Morris provided some insight into what they are planning for the immediate future. The biggest first step is tackling integration with the block editor. Currently, WPComplete uses shortcodes. The team’s next step is likely to begin with creating block equivalents for those shortcodes.

\n\n\n\n

“After that, we’ve touched on a few deeper integrations with Restrict Content Pro, like the possibility to restrict courses to memberships,” said Morris.

\n\n\n\n

The iThemes team does not plan to stop with WPComplete as part of its product lineup. One of the goals is to use the plugin for the iThemes website itself.

\n\n\n\n

“We always try to eat our own dogfood when we can,” said Morris. “You’ll see that with RCP and WPComplete early next year as we look to integrate them into our iThemes Training membership.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 20:59:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Exploring Full-Site Editing With the Q WordPress Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105676\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/exploring-full-site-editing-with-the-q-wordpress-theme?utm_source=rss&utm_medium=rss&utm_campaign=exploring-full-site-editing-with-the-q-wordpress-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7492:\"

I have been eagerly awaiting the moment when I could install a theme and truly test Gutenberg’s full-site editing feature. By and large, each time I have tested it over the past few months, the experience has felt utterly broken. This is why I have remained skeptical of seeing the feature land in WordPress 5.6 this December.

\n\n\n\n

The Q theme by Ari Stathopoulos is the first theme that seems to be a decent working example. Whether that is a stroke of luck with timing or that this particular theme is simply built correctly is hard to tell — Stathopoulos is a team rep for the Themes Team. Gutenberg 9.1 dropped last week with continued work toward site editing.

\n\n\n\n

Q is as experimental as it gets. The Themes Team put out an open call for experimental, block-based themes as far back as March this year. However, not many have taken the team up on this offer. If approved, Q stands to be the first block-based theme to go live in the official WordPress directory. It still has to work its way through the standard review process, awaiting its turn in the coming weeks.

\n\n\n\n

On the whole, full-site editing remains a frustrating and confusing experience. I still remain skeptical about its readiness, even in beta form, to show off to the world in WordPress 5.6.

\n\n\n\n

However, Q is an interesting theme to explore at this point for both end-users and theme developers. Users can install it and start tinkering with the site editing screen via the Gutenberg plugin. Developers can learn how global styles, templates, and template parts fit together from a working theme.

\n\n\n\n

Using the Site Editor

\n\n\n\nEditing a single post in the site editor.\n\n\n\n

The Q theme requires the Gutenberg plugin and its full-site editing mode to be enabled. Generally, requiring a plugin is not allowed for themes in the directory. However, experimental Gutenberg themes are allowed to bypass this guideline.

\n\n\n\n

Stathopoulos pointed out that the theme is highly experimental and should not be used on a production site. However, he is hopeful that it will get more eyes focused on full-site editing.

\n\n\n\n

He mentioned that several items are broken, such as category archives not showing the correct posts. This is a current limitation of the Query block in Gutenberg. However, one of the best ways to find and recognize these types of issues is to have a theme that stays up with the pace of development.

\n\n\n\n

Currently, the site editor feels like it is biting off more than it can chew. Not only can users edit the layout and design of the page, but they can also directly edit existing post content — don’t try this at home unless you are willing for your post titles to get switched to the hyphenated slug. Should the site editor be handling the double-duty of design and content editing? If so, should design and content editing be handled in separate locations in the long term or be merged into one feature?

\n\n\n\n

It feels raw. It is not geared toward users at this point.

\n\n\n\n

The bright spot with the site editor is the current progress on template parts in the editor. Template parts are essentially “modules” that handle one part of the page. For example, the typical theme will have a header and footer template part. Currently, end-users can insert custom template parts or switch one template part for another. This opens a world of possibilities, such as users choosing between multiple header designs (template parts) for their sites.

\n\n\n\nSwitching the header template part.\n\n\n\n

The downside to the entire template system is that it seems so divorced from the site editor that it is hard to believe the average user would understand what is going on. Templates and template parts reside under the Appearance menu in the admin. The Site Editor is a separate, top-level menu item. Without any preexisting knowledge of how these pieces work together, it can be confusing.

\n\n\n\n

Template parts worked for me in the site editor from the outset. However, they did not work on the front end at first. I continually received the “template part not found” message for hours. Then, at some point — whether through magic or a random save that pulled everything together — the feature began to output the previously-missing header and footer template parts.

\n\n\n\n

Glimpse Into the Future of Theme Development

\n\n\n\n

The Q theme has a scant few style rules, which it loads directly in the <head> section of the site in lieu of adding an extra stylesheet. It relies on the stock Gutenberg block styles on the front end with a few minor overrides. Most other custom styles are handled via the global styles system, which pulls from the theme’s experimental-theme.json config file (will be theme.json in the future).

\n\n\n\n

It begs the question of whether themes will necessarily need much in the way of CSS when full-site editing lands.

\n\n\n\n

If WordPress allows users to configure most styles via block options and global styles overrides, themes may not need much more than their config files. After that, it would come down to registering custom block styles and patterns.

\n\n\n\n

If this is the future that we are headed toward, anyone could essentially create a WordPress theme. And, those pieces, such as template parts and patterns, could all be shared between any site. In that future, themes may simply not matter anymore.

\n\n\n\n

Last year, Mike Schinkel proposed deprecating the theme system altogether and replacing it with web components.

\n\n\n\n

“Rather than look for a theme that has all the features one needs — which I have found always limits the choices to zero — a site owner could look for the components and modules they need and then assemble their site from those modules,” he said. “They could pick a header, a footer, a home-page hero, a set of article cards, a pricing module, and so on.”

\n\n\n\n

The more I tinker with full-site editing, the more it feels like that is the lane that it will ultimately merge into. Imagine a future where end-users could pick and choose the pieces they wanted and simply have it look right on the front end.

\n\n\n\n

It is exciting to think about that possibility. Both Schinkel and I have more of a background in programming than we do in design. It makes sense from that sort of analytical mindset to put everything into neat, reusable boxes because reuse is a cornerstone of smart programming.

\n\n\n\n

However, I worry about the state of design in such a system with so many replaceable parts. Will designers be able to take holistic approaches to theme development, creating truly intricate pieces of art? Will that system essentially create a web of cookie-cutter sites? Or, will designers simply find ways to think outside the box while within the constraints of the block system?

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 21:21:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Virtual Jamstack Conf to Feature Fireside Chat with Matt Mullenweg and Matt Biilmann, October 6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105680\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6?utm_source=rss&utm_medium=rss&utm_campaign=virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2618:\"
image credit: Jamstack Conf
\n\n\n\n

The greater Jamstack community is coming together on October 6-7, 2020, for a virtual conference. Organizers expect more than 15,000 attendees from around the globe over a two-day span that includes keynotes, sessions, interactive topic tables, workshops, speaker Q&As, and networking opportunities.

\n\n\n\n

Matt Mullenweg will be joining Netlify CEO Matt Biilmann on day 1 at 12PM PDT for a fireside chat moderated by CSS-Tricks Creator Chris Coyier. The chat will go deeper on recent topics of contention, including developer sentiment, complexity, security, and performance. Coyier also plans to discuss how the Jamstack and WordPress communities intersect through headless implementations of the CMS.

\n\n\n\n

A provocative post from TheNewStack at the end of August quoted Mullenweg as saying that “JAMstack is a regression for the vast majority of the people adopting it.” This sparked multiple heated exchanges across blogs and social media. Biilimann, who originally coined the term “Jamstack,” wrote a response to Mullenweg’s remarks, hailing “the end of the WordPress era.”

\n\n\n\n

Live conversations tend to be more cordial than shots fired across the blogosphere. It will be interesting to see if Biilimann cares to join Stackbit CEO Ohad Eder-Pressman in his wager that Jamstack will become the predominant architecture for the web by 2025. The fireside chat should be recorded, in case you cannot catch the live session. Recordings of talks from the previous virtual Jamstack event held in May are available on YouTube.

\n\n\n\n

Today is the last call for registration. Many of the workshops have already sold out, but tickets to the regular sessions on October 6 are still available. Sign up on the event website to get your free ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 20:12:50 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Gutenberg 9.1 Adds Patterns Category Dropdown and Reverts Block-Based Widgets in the Customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105629\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5615:\"

Gutenberg 9.1 was released to the public on Wednesday. The team announced over 200 commits from 77 contributors in its release post yesterday. One of the biggest changes to the interface was the addition of a new dropdown selector for block pattern categories. The team also reverted the block-based widgets section in the customizer and added an image size control to the Media & Text block.

\n\n\n\n

One of the main focuses of this release was improving the block-based widgets editor. The feature was taken out of the experimental stage in Gutenberg 8.9 and continues to improve. The widgets screen now uses the same inserter UI as the post-editing screen. However, users can currently only insert regular blocks. Patterns and reusable blocks are still not included.

\n\n\n\n

Theme authors can now control aspects of the block editor via a custom theme.json file. This is part of the ongoing Global Styles project, which will allow theme authors to configure features for their users.

\n\n\n\n

The development team has also added an explicit box-sizing style rule to the Cover and Group blocks. This is to avoid any potential issues with the new padding/spacing options. Theme authors who rely on the block editor styles should test their themes to make sure this change does not break anything.

\n\n\n\n

Better Pattern Organization

\n\n\n\nNew block patterns UI in the inserter.\n\n\n\n

I have been calling for the return of the tabbed pattern categories since Gutenberg 8.0, which was a regression from previous versions. For 11 versions, users have had to scroll and scroll and scroll through every block pattern just to find the one they wanted. The development team has sought to address this issue by using a category dropdown selector. When selecting a specific category, its patterns will appear.

\n\n\n\n

At first, I was unsure about this method over the old tabbed method. However, after some use, it feels like the right direction.

\n\n\n\n

As more and more theme and plugin authors add block pattern categories to users’ sites, the dropdown is a more sensible route. Even tabs could become unwieldy over time. The dropdown better organizes the list of categories and makes the UI cleaner. More than anything, I am enjoying the experience and look forward to this eventually landing in WordPress 5.6 later this year.

\n\n\n\n

Customizer Widgets Reverted

\n\n\n\nReverted widgets panel in the customizer.\n\n\n\n

On the subject of WordPress 5.6, one of its flagship features has been hitting some roadblocks. Block-based widgets are expected to land in core with the December release, but the team just reverted part of the feature. They had to remove the widgets block editor from the customizer they added just two major releases ago.

\n\n\n\n

It was for the best. The customizer’s block-based widgets editor was fundamentally broken. It was not ready for primetime and should have remained in the experimental stage until it was somewhat usable.

\n\n\n\n

“I will approve this since the current state of the customizer in the Gutenberg plugin is broken, and there is no clear path forward about how to fix that,” wrote Andrei Draganescu in the reversion ticket. “With this patch, the normal widgets can still be edited in the customizer and the block ones don’t break it anymore. This is NOT to mean that we won’t proceed with fixing the block editor in the customizer, that is still an ongoing discussion.”

\n\n\n\n

The current state of editing widgets via the customizer is at least workable with this change. If end-users add a block via the admin-side widgets editor, it will merely appear as an uneditable, faux widget named “Block” in the customizer. They will need to edit blocks via the normal widgets screen.

\n\n\n\n

There is no way that WordPress can ship the current solution when 5.6 rolls out. However, we are still two months out. This leaves plenty of time for a fix, but Draganescu’s note that “there is no clear path forward” may make some people a bit uneasy at this stage of development.

\n\n\n\n

Control Image Size for Media & Text

\n\n\n\nImage size dropdown selector for the Media & Text block.\n\n\n\n

One of the bright spots in this update is the addition of an image size control to the Media & Text block. Like the normal Image block, end-users can choose from any registered image size created for their uploaded image.

\n\n\n\n

This is a feature I have been looking forward to in particular. Previously, using the full-sized image often made the page weight a bit heftier than necessary. It is also nice to go along with themes that register sizes for both landscape and portrait orientations, giving users more options.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 20:56:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"WordPress.org blog: The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8711:\"

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches New Web Analytics Product Focusing on Privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105446\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-new-web-analytics-product-focusing-on-privacy?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-new-web-analytics-product-focusing-on-privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2448:\"

In pursuit of “democratizing web analytics,” Cloudflare announced it is launching privacy-first analytics as a new standalone product. The company is entering a market that has been dominated by Google Analytics for years but with a major differentiating feature – it will not track individual users by a cookie or IP address to show unique visits.

\n\n\n\n

Cloudflare Web Analytics defines a visit as “a successful page view that has an HTTP referer that doesn’t match the hostname of the request.” It’s not the same as Google’s “unique” metric, and Cloudflare says it may differ from other reporting tools. Weeding out bots from the total traffic numbers is a nascent feature that Cloudflare is improving as part of its Bot Management product.

\n\n\n\n
\n\n\n\n

Cloudflare Web Analytics is launching with features that are largely similar to Google Analytics but with some unique ways of zooming into different traffic segments and time ranges to see where traffic is originating from.

\n\n\n\n

“The most popular analytics services available were built to help ad-supported sites sell more ads,” Cloudflare product manager Jon Levine said. “But, a lot of websites don’t have ads. So if you use those services, you’re giving up the privacy of your users in order to understand how what you’ve put online is performing.

\n\n\n\n

“Cloudflare’s business has never been built around tracking users or selling advertising. We don’t want to know what you do on the Internet — it’s not our business.”

\n\n\n\n

Paying customers on the Pro, Biz, and Enterprise plans can access their analytics from their dashboards immediately. Cloudflare is also offering the product for free as JavaScript-based analytics for users who are not currently customers. Those who want access to the free plan can sign up for the waitlist.

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 04:03:01 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"WPTavern: Virtual WordPress Page Builder Summit Kicks Off October 5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105570\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:179:\"https://wptavern.com/virtual-wordpress-page-builder-summit-kicks-off-october-5?utm_source=rss&utm_medium=rss&utm_campaign=virtual-wordpress-page-builder-summit-kicks-off-october-5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6348:\"

From October 5 through October 9, the first Page Builder Summit will open its virtual doors to all attendees for free. Nathan Wrigley, the podcaster behind WP Builds, and Anchen le Roux, the founder and lead developer of Simply Digital Design, are hosting the five-day online event that focuses on the vast ecosystem of page builders for WordPress.

\n\n\n\n

The summit will include 35 sessions spread out over the event schedule. Each session will last around 30 minutes, so it will be easy to pop in and watch one in your downtime. Sessions will cover a range of builders, including the default WordPress block editor, Elementor, Beaver Builder, Oxygen, Brizy, and Divi.

\n\n\n\n

“It’s an event specifically for users of WordPress page builders, or those curious about what they can do,” said Wrigley. “I feel like a page builder style interface for creating websites is the future for our industry. WordPress itself is moving in this direction with the block editor (a.k.a. Gutenberg). With that in mind, it seemed like a good idea to create a dedicated event to share knowledge about this side of WordPress. We’ve tried to include presentations from as many page builders as we could.”

\n\n\n\n

Wrigley made sure to point out that it is not all geared toward developers, discussing the inner-workings of builders. Some of the sessions focus on marketing, optimization, and conversion, which provides a wider range of topics for potential attendees.

\n\n\n\n

The summit hosts created an online quiz for those who are unsure about which sessions to watch.

\n\n\n\n

There is a small catch. The sessions will be freely available only from the time they begin and the following 24 hours. After that, accessing the videos will come at a premium. Attendees can gain lifetime access to the PowerPack for $47 if they purchase within 15 minutes of signing up. Then, prices will rise to $97 until the event kicks off on October 5. Beyond, the price jumps to $147. The lifetime access includes access to the presentations, transcripts, a workbook, and other bonuses from the speakers.

\n\n\n\n

For those unsure about forking over the cash, they can still watch the sessions during the 24-hour window.

\n\n\n\n

The proceeds from the event will go out to paying affiliate commissions to speakers and partners. Some of it will go into planning and investing in a second summit down the road.

\n\n\n\n

“Both myself and Nathan have specific charities that we want to donate to after the event,” said le Roux. “It was part of our goals to be able to do this, but we didn’t want to make this an official contribution.”

\n\n\n\n

Why a Page Builder Summit?

\n\n\n\n

Both Wrigley and le Roux have their preferred builders. But, the goal of the summit is to offer a wide look at the tools available and help freelancers and agencies better streamline their businesses and create happier clients.

\n\n\n\n

“I’ve been a user of page builders for many years, but only at the point where they truly showed in the editing interface something that almost perfectly reflected what the end-user would see did I get really immersed,” said Wrigley. “Having come from a background in which I built entire websites from a collection of text files (HTML, CSS, PHP, etc.), I was fascinated that we’d reached a point where the learning curve for building a good website was significantly reduced.”

\n\n\n\n

He pointed out that it is not always so simple though. While the same level of coding skills may not be necessary, people must figure out how to navigate their preferred page builder, which can come with its own learning curve.

\n\n\n\n

“You need to learn their way of doing things and how to achieve your design choices,” he said. “It’s always going to work out better if you know the code, but the WordPress mission of democratizing publishing certainly seems to align quite nicely with the adoption of tools, like page builders, which mean that once-difficult tasks are now easier.”

\n\n\n\n

For le Roux, her interest in hosting the Page Builder Summit falls back to her design studio.

\n\n\n\n

“As a developer, my main reason for switching to page builders was around streamlining and creating more efficient but quality websites in the shortest amount of time,” she said. “Especially now that we focus on day rates, creating the best possible website that clients would love fast would not have been possible without page builders.”

\n\n\n\n

The Hosts’ Go-To Builders

\n\n\n\n

“We prefer using Beaver Builder with Themer at Simply Digital Design,” said le Roux. “We use Gutenberg for blog posts or where possible with custom post types or LMS software. However, we’ve also taken on a few Elementor projects where that’s the client’s preferred option.”

\n\n\n\n

Wrigley uses some of the same tools. His main work is on the WP Builds website where he hosts podcasts.

\n\n\n\n

“I have used Beaver Builder’s Themer to create templates for specific layouts, but for content creation within those layouts I’m using the block editor,” said Wrigley. “My content is mainly text and the WordPress editor is utterly remarkable in this situation. I kept the classic editor installed for a few months after WordPress 5.0 came about, but I soon realized that this was folly and that the editing interface of Gutenberg is superior. The ability to insert and move text, buttons, etc. is such a joy to work with, and the iterations that have been made in the last two years make it, in my opinion, the best text editing experience on the web.”

\n\n\n\n

Wrigley sees a future in which the WordPress block editor takes over much of the work that page builders are currently handling. However, that future is “still over the horizon.”

\n\n\n\n

“I’m excited about this future though, and we’ve got a few crystal ball-gazing presentations; trying to work out what that future might look like,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 20:31:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"WPTavern: Jetpack 9.0 to Introduce New Feature for Publishing WordPress Posts to Twitter as Threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105448\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3318:\"

Jetpack 9.0, coming on October 6, will debut a new feature that allows users to share blog posts as Twitter threads in multiples tweets. A recent version of Jetpack introduced the ability to import and unroll tweetstorms for publishing inside a post. The 9.0 release will run it back the other way so the content originates in WordPress, yet still reaps all the same benefits of circulation on Twitter as a thread.

\n\n\n\n

The new Twitter threads feature is being added as part of Jetpack’s Publicize module under the Twitter settings. After linking up a Twitter account, the Jetpack sidebar options for Publicize allow users to publish to Twitter as a link to the blog or a set of threaded tweets. It’s not just limited to text content – the threads feature will also upload and attach any images and videos included in the post.

\n\n\n\n\n\n\n\n

When first introduced to the idea of publishing a Twitter thread from WordPress, I wondered if threads might lose their trademark pithy punch, since users aren’t forced to keep each segment to the standard length of a tweet. Would each tweet be separated in an odd, unreadable way? The Jetpack team anticipated this, so the thread option adds more information to the block editor to show where the paragraphs will be split into multiple tweets.

\n\n\n\n

“Threads are wildly underused on Twitter,” Gary Pendergast said in a post introducing the feature. “I think a big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.” The tool Pendergast has been working on for Jetpack gives users the best of both worlds.

\n\n\n\n

In response to a comment requesting Automattic “concentrate on tools to get people off social media,” Pendergast said, “If we’re also able to improve the quality of conversations on social media, I think it’d be remiss of us to not do so.” He also credits IndieWeb discussions on Tweetstorms and POSSE (Publish (on your) Own Site, Syndicate Elsewhere) as inspirations for the feature.

\n\n\n\n

For years, blogging advocates have tried to convince those who post lengthy tweetstorms to switch to a publishing medium that is more suitable to the length of their thoughts. The problem is that Twitter users lose so much of the immediate feedback and momentum that their thoughts would have generated when composed as a tweetstorm.

\n\n\n\n

Instead of lecturing people about how they should really be blogging instead of tweetstorming, Jetpack is taking a fresh approach by enabling full content ownership with effortless social syndication. You can test out the experience for yourself by adding the Jetpack Beta Testers plugin and running the 9.0 RC version on your site.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 02:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: Ask the Bartender: How To WordPress in a Block World?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105491\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:167:\"https://wptavern.com/ask-the-bartender-how-to-wordpress-in-a-block-world?utm_source=rss&utm_medium=rss&utm_campaign=ask-the-bartender-how-to-wordpress-in-a-block-world\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9755:\"

I love your articles. And now, in the middle of the WordPress revolution, I realized I’m constantly searching for an answer regarding WP these days.

So many things are being said, so many previsions of the future, problems, etc., but, right now, I think I, as a designer, just want to understand one thing that seemed answered already but it’s never clear:

Is WordPress a good choice to build a client’s template where he just has to insert the info that will show in the frontend where I want to? And he doesn’t have to worry about formatting blocks? I love blocks, don’t get me wrong, but will normal templating end?

I just think that having a super CMS, HTML, CSS, and being able to play with a database with ACF is so powerful, that I’m wondering if it’s lost. After so much reading, I still don’t understand if this paradigm is going to disappear.

Right now, I don’t know if it’s best to stop making websites as I used to and adopt block patterns instead.

Ricardo
\n\n\n\n

WordPress is definitely changing. Over the past two years, we have seen much of it reshaped into something different from the previous decade and more. However, this is not new. WordPress has always been a constantly-changing platform. It just feels far too different this time around, almost foreign to many. The platform had to make a leap. Otherwise, it would have started falling behind.

\n\n\n\n

And, it is a big ask of the existing community to come along with it, to take that leap together.

\n\n\n\n

It can be scary as a developer whose livelihood has depended on things working a certain way or who has built tools and systems around pre-block WordPress. Many freelancers and agencies had their world turned upside down with the launch of the block editor. It is perfectly OK to feel a bit lost.

\n\n\n\n

Now, it is time for a little tough love. It has been two years. As a professional, you need to have a plan in place already. Whether that is an educational plan for yourself or a transitional plan for your clients, you should already be tackling projects that leverage the block editor. If you are at a point where you have not been building with blocks, you are now behind. However, you can still catch up and continue advancing in your WordPress career.

\n\n\n\n

There are so many changes coming down the pipeline that anyone who plans to develop for WordPress will be in continual education mode for years to come.

\n\n\n\n

When building for clients, the biggest thing to remember is that it is not about you. It is about getting something into the hands of your clients that addresses their specific needs. Freelancers and agencies need to often be the Jacks and Jills of all trades. Sometimes, this even means having a backup CMS or two that you can use that are not named WordPress. It helps to be well-rounded enough to jump around when needed, especially if you are not at a point in your career where you can demand specific work and pass on jobs that would put food on the table.

\n\n\n\n

It is also easy to look at every job as a nail and WordPress as the hammer. Or, even specific plugins as the tool that will always get the job done. I have seen developers in the past rely on tools like ACF, CMB2, or Meta Box but could not code a custom metadata solution when necessary to save their life. Sometimes a bigger toolbox is necessary.

\n\n\n\n

Every WordPress developer needs a solid, foundational understanding of the languages that WordPress uses. Gone are the days of skating by on HTML, CSS, and PHP knowledge. You need to learn JavaScript deeply. Matt Mullenweg, the co-founder of WordPress, was not joking around when he said this back in 2015. It holds true more and more each day. In another five years, it will tough to be a developer in the WordPress world without knowing JavaScript, at least for backend work.

\n\n\n\n

It also depends on what types of sites you are building. If you are primarily handling front-end design, you will likely be able to get by with a lower skill level. You will just need to know the “WordPress way” of building themes.

\n\n\n\n

Within the next year, you should be able to build just about any theme design with decent CSS and HTML knowledge along with an understanding of how the block system works. Full-site editing and block-based themes will change how we build the front end of the web. It is going to be a challenging transition at first, especially for those of us who are steeped in traditional theme development, but client sites will often be far easier to build. I highly recommend the twice-monthly block-based themes meetings if your focus is on the front end.

\n\n\n\n

Block Templates

\n\n\n\n

Based on your question, I am going to make some assumptions. You have a history of essentially building out meta boxes via ACF where the client just pops in their data. Then, you format that data on the front end. You are likely mixing this with custom post types (CPTs). This is a fairly common scenario.

\n\n\n\n

One of the great things about the block system is that you can lock the post editor for individual CPTs. WordPress already has you covered with its block templates feature, which allows you to define just what a post should look like. You can set up which blocks you want to appear and have the client drop their content in. At the moment, this feature is limited to the post type level. However, it should grow more robust over time, particularly when it works alongside the traditional “page templates” system.

\n\n\n\n

Block templates are a powerful tool in the ol’ toolbox that will come in handy when building client sites.

\n\n\n\n

Block Patterns

\n\n\n\n

You do not have to stop making websites as you are accustomed to at the moment. However, you should start leveraging new block features as they become available and make sense for a specific project. I am a fanatic when it comes to block patterns, so my bias will definitely show.

\n\n\n\n

The biggest thing with block patterns and clients is education. For the uninitiated, you will need to spend some time teaching them how to insert a pattern and how it can be used to their advantage. That is the hurdle you must jump.

\n\n\n\n

For many of the users that I have seen introduced to well-designed patterns, they have fallen in love with the feature. Even many who were reluctant to switch to the block editor became far more comfortable working with it after learning how patterns worked. This is not the case for every user or client, but it has been a good introduction point to the block editor for many.

\n\n\n\n

To answer your question regarding patterns: yes, you should absolutely begin to adopt them.

\n\n\n\n

ACF Is Evolving

\n\n\n\n

Because you are accustomed to ACF, you should be aware that the framework is evolving to keep up with the block editor. Version 5.8.0 introduced a PHP framework for creating custom blocks over a year ago. And, it has been improving ever since. There are even projects like ACF Blocks, which will provide even more tools for your arsenal.

\n\n\n\n

It is important to learn from what some of the larger agencies are doing. Read up on how WebDevStudios is tackling block development. The company also has an open-source block library for ACF.

\n\n\n\n

Solving Problems

\n\n\n\n

Your job as a developer is to be a problem solver. Whatever system you are building with is merely a part of your toolset. You need to be able to solve issues regardless of what tool you are using. At the end of the day, it is just code. If you can learn HTML, you can learn CSS. If you can learn those, you can learn PHP. And, if you can manage PHP, you can certainly pick up JavaScript.

\n\n\n\n

A decade or two from now, you will need to learn something else to stay relevant in your career. Web technology changes. You must change with it. Always consider yourself a student and continue your education. Surround yourself and learn from those who are more advanced than you. Emulate, borrow, and steal good ideas. Use what you have learned to make them great.

\n\n\n\n

There is no answer I can give that will be perfect for every scenario. Each client is unique, and you will need to decide the best direction for each.

\n\n\n\n

However, yes, you should already be on the path to building with a block-first mindset if you plan to continue working with WordPress for the long haul. Immerse yourself in the system. Read, study, and build something any chance you get.

\n\n\n\n

This is the first post in the Ask the Bartender series. Have a question of your own? Shoot it over.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Sep 2020 20:35:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: Supercharge the Default WordPress Theme With Twentig, a Toolbox for Twenty Twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:225:\"https://wptavern.com/supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty?utm_source=rss&utm_medium=rss&utm_campaign=supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6455:\"Custom page pattern from the Twentig plugin.\n\n\n\n

I am often on the hunt for those hidden gems when it comes to block-related plugins. I like to see the interesting places that plugin authors venture. That is why it came as a surprise when someone recommended I check out the Twentig plugin a few days ago. Somehow, it has flown under my radar for months. And, it has managed to do this while being one of the more interesting plugins for WordPress I have seen in the past year.

\n\n\n\n

Twentig is a plugin that essentially gives superpowers to the default Twenty Twenty theme. Diane and Yann Collet are the sibling co-founders and brains behind the plugin.

\n\n\n\n

While I have been generally a fan of Twenty Twenty since it was first bundled in core, it was almost a bit of a letdown in some ways. It was supposed to be the theme that truly showcased what the block editor could do — and it does a fine job of styling the default blocks — but there was a lot of potential left on the table. The Twentig plugin turns Twenty Twenty into something worthier of a showcase for the block editor. It is that missing piece, that extra mile in which WordPress should be marching its default themes.

\n\n\n\n

While the new Twenty Twenty-One default theme is just around the corner, Twentig is breathing new life into the past year’s theme. The developers behind the plugin are still fixing bugs and bringing new features users.

\n\n\n\n

Of its 34 reviews on WordPress.org, Twentig has earned a solid five-star rating. That is a nice score for a plugin with only 4,000 active installations. As I said, it has flown under the radar a bit, but the users who have found it have obviously discovered something that adds those extra touches to their sites they need.

\n\n\n\n

What Does Twentig Do?

\n\n\n\n

It is a toolbox for Twenty Twenty. The headline feature is its block editor features, such as custom patterns and page layouts. It also offers a slew of customizer options that allow end-users to put their own design spin on the default theme. However, my interest is primarily in how it extends the block editor.

\n\n\n\n

Let’s get this out of the way up front. Twentig’s one downside is that it adds a significant amount of additional CSS on top of the already-heavy Twenty Twenty and block editor styles. I will blame the current lack of a full design system from WordPress on most of this. Styling for the block editor can easily bloat a stylesheet. Adding an extra 100+ kb per page load might be a blocker for some who would like to try the plugin. Users will need to weigh the trade-offs between the additional features and the added page size.

\n\n\n\n

The thing that makes Twentig special is its extensive patterns and pages library, which offers one-click access to hundreds of layouts specifically catered to the Twenty Twenty theme.

\n\n\n\nInserting one of the hero patterns.\n\n\n\n

It took me a few minutes to figure out how to access the patterns — mainly because I did not read the manual. I expected to find them mixed in with the core patterns inserter. However, the plugin adds a new sidebar panel to the editor, which users can access by clicking the “tw” icon. After seeing the list of options, I can understand why they probably would not fit into WordPress’s limited block and patterns inserter UI.

\n\n\n\n

It would be easier to list what the plugin does not have than to go through each of the custom patterns and pages.

\n\n\n\n

The one thing that truly sets this plugin apart from the dozens of other block-library types of plugins is that there are no hiccups with the design. Almost every similar plugin or tool I have tested has had CSS conflicts with themes because they are trying to be a tool for every user. Twentig specifically targets the Twenty Twenty theme, which means it does not have to worry about whether it looks good with the other thousands of themes out there. It has one job, which is to extend its preferred theme, and it does it with well-designed block output.

\n\n\n\n

The other aspect of this is that it does not introduce new blocks. Every pattern and page layout option uses the core WordPress blocks, which includes everything from hero sections to testimonials to pricing tables to event listings. And more.

\n\n\n\n

Twentig does not stop adding features to the block editor with custom patterns. The useful and sometimes fun bits are on the individual block level, and I have yet to explore everything. I continue to discover new settings each time I open my editor.

\n\n\n\n

Whether it is custom pullquote styles, a photo image frame, or an inner border tweak to the Cover block (shown below), the plugin adds little extras that push what users can do with their content.

\n\n\n\nInner border style for the Cover block.\n\n\n\n

Each block also gets some basic top and bottom margin options, which comes in handy when laying out a page. At this point, I am simply looking forward to discovering features I have yet to find.

\n\n\n\n

Areas Themes Should Explore

\n\n\n\n

One of the things I dislike about many of these features being within the Twentig plugin is that I would like to see them within the Twenty Twenty theme instead. Obviously not every feature belongs in the theme — some features firmly land in plugin territory. The default WordPress themes should also leave some room for plugin authors to explore. But, shipping some of the more prominent patterns and styles with Twenty Twenty would make a more robust experience for the average end-user looking to get the most out of blocks.

\n\n\n\n

Block patterns were not a core WordPress feature when Twenty Twenty landed. However, for the upcoming Twenty Twenty-One theme, which is expected to bundle some unique patterns, the design team should explore what the Twentig plugin has brought to the current default. That is the direction that theme development should be heading, and theme developers can learn a lot by stealing borrowing from this plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 22:00:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Coming in Jetpack 9.0: Shortcode Embeds Module Updated to Handle Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105381\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2938:\"

Facebook and Instagram are dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers in an upcoming release. After evaluating third-party solutions, WordPress VIP is recommending its partners enable Jetpack’s Shortcode Embeds module. Jetpack will be shipping the update in its 9.0 release, which is anticipated to land prior to the October 24th deadline.

\n\n\n\n

The module is being updated to provide a seamless transition for users who might otherwise be negatively impacted by Facebook’s upcoming API change. WordPress contributors have run some simulations but are not yet sure what will happen to the display for previously embedded content.

\n\n\n\n

“It is possible that they change the contents of the JS file to manipulate cached embeds, perhaps to display a warning that the site is using an old method to embed content or that the request is not properly authenticated,” Jonathan Desrosiers commented on the trac ticket for removing the oEmbed providers.

\n\n\n\n

WordPress.com VIP roughly outlined what users can expect if they do not enable a solution to begin authenticating oEmbeds:

\n\n\n\n

By default, WordPress caches oEmbed contents in post metadata. These embeds will continue to display in previously-published content. If you edit older posts in the Block Editor, regardless of whether you update the post by saving changes, the embeds in the post will no longer be cached and will stop displaying. If you view these older posts using the Classic Editor, so long as the post is not re-saved, the embeds will continue to function and display properly. If you update the post content, the embed will cease functioning unless you have a mitigation installed.

\n\n\n\n

Although WordPress VIP recommends using the Jetpack module as the best solution, self-hosted WordPress users may want to investigate other options if they are not already using Jetpack. oEmbed Plus is a free plugin created specifically for solving the problem of WordPress dropping Facebook and Instagram as oEmbed providers but it is more work to set up and configure. It requires users to register as a Facebook developer and create an app to get API credentials.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 21:18:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"WPTavern: W3C Selects Craft CMS for Redesign Project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105265\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:149:\"https://wptavern.com/w3c-selects-craft-cms-for-redesign-project?utm_source=rss&utm_medium=rss&utm_campaign=w3c-selects-craft-cms-for-redesign-project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9407:\"

W3C has selected Craft CMS over Statamic for its redesign project, after dropping WordPress from consideration in an earlier round of elimination:

\n\n\n\n

In the end, our decision mostly came down to available resources. Craft had already committed to reach AA compliance in Craft 4 (it is currently on version 3.5, the release of version 4 is planned for April 2021). They had also arranged for an external agency to provide them with accessibility issues to tackle weekly. In the end, they decided instead to hire an in-house accessibility specialist to perform assessments and assist the development team in adopting accessibility patterns in the long run.

W3C CMS Selection Report
\n\n\n\n

Last week we published a post urging W3C to revisit Gutenberg for a fair shake against the proprietary CMS’s or consider adopting another open source option. During the selection process, Studio 24, the agency contracted for the redesign, cited its extensive experience with WordPress as the reason for not performing any accessibility testing on more recent versions of Gutenberg.

\n\n\n\n

When asked if the team contacted anyone from WordPress’ Accessibility Team during the process or put Gutenberg through the same tests as the proprietary CMS’s, Studio 24 founder Simon Jones confirmed they had not.

\n\n\n\n

“No, we only reached out to the two shortlisted CMS’s” Jones said. “I’m afraid we didn’t have time to do more. We did test GB a few months ago based on editing content – though it wasn’t the only factor in our choice. As an agency we do plan to keep reviewing GB in the future.”

\n\n\n\n

In response to our concerns regarding licensing, Jones penned an update titled “On not choosing WordPress,” which further elaborated on the reasons why the agency was not inclined towards using or evaluating the new editor:

\n\n\n\n

From a business perspective I also believe Gutenberg creates a complexity issue that makes it challenging for use by many agencies who create custom websites for clients; where we have a need to create lots of bespoke blocks and page elements for individual client projects.

The use of React complicates front-end build. We have very talented front-end developers, however, they are not React experts – nor should they need to be. I believe front-end should be built as standards-compliant HTML/CSS with JavaScript used to enrich functionality where necessary and appropriate.

As of yet, we have not found a satisfactory (and profitable) way to build custom Gutenberg blocks for commercial projects. 

\n\n\n\n

The CMS selection report also stated that W3C needs the CMS to be “usable by non-sighted users” by the launch date, since some members of the staff who contribute to the website are non-sighted.

\n\n\n\n

Since the most recent version of WordPress was not tested in comparison with the proprietary CMS’s, it’s unclear how much better they handle accessibility. Ultimately, W3C and Studio 24 were more comfortable moving forward with a proprietary vendor that was able to make certain assurances about the future accessibility of its authoring tool, despite having a smaller pool of contributors.

\n\n\n\n

“[I’m] also deeply curious since the cursory notes on accessibility for both of the reviewed CMSes seem to highlight a ton of issues like ‘Buttons and Checkboxes are built using div elements’ or most inputs lacking clear focus styles,” Gutenberg technical lead Matías Ventura said. “An element like the Calendar for choosing a post date seems entirely inoperable with keyboard on Craft, for example, while WordPress’ has had significant effort and rounds of feedback poured into that element alone to make it fully operable.”

\n\n\n\n

WordPress developer Anthony Burchell commented on how using a relatively new proprietary CMS seemed counter to W3C’s stated goal to select an option on the basis of longevity. Craft CMS’s continued success is contingent upon its business model and the company’s ability to remain profitable.

\n\n\n\n

“FOSS have the same opportunity of direct access to developers,” Burchell said. “I recognize there are many accessibility shortcomings in popular software, but I think it’s more constructive to rally behind and contribute, not use a proprietary CMS that boasts beer budget in their guidelines.”

\n\n\n\n

On the other side of the issue, accessibility advocates took the W3C’s decision as a referendum on Gutenberg’s continued struggles to meet WCAG AA standards. WordPress accessibility specialist Amanda Rush said it was “nice to see the W3C flip tables over this.”

\n\n\n\n

“Gutenberg is not mature software,” accessibility consultant and WordPress contributor Joe Dolson said in a post elaborating on his comments at WPCampus 2020 Online. He emphasized the lack of stability in the project that Studio 24 alluded to when documenting the reasons against using WordPress.

\n\n\n\n

“It is still undergoing rapid changes, and has grand goals to add a full-site editing experience for WordPress that almost guarantees that it will continue to undergo rapid changes for the next few years,” Dolson said. “Why would any organization that is investing a large amount into a site that they presumably hope will last another 10 years want to invest in something this uncertain?”

\n\n\n\n

Dolson also said the accessibility improvements he referenced regarding the audit were only a small part of the whole picture.

\n\n\n\n

“They only encompass issues that existed in the spring of 2019,” he said. “Since then, many features have been added and changed, and those features both resolve issues and have created new ones. The accessibility team is constantly playing catch up to try and provide enough support to improve Gutenberg. And even now, while it is more or less accessible, there are critical features that are not yet implemented. There are entirely new interface patterns introduced on a regular basis that break prior accessibility expectations.”

\n\n\n\n

WordPress is also being used by millions of people who are constantly reporting issues to fuel the software’s continued refinement, which increases the backlog of issues. Unfortunately, Studio 24 did not properly evaluate Gutenberg against the proprietary CMS’s in order to determine if these software projects are in any better shape.

\n\n\n\n

Instead, they decided that Craft CMS’s community was more receptive to collaborating on issues without reaching out to WordPress. Given the W3C’s stated preference for open source software, WordPress, as the only CMS under consideration with an OSD-compliant license, should have received the same accessibility evaluation.

\n\n\n\n

“I can’t make any statements that would be meaningful about the other content management systems under consideration; but if WordPress wants to be taken seriously in environments where accessibility is a legal, ethical, and mission imperative, there’s still a lot of work to be done,” Dolson said.

\n\n\n\n

Studio 24’s evaluation may not have been equitable to the only open source CMS under consideration, but the situation serves to highlight a unique quandary: when using open source software becomes the impractical choice for organizations requiring a high level of accessibility in their authoring tools.

\n\n\n\n

“Studio 24 ultimately determined that working with a CMS to make it better was more possible with a smaller, proprietary vendor than with a large open-source project,” accessibility advocate Brian DeConinck said. “Project leadership would be more receptive, and the smaller community means changes can be made more quickly. That should prompt a lot of soul-searching for…well, everyone. What does that say about the future of open source?”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 04:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"Gary: More than 280 characters\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"https://pento.net/?p=5405\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://pento.net/2020/09/29/more-than-280-characters/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5187:\"

It’s hard to be nuanced in 280 characters.

\n\n\n\n

The Twitter character limit is a major factor of what can make it so much fun to use: you can read, publish, and interact, in extremely short, digestible chunks. But, it doesn’t fit every topic, ever time. Sometimes you want to talk about complex topics, having honest, thoughtful discussions. In an environment that encourages hot takes, however, it’s often easier to just avoid having those discussions. I can’t blame people for doing that, either: I find myself taking extended breaks from Twitter, as it can easily become overwhelming.

\n\n\n\n

For me, the exception is Twitter threads.

\n\n\n\n

Twitter threads encourage nuance and creativity.

\n\n\n\n

Creative masterpieces like this Choose Your Own Adventure are not just possible, they rely on Twitter threads being the way they are.

\n\n\n\n
\n

Being Beyoncé’s assistant for the day: DONT GET FIRED THREAD pic.twitter.com/26ix05Hkhp

— green chyna (@CORNYASSBITCH) June 23, 2019
\n
\n\n\n\n

Publishing a short essay about your experiences in your job can bring attention to inequality.

\n\n\n\n
\n

DOWNTOWN BROOKLYN: I\'m working arraignments tonight, representing poor New Yorkers who were arrested yesterday on Thanksgiving.

It was the coldest Thanksgiving in more than a century. Tonight\'s also bitterly cold, even in the courtroom. I\'m wearing my scarf & coat.

— Rebecca Kavanagh (@DrRJKavanagh) November 24, 2018
\n
\n\n\n\n

And Tumblr screenshot threads are always fun to read, even when they take a turn for the epic (over 4000 tweets in this thread, and it isn’t slowing down!)

\n\n\n\n
\n

Tumblr textposts thread, probably?

— we are a family forged in bureaucracy (@ex_aItiora) August 26, 2019
\n
\n\n\n\n

Everyone can think of threads that they’ve loved reading.

\n\n\n\n

My point is, threads are wildly underused on Twitter. I think I big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.

\n\n\n\n

To help make this easier, I’ve been working on a tool that will help you publish an entire post to Twitter from your WordPress site, as a thread. It takes care of transforming your post into Twitter-friendly content, you can just… write. \"?\"

\n\n\n\n

It doesn’t just handle the tweet embeds from earlier in the thread: it handles handle uploading and attaching any images and videos you’ve included in your post.

\n\n\n\n\n\n\n\n

All sorts of embeds work, too. \"?\"

\n\n\n\n
\n
\n
\n\n\n\n

It’ll be coming in Jetpack 9.0 (due out October 6), but you can try it now in the latest Jetpack Beta! Check it out and tell me what you think. \"?\"

\n\n\n\n

This might not fix all of Twitter’s problems, but I hope it’ll help you enjoy reading and writing on Twitter a little more. \"?\"

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 02:33:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Gary\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Themes Team Releases a Web Fonts Loader, Likely To Prohibit Hotlinking Any Off-Site Assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105363\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets?utm_source=rss&utm_medium=rss&utm_campaign=themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5815:\"

Last Friday, the WordPress Themes Team announced the release of its new Webfonts Loader project. It is a drop-in script that allows theme authors to load web fonts from the user’s site instead of a third-party CDN. The secondary message included in the team’s announcement is that it no longer plans to allow themes to hotlink Google Fonts in the future.

\n\n\n\n

Throughout most of the team’s history, it has not allowed themes to hotlink or use CDNs for hosting theme assets, such as CSS, JavaScript, and fonts. The one exception to this rule was the use of Google Fonts. This allowed themes to have richer typography options at their disposal from what the team has generally declared a reliable source.

\n\n\n\n

“The exception was made because there was no practical way to not have the exception at the time,” said Aria Stathopoulos, a Themes Team representative and developer behind the Webfonts Loader project. “The exception for Google Fonts was made out of necessity. Now that there is another way, the exception will not be necessary.”

\n\n\n\n

In effect, disallowing the Google Fonts CDN would not be a new ban. It would be a removal of an exception to the existing ban.

\n\n\n\n

Google Fonts has become so embedded into the theme developer toolset over the years, there was no way the team could simply pull the plug and prohibit the use of the CDN overnight. If the Themes Team members wanted to focus more on privacy, they would need to build a tool that made it dead simple for theme authors to use.

\n\n\n\n

There is no hard deadline for when the team will remove the exception for Google Fonts, and it is not set in stone at this point. Stathopoulos said removing it has been the goal from the beginning, disallowing all CDNs. However, it took a while to find an efficient way to handle this. With a viable alternative in place, they can discuss moving forward.

\n\n\n\n

Webfonts Loader for Themes

\n\n\n\n

The Webfonts Loader project keeps it simple for theme authors. It introduces a new wptt_get_webfont_styles() function that developers can plug in a stylesheet URL. Once a page is loaded with that function call, it will download the fonts locally to a /fonts folder in the user’s /wp-content directory. This way, fonts will always be served from the user’s site.

\n\n\n\n

The system is not limited to Google Fonts either. Any URL that serves CSS with an @font-face {} rule will work. It does not currently include authentication for CDNs that require API keys, such as Adobe Fonts. However, that is something the team might add in the future.

\n\n\n\n

“For end-users, moving away from CDNs and locally hosting web fonts will improve performance (fewer handshake roundtrips for SSL), and is the privacy-conscious choice,” said Stathopoulos. “The only ‘valid privacy concern’ is that the web fonts’ CDN does not disclose information that is fundamental to the GDPR: what information gets logged, for how long these logs remain, how they are processed, if there is any cross-referencing with all the other wealth of information the company has from users, etc. The concern is a lack of disclosure and information. If a site owner doesn’t know what kind of information a third-party logs for its visitors, then they should ethically not enforce that on their visitors. With this package, the CDN is removed from the equation and the font still gets served fast — if not faster.”

\n\n\n\n

A Path to Core WordPress

\n\n\n\n

Today, there is now a broader focus on privacy concerns related to third-party resources, particularly with tech giants like Google. Such concerns extend to whether third parties are tracking users or collecting data. Additional concerns are around whether sites are disclosing the use of third-party resources, which may be required in some jurisdictions. Site owners who are often unable to work through the web of potential issues are stuck in the middle.

\n\n\n\n

Jono Alderson opened a ticket to create an API for loading web fonts locally in core WordPress in February 2019. It is a lengthy and detailed proposal, but it has yet to see much buy-in outside of a handful of developers.

\n\n\n\n

“If such a script is standardized and included in WordPress core, one of the main benefits would be more respect for the end-user’s privacy,” said Stathopoulos. “In the end, that’s all privacy is about: respecting users.”

\n\n\n\n

A standard API like Alderson proposes could solve some issues. Namely, it would virtually eliminate any privacy concerns. However, loading fonts locally could allow WordPress to optimize font loading and would create a shared system where plugins and themes do not load duplicate assets because of the current limitations of the enqueuing system. A standard API would also put the responsibility of efficiently loading fonts on WordPress’s shoulders instead of theme and plugin developers.

\n\n\n\n

The Themes Team’s new project is a solid start and strengthens the current proposal.

\n\n\n\n

“If we’re serious about WordPress becoming a fast, privacy-friendly platform, we can’t rely on theme developers to add and manage fonts without providing a framework to support them,” wrote Alderson in the ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 28 Sep 2020 20:58:48 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: Fuxia Scholz First to Pass 100K Reputation Points on WordPress Stack Exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105282\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange?utm_source=rss&utm_medium=rss&utm_campaign=fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5096:\"

Fuxia Scholz, a prolific WordPress Stack Exchange (WPSE) contributor, is the first member to reach 100,000 reputation points. The popular Q&A community site rewards expert advice by floating the highest quality answers to the top, allowing users to earn reputation points. The gamified help community has proven to be more motivating for developers than many traditional forums, since the upvotes communicate how useful their answers are to others.

\n\n\n\n
\n\n\n\n

Scholz started on Stack Overflow a few months before WordPress had its own site. She wrote around 50 answers and made connections with other WordPress developers ahead of the site’s beta phase in June 2010. Once the site graduated and got its own logo and design, Scholz started writing more.

\n\n\n\n

“One core idea for all Stack Exchange sites is gamification: You earn reputation, and you get access to certain privileges,” Scholz said.

\n\n\n\n

“You can say I got a bit addicted. My favorite questions were about problems for which I didn’t know the answer, and couldn’t find one with a search engine, because no one else had solved that before. I used my answers to teach myself, and I learned a lot this way! In May 2011 my reputation on WPSE was already higher than on Stack Overflow, and for the next years it went up in a steep curve.” Ten years after WPSE launched, Scholz has become the first to reach 100,000 reputation points.

\n\n\n\n

“What reputation and karma do is send a message that this is a community with norms, it’s not just a place to type words onto the internet. (That would be 4chan.)” Stack Overflow co-creator Joel Spolsky said. “We don’t really exist for the purpose of letting you exercise your freedom of speech. You can get your freedom of speech somewhere else. Our goal is to get the best answers to questions. All the voting makes it clear that we have standards, that some posts are better than others, and that the community itself has some norms about what’s good and bad that they express through the vote.”

\n\n\n\n

The reputation points were originally inspired by Reddit Karma. Spolsky admits that the points not a perfect system but they do tend to “drive a tremendous amount of good behavior.” Gamification can shape and encourage certain behaviors but Spolsky said it’s a weak force that cannot motivate people to do things they are not already interested in doing. For Scholz, it was the community aspect and an earned sense of ownership and responsibility that kept her hooked.

\n\n\n\n

“In 2012, the community elected me as a moderator, and that changed a lot,” she said. “Now it wasn’t just a game anymore, it was a duty. I felt responsible for the site. I still do. I also found some friends on there. We met at WordCamps and in private, and worked together on different projects.”

\n\n\n\n

Scholz no longer works in development and said she doesn’t care about WordPress anymore, but she is still a regular contributor on the WPSE.

\n\n\n\n

“I switched careers and work as a writer, translator, and community manager for Chess24.com now,” she said. “But I still care about the site WordPress Stack Exchange! I keep an eye on new tags, handle flagged posts and comments, try to make every new user feel welcome, and I search for people who are abusing the system — vote fraud and spam. And, very rarely, I even write an answer, because I still know all this stuff.

\n\n\n\n

“Checking the site has become a part of my daily routine, like feeding the cat.”

\n\n\n\n

This daily habit has snowballed into Scholz racking up more than 2,000 answers. She is getting upvotes on many of her old answers nearly every day, which is what pushed her over the 100k milestone.

\n\n\n\n

“There is a lot to say about the way our site developed over the years,” Scholz said. “I’m not happy about some things. The enthusiasm of the early days is gone. We don’t have enough regulars, there is no discussion about the site on WordPress Development Meta Stack Exchange, and our chat, once very active, funny, and friendly, is now almost dead.

\n\n\n\n

“Maybe that’s normal, I don’t know. But it’s still ‘my’ site. Reputation and badges don’t really mean anything for a long time now, but keeping the site working, useful and friendly is more important.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 26 Sep 2020 15:27:03 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: PhotoPress Plugin Seeks to Revolutionize Photography for WordPress Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=104770\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:209:\"https://wptavern.com/photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users?utm_source=rss&utm_medium=rss&utm_campaign=photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5638:\"

Peter Adams, the owner of the PhotoPress plugin, announced a couple of weeks ago that now is the time for his project to take center stage. “It’s Time for PhotoPress,” read the title of his post in which he laid out a four-phase plan for the future of his project.

\n\n\n\n

Adams is no stranger to manipulating WordPress to suit the needs of photographers. He described photography as his first love and second career. He initially found the art of taking photos in high school and set off to college to become a professional photographer in the early ’90s.

\n\n\n\n

As his university graduation loomed, he was recruited to run web development for an internet ad agency that built websites for Netscape, Bill Clinton’s White House, and dozens of Fortune 500 companies. He spent the next 15 years starting or running tech companies before returning to his roots as a photographer.

\n\n\n\n

Today, he photographs for various magazines and companies. And, that’s where his PhotoPress project comes in.

\n\n\n\n

“As far as WordPress has come, it is at risk of losing an entire generation of photographers to photo website services such as Photoshelter, SmugMug, Squarespace, and PhotoFolio,” he said. Adams wants to change that, making WordPress the go-to platform for photographers around the world.

\n\n\n\n

The Jetpack of Photography Plugins

\n\n\n\n

If you dig into the history of the PhotoPress plugin on WordPress.org, it seems to have a 15-year history. However, this is not the same plugin that was published a decade and a half ago by a different developer. The original plugin is now defunct, and Adams took over when the name was freed up on the directory.

\n\n\n\n

Adams wrote in his announcement post that WordPress has done a great job of delivering several media features over the years. “Yet despite that, there are still many rough edges and missing features that keep WordPress from being the first choice for a photographer that needs to publish a beautiful portfolio of their work, put their image catalog/archive online, or showcase a photo editorial/project.”

\n\n\n\n

He outlined a list of 10 specific problem areas that he wants to address in a “Jetpack-like” plugin for photographers. This is the bread and butter of the first of the planned four phases, which he said is about 80% finished. He had originally planned to develop PhotoPress as a series of separate plugins, each addressing a specific problem. Now, it is a single plugin with modules than can be enabled or disabled.

\n\n\n\n

When asked why the “right time” is now, Adams explained it is because the Gutenberg (block editor) project is a giant leap forward in usability in terms of creating photography blogs.

\n\n\n\nPhotoPress Gallery block in the editor.\n\n\n\n

“Photogs are a rare breed of non-technical users with high design sense,” he said. “Things that I used to have to teach photographers to do using shortcode syntax and custom CSS can now be simple controls with live feedback inside a Gutenberg block. It’s really a game-changer for getting people comfortable with customizing things like gallery styling — which is the number one thing photographers need to do.”

\n\n\n\n

The primary piece of the PhotoPress plugin is its custom PhotoPress Gallery block. It allows users to choose between a range of gallery styles, such as columns, masonry, justified, and mosaic. Each style has its own options. Images can also be launched into a slideshow when one is clicked.

\n\n\n\n

Based on some quick tests, the block’s front-end output will go farther with some themes than others. This is mainly because of conflicting CSS and issues which can be solved by testing against more themes.

\n\n\n\n

Aside from the block, the plugin can automatically extract image metadata and group that data through custom taxonomies, such as cameras, lenses, locations, keywords, and more. WordPress stores this information out of the box, but it is hidden away as post meta. The plugin uses the taxonomy system to make it manageable for end-users.

\n\n\n\n

Ultimately, Adams set out to create a photography plugin that fits in with the WordPress admin user interface and experience, which he has accomplished.

\n\n\n\n

The Future of PhotoPress

\n\n\n\n

The project is still a work in progress. Adams is still moving through Phase I of the four-phase plan. Once it is complete, he can move on to the next steps in the process.

\n\n\n\n

Phase II is to create themes that are designed specifically to work with the PhotoPress plugin. He has three planned thus far. One for handling portfolio sites. Another for creating a stock photo archive. And the last for photojournalism and exhibits. Each will be built on top of his photography theme framework.

\n\n\n\n

The themes in Phase II will likely be commercial products. Adams said he needs a way to fund the next phases of the project. He hopes to have this step underway by the end of the year.

\n\n\n\n

For 2021, he wants to begin tackling Phases III and IV. The former will be a website-as-a-service (WaaS) similar to WordPress.com but for photographers. It will begin as a paid project but could have some free options for emerging photographers and students. The final phase is to build an onboarding system.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 25 Sep 2020 19:08:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: Google Officially Releases Its Web Stories for WordPress Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105227\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:191:\"https://wptavern.com/google-officially-releases-its-web-stories-for-wordpress-plugin?utm_source=rss&utm_medium=rss&utm_campaign=google-officially-releases-its-web-stories-for-wordpress-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5593:\"Web Stories for WordPress dashboard.\n\n\n\n

Two and a half months after the launch of its public beta, Google released its Web Stories for WordPress plugin. So far, the plugin has over 10,000 active installations and has garnered a solid five-star rating from four reviews.

\n\n\n\n

Google created the Web Stories format through its AMP Project to allow publishers to create visually-rich stories. It is primarily geared toward mobile site visitors, allowing them to quickly jump through story pages with small chunks of content.

\n\n\n\n

The Web Stories plugin creates a visual interface within WordPress for creating Stories. It breaks away from the traditional WordPress interface and introduces users to an almost Photoshop-like experience for building out individual Stories. The Stories editor is completely drag-and-drop.

\n\n\n\n

The plugin also offers eight predesigned templates out of the box that cover a small range of niches. However, according to Google’s announcement, the company plans to add more templates in future updates.

\n\n\n\n

Web Stories Are for Storytelling

\n\n\n\n

“Firstly…the power of Stories,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Stories are how we (humans) see the world and share our experiences. Up to now the platforms that we have to tell stories have been limited to books/films/tv/websites/blogs/instagram stories etc.”

\n\n\n\n

“Websites are ok for telling stories but in many ways the format doesn’t really fit the linear arc of storytelling. When Marshall McLuhan said ‘the medium is the message’ in 1964 he was talking about how the medium itself has a social impact, and change the communication itself…and the possibilities for what is communicated and how it is perceived. But we should keep coming back to Stories. Stories are the key here imo. Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Marsland finished his thread by saying that using Stories as a replacement for a brochure or website is a missed opportunity. He said that it was a platform for storytelling and should be used as such.

\n\n\n\n

It is far too early to tell if Web Stories will simply be a fad or still in wide use years from now. The technology certainly lends itself well to telling stories, particularly in mobile format, but I doubt we have seen the best of what is possible on the web. The format feels too limited to be the end-all-be-all of storytelling. It is merely one medium that will live and die by its popularity with users.

\n\n\n\n

With the right design skills, some people will craft beautiful Web Stories. And, that is just what Marsland has done with the first Story he shared:

\n\n\n\nPage from the Wilson and Pootle Web Story by Jamie Marsland.\n\n\n\n

I agree with his conclusion. Web Stories should be about storytelling. When you move outside of that zone, the technology feels out of place.

\n\n\n\n

Where I disagree is that websites are not ideal for storytelling. Ultimately, the WordPress block editor will allow artistic end-users to craft intricate stories, mixing content and design in ways that we have not seen. We are just now scratching the surface. I expect our community of developers to build more intricate tools than what the Web Stories plugin currently allows, and we can do so in a way that revolutionizes storytelling on the web.

\n\n\n\n

New Features

\n\n\n\nStory editor with Unsplash photo integration.\n\n\n\n

The Web Stories plugin now adds support for Unsplash images and Coverr videos out of the box. The plugin adds a new tab with a “media” icon. For users of the first beta version of the plugin, this may be a bit confusing. The previous media icon was for a tab that displayed the user’s media. Now, the user’s media is under the tab with the “upload” icon.

\n\n\n\n

It is also not immediately clear that the Unsplash images and Coverr videos are not hosted on the site itself. There is a “powered by” notice at the bottom of the tab, but it can be easy to miss because it blends in with the media in the background.

\n\n\n\n

Media from Unsplash and Coverr is hosted off-site and not downloaded to the user’s WordPress media library. I could find no mention of this in the plugin’s documentation. Such hotlinking was a cause for debate over the recent official release of the Unsplash plugin.

\n\n\n\n

Google also announced it planned to add more “stock media integrations” in the near future. According to a document shared via a GitHub ticket, such future integrations may include Google Photos and GIF-sharing site Tenor.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 21:13:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: W3C Drops WordPress from Consideration for Redesign, Narrows CMS Shortlist to Statamic and Craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105108\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft?utm_source=rss&utm_medium=rss&utm_campaign=w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:11563:\"

The World Wide Web Consortium (W3C), the international standards organization for the web, is redesigning its website and will soon be selecting a new CMS. Although WordPress is already used to manage W3C’s blog and news sections of the website, the organization is open to adopting a new CMS to meet its list of preferences and requirements.

\n\n\n\n

Studio 24, the digital agency selected for the redesign project, narrowed their consideration to three CMS candidates:

\n\n\n\n
  1. Statamic
  2. Craft CMS
  3. WordPress
\n\n\n\n

Studio 24 was aiming to finalize their recommendations in July but found that none of them complied with the W3C’s authoring tool accessibility guidelines. The CMS’s that were better at compliance with the guidelines were not as well suited to the other project requirements.

\n\n\n\n

In the most recent project update posted to the site, Studio 24 reported they have shortlisted two CMS platforms. Coralie Mercier, Head of Marketing and Communications at W3C, confirmed that these include Statamic and Craft CMS.

\n\n\n\n

WordPress was not submitted to the same review process as the Studio 24 team claims to have extensive experience working with it. In the summary of their concerns, Studio 24 cited Gutenberg, accessibility issues, and the fact that the Classic Editor plugin will stop being officially maintained on December 31st, 2021:

\n\n\n\n

First of all, we have concerns about the longevity of WordPress as we use it. WordPress released a new version of their editor in 2018: Gutenberg. We have already rejected the use of Gutenberg in the context of this project due to accessibility issues.

If we choose to do away with Gutenberg now, we cannot go back to it at a later date. This would amount to starting from scratch with the whole CMS setup and theming.

Gutenberg is the future of WordPress. The WordPress core development team keeps pushing it forward and wants to roll it out to all areas of the content management system (navigation, sidebar, options etc.) as opposed to limiting its use to the main content editor as is currently the case.

This means that if we want to use WordPress long term, we will need to circumvent Gutenberg and keep circumventing it for a long time and in more areas of the CMS as time goes by. 

\n\n\n\n

Another major factor in the decision to remove WordPress from consideration was that they found “no elegant solution to content localization and translation.”

\n\n\n\n

Studio 24 also expressed concerns that tools like ACF, Fewbricks, and other plugins might not being maintained for the Classic Editor experience “in the context of a widespread adoption of Gutenberg by users and developers.”

\n\n\n\n

“More generally, we think this push to expand Gutenberg is an indication of WordPress focusing on the requirements of their non-technical user base as opposed to their audience of web developers building custom solutions for their clients.”

\n\n\n\n

It seems that the digital agency W3C selected for the project is less optimistic about the future of Gutenberg and may not have reviewed recent improvements to the overall editing experience since 2018, including those related to accessibility.

\n\n\n\n

Accessibility consultant and WordPress contributor Joe Dolson recently gave an update on Gutenberg accessibility audit at WPCampus 2020 Online. He reported that while there are still challenges remaining, many issues raised in the audit have been addressed across the whole interface and 2/3 of them have been solved. “Overall accessibility of Gutenberg is vastly improved today over what it was at release,” Dolson said.

\n\n\n\n

Unfortunately, Studio 24 didn’t put WordPress through the same content creation and accessibility tests that it used for Statamic and Craft CMS. This may be because they had already planned to use a Classic Editor implementation and didn’t see the necessity of putting Gutenberg through the paces.

\n\n\n\n

These tests involved creating pages with “flexible components” which they referred to as “blocks of layout,” for things like titles, WYSIWYG text input, and videos. It also involved creating a template for news items where all the content input by the user would be displayed (without formatting).

\n\n\n\n

Gutenberg would lend itself well to these uses cases but was not formally tested with the other candidates, due to the team citing their “extensive experience” with WordPress. I would like to see the W3C team revisit Gutenberg for a fair shake against the proprietary CMS’s.

\n\n\n\n

W3C Is Prioritizing Accessibility Over Its Open Source Licensing Preferences

\n\n\n\n

The document outlining the CMS requirements for the project states that “W3C has a strong preference for an open-source license for the CMS platform” as well as “a CMS that is long-lived and easy to maintain.” This preference may be due to the economic benefits of using a stable, widely adopted CMS, or it may be inspired by the undeniable symbiosis between open source and open standards.

\n\n\n\n

“The industry has learned by experience that the only software-related standards to fully achieve [their] goals are those which not only permit but encourage open source implementations. Open source implementations are a quality and honesty check for any open standard that might be implemented in software…”

Open Source Initiative
\n\n\n\n

WordPress is the only one of the three original candidates to be distributed under an OSD-compliant license. (CMS code available on GitHub isn’t the same.)

\n\n\n\n

Using proprietary software to publish the open standards that underpin the web isn’t a good look. While proprietary software makers are certainly capable of implementing open standards, regardless of licensing, there are a myriad of benefits for open standards in the context of open source usage:

\n\n\n\n

“The community of participants working with OSS may promote open debate resulting in an increased recognition of the benefits of various solutions and such debate may accelerate the adoption of solutions that are popular among the OSS participants. These characteristics of OSS support evolution of robust solutions are often a significant boost to the market adoption of open standards, in addition to the customer-driven incentives for interoperability and open standards.”

International Journal of Software Engineering & Applications
\n\n\n\n

Although both Craft CMS and Statamic have their code bases available on GitHub, they share similarly restrictive licensing models. The Craft CMS contributing document states:

\n\n\n\n

Craft isn’t FOSS
Let’s get one thing out of the way: Craft CMS is proprietary software. Everything in this repo, including community-contributed code, is the property of Pixel & Tonic.

That comes with some limitations on what you can do with the code:

– You can’t change anything related to licensing, purchasing, edition/feature-targeting, or anything else that could mess with our alcohol budget.
– You can’t publicly maintain a long-term fork of Craft. There is only One True Craft.

\n\n\n\n

Statamic’s contributing docs have similar restrictions:

\n\n\n\n

Statamic is not Free Open Source Software. It is proprietary. Everything in this and our other repos on Github — including community-contributed code — is the property of Wilderborn. For that reason there are a few limitations on how you can use the code:

\n\n\n\n

Projects with this kind of restrictive licensing often fail to attract much contribution or adoption, because the freedoms are not clear.

\n\n\n\n

In a GitHub issue requesting Craft CMS go open source, Craft founder and CEO Brandon Kelly said, “Craft isn’t closed source – all the source code is right here on GitHub,” and claims the license is relatively unrestrictive as far as proprietary software goes, that contributing functions in a similar way to FOSS projects. This rationale is not convincing enough for some developers commenting on the thread.

\n\n\n\n

“I am a little hesitant to recommend Craft with a custom open source license,” Frank Anderson said. “Even if this was a MIT+ license that added the license and payment, much like React used to have. I am hesitant because the standard open source licenses have been tested.”

\n\n\n\n

When asked about the licensing concerns of Studio 24 narrowing its candidates to two proprietary software options, Coralie Mercier told me, “we are prioritizing accessibility.” A recent project update also reports that both CMS suppliers W3C is reviewing “have engaged positively with authoring tool accessibility needs and have made progress in this area.”

\n\n\n\n

Even if you have cooperative teams at proprietary CMS’s that are working on accessibility improvements as the result of this high profile client, it cannot compare to the massive community of contributors that OSD-compliant licensing enables.

\n\n\n\n

It’s unfortunate that the state of open source CMS accessibility has forced the organization to narrow its selections to proprietary software options for its first redesign in more than a decade.

\n\n\n\n

Open standards go hand in hand with open source. There is a mutually beneficial connection between the two that has caused the web to flourish. I don’t see using a proprietary CMS as an extension of W3C values, and it’s not clear how much more benefit to accessibility the proprietary options offer in comparison. W3C may be neutral on licensing debates, but in the spirit of openness, I think the organization should adopt an open source CMS, even if it is not WordPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 20:13:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WPTavern: First Look at Twenty Twenty-One, WordPress’s Upcoming Default Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105166\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme?utm_source=rss&utm_medium=rss&utm_campaign=first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6907:\"

Fashion is ephemeral. Art is eternal. Indeed what is a fashion really? A fashion is merely a form of ugliness so absolutely unbearable that we have to alter it every six months!

\n\n\n\n

Thus wrote Oscar Wilde on Victorian-era fashion in an article titled “The Philosophy of Dress” for the New-York Tribune in 1885.

\n\n\n\n

In many ways, WordPress theming is the same as the ever-changing landscape of fashion. Rounded corners are in one day and out the next. Box shadows are in one year after being frowned up just months earlier. Perhaps web design is so intolerable that we must change it every six months. Or, at least freshen it up every year in the case of WordPress.

\n\n\n\n

If art is eternal, there are only two default, Twenty* themes that I can truly recall from past years: Twenty Ten and Twenty Fourteen — yes, Twenty Twenty is memorable, but it is also still the current default. Twenty Ten was a classic that paid homage to WordPress’s past. Twenty Fourteen was such a leap away from tradition that it is hard to forget. Everything else has seemed to fade to varying degrees.

\n\n\n\n

With WordPress 5.6 and the end of the year looming, it is time to look forward to the latest trend. As Mel Choyce-Dwan noted in the announcement of Twenty Twenty-One, the next default theme, “Pastels and muted colors are pretty in right now.”

\n\n\n\n

She is not wrong. The colors are a refreshing change of pace. Now that we are into the second day of autumn, I am getting the good kind of vibes from some of the more earthy-tones from a couple of the color palettes expected to ship with the theme.

\n\n\n\nPotential color palette options for Twenty Twenty-One.\n\n\n\n

Whether Twenty Twenty-One will be a fashionable theme for the year or art that we can remember a decade from now, only history will be able to judge. For now, let’s enjoy the creation and take a look at what we should expect from the next default WordPress theme.

\n\n\n\n

The Current Twenty Twenty-One

\n\n\n\n

The new default theme is a fork of Automattic’s Seedlet, a project in which I lauded as the next step in the evolution of theming. It is a theme that is focused on WordPress’s future of being completely comprised of blocks. It gives us an ideal insight into where theme development is heading. It makes sense as the foundation for the new default. Few other themes would make for a good starting point right now. With WordPress theme development in flux, Seedlet is simply ahead of the pack in terms of foundational elements.

\n\n\n\nSeedlet WordPress theme screenshot.\n\n\n\n

“This provides us with a thorough system of nested CSS variables to make child theming easier, and to help integrate with the global styles functionality that’s under development for full-site editing,” wrote Choyce-Dwan of using Seedlet as a starting point.

\n\n\n\n

There are no plans to spin up a Google Web Font for this theme. The design team is going native and sticking with the default system font stack. Choyce-Dwan listed several reasons for the choice, such as keeping a neutral font that allows broad use, speed, and customizability via a child theme.

\n\n\n\n

Despite the neutral font, the default pastel green is a fairly opinionated design decision. It will not be used broadly across industries. However, the team plans to create multiple color palettes that will give it more range. Presumably, these palettes can also be overwritten.

\n\n\n\nPastel green color scheme on single post view.\n\n\n\n

Other than the colors, the design is relatively simple. Choyce-Dwan said that the theme’s block patterns support is where it will be truly unique.

\n\n\n\n

I was initially unhappy with the patterns that were going to ship with WordPress 5.5. However, an 11th-hour update improved the situation so that they did not feel entirely experimental. The foundational Seedlet theme for Twenty Twenty-One has some unique patterns that begin to scratch the surface of what’s possible with this WordPress feature. My hope is that the new default theme steps it up a notch.

\n\n\n\n

Currently, the theme does not register any custom patterns. However, it has a placeholder file and a note that they are a work in progress. Choyce-Dwan shared some patterns the team has already designed in the announcement.

\n\n\n\nCurrently-designed block patterns.\n\n\n\n

“We’ll be relying on our talented community designers for more ideas,” she wrote. The team has also created a GitHub template for anyone to contribute pattern design ideas.

\n\n\n\n

Currently, the theme does not support the upcoming full-site editing feature of WordPress. After the Beta 1 release of WordPress 5.6, the team plans to begin exploring the addition of this support. WordPress is expected to ship a public beta of full-site editing in its next major release, but it is unclear whether it will be far enough along to be a headline feature for the Twenty Twenty-One theme.

\n\n\n\n

The team and volunteers have less than a month before the October 20th deadline for committing the new theme to trunk, the core WordPress development branch. At that stage, the theme should be nearly complete and ready for production. Of course, there will be several rounds of patches, bug fixes, and updates before WordPress 5.6 lands in December. Right now is the best time for anyone who wants to get involved with Twenty Twenty-One to do so.

\n\n\n\n

Useful links with more information:

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 20:01:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:37:\"HeroPress: Hello World – Hevo Nyika\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=3308\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:176:\"https://heropress.com/essays/hello-world-discovering-the-world-through-wordpress/#utm_source=rss&utm_medium=rss&utm_campaign=hello-world-discovering-the-world-through-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14438:\"\"Pull

Unokwanisa kuverenga rondedzero iyi muChiShona

\n

So I chose a career in Web Development!!

\n

To be honest it’s kind of funny when I think about it and quite surreal to be here talking about my story. It has been a journey and I would like to share my story with you.

\n

I have been lucky in the Dad department. My Dad encouraged me to work hard and dream big from a very young age. I remember occasionally having ‘when I grow up’ talks.

\n

For quite some time I wanted to be a Judge, however awesome this dream sounds it was not very inspired. After binge-watching Judge Judy for a whole weekend, I started calling myself Judge Thelma. Though I don’t remember much of this my sister says that I used to say I would arrest all the men in the World if I ever became a Judge. HAHAHA! (clearly I didn’t understand how the World works)

\n

I did not understand what being a Judge meant or what was required for me to start banging that gavel to my heart’s desire. Eventually, I learnt that I had to become a lawyer first then magistrate before I could be nominated to be a Judge and let us just say that is how I sentenced that dream to a lifetime down the drain.

\n

See what I did there? hahaha!

\nWith Daddy Dearest\n

A few years later, I was in High School and that is when I decided to pursue a career in Computer Science. I did not know what I would be doing or how I would get there but I just knew that I was going to pursue a career in ICT. I wrote my first line of code when I was 16 years old.

\n

This was after I had joined the school’s computer class, initially, I thought I would be learning about Excel Sheets and Word Documents until I was assigned to write my first program in C (talk about a double-take!!). It was not easy but it was very exciting, l remember writing up simple code for a Video Club – a simple check-in/out for VHS tapes and CDs. Dear World, thus began my fascination with computers.

\n

Seven years later, I was now in university studying ICT as I had always wanted. I was doing a Bachelors in Business Management & Information Technology. In my third year, I was interning at a local Webdesign and hosting company. This was never my plan, I only took on that job after I had failed to get a job with local banks or telecommunications companies. Before I was introduced to Website Design I envisioned myself suiting up and working in IT Audit or offering IT support. Even though things did not go as I had planned, I am glad they did not exactly go my way in that aspect. So in 2017, I was designing websites using HTML, CSS, PHP, JavaScripts and Joomla which was the prefered content management system at that company. I knew about WordPress but I was not using it for anything. People have this misconception that WordPress is not for real developers and it is not secure and at that time I was one of those people.

\n

Finding my tribe

\n

One day when I was working at the front desk Thabo Tswana came to give a colleague of mine a purple WooCommerce pen. I did not know what WooCommerce was at that time but I was taken by the purple shirt and pen he was carrying. I asked him about it and he explained what WooCommerce was and that what he was carrying was called ‘swag’. So the love of freebies led me to the WordCamp Harare website, instead of buying a ticket I applied to volunteer. I learnt more about WordPress, I was a volunteer, without any knowledge on WordPress.org or WordPress.com. I only started using WordPress because of the awesome people that l had met at that Wordcamp.

\n

Everyone was so welcoming, a week later with help from Thabo I designed my first ever WP website.

\n

Soon after I was part of the community and a bit more involved in the meetups. We had our first-ever Women Who WordPress meetup in 2018. So many ladies came on board bloggers and developers alike. We were free to talk and discuss a lot of things. We had more time to discuss the difference between WordPress.com and WordPress.org we shared views on how to handle discrimination at work, how to promote your website and a whole lot of other things.

\n

\n

Establishing roots

\n

In 2018, Harare had its first-ever female Lead Organiser Tapiwanashe Manhobo whoop whoop! I was also part of the organising team that year, I was assigned to handle Harare’s first Kids Camp. The planning process was stressful because the economic crisis in Zimbabwe was getting worse, luckily we had over 8 months to plan and with help from sponsors, we managed to pull through. In the end, everything turned out great. I wrote an article about the Kids Camp here.

\n

After the first Kids Camp, we had several WordPressors that were enthusiasts about encouraging kids to embrace ICT. In 2019 we had not planned to have a Kids Camp because of financial constraints but to our surprise, we had some anonymous donations and we managed to have a WordPress Community outreach to a youth centre a week after our WordCamp. We had the outreach at the Centre for Total Transformation which is a non-formal school that caters for underprivileged and vulnerable children. We taught them about WordPress, Computer Hardware and Software.

\n

Here is a small video I took with Ellen when we were about to leave. Did l mention that I am terrible on camera? hahaha!

\n\n

Kids Camp 2019 – Centre for Total Transformation

\n

I have fallen deeply for WordPress because of the Community, I enjoy attending WordCamps, meeting new people and just learning new stuff. I have a huge list of WordCamps I need to attend before l kick the bucket, hopefully. Last year I managed to cross WordCamp

\n

Johannesburg off my bucket list. This year I was going to attend WordCamp Capetown but unfortunately, 2020 had other plans for the whole world. Anyway when everything is back to normal my plan to travel to WordCamps will proceed. (fingers crossed)

\n

Reaping Fruits

\n

Meanwhile, my plan to improve my developing skills has not been on hold. Even though I can still cook up code in C and Java, for now, I have also included WordPress PHP functions to the mix. It was not easy to get to this point, daring myself got me to this slightly better stage. My IQ is not way up there, however, I try to do my best where I can and I am happy to say it has paid off so far.

\n

Around November last year, I was designing as a freelancer while job hunting. Out of the blue l got a call for a job offer from Trust Nhokovenzo who is big on Digital marketing and also part of the WordPress Community. He had asked someone in the community about developers and my name happened to come up. So since February, I have been part of his team at Calmlock Digital Marketing Agency.

\n

There is so much more in the world of WordPress that l am yet to tap into so even though I am ending my write up here, for now, my story is going to continue …

\n

Until next time…

\n

Hevo Nyika

\n

Saka ini ndakasarudza kugadzira mawebhusayiti.

\n

Ndakaita rombo rakanaka pana baba vandakapihwa naMwari. Baba vangu vaindikurudzira kuti ndishande nesimba. Ndinoyeuka pano neapo tichiita hurukuro dzedu dzekuti ‘kana ndakura ndoda kuveyi’.

\n

Kwenguva yakati rebei ndaida kuve Mutongi. Kunyangwe ini ndisingazvirangariri mukoma wangu anotaura kuti ndaiti ndaizosunga varume vese vari pasi rino kana ndikangoita mutongi HAHAHA zveshuwa handaiziva kuti mitemo yenyika inofambiswa seyi.
\nNdanga ndisinga nzwisisi kuti kuva mutongi kwairevei kana zvaidikanwa kwandiri kuti nditange kurova iro ghavheu kuchishuwo chemoyo wangu. Pakupedzisira, ndakadzidza kuti ndaifanirwa kuzoita gweta ipapo magistrate ndisati ndasarudzwa kuita Mutongi naizvozvo ndokupera kwakaita chiroto chekuva Mutongi.

\nNa Baba Vangu\n

Gare gare papfura makore mashoma pandakanga ndave kuHigh School ndakanga ndakuda kuita basa rema kombiyuta. Ndakanyora mutsara wekutanga wekodhi pandaive nemakore gumi nematanhatu. Izvi zvakaitika mushure mekunge ndapinda mukirasi yemakombiyuta, pakutanga ndaifunga kuti ndinenge ndichidzidza nezveExcel Sheets neWord zvisineyi ndakaona ndakunyora kodhi yangu yekutanga muC. Zvaisave nyore kunyora kodhi asi zvainakidza kwazvo, ndorangarira ndichinyora kodhi yeVhidhiyo Kirabhu.

\n

Makore manomwe apfura, ndakanga ndava kuyunivhesiti ndichidzidza ICT zvandakagara ndakaronga. Ndaiita Bachelors muBusiness Management & Information Technology. Mugore rangu rechitatu ndainge ndave kushanda kune imwe kambani yaita zvekugadzira mawebhusaiti. Ndakawana basa iri mushure mekunge ndatadza kuwana basa kumabhanga. Kunyangwe hazvo zvinhu zvisina kuenda sezvandaive ndakaronga, ndinofara kuti hazvina kunyatso enda nenzira yangu. Saka muna 2017 ndaigadzira mawebhusaiti ndichishandisa HTML, CSS, PHP, JavaScript uye Joomla iyo yaive iyo inokurudzirwa kukambani kwandaive. Panguva iyi ndaiziva nezve WordPress asi ndakanga ndisingaishandisi.

\n

Kuwanana neWordPress

\n

Rimwe zuva pandakanga ndichishanda ndakaona Thabo Tswana akauya kuzopa mumwe mukomana wandayishanda naye chinyoreso cheWooCommerce. Ndakanga ndisingazive kuti WooCommerce yaive chii asi ndakafarira chinyoreso nehembe ye WooCommerce yaanga akapfeka. Ndakamubvunza nezvazvo akatsanangura kuti WooCommerce yaive chii. Saka nekudawo zvakanaka, zvemahara ndakaenda pawebhusaiti yeWordCamp Harare ndikabata zvimbo zvegore iroro. Ndakazvipira kubatsirawo vamwe vekuWordPress kuWordCamp Harare. Nerubatsiro kubva kunaThabo ndakagadzira webhusaiti yangu yekutanga yeWordPress vhiki rakatevera .

\n

Mushure mekunge ndaitawo chipato cheavo vanoshandisa WordPress ndakanga ndakuenda kumisangano yeWordPress yaitwa muHarare. Takaita musangano wevakadzi chete muna 2018. Vakadzi vazhinji vakauya kumusangano uyu. Tainga takasununguka kukurukura zvinhu zvakawanda. Takakurukura pamusoro pemutsauko uripo pakati peWordPress.com neWordPress.org takagovana maonero ekugadzirisa rusarura kubasa nezvimwewo.

\n

\n

Nguva yandakatanga kushandisa WordPress

\n

Muna 2018, kurongwa kweWordCamp Harare kwakatungamirwa kekutanga nemusikana ainzi Tapiwanashe Manhobo (waiva mufaro mukuru). Ndakanga ndiri mumwe wevairongawo naye. Hurongwa hwekuronga WordCamp Harare mugore iri hwainetsa pamusaka pekuoma kwehupfumi wemuZimbabwe, zvisineyi takaita rombo rakanaka nokuti takawana rubatsiro kubva kunevamwewo vanhu vakatiwedzera mari. Pakupedzisira, zvese zvakabudirira zvakanaka. Takarongawo WordCamp yevana varipasi pemakore gumi nechishanu, munokwanisa kuverenga pamusoro pezuva iri pawebhisaiti yangu apa.

\n

Mushure mekuita WordCamp yevana, takave nevamwe vanhu veWordPress aifarira kukurudzira vana kuti vagamuchire ICT. Muna 2019 takanga tisina kuronga kuve neWordCamo yeVana nekuda kwezvimhingamupinyi zvemari asi chakatishamisa ndechekuti takawana mari kubvawo kune vamwe. Takaita Camp iyi paCentre for Total Transformation chinova chikoro chisiri chepamutemo chinodzidzisa vana vanotambura. Tadzidzisa vana ava pamusoro peWordPress, Computer Hardware uye Software.

\n\n

Ndofarira WordPress zvakanyanya nekuda kweavo varimu nharaunda yacho, ini ndinonakidzwa nekuenda kumaWordCampi, kusangana nevanhu vatsva uye kungo dzidza zvinhu zvitsva. Gore rakapera ndakakwanisa kuyambuka muganhu weZimbabwe ndichienda kuWordCamp Johannesburg, dai pasina kuti 2020 nyika dzepasi rino dzakawirwa nedenda reCOVID 19 zvimwe ndingadayi ndakaenda kuWordCamp Capetown. Zvisinei hazvo kana denda ranani zvimwe ndichakwanisa kufamba ndichienda kumaWordCamp edzimwe nyika.

\n

Kukowa zvandakadyara

\n

Zvichakadaro, chirongwa changu chekuvandudza hunyanzvi hwangu hachina kumira. Kunyangwe ini ndichiri kukwanisa kubika kodhi muC uye Java, ikozvino, ndasanganisirawo WordPress PHP. Zvaive zvisiri nyore kusvika apa, zvakatora kuzvishingisa nekushanda nesimba. Ndinofara mwari aiva neni pamufambo wangu uyu.

\n

Muna Mbudzi gore rakapera, ndaive ndichigadzira mawebhusayiti apo nditsvaga basa. Pasina nguva ndakataura naTrust Nhokovenzo uyo akaandipa basa mukambani yake, kambani iyi inonzi Calmlock Digital Marketing Agency.

\n

Pane zvimwe zvakawanda kuWordPress zvandisati ndapinda mazviri. Nhaizvozvo kunyangwe ndiri kupedzisa kunyora kwangu apa, nyaya yehupenyu wangu ichaenderera mberi…

\n

Kusvikira nguva inotevera …

\n

…. tsvaga chinangwa chako, chiite mushe mushe ..

\n

The post Hello World – Hevo Nyika appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 06:00:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Thelma Mutete\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: WordPress Contributors Debate Dashboard Notice for Upcoming Facebook oEmbed Provider Removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105132\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5885:\"

WordPress contributors are discussing different strategies for responding to Facebook and Instagram dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers. When a user attempts to embed content by pasting a URL as they have in the past, they may not understand why it no longer works. They may assume that WordPress broke embeds, causing an increase in the support burden for this change.

\n\n\n\n

A few participants on the trac ticket for this issue have suggested WordPress detect users who will be impacted and attempt to warn them with a notice.

\n\n\n\n

“Since this may impact users unknowingly, it is possible to push a dashboard notice to users who have Facebook/Instagram embeds in their content, showing for site admins, as a one-off that can be dismissed,” Marius Jensen said.

\n\n\n\n

“We’ve previously done post-update-processing to clean up comments, so the idea of looking over content for an embed isn’t completely outlandish, and would help with those who don’t follow WordPress’ usual channels to learn of this.”

\n\n\n\n

Others don’t see the necessity. “Why should we make exception here?” Milan Dinić said. “It’s not the first time oEmbed support was discontinued for a provider, and I don’t remember anything specific was done then.”

\n\n\n\n

There is still some uncertainty about what will happen with existing oEmbeds after Facebook updates its API. During a recent core developer meeting, Helen Helen Hou-Sandí confirmed that WordPress does not clear oEmbed caches regularly. “Technically oEmbed caches are cleared if you save and a valid response is returned, we do not do cron-based garbage collection,” Hou-Sandí said.

\n\n\n\n

In a post today on the core development blog, Jake Spurlock assured users and developers that the existing embeds added before Facebook’s API change should still work:

\n\n\n\n

Because oEmbed responses are cached in the database using the hidden oembed_cache post type, any embed added prior to the October 24th deadline will be preserved past the deprecation date. These posts are not purged by default in WordPress Core, so the contents of the embed will persist unless manually deleted.

\n\n\n\n

Marius Jensen cautioned that there is still the possibility that existing embeds may not work going, depending on what Facebook does.

\n\n\n\n

“We don’t know how they plan on implementing the use of unauthorized embed attempts,” Jensen said. “It could not return an embed code and your link would remain a plain link, or maybe they decide to return some kind of embedded ‘unauthorized’ content. I don’t think anyone has heard any specifics on how Facebook plans on doing this, so we’re all just kinda waiting to either hear more, or see what happens.”

\n\n\n\n

Jensen said WordPress doesn’t re-check the cached results except when something changes with the post, but there may be plugins that clean up temporary data that may create an unpredictable outcome.

\n\n\n\n

“The reliability of the caches are hard to determine (and being caches, it’s sort of in the term that it’s not guaranteed to always be there, but rather fetched and saved for a while when needed),” Jensen said.

\n\n\n\n

Ideally WordPress’ oEmbed caches will prevent millions of embeds from breaking, but it’s still unknown how Facebook and third party plugins could change things.

\n\n\n\n

Coming off a rocky 5.5 core update that deprecated jQuery Migrate and flooded official support forums with reports of broken sites, some contributors are wary of having another situation where users are left in the dark.

\n\n\n\n

“I think a dashboard notice is desirable,” Jon Brown said. “Otherwise we’re not preemptively warning people in a way they can prepare and transition to another solution. We’re letting them know the same instant it’s going to break (when editing a specific post). I don’t think we can safely assume cached data is going to persist forever either, plenty of routines out there purge transient data before its stated expiration date.

\n\n\n\n

“I see this as potentially being similar to the problems seen in dropping JQM. It’ll cause avoidable and silent breakage client side without even any error logging for a site developer to pick up on. In hindsight, what ideally would have happened with JQM would have been incorporating the detection code from Enable jQuery Migrate Helper into core temporarily, or simply installing that plugin automatically on behalf of users.”

\n\n\n\n

Brown suggested WordPress detect calls to the cached embeds and warn users before the calls have the chance to fail so they can consider enabling a plugin to keep their embeds working more reliably.

\n\n\n\n

The discussion remains open in the make.wordpress.org/core post and the corresponding trac ticket. Spurlock said WordPress will likely remove Facebook and Instagram oEmbed providers in the upcoming 5.6 release (scheduled for December 8) but it could also be shipped in a 5.x minor release that happens after October 24.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 04:28:56 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Gutenberg Hub Launches Landing Page Templates Directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105009\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:175:\"https://wptavern.com/gutenberg-hub-launches-landing-page-templates-directory?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-hub-launches-landing-page-templates-directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7657:\"\n\n\n\n

Munir Kamal has created copy-and-paste blocks. He has built sections or “patterns” from those blocks. He has created a plugin that allows users to completely customize the two features via block options. Yesterday, he released an initial offering of 22 landing page templates that build upon his earlier work.

\n\n\n\n

Gutenberg Hub can almost be called his magnum opus, at least at this stage of his career. It is a continually growing library of free tools for WordPress’s block editor.

\n\n\n\n

Like previous projects, Gutenberg Hub’s landing templates require the EditorPlus plugin. This plugin is essentially a suite of design controls for the core WordPress blocks. The templates make use of these options by default. Given the limitations of the block editor’s current design controls, the use of such a plugin is necessary. Otherwise, there would be few other ways to realistically create a template system like this.

\n\n\n\n

Currently, users must copy the block code — via a convenient “copy” button — from the Gutenberg Hub website and then paste it in the editor. It is not an ideal situation, and I have been asking Kamal whether he would consider building a template inserter for months now.

\n\n\n\n

This time around, he preemptively said, “And, by the way, I am already working on adding a Template Inserter in my EditorPlus plugin. That will allow users to browse and insert these templates directly from Gutenberg without leaving the website.”

\n\n\n\n

He knew the question was coming. No need for me to ask again. He was unable to share a current screenshot of what the inserter looks like, but he is asking for feedback on what people expect of the user experience and interface.

\n\n\n\n

“Earlier, I created a template inserter similar to other blocks plugins, but later I changed my mind and thought that I should integrate with the Gutenberg Patterns API and load the templates into the ‘patterns’ panel in the block inserter,” he said. “But, I am having a few issues and thinking about going back to the original idea to have a Templates button on the top toolbar that opens a popup window to browse and filter templates that users can insert on a click.”

\n\n\n\n

For now, it is still early. However, at least it is on the long-term roadmap and being worked on.

\n\n\n\n

The Landing Page Templates

\n\n\n\nTesting the photography template (with minor adjustments).\n\n\n\n

At the moment, Gutenberg Hub offers 22 landing page templates. The “page” terminology may not mean “full page.” It simply depends on the active theme. Some themes have an open-canvas type of template that allows users to create the entire page via the editor. However, that is not a common feature, so these page templates will be confined to the post content area in most cases.

\n\n\n\n

The templates also work better with themes that have at least a full-width or no-sidebar option. End-users will want a lot of breathing room to use the templates and tinker with their designs.

\n\n\n\n

Kamal has built templates that stretch across a variety of industries. From restaurants to gyms to education to fashion, there is a lot to choose from right now. He promises more are on the way and at least a 23rd template in the next few days.

\n\n\n\n

“For the niches, I did some research from the top WordPress and HTML marketplaces and found the following most common or popular niches,” he said. “I think I will stick with these niches unless I get some more recommendations.”

\n\n\n\n

In comparison, Redux Templates offers access to over 1,000 sections and templates. Of course, there are trade-offs, such as some of those being commercial and the plugin typically requiring other third-party plugins. While quantity is not the only thing to look at, it proves there are miles of landscape that Gutenberg Hub’s templates have not yet explored. But, it is merely the beginning.

\n\n\n\n

Gutenberg Hub’s full-page templates are not quite as plug-and-play as its blocks and section templates. This is not so much a fault from the developer’s end. It is an issue of the platform, which is constantly being updated, and the range of support from current themes. End-users will start seeing some of the current limitations of the system when a layout does not quite look right with one theme but does with another. Or, if their theme has not been updated to support a new feature, such as the Social Links block, the typical horizontal menu design will likely be a normal vertical list of links instead.

\n\n\n\n

These are not insurmountable issues. Gutenberg and themes need more time to mature before projects like Gutenberg Hub’s landing templates are perfect or at least as close to perfect as can be expected.

\n\n\n\n

There are some things that Gutenberg Hub could improve with its templates. With several that I tested, I needed to switch specific blocks to be full width. This should be set up as the default with templates that are clearly meant to be full width in the example screenshots available on the site. It is a minor issue, but correcting this in the editor fixed several layout issues I was having when using the templates.

\n\n\n\n

Monetization Plans

\n\n\n\n

The second question that Kamal has not been prepared to answer fully over the past several months is how he will monetize Gutenberg Hub. Eventually, developers need some return on their investment when building tons of free tools. Many would do it all for free as long as their bills somehow got paid, but the reality is that there will come a tipping point where their projects need funding for long-haul maintenance.

\n\n\n\n

Kamal said he has laid the groundwork for funding but has not finalized anything yet. Currently, he is working on three ideas:

\n\n\n\n
  • Creating a pro version of his EditorPlus plugin.
  • Offering premium templates and blocks but is looking for a talented designer to work with.
  • Using ads specific to Gutenberg users, but he is not a fan of going this route or ads in general.
\n\n\n\n

He is open to feedback on how to best monetize the website and its projects. However, he said he is unwilling to compromise on giving away current and future free templates and tools.

\n\n\n\n

Future Gutenberg Projects

\n\n\n\n

Kamal said he does not have any new Gutenberg-related projects in the pipeline. The current plan is to work on what he has already created, which is a large ecosystem of Gutenberg tools that somehow work together.

\n\n\n\n

Outside of blocks, templates, and plugins, he is beginning to write more free tutorials on the Gutenberg Hub blog and focusing on creating videos around the project, including a new tutorial series for beginners.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 22 Sep 2020 21:05:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"WPTavern: WordPress Mobile Engineers Propose Dual Licensing Gutenberg under GPL v2.0 and MPL v2.0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105025\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:239:\"https://wptavern.com/wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6556:\"

During a Q&A session at WordCamp Europe 2020 online, Matt Mullenweg mentioned that Gutenberg contributors were considering dual licensing for embedding Gutenberg in mobile apps, along with the requirement that they would need to get an agreement from all contributors. WordPress mobile engineer Maxime Biais has just published a proposal for discussion, recommending dual licensing the editor under GPL v2.0 and MPL v2.0.

\n\n\n\n

“The GPL v2.0 license is a blocker for distributing the Gutenberg library in proprietary mobile apps,” Biais said in the corresponding GitHub issue. “Currently the only known users of Gutenberg on mobile are the WordPress mobile apps which are under GPL v2.0 (WordPress for AndroidWordPress for iOS). Mobile apps under the GPL v2.0 are not common and this limits Gutenberg usage in many apps.

\n\n\n\n

“Rich text editor libraries in the mobile space are lacking. There is no well known open source rich text editor for Android or iOS. We believe that Gutenberg could be a key library for many mobile apps, but that will never happen with the GPL v2.”

\n\n\n\n

Mobile app developers are limited by the GPL, because it requires the entire app to be distributed under the same license. The team is proposing dual licensing under MPL v2.0, a weaker copyleft license that is often considered to be more “business-friendly.” It allows users to combine the software with proprietary code. MPL v2.0 requires the source code for any changes to be available under the MPL, ensuring improvements are shared back to the community. The rest of the app can be distributed under any terms with the MPL v2.0 code included as part of a “larger work.”

\n\n\n\n

“The idea here is to keep some of the WordPress-specific modules under the GPL v2.0 only; some of them are not needed and not relevant for using Gutenberg in another software. Ideally, there would be a different way of bundling the project for being used in WordPress or in a non-GPL software,” Biais said.

\n\n\n\n

The GitHub ticket has several comments from developers who hope to be able to use the editor in their own projects. Radek Pietruszewski, tech lead for a collaborative todo app called Nozbe Teams, has been requesting a relicensing of Gutenberg since October 2019.

\n\n\n\n

“Our tech stack is essentially React on web and React Native on iOS and Android,” Pietruszewski said. “We’re a tiny company, and so we share >80% of app’s codebase between these 3 platforms.

\n\n\n\n

“Our app sorely lacks a WYSIWYG editor. We had a working implementation on web, but we decided to scrap it, because there was no way to port it on iOS and Android. There are pretty much no viable rich text editors for iOS or Android, yet alone both. But even then, shipping three completely separate, but somehow compatible editors would be a vast amount of work.”

\n\n\n\n

When Peitruszewski originally made his case to the mobile team, he identified Gutenberg/Aztec as a basic infrastructure that has the potential to enable many different apps:

\n\n\n\n

And that infrastructure is sorely lacking. There are very few rich text editor libraries on both iOS and Android — and most of them suck. And if you want an editor that has a shared API for both platforms… you’re stuck. There are no options – Gutenberg is the only game in town (and it’s really good).

And it’s very hard to create this infrastructure. WYSIWYG editors are very hard, and it takes entire teams years to develop them (and they still usually suck). Almost no-one has the resources to develop it just for themselves, and if they do, they’re unwilling to open-source it.

\n\n\n\n

Automattic’s mobile app engineers have struggled to get regular contributions to the apps, despite them being open source. Dual licensing Gutenberg could open up a new world of contributors with the editor being used more widely across the industry.

\n\n\n\n

“While we might not be big enough to be able to tackle a challenge of developing a rich text editor from scratch, we’re big enough to contribute features and bug fixes to open source projects,” Pietruszewski said.

\n\n\n\n

Matt Mullenweg was the first comment on Biais’ post in favor of the change:

\n\n\n\n

I think Gutenberg has a chance to become a cross-CMS standard, giving users a familiar interface any place they currently have a rich text box. There are hundreds and hundreds of engineers at other companies solving similar problems in a proprietary way, it would be amazing to get them working together but a huge barrier now is supporting Gutenberg in mobile apps, which every modern web service or CMS has. (Hypothetically, think of Mailchimp as a possible consumer and collaborator here, but it could be any company, SaaS, or other open source CMS.)

\n\n\n\n

Unless any major blockers come up in further discussion, this dual licensing change appears to be on track to move forward. Biais noted that a similar license change has already happened on Aztec-Android and Aztec-iOS. The last hurdle is gaining the approval of all the original code contributors or rewriting the code for those who decline to give approval.

\n\n\n\n

Once Gutenberg can be used under the MPL v2.0, the editor will gain a broader reach, with people already on deck wanting to use it. Other companies and projects that are normally outside WordPress’ open source orbit will also have the opportunity to enrich Gutenberg’s ecosystem with contributions back to the project. At the same time, the MPL 2.0 protects Gutenberg from companies that would try to re-release the code as a closed-source project.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 22:59:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"WPTavern: GitHub to Use ‘Main’ Instead of ‘Master’ as the Default Branch on All New Repositories Starting Next Month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:269:\"https://wptavern.com/github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month?utm_source=rss&utm_medium=rss&utm_campaign=github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4844:\"

In August, GitHub announced that it would change the “master” branch name for all new repositories created on the platform to “main” starting October 1. The date is less than two weeks away, and WordPress developers need to be prepared for the change if they use the service for version control or project management.

\n\n\n\n

The larger tech and web development community began conversations through various venues in June, a time in which the Black Lives Matter was gaining more traction in the U.S. and worldwide. The discussion centered on removing any terminology that could be discriminatory or oppressive to specific groups of people. This ongoing discussion has shown that there is a deep division over whether such changes are necessary or even helpful.

\n\n\n\n

The WordPress community is dealing with this division itself. Aaron Jorbin proposed a change at the same time to rename the default branch name on WordPress-owned repositories. Through discussion on his post and elsewhere, the community landed on “trunk,” which keeps WordPress projects in line with its SVN roots.

\n\n\n\n

“To close the circle on this, a decision was made in June and earlier today (August 19),” wrote Helen Hou-Sandí, a lead WordPress developer, in the comments of the original proposal. “I updated the default branch name for new GitHub repositories under the WordPress organization to be trunk after GitHub enabled early access to that feature.”

\n\n\n\n

As evidenced by the comments on the Tavern’s coverage of the proposal and those on the original post, the WordPress development community as a whole did not support this decision.

\n\n\n\n

Jorbin has updated several of WordPress’s repositories and switched them to use trunk instead of master. However, there are still some lingering projects yet to be updated, including the primary WordPress and WordPress Develop repositories. He left a comment with an updated list in June. There is no public word on whether the existing, leftover projects will be changed.

\n\n\n\n

WordPress Developer Preparations

\n\n\n\nCustomizing the default branch for a user’s GitHub repositories.\n\n\n\n

GitHub is merely changing the default branch name for new repositories starting on October 1. This change does not affect existing repositories. Individual users, organization owners, and enterprise administrators can customize the default branch via their account settings now before the switch is made. Owners can also change the default branch name for individual repositories.

\n\n\n\n

The biggest thing that developers need to watch out for is their tooling or other integrations that might still require the master branch. There may be cases where an alternative default branch name will break workflows. If planning to use a different branch name, the best thing to do right now is to spin up the tools you use on a test repository. If something breaks, check to see whether the particular tool you are using will be getting an update. In most cases, this should not be a problem because customized default branch names will be an industry standard.

\n\n\n\n

The great thing about how GitHub is rolling out this feature is that it offers a choice. Those who believe that “master” is oppressive can change the branch name to something they feel is more inclusive. For those who believe otherwise, they can keep their master branch. But, everyone can use the branch name they prefer.

\n\n\n\n

For existing repositories, GitHub is asking that developers be patient for now. The company is investing in tools to make this a seamless experience later this year. There are a few technical hurdles to clear first.

\n\n\n\n

Developers should read the full GitHub guide on setting the default branch for more information.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 20:39:55 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:31 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:13:\"last-modified\";s:29:\"Thu, 22 Oct 2020 16:15:08 GMT\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 2\";s:16:\"content-encoding\";s:4:\"gzip\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(133,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(134,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603384291','no'),(135,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1603427491','no'),(136,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(139,'theme_mods_twentytwenty','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(140,'recently_activated','a:0:{}','yes'); +INSERT INTO `wp55_options` VALUES (1,'siteurl','http://localhost','yes'),(2,'home','http://localhost','yes'),(3,'blogname','Datadog Test Application','yes'),(4,'blogdescription','Just another WordPress site','yes'),(5,'users_can_register','0','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\d_-]+?)-(\\d+?)\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\d+?)\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\"([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\".?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwenty','yes'),(41,'stylesheet','twentytwenty','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','48748','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:2:{i:2;a:4:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:12:\"hierarchical\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1618936275','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'initial_db_version','48748','yes'),(96,'wp55_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(97,'fresh_site','0','yes'),(98,'widget_search','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(99,'widget_recent-posts','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(100,'widget_recent-comments','a:2:{i:2;a:2:{s:5:\"title\";s:0:\"\";s:6:\"number\";i:5;}s:12:\"_multiwidget\";i:1;}','yes'),(101,'widget_archives','a:2:{i:2;a:3:{s:5:\"title\";s:0:\"\";s:5:\"count\";i:0;s:8:\"dropdown\";i:0;}s:12:\"_multiwidget\";i:1;}','yes'),(102,'widget_meta','a:2:{i:2;a:1:{s:5:\"title\";s:0:\"\";}s:12:\"_multiwidget\";i:1;}','yes'),(103,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:8:\"search-2\";i:1;s:14:\"recent-posts-2\";i:2;s:17:\"recent-comments-2\";}s:9:\"sidebar-2\";a:3:{i:0;s:10:\"archives-2\";i:1;s:12:\"categories-2\";i:2;s:6:\"meta-2\";}s:13:\"array_version\";i:3;}','yes'),(104,'cron','a:7:{i:1603384279;a:5:{s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1603384285;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1603384288;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1603384345;a:1:{s:28:\"wp_update_comment_type_batch\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1603384346;a:1:{s:8:\"do_pings\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:2:{s:8:\"schedule\";b:0;s:4:\"args\";a:0:{}}}}i:1603470679;a:1:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}}s:7:\"version\";i:2;}','yes'),(105,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(106,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'_transient_doing_cron','1603384688.0394918918609619140625','yes'),(115,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-5.5.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-5.5.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-5.5.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"5.5.1\";s:7:\"version\";s:5:\"5.5.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"5.3\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1603384286;s:15:\"version_checked\";s:5:\"5.5.1\";s:12:\"translations\";a:0:{}}','no'),(116,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384412;s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"4.1.6\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"4.1.6\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.4.1.6.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:59:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=969272\";s:2:\"1x\";s:59:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=969272\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}}s:9:\"hello.php\";O:8:\"stdClass\":9:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}}}}','no'),(117,'_site_transient_timeout_theme_roots','1603386087','no'),(118,'_site_transient_theme_roots','a:3:{s:14:\"twentynineteen\";s:7:\"/themes\";s:15:\"twentyseventeen\";s:7:\"/themes\";s:12:\"twentytwenty\";s:7:\"/themes\";}','no'),(119,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1603384287;s:7:\"checked\";a:3:{s:14:\"twentynineteen\";s:3:\"1.7\";s:15:\"twentyseventeen\";s:3:\"2.4\";s:12:\"twentytwenty\";s:3:\"1.5\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:14:\"twentynineteen\";a:6:{s:5:\"theme\";s:14:\"twentynineteen\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:44:\"https://wordpress.org/themes/twentynineteen/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/theme/twentynineteen.1.7.zip\";s:8:\"requires\";s:5:\"4.9.6\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:15:\"twentyseventeen\";a:6:{s:5:\"theme\";s:15:\"twentyseventeen\";s:11:\"new_version\";s:3:\"2.4\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentyseventeen/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentyseventeen.2.4.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}s:12:\"twentytwenty\";a:6:{s:5:\"theme\";s:12:\"twentytwenty\";s:11:\"new_version\";s:3:\"1.5\";s:3:\"url\";s:42:\"https://wordpress.org/themes/twentytwenty/\";s:7:\"package\";s:58:\"https://downloads.wordpress.org/theme/twentytwenty.1.5.zip\";s:8:\"requires\";s:3:\"4.7\";s:12:\"requires_php\";s:5:\"5.2.4\";}}s:12:\"translations\";a:0:{}}','no'),(120,'_site_transient_timeout_browser_6daa110c3e56e442b403473c9591e946','1603989088','no'),(121,'_site_transient_browser_6daa110c3e56e442b403473c9591e946','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:12:\"86.0.4240.80\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(122,'_site_transient_timeout_php_check_56babb1797dd31750a342dc4c8a11025','1603989088','no'),(123,'_site_transient_php_check_56babb1797dd31750a342dc4c8a11025','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(124,'_site_transient_timeout_community-events-e0e4f94be3c2d577e126ec3b012627f2','1603427490','no'),(125,'_site_transient_community-events-e0e4f94be3c2d577e126ec3b012627f2','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:12:\"192.168.16.0\";}s:6:\"events\";a:2:{i:0;a:10:{s:4:\"type\";s:6:\"meetup\";s:5:\"title\";s:58:\"Discussion Group: WordPress Troubleshooting Basics: Part 1\";s:3:\"url\";s:68:\"https://www.meetup.com/learn-wordpress-discussions/events/273993927/\";s:6:\"meetup\";s:27:\"Learn WordPress Discussions\";s:10:\"meetup_url\";s:51:\"https://www.meetup.com/learn-wordpress-discussions/\";s:4:\"date\";s:19:\"2020-10-23 06:00:00\";s:8:\"end_date\";s:19:\"2020-10-23 07:00:00\";s:20:\"start_unix_timestamp\";i:1603458000;s:18:\"end_unix_timestamp\";i:1603461600;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"US\";s:8:\"latitude\";d:37.779998779297;s:9:\"longitude\";d:-122.41999816895;}}i:1;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:17:\"WordCamp Bulgaria\";s:3:\"url\";s:35:\"https://bulgaria.wordcamp.org/2020/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2020-10-24 10:00:00\";s:8:\"end_date\";s:19:\"2020-10-24 10:00:00\";s:20:\"start_unix_timestamp\";i:1603522800;s:18:\"end_unix_timestamp\";i:1603522800;s:8:\"location\";a:4:{s:8:\"location\";s:6:\"Online\";s:7:\"country\";s:2:\"BG\";s:8:\"latitude\";d:42.733883;s:9:\"longitude\";d:25.48583;}}}}','no'),(126,'can_compress_scripts','0','no'),(127,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(128,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:49:\"\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:27:\"News – – WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 20:10:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=5.6-beta1-49274\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n\n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WordPress 5.6 Beta 1 is now available for testing!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8236:\"\n

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:363:\"This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September.  WordPress 5.5.1 Launch On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8713:\"\n

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"WordPress 5.5.1 Maintenance Release\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://wordpress.org/news/2020/09/wordpress-5-5-1-maintenance-release/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 19:13:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8979\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:460:\"WordPress 5.5.1 is now available! This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade. You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process. […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9020:\"\n

WordPress 5.5.1 is now available!

\n\n\n\n

This maintenance release features 34 bug fixes, 5 enhancements, and 5 bug fixes for the block editor. These bugs affect WordPress version 5.5, so you’ll want to upgrade.

\n\n\n\n

You can download WordPress 5.5.1 directly, or visit the Dashboard → Updates screen and click Update Now. If your sites support automatic background updates, they’ve already started the update process.

\n\n\n\n

WordPress 5.5.1 is a short-cycle maintenance release. The next major release will be version 5.6.

\n\n\n\n

To see a full list of changes, you can browse the list on Trac, read the 5.5.1 RC1 and 5.5.1 RC2 posts, or visit the 5.5.1 documentation page.

\n\n\n\n

Thanks and props!

\n\n\n\n

The 5.5.1 release was led by @audrasjb, @azhiyadev, @davidbaumwald, @desrosj, @johnbillion, @planningwrite, @sergeybiryukov and @whyisjake.

\n\n\n\n

Thank you to everyone who helped make WordPress 5.5.1 happen:

\n\n\n\nAmit Dudhat, Andrea Fercia, Andrey “Rarst” Savchenko, Andy Fragen, Angel Hess, avixansa, bobbingwide, Brian Hogg, chunkysteveo, Clayton Collie, David Baumwald, David Herrera, dd32, demetris, Dominik Schilling, dushakov, Earle Davies, Enrique Sánchez, Frankie Jarrett, fullofcaffeine, Garrett Hyder, Gary Jones, gchtr, Hauwa, Herre Groen, Howdy_McGee, Ipstenu (Mika Epstein), Jb Audras, Jeremy Felt, Jeroen Rotty, Joen A., Johanna de Vos, John Blackbourn, John James Jacoby, Jonathan Bossenger, Jonathan Desrosiers, Jonathan Stegall, Joost de Valk, Jorge Costa, Justin Ahinon, Kalpesh Akabari, Kevin Hagerty, Knut Sparhell, Kyle B. Johnson, landau, Laxman Prajapati, Lester Chan, mailnew2ster, Marius L. J., Mark Jaquith, Mark Uraine, Matt Gibson, Michael Beckwith, Mikey Arce, Mohammad Jangda, Mukesh Panchal, Nabil Moqbel, net, oakesjosh, O André, Omar Reiss, Ov3rfly, Paddy, Pascal Casier, Paul Biron, Peter Wilson, rajeshsingh520, Rami Yushuvaev, rebasaurus, riaanlom, Riad Benguella, Rodrigo Arias, rtagliento, salvoaranzulla, Sanjeev Aryal, sarahricker, Sergey Biryukov, Stephen Bernhardt, Steven Stern (sterndata), Thomas M, Timothy Jacobs, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), TwentyZeroTwo, Winstina, wittich, and Yoav Farhi.\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8979\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"The Month in WordPress: August 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2020/09/the-month-in-wordpress-august-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 01 Sep 2020 09:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8983\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:362:\"August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world. WordPress 5.5 Launch […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9605:\"\n

August was special for WordPress lovers, as one of the most anticipated releases, WordPress 5.5, was launched. The month also saw several updates from various contributor teams, including the soft-launch of the Learn WordPress project and updates to Gutenberg. Read on to find out about the latest updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Launch

\n\n\n\n

The team launched WordPress 5.5 on August 11. The major release comes with a host of features like automatic updates for plugins and themes, enabling updates over uploaded ZIP files, a block directory, XML sitemaps, block patterns, inline image editing, and lazy-loading images, to name a few. WordPress 5.5 is now available in 50 languages too! You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. Subsequent to the 5.5 release, the 5.5.1 release candidate came out on August 28, which will be followed by its official launch of the minor release on September 1.

\n\n\n\n

A record 805 people contributed to WordPress 5.5, hailing from 58 different countries. @audrasjb has compiled many more stats like that and they’re well worth a read!

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.7 and 8.8

\n\n\n\n

The core team launched Gutenberg 8.7 and 8.8. Version 8.7 saw many improvements to the Post Block suite, along with other changes like adding a block example to the Buttons block, consistently autosaving edits, and updating the group block description. Version 8.8 offers updates to Global Styles, the Post Block suite, and Template management. The release significantly improves the back-compatibility of the new Widget Screen, and also includes other important accessibility and mobile improvements to user interfaces like the Toolbar, navigation menus, and Popovers. For full details on the latest versions of these Gutenberg releases, visit these posts about 8.7 and 8.8.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Check out the brand new Learn WordPress platform!

\n\n\n\n

Learn WordPress is a brand new cross-team initiative led by the WordPress Community team, with support from the training team, the TV team, and the meta team. This platform is a learning repository on learn.wordpress.org, where WordPress learning content will be made available. Video workshops published on the site will be followed up by supplementary discussion groups based on workshop content. The first of these discussion groups have been scheduled, and you can join an upcoming discussion on the dedicated meetup group. The community team invites members to contribute to the project. You can apply to present a workshop, assist with reviewing submitted workshops, and add ideas for workshops that you would like to see on the site. You can also apply to be a discussion group leader to organize discussions directly through the learn.wordpress.org platform. We are also creating a dedicated Learn WordPress working group and have posted a call for volunteers. Meetup organizers can use Learn WordPress content for their meetup events (without applying as a discussion group leader). Simply ask your meetup group to watch one of the workshops in the weeks leading up to your scheduled event, and then host a discussion group for that content as your event.

\n\n\n\n

Want to get involved with the Community team? Follow the Community blog, or join them in the #community-events channel in the Making WordPress Slack group. To organize a local WordPress community event, visit the handbook page

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8983\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n\n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"WordPress 5.5 “Eckstine”\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://wordpress.org/news/2020/08/eckstine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 11 Aug 2020 19:03:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8799\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:354:\"Version 5.5 \"Eckstine\" of WordPress is available for download or update in your WordPress dashboard. With this release, your site gets new power in three major areas: \nspeed (lazy-loading images), search (sitemaps included by default), and security (auto-updates for plugins and themes), along with many new features and improvements to the block editor.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:3:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:48:\"https://s.w.org/images/core/5.5/auto-updates.mp4\";s:6:\"length\";s:6:\"238264\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:50:\"https://s.w.org/images/core/5.5/block-patterns.mp4\";s:6:\"length\";s:7:\"3518792\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:56:\"https://s.w.org/images/core/5.5/inline-image-editing.mp4\";s:6:\"length\";s:7:\"3145140\";s:4:\"type\";s:9:\"video/mp4\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Matt Mullenweg\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:71062:\"\n

Here it is! Named “Eckstine” in honor of Billy Eckstine, this latest and greatest version of WordPress is available for download or update in your dashboard.

\n\n\n\n
\"\"
\n\n\n\n
\n

Welcome to WordPress 5.5.

\n\n\n\n

In WordPress 5.5, your site gets new power in three major areas:
speed, search, and security.

\n
\n\n\n\n
\n
\n\n\n\n
\n

Speed

\n\n\n\n

Posts and pages feel faster, thanks to lazy-loaded images.

\n\n\n\n

Images give your story a lot of impact, but they can sometimes make your site seem slow.

\n\n\n\n

In WordPress 5.5, images wait to load until they’re just about to scroll into view. The technical term is ‘lazy loading.’

\n\n\n\n

On mobile, lazy loading can also keep browsers from loading files meant for other devices. That can save your readers money on data — and help preserve battery life.

\n\n\n\n

Search

\n\n\n\n

Say hello to your new sitemap.

\n\n\n\n

WordPress sites work well with search engines.

\n\n\n\n

Now, by default, WordPress 5.5 includes an XML sitemap that helps search engines discover your most important pages from the very minute you go live.

\n\n\n\n

So more people will find your site sooner, giving you more time to engage, retain and convert them to subscribers, customers or whatever fits your definition of success.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Security

\n\n\n\n
Now you can choose to update plugins and themes automatically–or pick just a few–from the screens you’ve always used.
\n\n\n\n

Auto-updates for Plugins and Themes

\n\n\n\n

Now you can set plugins and themes to update automatically — or not! — in the WordPress admin. So you always know your site is running the latest code available.

\n\n\n\n

You can also turn auto-updates on or off for each plugin or theme you have installed — all on the same screens you’ve always used.

\n\n\n\n

Update by uploading ZIP files

\n\n\n\n

If updating plugins and themes manually is your thing, now that’s easier too — just upload a ZIP file.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Highlights from the block editor

\n\n\n\n

Once again, the latest WordPress release packs a long list of exciting new features for the block editor. For example:

\n\n\n\n
\n\n\n\n
\n
\n

Block patterns

\n\n\n\n

New block patterns make it simple and fun to create complex, beautiful layouts, using combinations of text and media that you can mix and match to fit your story.

\n\n\n\n

You will also find block patterns in a wide variety of plugins and themes, with more added all the time. Pick any of them from a single place — just click and go!

\n
\n\n\n\n
\n

The new block directory

\n\n\n\n

Now it’s easier than ever to find the block you need. The new block directory is built right into the block editor, so you can install new block types to your site without ever leaving the editor.

\n\n\n\n

Inline image editing

\n\n\n\n

Crop, rotate, and zoom your photos right from the image block. If you spend a lot of time on images, this could save you hours!

\n
\n
\n\n\n\n
\n\n\n\n

And so much more.

\n\n\n\n

The highlights above are a tiny fraction of the new block editor features you’ve just installed. Open the block editor and enjoy!

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

Accessibility

\n\n\n\n

Every release adds improvements to the accessible publishing experience, and that remains true for WordPress 5.5.

\n\n\n\n

Now you can copy links in media screens and modal dialogs with a button, instead of trying to highlight a line of text.

\n\n\n\n

You can also move meta boxes with the keyboard, and edit images in WordPress with your assistive device, as it can read you the instructions in the image editor.

\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

For developers

\n\n\n\n

5.5 also brings a big box of changes just for developers.

\n\n\n\n
\n
\n

Server-side registered blocks in the REST API

\n\n\n\n

The addition of block types endpoints means that JavaScript apps (like the block editor) can retrieve definitions for any blocks registered on the server.

\n\n\n\n

Defining environments

\n\n\n\n

WordPress now has a standardized way to define a site’s environment type (staging, production, etc). Retrieve that type with wp_get_environment_type() and execute only the appropriate code.

\n\n\n\n

Dashicons

\n\n\n\n

The Dashicons library has received its final update in 5.5. It adds 39 block editor icons along with 26 others.

\n\n\n\n

Passing data to template files

\n\n\n\n

The template loading functions (get_header()get_template_part(), etc.) have a new $args argument. So now you can pass an entire array’s worth of data to those templates.

\n
\n\n\n\n
\n

More changes for developers

\n\n\n\n
  • The PHPMailer library just got a major update, going from version 5.2.27 to 6.1.6.
  • Now get more fine-grained control of redirect_guess_404_permalink().
  • Sites that use PHP’s OPcache will see more reliable cache invalidation, thanks to the new wp_opcache_invalidate() function during updates (including to plugins and themes).
  • Custom post types associated with the category taxonomy can now opt-in to supporting the default term.
  • Default terms can now be specified for custom taxonomies in register_taxonomy().
  • The REST API now officially supports specifying default metadata values through register_meta().
  • You will find updated versions of these bundled libraries: SimplePie, Twemoji, Masonry, imagesLoaded, getID3, Moment.js, and clipboard.js.
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n
\n
\n\n\n\n
\n

The Squad

\n\n\n\n

Leading this release were Matt MullenwegJake Spurlock, and David Baumwald. Supporting them was this highly enthusiastic release squad:

\n\n\n\n\n\n\n\n

Joining the squad throughout the release cycle were 805 generous volunteer contributors who collectively worked on over 523 tickets on Trac and over 1660 pull requests on GitHub.

\n\n\n\n

Put on a Billy Eckstine playlist, click that update button (or download it directly), and check the profiles of the fine folks that helped:

\n\n\nA2 Hosting, a4jp . com, a6software, Aaron D. Campbell, Aaron Jorbin, abderrahman, Abha Thakor, Achal Jain, achbed, Achyuth Ajoy, acosmin, acsnaterse, Adam Silverstein, Addie, addyosmani, adnan.limdi, adrian, airamerica, Ajay Ghaghretiya, Ajit Bohra, akbarhusen, akbarhusen429, Akhilesh Sabharwal, Akira Tachibana, Alain Schlesser, Albert Juhé Lluveras, Alex Concha, Alex Kirk, Alex Lende, Alex Shiels, Ali Shan, ali11007, Allen Snook, amaschas, Amit Dudhat, anbumz, andfinally, Andrea Fercia, Andrea Middleton, Andrea Tarantini, Andrei Draganescu, Andrew Duthie, Andrew Nacin, Andrew Nevins, Andrew Ozz, Andrey \"Rarst\" Savchenko, Andrés Maneiro, Andy Fragen, Andy Meerwaldt, Andy Peatling, Angel Hess, Angela Jin, Angelika Reisiger, Anh Tran, Ankit Gade, Ankit K Gupta, Ankit Panchal, Anne McCarthy, Anthony Burchell, Anthony Hortin, Anton Timmermans, Antonis Lilis, apedog, archon810, argentite, Arpit G Shah, Arslan Ahmed, asalce, ashiagr, ashour, Atharva Dhekne, Aurélien Joahny, aussi, automaton, avixansa, Ayesh Karunaratne, BackuPs, Barry, Barry Ceelen, Bart Czyz, bartekcholewa, bartkalisz, Bastien Ho, Bastien Martinent, bcworkz, bdbch, bdcstr, Ben Dunkle, Bence Szalai, bencroskery, Benjamin Gosset, Benoit Chantre, Bernhard Reiter, BettyJJ, bgermann, bigcloudmedia, bigdawggi, Bill Erickson, Birgir Erlendsson (birgire), Birgit Pauli-Haack, BjornW, bobbingwide, bonger, Boone Gorges, Boris Brdarić, Boy Witthaya, Brandon Kraft, Brandon Payton, Brent Swisher, Brian Hogg, Brian Krogsgard, bruandet, Bunty, Burhan Nasir, caiocrcosta, Cameron Voell, cameronamcintyre, Carike, Carl Wuensche, Carlos Galarza, Carolina Nymark, Caroline Moore, Carrigan, ceyhun, Chad, Chad Butler, Charles Fulton, Chetan Prajapati, Chintan hingrajiya, Chip Snyder, Chloé Bringmann, Chouby, Chris Van Patten, chriscct7, Christian Chung, Christian Jongeneel, Christian Sabo, Christian Wach, Christoph Herr, Christopher Churchill, chunkysteveo, cklee, clayray, Clayton Collie, Clifford Paulick, codeforest, Commeuneimage, Copons, Corey McKrill, cpasqualini, Cristovao Verstraeten, Csaba (LittleBigThings), Curtis Belt, Cyrus Collier, D.PERONNE, d6, Daniel Bachhuber, Daniel Hüsken, Daniel James, Daniel Llewellyn, Daniel Richards, Daniel Roch, Daniele Scasciafratte, Danny, Darko G., Darren Ethier (nerrad), Dave McHale, Dave Whitley, David A. Kennedy, David Aguilera, David Anderson, David Artiss, David Baumwald, David Brumbaugh, David E. Smith, David Herrera, David Ryan, David Shanske, David Smith, david.binda, davidvee, dchymko, Debabrata Karfa, Deepak Lalwani, dekervit, Delowar Hossain, demetris, Denis Yanchevskiy, derekakelly, Derrick Hammer, Derrick Tennant, Diane Co, Dilip Bheda, Dimitris Mitsis, dingo-d, Dion Hulse, Dixita Dusara, djennez, dmenard, dmethvin, doc987, Dominik Schilling, donmhico, Dono12, Doobeedoo, Dossy Shiobara, dpacks, dratwas, Drew Jaynes, DrLightman, DrProtocols, dsifford, dudo, dushakov, Dustin Bolton, dvershinin, Dylan Kuhn, Earle Davies, ecotechie, Eddie Moya, Eddy, Edi Amin, ehtis, Eileen Violini, Ekaterina, Ella van Durpe, elmastudio, Emanuel Blagonic, Emilie LEBRUN, Emmanuel Hesry, Enej Bajgoric, Enrico Sorcinelli, Enrique Piqueras, Enrique Sánchez, Eric, Eric Andrew Lewis, Eric Binnion, Erik Betshammar, Erin \'Folletto\' Casali, esemlabel, esoj, espiat, Estela Rueda, etoledom, etruel, Ev3rywh3re, Evan Mullins, Fabian Kägy, Fabian Todt, Faisal Ahmed, Felix Arntz, Felix Edelmann, ferdiesletering, finomeno, Florian Brinkmann, Florian TIAR, Florian Truchot, florianatwhodunit, FolioVision, Francesca Marano, Francois Thibaud, Frank Goossens, Frank Klein, Frank.Prendergast, Frankie Jarrett, Franz Armas, fullofcaffeine, Gabriel Koen, Gabriel Maldonado, Gabriel Mays, gadgetroid, Gal Baras, Garavani, garethgillman, Garrett Hyder, Gary Cao, Gary Jones, Gary Pendergast, gchtr, Geert De Deckere, Gemini Labs, Gennady Kovshenin, geriux, Giorgio25b, gisselfeldt, glendaviesnz, goldsounds, Goto Hayato, Govind Kumar, Grégory Viguier, gradina, Greg Ziółkowski, gregmulhauser, grierson, Grzegorz.Janoszka, gsmumbo, Guido Scialfa, guidobras, Gunther Pilz, gwwar, H-var, hakre, Halacious, hankthetank, Hapiuc Robert, Hareesh, haukep, Hauwa, Haz, Hector Farahani, Helen Hou-Sandi, Henry Wright, Herre Groen, hlanggo, hommealone, Hoover, Howdy_McGee, Hronak Nahar, huntlyc, Ian Belanger, Ian Dunn, Ian Stewart, ianjvr, ifrins, infinum, Ipstenu (Mika Epstein), Isabel Brison, ishitaka, J.D. Grimes, jackfungi, jacklinkers, Jadon N, jadpm, jagirbahesh, Jake Spurlock, Jake Whiteley, James Koster, James Nylen, Jan Koch, Jan Reilink, Jan Thiel, Janvo Aldred, Jarret, Jason Adams, Jason Coleman, Jason Cosper, Jason Crouse, Jason LeMahieu (MadtownLems), Jason Rouet, JasWSInc, Javier Casares, Jayson Basanes, jbinda, jbouganim, Jean-Baptiste Audras, Jean-David Daviet, Jeff Chandler, Jeff Farthing, Jeff Ong, Jeff Paul, Jen, Jenil Kanani, Jeremy Felt, Jeremy Herve, Jeremy Yip, Jeroen Rotty, jeryj, Jesin A, Jignesh Nakrani, Jim_Panse, Jip Moors, jivanpal, Joe Dolson, Joe Hoyle, Joe McGill, Joen Asmussen, Johanna de Vos, John Blackbourn, John Dorner, John James Jacoby, John P. Green, John Richards II, John Watkins, johnnyb, Jon Quach, Jon Surrell, Jonathan Bossenger, Jonathan Champ, Jonathan Christopher, Jonathan Desrosiers, Jonathan Stegall, jonkolbert, Jonny Harris, jonnybot, Jono Alderson, Joost de Valk, Jorge Bernal, Jorge Costa, Joseph Dickson, Josepha Haden, Josh Smith, JoshuaWold, Joy, Juanfra Aldasoro, juanlopez4691, Jules Colle, julianm, Juliette Reinders Folmer, Julio Potier, Julka Grodel, Justin Ahinon, Justin de Vesine, Justin Tadlock, justlevine, justnorris, K. Adam White, kaggdesign, Kailey (trepmal), Kaira, Kaitlin Bolling, Kalpesh Akabari, KamataRyo, Kantari Samy, Kaspars, Kavya Gokul, keesiemeijer, Kelly Dwan, kennethroberson5556, Kevin Hagerty, Kharis Sulistiyono, Khokan Sardar, kinjaldalwadi, Kiril Zhelyazkov, Kirsty Burgoine, Kishan Jasani, kitchin, Kite, Kjell Reigstad, Knut Sparhell, Konstantin Obenland, Konstantinos Xenos, ksoares, KT Cheung, Kukhyeon Heo, Kyle B. Johnson, lalitpendhare, landau, Laterna Studio, laurelfulford, Laurens Offereins, Laxman Prajapati, Lester Chan, Levdbas, Lew Ayotte, Lex Robinson, linyows, lipathor, Lisa Schuyler, liuhaibin, ljharb, logig, lucasbustamante, luiswill, Luke Cavanagh, Luke Walczak, lukestramasonder, M Asif Rahman, M.K. Safi, Maarten de Boer, Mahfoudh Arous, mailnew2ster, manojlovic, Manuel Schmalstieg, maraki, Marcin Pietrzak, Marcio Zebedeu, Marco Pereirinha, MarcoZ, Marcus, Marcus Kazmierczak, Marek Dědič, Marek Hrabe, Mario Valney, Marius Jensen, Mark Chouinard, Mark Jaquith, Mark Parnell, Mark Uraine, markdubois, markgoho, Marko Andrijasevic, Marko Heijnen, MarkRH, markshep, markusthiel, Martijn van der Kooij, martychc23, Mary Baum, Matheus Martins, Mathieu Viet, Matias Ventura, matjack1, Matt Cromwell, Matt Gibson, Matt Mullenweg, Matt Radford, Matt van Andel, mattchowning, Matthew Boynes, Matthew Eppelsheimer, Matthew Gerring, Matthias Kittsteiner, Matthias Pfefferle, Matthieu Mota, mattyrob, Maxime Culea, Maxime Pertici, maxme, Mayank Majeji, mcshane, Mel Choyce-Dwan, Menaka S., mensmaximus, metalandcoffee, Michael, Michael Arestad, Michael Arestad, Michael Beckwith, Michael Fields, Michael Nelson, Michele Butcher-Jones, Michelle, Miguel Fonseca, mihdan, Miina Sikk, Mikael Korpela, mikaumoto, Mike Crantea, Mike Glendinning, Mike Haydon, Mike Schinkel [WPLib Box project lead], Mike Schroder, Mikey Arce, Milana Cap, Milind More, mimi, mislavjuric, Mohammad Jangda, Mohammad Rockeybul Alam, Mohsin Rasool, Monika Rao, Morgan Kay, Morten Rand-Hendriksen, Morteza Geransayeh, moto hachi ( mt8.biz ), mrgrt, mrmist, mrTall, msaggiorato, Muhammad Usama Masood, Mukesh Panchal, munyagu, Nabil Moqbel, Nadir Seghir, Nahid Ferdous Mohit, Nalini Thakor, Naoko Takano, narwen, Nate Gay, Nathan Rice, Navid, neonkowy, net, netpassprodsr, Nextendweb, Ngan Tengyuen, Nick Daugherty, Nicky Lim, nicolad, Nicolas Juen, NicolasKulka, Nidhi Jain, Niels de Blaauw, Niels Lange, nigro.simone, Nik Tsekouras, Nikhil Bhansi, Nikolay Bachiyski, Nilo Velez, Niresh, nmenescardi, Noah Allen, NumidWasNotAvailable, oakesjosh, obliviousharmony, ockham, Olga Gleckler, Omar Alshaker, Omar Reiss, onokazu, Optimizing Matters, Ov3rfly, ovann86, overclokk, p_enrique, Paal Joachim Romdahl, Pablo Honey, Paddy, palmiak, Paresh Shinde, Parvand, Pascal Birchler, Pascal Casier, Paul Bearne, Paul Biron, Paul Fernhout, Paul Gibbs, Paul Ryan, Paul Schreiber, Paul Stonier, Paul Von Schrottky, pavelevap, Pedro Mendonça, pentatonicfunk, pepe, Peter \"Pessoft\" Kolínek, Peter Westwood, Peter Wilson, Phil Derksen, Phil Johnston, Philip Jackson, Pierre Gordon, pigdog234, pikamander2, pingram, Pionect, Piyush Patel, pkarjala, pkvillanueva, Prashant Baldha, pratik028, Pravin Parmar, Presskopp, Presslabs, Priyank Patel, Priyo Mukul, ProGrafika, programmin, Puneet Sahalot, pvogel2, r-a-y, Raaj Trambadia, Rachel Peter, raine, rajeshsingh520, Ramanan, Rami Yushuvaev, RavanH, Ravat Parmar, ravenswd, rawrly, rebasaurus, Red Sand Media Group, Remy Perona, Remzi Cavdar, Renatho, renggo888, retlehs, retrofox, riaanlom, Riad Benguella, Rian Rietveld, riasat, Rich Tabor, Ringisha, ritterml, Rnaby, Rob Cutmore, Rob Migchels, rob006, Robert Anderson, Robert Chapin, Robert Peake, Robert Windisch, Rodrigo Arias, Ronald Huereca, Rostislav Wolný, Roy Tanck, rtagliento, ruxandra, Ryan Boren, Ryan Fredlund, Ryan Kienstra, Ryan McCue, Ryan Welcher, Ryota Sakamoto, ryotsun, Sören Wrede, Søren Brønsted, Sachit Tandukar, Sagar Jadhav, Sajjad Hossain Sagor, Sal Ferrarello, Salvatore Formisano, salvoaranzulla, Sam Fullalove, Sam Webster, Samir Shah, Samuel Wood (Otto), samueljseay, Sander van Dragt, Sanjeev Aryal, Sanket Mehta, sarahricker, Sathiyamoorthy V, Sayed Taqui, scarolan, scholdstrom, Scott Kingsley Clark, Scott Reilly, Scott Smith, Scott Taylor, scribu, scruffian, Sean Hayes, seanpaulrasmussen, seayou, senatorman, Sergey Biryukov, Sergey Predvoditelev, Sergio de Falco, sergiomdgomes, Shannon Smith, Shantanu Desai, shaunandrews, Shawn Hooper, shawnz, Shital Marakana, shulard, siliconforks, Simon Wheatley, simonjanin, sinatrateam, sjmur, skarabeq, skorasaurus, skoskie, slushman, snapfractalpop, SpearsMarketing, sphakka, squarecandy, sreedoap, Stanimir Stoyanov, Stefano Minoia, Stefanos Togoulidis, Steph Wells, Stephen Bernhardt, Stephen Cronin, Stephen Edgar, Steve Dufresne, stevegibson12, Steven Stern (sterndata), Steven Word, stevenkussmaul, stevenlinx, Stiofan, Subrata Sarkar, SUM1, Sunny, Sunny Ratilal, Sushyant Zavarzadeh, suzylah, Sybre Waaijer, Synchro, Sérgio Estêvão, Takayuki Miyauchi, Tammie Lister, Tang Rufus, TeBenachi, Tessa Watkins LLC, Tetsuaki Hamano, theMikeD, theolg, Thierry Muller, Thimal Wickremage, Thomas M, Thorsten Frommen, Thrijith Thankachan, Tiago Hillebrandt, Till Krüss, Timothy Jacobs, Tkama, tmdesigned, tmoore41, TobiasBg, tobifjellner (Tor-Bjorn Fjellner), Tofandel, tomdude, Tommy Ferry, Tony G, Toro_Unit (Hiroshi Urabe), torres126, Torsten Landsiedel, Toru Miki, Travis Northcutt, treecutter, truongwp, tsimmons, Tung Du, Udit Desai, Ulrich, Vagios Vlachos, valchovski, Valentin Bora, Vayu Robins, veromary, Viktor Szépe, vinkla, virginienacci, Vladimir, Vladislav Abrashev, vortfu, voyager131, vtieu, webaware, Weston Ruter, William Earnhardt, williampatton, Winstina, wittich, wpdesk, WPDO, WPMarmite, wppinar, Yahil Madakiya, yashrs, yoancutillas, Yoav Farhi, yohannp, yuhin, Yui, Yuri Salame, Yvette Sonneveld, Zack Tollman, zaheerahmad, zakkath, Zebulan Stanphill, zieladam, and Česlav Przywara.\n\n\n\n

 

\n\n\n\n

Many thanks to all of the community volunteers who contribute in the support forums. They answer questions from people across the world, whether they are using WordPress for the first time or since the first release. These releases are more successful for their efforts!

\n\n\n\n

Finally, thanks to all the community translators who worked on WordPress 5.5. Their efforts bring WordPress fully translated to 46 languages at release time, with more on the way.

\n\n\n\n

If you want to learn more about volunteering with WordPress, check out Make WordPress or the core development blog.

\n
\n\n\n\n
\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8799\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"WordPress 5.5 Release Candidate 2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2020/08/wordpress-5-5-release-candidate-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 04 Aug 2020 19:12:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8764\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:420:\"The second release candidate for WordPress 5.5 is here! WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time! You can test the WordPress 5.5 release candidate in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding edge nightlies” option) Or download the release […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2503:\"\n

The second release candidate for WordPress 5.5 is here!

\n\n\n\n

WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

For a more detailed breakdown of the changes included in WordPress 5.5, check out the WordPress 5.5 beta 1 post. The WordPress 5.5 Field Guide is also out! It’s your source for details on all the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8764\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n\n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:33:\"The Month in WordPress: July 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"https://wordpress.org/news/2020/08/the-month-in-wordpress-july-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 03 Aug 2020 13:54:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8755\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:340:\"July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11539:\"\n

July was an action-packed month for the WordPress project. The month saw a lot of updates on one of the most anticipated releases – WordPress 5.5! WordCamp US 2020 was canceled and the WordPress community team started experimenting with different formats for engaging online events, in July. Read on to catch up with all the updates from the WordPress world.

\n\n\n\n
\n\n\n\n

WordPress 5.5 Updates

\n\n\n\n

July was full of WordPress 5.5 updates! The WordPress 5.5 Beta 1 came out on July 7, followed by Beta 2 on July 14, Beta 3 on July 21, and Beta 4 on July 27. Subsequently, the team also published the first release candidate of WordPress 5.5 on July 28. 

\n\n\n\n

WordPress 5.5, which is slated for release on August 11, 2020, is a major update with features like automatic updates for plugins and themes, a block directory, XML sitemaps, block patterns, and lazy-loading images, among others. To learn more about the release, check out its field guide post.

\n\n\n\n

Want to get involved in building WordPress Core? Follow the Core team blog, and join the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 8.5 and 8.6

\n\n\n\n

The core team launched Gutenberg 8.5 and 8.6. Version 8.5 – the last plugin release will be included entirely (without experimental features) in WordPress 5.5, introduced improvements to block drag-and-drop and accessibility, easier updates for external images, and support for the block directory. Version 8.6 comes with features like Cover block video position controls and block pattern updates. For full details on the latest versions on these Gutenberg releases, visit these posts about 8.5 and 8.6.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Reimagining Online WordPress Events

\n\n\n\n

The Community team made the difficult decision to suspend in-person WordPress events for the rest of 2020 in light of the COVID-19 pandemic. The team has also started working on reimagining online events. Based on feedback from the community members, the team decided to make changes to the current online WordCamp format. Key changes include wrapping up financial support for A/V vendors, ending event swag support for newer online WordCamps, and suspending the Global Community Sponsorship program for 2020. The team encourages upcoming online WordCamps to experiment with their events to facilitate an effective learning experience for attendees while avoiding online event fatigue. The team is currently working on a proposal to organize community-supported recorded workshops and synchronous discussion groups to help community members learn WordPress.

Want to get involved with the Community team? Follow the Community blog here, or join them in the #community-events channel in the Making WordPress Slack group. To organize a Meetup or WordCamp, visit the handbook page

\n\n\n\n

WordCamp US 2020 is canceled

\n\n\n\n

The organizers of WordCamp US 2020 have canceled the event in light of the continued pandemic and online event fatigue. The flagship event, which was originally scheduled for October 27-29 as an in-person event, had already planned to transition to an online event. Several WCUS Organizers will be working with the WordPress Community team to focus on other formats and ideas for online events, including a 24-hour contributor day, and contributing to the workshops initiative currently being discussed. Matt Mullenweg’s State of the Word (which typically accompanies WordCamp US) is likely to take place in a different format later in 2020.

\n\n\n\n

Plugin and theme updates are now available over zip files

\n\n\n\n

After eleven years, WordPress now allows users to update plugins and themes by uploading a ZIP file, in WordPress 5.5.  The feature, which was merged on July 7, has been one of the most requested features in WordPress. Now, when a user tries to upload a plugin or theme zip file from the WordPress dashboard by clicking the “Install Now” button, WordPress will direct users to a new screen that compares the currently-installed extension with the uploaded versions. Users can then choose between continuing with the installation or canceling. WordPress 5.5 will also offer automatic plugin and theme updates

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n
  • The Block directory is coming to WordPress with the 5.5 release. Plugin authors can now submit their Block plugins to the directory.
  • The Core team has opened up the call for features in the WordPress 5.6 release. You can comment on the post with features that you’d like to be included, current UX pain points, or maintenance tickets that need to be addressed. August 20 is the deadline for feature requests. 
  • Editor features such as the new Navigation block, the navigation screen, and the widget screen that were originally planned to be merged with WordPress 5.5 have been pushed for the next release
  • The Theme team is inviting proposals on whether to allow themes to place an additional top-level menu link in the admin.
  • BuddyPress 6.2 beta is out in the wild, and the team will soon release the stable version. The update includes changes that will make BuddyPress fully compatible with WordPress 5.5.
  • WordCamp EU 2021, which was being planned as an in-person event in Porto, Portugal, is moving online. The team is considering an in-person WordCamp EU in 2022. 
  • The Polyglots team has prepared and finalized a Translation Editor & Locale Manager Vetting Criteria to provide more clarity on how global mentors assign PTE/GTE/Locale Managers and to help locale teams set their own guidelines. The document, which was finalized after a lot of discussion, is now available in the Polyglots handbook.
  • Members of the Community team are discussing whether WordCamp volunteers, WordCamp attendees, or Meetup attendees should be awarded a WordPress.org profile badge. The ongoing discussion will be open for comments until August 13.
  • The WP Notify project, which aims to create a better way to manage and deliver notifications to the relevant audience, is on to its next steps. The team has finalized the initial requirements, and is kicking off the project build.
  • The WordPress documentation team is considering a ban on links to commercial websites in a revision to its external linking policy. The policy change does not remove external links to commercial sites from WordPress.org and only applies to documentation sites. The idea is to protect documentation from being abused, and to prevent the WordPress project from being biased. Discussion on this post is still ongoing, and a decision has not yet been made. Feel free to comment on the discussion posts, if you would like to share your thoughts on the topic.
\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8755\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"WordPress 5.5 Release Candidate\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://wordpress.org/news/2020/07/wordpress-5-5-release-candidate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 28 Jul 2020 19:08:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8732\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:370:\"The first release candidate for WordPress 5.5 is now available! This is an important milestone in the community’s progress toward the final release of WordPress 5.5. “Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"Jb Audras\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:2970:\"\n

The first release candidate for WordPress 5.5 is now available!

\n\n\n\n

This is an important milestone in the community’s progress toward the final release of WordPress 5.5.

\n\n\n\n

“Release Candidate” means that the new version is ready for release, but with millions of users and thousands of plugins and themes, it’s possible something was missed. WordPress 5.5 is slated for release on August 11, 2020, but we need your help to get there—if you haven’t tried 5.5 yet, now is the time!

\n\n\n\n

You can test the WordPress 5.5 release candidate in two ways:

\n\n\n\n\n\n\n\n

Thank you to all of the contributors who tested the Beta releases and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

What’s in WordPress 5.5?

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developer notes tag for updates on those and other changes that could affect your products.

\n\n\n\n

Plugin and Theme Developers

\n\n\n\n

Please test your plugins and themes against WordPress 5.5 and update the Tested up to version in the readme file to 5.5. If you find compatibility problems, please be sure to post to the support forums, so those can be figured out before the final release.

\n\n\n\n

The WordPress 5.5 Field Guide, due very shortly, will give you a more detailed dive into the major changes.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help us translate WordPress into more than 100 languages! This release also marks the hard string freeze point of the 5.5 release schedule.

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, fill one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8732\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 4\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-4/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 27 Jul 2020 20:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8719\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:313:\"WordPress 5.5 Beta 4 is now available! This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 4 in two ways: Try the WordPress Beta Tester plugin (choose the […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"David Baumwald\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3812:\"\n

WordPress 5.5 Beta 4 is now available!

\n\n\n\n

This software is still in development, so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 4 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 3 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 3, 43 bugs have been fixed. Here are a few changes in beta 4:

\n\n\n\n
  • Add \"loading\" as an allowed kses image attribute (see #50731).
  • Add filter for the plugin/theme auto-update message in the Info tab of Site health (see #50663).
  • $_SERVER[\'SERVER_NAME\'] not a reliable when generating email host names (see #25239)
  • Several backported fixes from Gutenberg are included in WordPress 5.5 Beta 4 (See PR #24218)
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8719\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:20:\"WordPress 5.5 Beta 3\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/07/wordpress-5-5-beta-3/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 21 Jul 2020 17:51:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:11:\"Development\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Releases\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:3:\"5.5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=8706\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:324:\"WordPress 5.5 Beta 3 is now available! This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version. You can test WordPress 5.5 Beta 3 in two ways: Try the WordPress Beta Tester plugin (choose the “bleeding […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Jake Spurlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:3876:\"\n

WordPress 5.5 Beta 3 is now available!

\n\n\n\n

This software is still in development,so it’s not recommended to run this version on a production site. Consider setting up a test site to play with the new version.

\n\n\n\n

You can test WordPress 5.5 Beta 3 in two ways:

\n\n\n\n\n\n\n\n

WordPress 5.5 is slated for release on August 11th, 2020, and we need your help to get there!

\n\n\n\n

Thank you to all of the contributors who tested the beta 2 development release and gave feedback. Testing for bugs is a critical part of polishing every release and a great way to contribute to WordPress.

\n\n\n\n

Some highlights

\n\n\n\n

Since beta 2, 43 bugs have been fixed. Here are a few changes in beta 3:

\n\n\n\n
  • Plugin and theme versions are now shared in the emails when automatically updated (see #50350).
  • REST API routes without a permission_callback now trigger a _doing_it_wrong() warning (see #50075).
  • Over 23 Gutenberg changes and updates (see #24068 and #50712).
  • A bug with the new import and export database Dashicons has been fixed (see #49913).
\n\n\n\n

Developer notes

\n\n\n\n

WordPress 5.5 has lots of refinements to polish the developer experience. To keep up, subscribe to the Make WordPress Core blog and pay special attention to the developers’ notes for updates on those and other changes that could affect your products.

\n\n\n\n

How to Help

\n\n\n\n

Do you speak a language other than English? Help translate WordPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post to the Alpha/Beta area in the support forums. We’d love to hear from you!

\n\n\n\n

If you’re comfortable writing a reproducible bug report, file one on WordPress Trac, where you can also find a list of known bugs.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"8706\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1 \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:9:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:30 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Wed, 21 Oct 2020 20:10:31 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(129,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603427491','no'),(130,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1603384291','no'),(131,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(132,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Loginizer Plugin Gets Forced Security Update for Vulnerabilities Affecting 1 Million Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106557\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users?utm_source=rss&utm_medium=rss&utm_campaign=loginizer-plugin-gets-forced-security-update-for-vulnerabilities-affecting-1-million-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5484:\"

WordPress.org has pushed out a forced security update for the Loginizer plugin, which is active on more than 1 million websites. The plugin offers brute force protection in its free version, along with other security features like two-factor auth, reCAPTCHA, and PasswordLess login in its commercial upgrade.

\n\n\n\n

Last week security researcher Slavco Mihajloski discovered an unauthenticated SQL injection vulnerability, and an XSS vulnerability, that he disclosed to the plugin’s authors. Loginizer version 1.6.4 was released on October 16, 2020, with patches for the two issues, summarized on the plugin’s blog:

\n\n\n\n

1) [Security Fix] : A properly crafted username used to login could lead to SQL injection. This has been fixed by using the prepare function in PHP which prepares the SQL query for safe execution.

2) [Security Fix] : If the IP HTTP header was modified to have a null byte it could lead to stored XSS. This has been fixed by properly sanitizing the IP HTTP header before using the same.

\n\n\n\n

Loginizer did not disclose the vulnerability until today in order to give users the time to upgrade. Given the severity of the vulnerability, the plugins team at WordPress.org pushed out the security update to all sites running Loginizer on WordPress 3.7+.

\n\n\n\n

In July, 2020, Loginizer was acquired by Softaculous, so the company was also able to automatically upgrade any WordPress installations with Loginizer that had been created using Softaculous. This effort, combined with the updates from WordPress.org, covered a large portion of Loginizer’s user base.

\n\n\n\n
\n

Any #WordPress install with @loginizer probably isn\'t using another WAF solution. As you can notice from the graph 600k+500k active installs were updated upside down, so … Preauth SQLi in it, reported by me. Update! Crunching write up :) https://t.co/gkEVWun9wt pic.twitter.com/XWXVMYO1ED

— mslavco (@mslavco) October 19, 2020
\n
\n\n\n\n

The automatic update took some of the plugin’s users by surprise, since they had not initiated it themselves and had not activated automatic updates for plugins. After several users posted on the plugin’s support forum, plugin team member Samuel Wood said that “WordPress.org has the ability to turn on auto-updates for security issues in plugins” and has used this capability many times.

\n\n\n\n

Mihajloski published a more detailed proof-of-concept on his blog earlier today. He also highlighted some concerns regarding the systems WordPress has in place that allowed this kind of of severe vulnerability to slip through the cracks. He claims the issue could have easily been prevented by the plugin review team since the plugin wasn’t using the prepare function for safe execution of SQL queries. Mihajloski also recommended recurring code audits for plugins in the official directory.

\n\n\n\n

“When a plugin gets into the repository, it must be reviewed, but when is it reviewed again?” Mihajloski said. “Everyone starts with 0 active installs, but what happens on 1k, 10k, 50k, 100k, 500k, 1mil+ active installs?”

\n\n\n\n

Mihajloski was at puzzled how such a glaring security issue could remain in the plugin’s code so long, given that it is a security plugin with an active install count that is more than many well known CMS’s. The plugin also recently changed hands when it was acquired by Softaculus and had been audited by multiple security organizations, including WPSec and Dewhurst Security.

\n\n\n\n

Mihajloski also recommends that WordPress improve the transparency around security, as some site owners and closed communities may not be comfortable with having their assets administered by unknown people at WordPress.org.

\n\n\n\n

“WordPress.org in general is transparent, but there isn’t any statement or document about who, how and when decides about and performs automatic updates,” Mihajloski said. “It is kind of [like] holding all your eggs in one basket.

\n\n\n\n

“I think those are the crucial points that WP.org should focus on and everything will came into place in a short time: complete WordPress tech documentation for security warnings, a guide for disclosure of the bugs (from researchers, but also from a vendor aspect), and recurring code audits for popular plugins.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Oct 2020 03:47:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"Post Status: Joe Casabona on creating quality content and courses\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=80099\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://poststatus.com/joe-casabona-on-creating-quality-content-and-courses/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1407:\"

David Bisset interviews Joe Casabona, an independent creator and teacher, and discusses what it\'s like to be a creator as his job, plus some news topics.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Sandhills Development

\n\n\n\n

Sandhills Development crafts ingenuity, developed with care:

\n\n\n\n
  • Easy Digital Downloads – Sell digital products with WordPress
  • AffiliateWP – A full-featured affiliate marketing solution
  • Sugar Calendar – WordPress event management made simple
  • WP Simple Pay – A lightweight Stripe payments plugin
\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:17:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: MakeStories 2.0 Launches Editor for WordPress, Rivaling Google’s Official Web Stories Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106327\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin?utm_source=rss&utm_medium=rss&utm_campaign=makestories-2-0-launches-editor-for-wordpress-rivaling-googles-official-web-stories-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8860:\"Recipe slide from the MakeStories WordPress plugin.\n\n\n\n

Earlier today, MakeStories launched version 2.0 of its plugin for creating Web Stories with WordPress. In many ways, this is a new plugin launch. The previous version simply allowed users to connect their WordPress installs to the MakeStories site. With the new version, users can build and edit their stories directly from the WordPress admin.

\n\n\n\n

Version 2.0 of the plugin still requires an account and a connection with the MakeStories.io website. However, it is simple to set up. Users can log in without leaving their WordPress admin interface. This API connection means that user-created Stories are stored on the MakeStories servers. If an end-user wanted to jump platforms from WordPress to something else, this would allow them to take their Stories with them.

\n\n\n\n

“One of the things we would like to assure is your content is still yours, and none of the user data is being consumed or analyzed by us,” said Pratik Ghela, the founder and product manager at MakeStories. “We only take enough data to help serve you better.”

\n\n\n\n

The plugin is a competing solution to the official Web Stories plugin by Google. While the two share similarities in the final output (they are built to utilize the same front-end format for creating Stories on the web), they take different paths to get there.

\n\n\n\n

The two share similarities on the backend too. However, MakeStories may be more polished in some areas. For example, it allows users to zoom in on the small canvas area. Having the ability to reorder slides from the grid view also feels more intuitive.

\n\n\n\n

“The main unique selling proposition of our plugin is that it comes with a guarantee of the MakeStories team,” said Ghela. “We as a team have been building this for over two years, and we are proud to be one of the tools that has stood the test of time, and competition and is still growing at a very fast pace.”

\n\n\n\n

The team also wants to make the Story-creating process faster, safer, and rewarding. The goal is to cater to designers, developers, and content creators. Ghela also feels like his team’s support turnaround time of a few hours will be the key to success and is a good reason for users to give this plugin a try before settling on something else.

\n\n\n\n

“We feel that our goal is to see Web Stories flourish,” he said. “And we may have different types of users looking out for various options. So, the official plugin from Google and the one from MakeStories at least opens up the options for users to choose from. And we feel that the folks at Google are also building a great editor, and, at the end of the day, it’s up to the user to select what they feel is the best.

\n\n\n\n

Technically, MakeStories is a SaaS (software as a service) product. Even though it is a free plugin, there will eventually be a commercial component to it. Currently, it is free at least until the first quarter of 2021, which may be extended based on various factors. There is no word on what pricing tiers may be available after that.

\n\n\n\n

“There will always be a free tier, and we have always stood for it that your data belongs to you,” said Ghela. “In case you do not like the pricing, we will personally assist you to port out from using our editor and still keep the data and everything totally intact.”

\n\n\n\n

Diving Into the Plugin

\n\n\n\nStory management screen.\n\n\n\n

MakeStories is a drag-and-drop editor for building Web Stories. It works and feels much like typical design editors like Gimp or Photoshop. It shares similarities with QuarkXPress or InDesign, for those familiar with page layout programs. In some ways, it feels a lot like a light-colored version of Google’s Web Stories plugin with more features and a slightly more intuitive interface.

\n\n\n\n

The end goal is simple: create a Story through designing slides/pages that site visitors will click through as the narrative unfolds.

\n\n\n\n

The plugin provides a plethora of shapes, textures, and animations. These features are easy to find and implement. It also includes free access to images, GIFs, and videos. These are made possible via API integrations with Unsplash, Tenor, and Pexels.

\n\n\n\n

MakeStories includes access to 10 templates at the moment. However, what makes this feature stand out is that end-users can create and save custom templates for reuse down the road.

\n\n\n\nEditing a Story from a predesigned template.\n\n\n\n

One of the more interesting, almost hidden, features is the available text patterns. The plugin allows users to insert these patterns from a couple of dozen choices. This makes it easier to visualize a design without having to build everything from scratch.

\n\n\n\nInserting a text pattern and adjusting its size.\n\n\n\n

While the editing process is a carefully-crafted experience that makes the plugin worth a look, it is the actual publishing aspect of the workflow that is a bit painful. Traditional publishing in WordPress means hitting the “publish” button to make content live. This is not the case with the MakeStories plugin. It takes you through a four-step process of entering various publisher details, setting up metadata and SEO, validating the Story content, and analytics. It is not that these steps are necessarily bad. For example, MakeStories lets you know when images are missing alt text, which is needed information screen readers. The problem is that it feels out of place to go through all of these details when I, as a user, simply want my content published. And, many of these details, such as the publisher (author), should be automatically filled in.

\n\n\n\n

Updating a Story is not as simple as hitting an “update” button either. The system needs to run through some of the same steps too.

\n\n\n\n

Ghela said the publishing process might be a bit tough but will prove fruitful in the end. The plugin takes care of the technical aspects of adding title tags, meta, and other data on the front end after the user fills in the form fields.

\n\n\n\n

“We will definitely work on improving the flow as the community evolves and improve it a lot to be easier, faster, and, most importantly, still very customizable,” he said.

\n\n\n\n

The MakeStories team has no plans of stopping at its current point on the roadmap. Ghela sounded excited about some of the upcoming additions they are planning, including features like teams, branding, easy template customization, polls, and quizzes.

\n\n\n\n

On the Web Stories Format

\n\n\n\nUN report on COVID-19 and poverty published with MakeStories.\n\n\n\n

Many will ultimately hesitate to use any plugin that implements Web Stories given Google’s history of dropping projects. There is also a feeling that the format is a bit of a fad and will not stand the test of time.

\n\n\n\n

“We greatly believe in AMP and Web Stories as a content format,” said Ghela. “We, as an agency, have been involved a lot in AMP and have done a lot of experiments with it, including a totally custom WooCommerce site in fully-native, valid AMP with support for variable products, subscriptions, and other functionalities.”

\n\n\n\n

The company is all-in on the format and feels like it will be around for the long term, particularly if there is a good ecosystem around monetization.

\n\n\n\n

“We think that the initial reactions are because there are not enough proven results and because we never imagined the story format to come to the web,” said Ghela. “There were definitely plugins that did this. Few folks tried to build stories using good ol’ HTML, CSS, and JavaScript. But, the performance and UX were not that great. On the other hand, the engineers at the AMP Team are making sure that everything is just perfect. The UX, load time, WCV Score, just everything.”

\n\n\n\n

He feels that some of the early criticisms are unwarranted and that the web development community should give the format a try and provide feedback.

\n\n\n\n

“The more data we all get, actually gives the AMP team a clear idea of what’s needed, and they can design the roadmap accordingly,” he said. “So, just giving out early reactions won’t help, but constructive criticism and getting back to the AMP team with what you are doing will.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 21 Oct 2020 21:12:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"WordPress.org blog: WordPress 5.6 Beta 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9085\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://wordpress.org/news/2020/10/wordpress-5-6-beta-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7956:\"

WordPress 5.6 Beta 1 is now available for testing!

\n\n\n\n

This software is still in development, so we recommend that you run this version on a test site.

\n\n\n\n

You can test the WordPress 5.6 beta in two ways:

\n\n\n\n\n\n\n\n

The current target for final release is December 8, 2020. This is just seven weeks away, so your help is needed to ensure this release is tested properly.

\n\n\n\n

Improvements in the Editor

\n\n\n\n

WordPress 5.6 includes seven Gutenberg plugin releases. Here are a few highlighted enhancements:

\n\n\n\n
  • Improved support for video positioning in cover blocks.
  • Enhancements to Block Patterns including translatable strings.
  • Character counts in the information panel, improved keyboard navigation, and other adjustments to help users find their way better.
  • Improved UI for drag and drop functionality, as well as block movers.
\n\n\n\n

To see all of the features for each release in detail check out the release posts: 8.6, 8.7, 8.8, 8.9, 9.0, 9.1, and 9.2 (link forthcoming).

\n\n\n\n

Improvements in Core

\n\n\n\n

A new default theme

\n\n\n\n

The default theme is making its annual return with Twenty Twenty-One. This theme features a streamlined and elegant design, which aims to be AAA ready.

\n\n\n\n

Auto-update option for major releases

\n\n\n\n

The much anticipated opt-in for major releases of WordPress Core will ship in this release. With this functionality, you can elect to have major releases of the WordPress software update in the background with no additional fuss for your users.

\n\n\n\n

Increased support for PHP 8

\n\n\n\n

The next major version release of PHP, 8.0.0, is scheduled for release just a few days prior to WordPress 5.6. The WordPress project has a long history of being compatible with new versions of PHP as soon as possible, and this release is no different.

\n\n\n\n

Because PHP 8 is a major version release, changes that break backward compatibility or compatibility for various APIs are allowed. Contributors have been hard at work fixing the known incompatibilities with PHP 8 in WordPress during the 5.6 release cycle.

\n\n\n\n

While all of the detectable issues in WordPress can be fixed, you will need to verify that all of your plugins and themes are also compatible with PHP 8 prior to upgrading. Keep an eye on the Making WordPress Core blog in the coming weeks for more detailed information about what to look for.

\n\n\n\n

Application Passwords for REST API Authentication

\n\n\n\n

Since the REST API was merged into Core, only cookie & nonce based authentication has been available (without the use of a plugin). This authentication method can be a frustrating experience for developers, often limiting how applications can interact with protected endpoints.

\n\n\n\n

With the introduction of Application Password in WordPress 5.6, gone is this frustration and the need to jump through hoops to re-authenticate when cookies expire. But don’t worry, cookie and nonce authentication will remain in WordPress as-is if you’re not ready to change.

\n\n\n\n

Application Passwords are user specific, making it easy to grant or revoke access to specific users or applications (individually or wholesale). Because information like “Last Used” is logged, it’s also easy to track down inactive credentials or bad actors from unexpected locations.

\n\n\n\n

Better accessibility

\n\n\n\n

With every release, WordPress works hard to improve accessibility. Version 5.6 is no exception and will ship with a number of accessibility fixes and enhancements. Take a look:

\n\n\n\n
  • Announce block selection changes manually on windows.
  • Avoid focusing the block selection button on each render.
  • Avoid rendering the clipboard textarea inside the button
  • Fix dropdown menu focus loss when using arrow keys with Safari and Voiceover
  • Fix dragging multiple blocks downwards, which resulted in blocks inserted in wrong position.
  • Fix incorrect aria description in the Block List View.
  • Add arrow navigation in Preview menu.
  • Prevent links from being focusable inside the Disabled component.
\n\n\n\n

How You Can Help

\n\n\n\n

Keep your eyes on the Make WordPress Core blog for 5.6-related developer notes in the coming weeks, breaking down these and other changes in greater detail.

\n\n\n\n

So far, contributors have fixed 188 tickets in WordPress 5.6, including 82 new features and enhancements, and more bug fixes are on the way.

\n\n\n\n

Do some testing!

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute.

\n\n\n\n

If you think you’ve found a bug, please post to the Alpha/Beta area in the support forums. We would love to hear from you! If you’re comfortable writing a reproducible bug report, file one on WordPress Trac. That’s also where you can find a list of known bugs.

\n\n\n\n

Props to @webcommsat@yvettesonneveld@estelaris, @cguntur, @desrosj, and @marybaum for editing/proof reading this post, and @davidbaumwald for final review.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 22:14:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"WPTavern: WordPress 5.6 Release Team Pulls the Plug on Block-Based Widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106466\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:193:\"https://wptavern.com/wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-release-team-pulls-the-plug-on-block-based-widgets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8762:\"Current block-based widgets admin screen design.\n\n\n\n

I was wrong. I assured our readers that “the block-based widget system will be ready for prime time when WordPress 5.6 lands” in my previous post on the new feature’s readiness. I also said that was on the condition of not trying to make it work with the customizer — that experience was still broken. However, the 5.6 team pulled the plug on block-based widgets for the second time this year.

\n\n\n\n

One week ago, WordPress 5.6 release lead Josepha Haden seemed to agree that it would be ready. However, things can change quickly in a development cycle, and tough decisions have to be made with beta release deadlines.

\n\n\n\n

This is not the first feature the team has punted to a future release. Two weeks ago, they dropped block-based nav menus from the 5.6 feature list. Both features were originally planned for WordPress 5.5.

\n\n\n\n

A new Widgets admin screen has been under development since January 2019, which was not long after the initial launch of the block editor in WordPress 5.0. For now, the block-based widgets feature has been punted to WordPress 5.7. It has also been given the “early” tag, which means it should go into core WordPress soon after the 5.7 release cycle begins. This will give it more time to mature and more people an opportunity to test it.

\n\n\n\n

Helen Hou-Sandì, the core tech lead for 5.6, provided a historical account of the decision and why it was not ready for inclusion in the new ticket:

\n\n\n\n

My question for features that affect the front-end is “can I try out this new thing without the penalty of messing up my site?” — that is, user trust. At this current moment, given that widget areas are not displayed anything like what you see on your site without themes really putting effort into it and that you have to save your changes live without revisions to get an actual contextual view, widget area blocks do not allow you to try this new feature without penalizing you for experimenting.

\n\n\n\n

She went on to say that the current experience is subpar at the moment. Problems related to the customizer experience, which I covered in detail over a month ago, were also mentioned.

\n\n\n\n

“So, when we come back to this again, let’s keep sight of what it means to keep users feeling secure that they can get their site looking the way they want with WordPress, and not like they are having to work around what we’ve given them,” said Hou-Sandì.

\n\n\n\n

This is a hopeful outlook despite the tough decision. Sometimes, these types of calls need to be made for the good of the project in the long term. Pushing back a feature to a future version for a better user experience can be better than launching early with a subpar experience.

\n\n\n\n

“The good part of this is that now widgets can continue to be ‘re-imagined’ for 5.7, and get even more enhancements,” said lead WordPress developer Andrew Ozz in the ticket. “Not sure how many people have tested this for a bit longer but having blocks in the widgets areas (a.k.a. sidebars) opens up many new possibilities and makes a lot of the old, limited widgets obsolete. The ‘widget areas’ become something like ‘specialized posts with more dynamic content,’ letting users (and designers) do a lot of stuff that was either hard or impossible with the old widgets.”

\n\n\n\n

After the letdown of seeing one of my most anticipated features of 5.6 being dropped, it is encouraging to see the positive outlook from community leaders on the project.

\n\n\n\n

“You know, I was really hopeful for it too, and that last-minute call was one I labored over,” said Haden. “When I last looked, it did seem close to ready, but then more focused testing was done and there were some interactions that are a little rough for users. I’m grateful for that because the time to discover painful user experiences is before launch rather than after!”

\n\n\n\n

Despite dropping its second major feature, WordPress 5.6 still has some big highlights that will be shipping in less than two months. The new Twenty Twenty-One theme looks to be a breath of fresh air and will explore block-related features not seen in previous default themes. Haden also pointed out auto-updates for major releases, application passwords support for the REST API, and accessibility improvements as features to look forward to.

\n\n\n\n

WordPress 5.6 Beta 1 is expected to ship today.

\n\n\n\n

Adding New Features To an Old Project

\n\n\n\n

At times, it feels like the Gutenberg project has bitten off more than it can chew. Many of the big feature plans continually miss projections. Between full-site editing, global styles, widgets, nav menus, and much more, it is tough to get hyper-focused on one feature and have it ready to ship. On the other hand, too much focus one way can be to the detriment to other features in the long run. All of these pieces must eventually come together to create a more cohesive whole.

\n\n\n\n

WordPress is also 17 years old. Any new feature could affect legacy features or code. The goal for block-based widgets is to transition an existing feature to work within a new system without breaking millions of websites in the process. Twenty-one months of work on a single feature shows that it is not an easy problem to solve.

\n\n\n\n

“You are so right about complex engineering problems!” said Haden. “We are now at a point in the history of the project where connecting all of the pieces can have us facing unforeseen complications.”

\n\n\n\n

The project also needs to think about how it can address some of the issues it has faced with not quite getting major features to completion. Is the team stretched too thin to focus on all the parts? Are there areas we can improve to push features forward?

\n\n\n\n

“There will be a retrospective where we can identify what parts of our process can be improved in the future, but I also feel like setting stretch goals is good for any software project,” said Haden. “Many contributors have a sense of urgency around bringing the power of blocks to more spaces in WordPress, which I share, but when it’s time to ship, we have to balance that with our deep commitment to usability.”

\n\n\n\n

One problem that has become increasingly obvious is that front-end editing has become tougher over the years. Currently, widgets and nav menus can be edited in two places in WordPress with wildly different interfaces. Full-site editing stands to add an entirely new interface to the mix.

\n\n\n\n

“I think one of the problems that we’re trying to solve with Gutenberg has always been a more consistent experience for editing elements across the WordPress interface,” said Haden. “No user should have to learn five different workflows to make sure their page looks the way they imagined it when it’s published.”

\n\n\n\n

In the meantime, which may be numbered in years, end-users will likely have these multiple interfaces to deal with — overlap while new features are being developed. This may simply be a necessary growing pain of an aging project, one that is trying to lead the pack of hungry competitors in the CMS space.

\n\n\n\n

“There’s a lot of interest in reducing the number of workflows, and I’m hopeful that we can consolidate down to just one beautiful, intuitive interface,” said Haden.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 21:16:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce Tests New Instagram Shopping Checkout Feature, Now in Closed Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106398\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-tests-new-instagram-shopping-checkout-feature-now-in-closed-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2878:\"

Instagram’s checkout feature, which allows users to purchase products without leaving the app, has become an even more important part of Facebook’s long-term investment in e-commerce now that the pandemic has so heavily skewed consumer behavior towards online shopping. When Instagram introduced checkout in 2019, it reported that 130 million users were tapping to reveal product tags in shopping posts every month.

\n\n\n\nimage credit: Instagram\n\n\n\n

Business owners who operate an existing store can extend their audience to Instagram by funneling orders from the social network into their own stores, without shoppers having to leave Instagram. Checkout supports integration with several e-commerce platform partners, including Shopify and BigCommerce, and will soon be available for WooCommerce merchants.

\n\n\n\n

WooCommerce is testing a new Instagram Shopping Checkout feature for its Facebook for WooCommerce plugin. The free extension is used on more than 900,000 websites and will provide the bridge for store owners who want to tap into Instagram’s market. The checkout capabilities are currently in closed beta. Anyone interested to test the feature can sign up for consideration. Businesses registered in the USA that meet certain other requirements may be selected to participate, and the beta is also expanding to other regions soon.

\n\n\n\n

WooCommerce currently supports shoppable posts, which are essentially products sourced from a product catalog created on Facebook that are then linked to the live store through an Instagram business account. Instagram’s checkout takes it one step further to provide a native checkout experience inside the app. Merchants pay no selling fees until December 31, 2020. After that time, the fee is 5% per shipment or a flat fee of $0.40 for shipments of $8.00 or less. 

\n\n\n\n

On the customer side, shoppers only have to enter their information once and thereafter it is stored for future Instagram purchases. Instagram also pushes shipment and delivery notifications inside the app. Store owners will need to weigh whether the convenience of the in-app checkout experience is worth forking over 5% to Facebook, or if they prefer funneling users over to the live store instead.

\n\n\n\n

Instagram Shopping Checkout is coming to WooCommerce in the near future but the company has not yet announced a launch date, as the feature is just now entering closed beta.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Oct 2020 04:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Past Twenty* WordPress Themes To Get New Block Patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106396\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/past-twenty-wordpress-themes-to-get-new-block-patterns?utm_source=rss&utm_medium=rss&utm_campaign=past-twenty-wordpress-themes-to-get-new-block-patterns\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6608:\"

Mel Choyce-Dwan, the Default Theme Design Lead for WordPress 5.6, kick-started 10 tickets around two months ago that would bring new features to the old default WordPress themes. The proposal is to add unique block patterns, a feature added to WordPress 5.5, to all of the previous 10 Twenty* themes. It is a lofty goal that could breathe some new life into old work from the previous decade.

\n\n\n\n

Currently, only the last four themes are marked for an update by the time WordPress 5.6 lands. Previous themes are on the list to receive their block patterns in a future release. For developers and designers interested in getting involved, the following is a list of the Trac tickets for each theme:

\n\n\n\n\n\n\n\n

If you are wondering where Twenty Eighteen is in that list, that theme does not actually exist. It is the one missing year the WordPress community has had since the one-default-theme-per-year era began with Twenty Ten. It is easy to forget that we did not get a new theme for the 2017-2018 season. With all that has happened in the world this year, we should count ourselves fortunate to see a new default theme land for WordPress this December. WordPress updates and its upcoming default theme are at least one consistency that we have had in an otherwise chaotic time.

\n\n\n\n

More than anything, it is nice to see some work going toward older themes — not just in terms of bug fixes but feature updates. The older defaults are still a part of the face of WordPress. Twenty Twenty and Twenty Seventeen each have over one million active installs. Twenty Nineteen has over half a million. The other default themes also have significant user bases in the hundreds of thousands — still some of the most-used themes in the directory. We owe it to those themes’ users to keep them fresh, at least as long as they maintain such levels of popularity.

\n\n\n\n

This is where the massive theme development community could pitch in. Do some testing of the existing patches. Write some code for missing patterns or introduce new ideas. This is the sort of low-hanging fruit that almost anyone could take some time to help with.

\n\n\n\n

First Look at the New Patterns

\n\n\n\n

None of the proposed patterns have landed in trunk, the development version of WordPress, yet. However, several people have created mockups or added patches that could be committed soon.

\n\n\n\n

One of my favorite patterns to emerge thus far is from Beatriz Fialho for the Twenty Nineteen theme. Fialho has created many of the pattern designs proposed thus far, but this one, in particular, stands out the most. It is a simple two-column, two-row pattern with a circular image, heading, and paragraph for each section. Its simplicity fits in well with the more elegant, business-friendly look of the Twenty Nineteen theme.

\n\n\n\nServices pattern for Twenty Nineteen.\n\n\n\n

It is also fitting that Twenty Nineteen get a nice refresh with new patterns because it was the default theme to launch with the block editor. Ideally, it would continually be updated to showcase block-related features.

\n\n\n\n

While many people will focus on some of the more recent default themes, perhaps the most interesting one is a bit more dated. Twenty Thirteen was meant to showcase the new post formats feature in WordPress 3.6. According to Joen Asmussen, the theme’s primary designer, the original idea was for users to compose a ribbon of alternating colors as each post varied its colors.

\n\n\n\n

“The alternating ribbon of colors did not really come to pass because post formats were simply not used enough to create an interesting ribbon,” he wrote in the Twenty Thirteen ticket. “However, perhaps for block patterns, we have an opportunity to revisit those alternating ribbons of colors. In other words, I’d love to see those warm bold colors used in big swathes that take up the whole pattern background.”

\n\n\n\n
Patterns designed to match post formats.\n\n\n\n

This could be a fun update for end-users who are still using that feature that shall not be named post formats.

\n\n\n\n

There is a lot to like about many of the pattern mockups so far. I look forward to seeing what lands along with WordPress 5.6 and in future updates.

\n\n\n\n

Establishing Pattern Category Standard

\n\n\n\n

With the more recent Twenty Twenty-One theme’s block patterns and the new patterns being added to some of the older default themes, it looks like a specific pattern category naming scheme is starting to become a standard. Of the patches thus far, each is creating a new pattern category named after the theme itself.

\n\n\n\n

This makes sense. Allowing users to find all of their theme’s patterns in one location means that they can differentiate between them and those from core or other plugins. Third-party theme authors should follow suit and stick with this convention for the same reason.

\n\n\n\n

Developers can also define multiple categories for a single pattern. This allows theme authors to create a category that houses all of their patterns in one location. However, they can also split them into more appropriate context-specific categories for discoverability.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 19 Oct 2020 21:13:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"BuddyPress: BuddyPress 7.0.0-beta1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://buddypress.org/?p=315150\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://buddypress.org/2020/10/buddypress-7-0-0-beta1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4332:\"

BuddyPress 7.0.0-beta1 is now available for testing!

\n\n\n\n

Please note the plugin is still in development, so we recommend running this beta release on a testing site.

\n\n\n\n

You can test BuddyPress 7.0.0-beta1 in 4 ways :

\n\n\n\n\n\n\n\n

The 7.0.0 stable release is slated to the beginning of December, and we’d love you to give us a hand to get there!

\n\n\n\n

Please note BuddyPress 7.0.0 will require at least WordPress 4.9.

\n\n\n\n

Testing for bugs is an important part of polishing the release during the beta stage and a great way to contribute. Here are some of the big changes and features to pay close attention to while testing (Check out this report on Trac for the full list).

\n\n\n\n
\n\n\n\n

New Administration screens to manage BuddyPress types

\n\n\n\n

In BuddyPress 7.0.0 site administrators will be able to add, edit or delete Member & Group types using their WordPress Administration Screens just like they would do for Post tags.

\n\n\n\n

Read this development note to learn more about it.

\n\n\n\n
\n\n\n\n

Let’s welcome 3 new BP Blocks into our Block Editor

\n\n\n\n
  • The Activity Embed block let authors embed an activity into their post or page.
  • Use the BP Members block to select community users you want to feature into a post or a page.
  • Enjoy the BP Groups block to pick the groups you want to highlight into a post or a page.
\n\n\n\n

Get to know these new blocks reading this development note.

\n\n\n\n
\n\n\n\n

Improved support for WP CLI

\n\n\n\n

WP-CLI is the command-line interface for WordPress. You can update plugins, configure multisite installs, and much more, without using a web browser. In 7.0.0, you will be able to Enjoy new BuddyPress CLI commands to manage BuddyPress Group Meta, BuddyPress Activity Meta, activate or deactivate the BuddyPress signup feature and create BuddyPress specific testing code for plugins.

\n\n\n\n

Discover more about it from this development note.

\n\n\n\n
\n\n\n\n

And so much more such as improvements to the BP REST API, our Template pack, images and iframes lazy loading support…

\n\n\n\n
\n\n\n\n

How You Can Help

\n\n\n\n

Do you speak a language other than English? Help us translate BuddyPress into more than 100 languages!

\n\n\n\n

If you think you’ve found a bug, you can post in the support forums. We’d love to hear from you! If you’re comfortable writing a reproducible bug report, file one on BuddyPress Trac.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 22:30:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:12:\"Mathieu Viet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"WPTavern: Using the Web Stories for WordPress Plugin? You Better Play By Google’s Rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105848\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:215:\"https://wptavern.com/using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules?utm_source=rss&utm_medium=rss&utm_campaign=using-the-web-stories-for-wordpress-plugin-you-better-play-by-googles-rules\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4080:\"Web Stories dashboard screen in WordPress.\n\n\n\n

What comes as a surprise to few, Google has updated its content guidelines for its Web Stories format. For users of its recently-released Web Stories for WordPress plugin, they will want to follow the extended rules for their Stories to appear in the “richer experiences” across Google’s services. This includes the grid view on Search, Google Images, and Google Discover’s carousel.

\n\n\n\n

Google released its Web Stories plugin in late September to the WordPress community. It is a drag-and-drop editor that allows end-users to create custom Stories from a custom screen in their WordPress admin.

\n\n\n\n
Visual Stories on Search.
\n\n\n\n

The plugin does not directly link to Google’s content guidelines anywhere. For users who do not do a little digging, they may be caught unaware if their stories are not surfaced in various Google services.

\n\n\n\n

On top of the Discover and Webmaster guidelines, Web Stories have six additional restrictions related to the following:

\n\n\n\n
  • Copyrighted content
  • Text-heavy Web Stories
  • Low-quality assets
  • Lack of narrative
  • Incomplete stories
  • Overly commercial
\n\n\n\n

While not using copyrighted content is one of those reasonably-obvious guidelines, the others could trip up some users. Because Stories are meant to represent bite-sized bits of information on each page, they may become ineligible if most pages have more than 180 words of text. Videos should also be limited to fewer than 60 seconds on each page.

\n\n\n\n

Low-quality media could be a flag for Stories too. Google’s guidelines point toward “stretched out or pixelated” media that negatively impacts the reader’s experience. They do not offer any specific resolution guidelines, but this should mostly be a non-issue today. The opposite issue is far more likely — users uploading media that is too large and not optimized for viewing on the web.

\n\n\n\n

The “lack of narrative” guideline is perhaps the vaguest, and it is unclear how Google will monitor or police narrative. However, the Stories format is about storytelling.

\n\n\n\n

“Stories are the key here imo,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Google specifically states that Stories need a “binding theme or narrative structure” from one page to the next. Essentially, the company is telling users to use the format for the purpose it was created for. They also do not want users to create incomplete stories where readers must click a link to finish the Story or get information.

\n\n\n\nCNN’s Web Story on Remembering John Lennon.\n\n\n\n

Overly commercial Stories are frowned upon too. While Google will allow affiliate marketing links, they should be restricted to a minor part of the experience.

\n\n\n\n

Closing his Twitter thread, Marsland seemed to hit the point. “I’ve seen some initial Google Web Stories where the platform is being used as a replacement for a brochure or website,” he wrote. “In my view that’s a huge missed opportunity. If I was advising brands I would say ‘Tell Stories’ this is a platform for Story Telling.”

\n\n\n\n

If users of the plugin follow this advice, their Stories should surface on Google’s rich search experiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Oct 2020 20:51:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"WPTavern: Stripe Acquires Paystack for $200M+\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106269\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"https://wptavern.com/stripe-acquires-paystack-for-200m?utm_source=rss&utm_medium=rss&utm_campaign=stripe-acquires-paystack-for-200m\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3196:\"

The big news in the world of e-commerce today is Stripe’s acquisition of Paystack, a Nigeria-based payments system that is widely used throughout African markets. The company, which became informally known as “the Stripe of Africa” picked up $8 million in Series A funding in 2018, led by Stripe, Y Combinator, and Tencent. Paystack has grown to power more than 60,000 businesses, including FedEx, UPS, MTN, the Lagos Internal Revenue Service, and AXA Mansar.

\n\n\n\n

Stripe’s acquisition of the company is rumored to be more than $200M, a small price to pay for a foothold in emerging African markets. In the company’s announcement, Stripe noted that African online commerce is growing 21% year-over-year, 75% faster than the global average. Paystack dominates among payment systems, accounting for more than half of all online transactions in Nigeria.

\n\n\n\n

“In just five years, Paystack has done what many companies could not achieve in decades,” Stripe EMEA business lead Matt Henderson said. “Their tech-first approach, values, and ambition greatly align with our own. This acquisition will give Paystack resources to develop new products, support more businesses and consolidate the hyper-fragmented African payments market.”

\n\n\n\n

Long term, Stripe plans to embed Paystack’s capabilities in its Global Payments and Treasury Network (GPTN), the company’s programmable infrastructure for global money movement.

\n\n\n\n

“Paystack merchants and partners can look forward to more payment channels, more tools, accelerated geographic expansion, and deeper integrations with global platforms,” Paystack CEO and co-founder Shola Akinlade said. He also assured customers that there’s no need to make any changes to their technical integrations, as Paystack will continue expanding and operating independently in Africa.

\n\n\n\n

Paystack is used as a payment gateway for thousands of WordPress-powered stores through plugins for WooCommerce, Easy Digital Downloads, Paid Membership Pro, Give, Contact Form 7, and an assortment of booking plugins. The company has an official WordPress plugin, Payment Forms for Paystack, which is active on more than 6,000 sites, but most users come through the Paystack WooCommerce Payment Gateway (20,000+ active installations).

\n\n\n\n

Stripe’s acquisition was a bit of positive news during what is currently a turbulent time in Nigeria, as citizens are actively engaged in peaceful protests to end police brutality. Paystack’s journey is an encouraging example of the flourishing Nigerian tech ecosystem and the possibilities available for smaller e-commerce companies that are solving problems and removing barriers for businesses in emerging markets.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 22:26:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: Diving Into the Book Review Block Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106273\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:145:\"https://wptavern.com/diving-into-the-book-review-block-plugin?utm_source=rss&utm_medium=rss&utm_campaign=diving-into-the-book-review-block-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6791:\"

Created by Donna Peplinskie, a Product Wrangler at Automattic, the Book Review Block plugin is nearly three years old. However, it only came to my attention during a recent excursion to find interesting block plugins.

\n\n\n\n

The plugin does pretty much what it says on the cover. It is designed to review books. It generally has all the fields users might need to add to their reviews, such as a title, author, image, rating, and more. The interesting thing is that it can automatically fill in those details with a simple ISBN value. Plus, it supports Schema markup, which may help with SEO.

\n\n\n\n

Rain or shine, sick or well, I read every day. I am currently a month and a half shy of a two-year reading streak. When the mood strikes, I even venture to write a book review. As much as I want to share interesting WordPress projects with the community, I sometimes have personal motives for testing and writing about plugins like Book Review Block. Anything that might help me or other avid readers share our thoughts on the world of literature with others is of interest.

\n\n\n\n

Admittedly, I was excited as I plugged in the ISBN for Rhthym of War, the upcoming fourth book of my favorite fantasy series of all time, The Stormlight Archive. I merely needed to click the “Get Book Details” button.

\n\n\n\n

Success! The plugin worked its magic and pulled in the necessary information. It had my favorite author’s name, the publisher, the upcoming release date, and the page count. It even had a long description, which I could trim down in the editor.

\n\n\n\nDefault output of the Book Review block.\n\n\n\n

There was a little work to make this happen before the success. To automatically pull in the book details, end-users must have an API Key from Google. It took me around a minute to set that up and enter it into the field available in the block options sidebar. The great thing about the plugin is that it saves this key so that users do not have to enter each time they want to review a book.

\n\n\n\n

Book Review Block a good starting point. It is straightforward and simple to use. It is not yet at a point where I would call it a great plugin. However, it could be.

\n\n\n\n

Falling Short

\n\n\n\n

The plugin’s Book Review block should be taking its cues from the core Media & Text block. When you get right down to it, the two are essentially doing the same thing visually. Both are blocks with an image and some content sitting next to each other.

\n\n\n\n

The following is a list of items where it should be following core’s lead:

\n\n\n\n
  • No way to edit alt text (book title is automatically used).
  • The image is always aligned left and the content to the right with no way to flip them.
  • The media and content are not stackable on mobile views.
  • Cannot adjust the size of the image or content columns.
  • While inline rich-text controls are supported, users cannot add Heading, List, or Paragraph blocks to the content area and use their associated block options.
\n\n\n\n

That is the shortlist that could offer some quick improvements to the user experience. Ultimately, the problems with the plugin essentially come down to not offering a way to customize the output.

\n\n\n\n

One of the other consistent problems is that the book image the plugin loads is always a bit small. This seems to be more of an issue from the Google Books API than the plugin. Each time I tested a book, I opted to add a larger image — the plugin does allow you to replace the default.

\n\n\n\n

The color settings are limited. The block only offers a background color option with no way to adjust the text color. A better option for plugin users is to wrap it in a Group block and adjust the background and text colors there.

\n\n\n\nBook Review block wrapped inside a Group block.\n\n\n\n

It would also be nice to have wide and full-alignment options, which is an often-overlooked featured from many block plugin authors.

\n\n\n\n

Using the Media & Text Block to Recreate the Book Review Block

\n\n\n\n

The Book Review Block plugin has a lot of potential, and I want to see it evolve by providing more flexibility to end-users. Because the Media & Text block is the closest core block to what the plugin offers, I decided to recreate a more visually-appealing design with it.

\n\n\n\nBook review section created with the Media & Text block.\n\n\n\n

I made some adjustments on the content side of things. I used the Heading block for the book title, a List block for the book metadata, and a Paragraph block for the description.

\n\n\n\n

The Media & Text block also provided me the freedom to adjust the alignment, stack the image and content on mobile views, and tinker with the size of the image. Plus, it has that all-important field for customizing the image alt attribute.

\n\n\n\n

The Media & Text block gave me much more design mileage.

\n\n\n\n

However, there are limitations to the core block. It does not fully capture some of the features available via the Book Review block. The most obvious are the automatic book details via an ISBN and the Schema markup. Less obvious, there is no easy way to recreate the star rating — I used emoji stars — and long description text does not wrap under the image. To recreate that, you would have to opt to use a left-aligned image followed by content.

\n\n\n\n

Overall, the Media & Text block gives me the ability to better style the output, which is what I am more interested in as a user. I want to put my unique spin on things. That is where the Book Review Plugin misfires. It is also the sort of thing that the plugin author can iterate on, offering more flexibility in the future.

\n\n\n\n

This is where many block plugins go wrong, particularly when there is more than one or two bits of data users should enter. Blocks represent freedom in many ways. However, when plugin developers stick to a rigid structure, users can sometimes lose that sense of freedom that they would otherwise have with building their pages.

\n\n\n\n

One of the best blocks, hands down, that preserves that freedom is from the Recipe Block plugin. It has structured inputs and fields. However, it allows freeform content for end-users to make it their own.

\n\n\n\n

When block authors push beyond this rigidness, users win.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 20:44:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: WooCommerce 4.6 Makes New Home Screen the Default for New and Existing Stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores?utm_source=rss&utm_medium=rss&utm_campaign=woocommerce-4-6-makes-new-home-screen-the-default-for-new-and-existing-stores\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3018:\"

WooCommerce 4.6 was released today. The minor release dropped during WooSesh, a global, virtual conference dedicated to WooCommerce and e-commerce topics. It features the new home screen as the default for all stores. Previously, the screen was only the default on new stores. Existing store owners had to turn the feature on in the settings.

\n\n\n\n
\n\n\n\n

The updated home screen, originally introduced in version 4.3, helps store admins see activity across the site at a glance and includes an inbox, quick access to store management links, and an overview of stats on sales, orders, and visitors. This redesigned virtual command center arrives not a moment too soon, as anything that makes order management more efficient is a welcome improvement, due to the sheer volume of sales increases that store owners have seen over the past eight months.

\n\n\n\n

In stark contrast to industries like hospitality and entertainment that have proven to be more vulnerable during the pandemic, e-commerce has seen explosive growth. During the State of the Woo address at WooSesh 2020, the WooCommerce team shared that e-commerce is currently estimated to be a $4 trillion market that will grow to $4.5 trillion by 2021. WooCommerce accounts for a sizable chunk of that market with an estimated total payment volume for 2020 projected to reach $20.6 billion, a 74% increase compared to 2019.

\n\n\n\n

The WooCommerce community is on the forefront of that growth and is deeply invested in the products that are driving stores’ success. The WooCommerce team shared that 75% of people who build extensions also build and maintain stores for merchants, and 70% of those who build stores for merchants also build and maintain extensions or plugins. In 2021, they plan to invest heavily in unlocking more features in more countries and will make WooCommerce Payments the native payment method for the global platform.

\n\n\n\n

A new report from eMarketer shows that US e-commerce growth has jumped 32.4%, accelerating the online shopping shift by nearly two years. Experts also predict the top 10 e-commerce players will swallow up more of US retail spending to account for 63.2% of all online sales this year, up from 57.9% in 2019.

\n\n\n\n

The increase in e-commerce spending may not be entirely tied to the pandemic, as some experts believe this historic time will mark permanent changes in consumer spending habits. This is where independent stores, powered by WooCommerce and other technologies, have the opportunity to establish a strong reputation for themselves by providing quality products and reliable service, as well as by being more nimble in the face of pandemic-driven increases in volume.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 15 Oct 2020 03:48:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"WPTavern: The Future of Starter Content: WordPress Themes Need a Modern Onboarding and Importing Tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106177\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:245:\"https://wptavern.com/the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool?utm_source=rss&utm_medium=rss&utm_campaign=the-future-of-starter-content-wordpress-themes-need-a-modern-onboarding-and-importing-tool\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7385:\"Image credit: picjumbo.com on Pexels.\n\n\n\n

Starter content. It was a grand idea, one of those big dreams of WordPress. It was the new kid on the block in late 2016. Like the introduction of post formats in 2011, the developer community was all in for at least that particular release version. Then, it was on to the next new thing, with the feature dropping off the radar for all but the most ardent evangelists.

\n\n\n\n

Some of us were burned over the years, living and dying by the progress of features that we wanted most.

\n\n\n\n

Released in WordPress 4.7, starter content has since seemed to be going the way of post formats. After four years, only 141 themes in the WordPress theme directory support the feature. There has been no movement to push it beyond its initial implementation. And, it never really covered the things that theme authors wanted in the first place. It was a start. But, themers were ultimately left to their own devices, rolling custom solutions for something that never panned out — fully-featured demo and imported content. Four years is an eternity in the web development world, and there is no sense in waiting around to see if WordPress would push forward.

\n\n\n\n

Until Helen Hou-Sandí published Revisiting Starter Content last week, most would have likely assumed the feature would be relegated to legacy code used by old-school fans of the feature and those theme authors who consider themselves completionists.

\n\n\n\n

“Starter content in 4.7 was always meant to be a step one, not the end goal or even the resting point it’s become,” wrote Hou-Sandí. “There are still two major things that need to be done: themes should have a unified way of showing users how best to put that theme to use in both the individual site and broader preview contexts, and sites with existing content should also be able to take advantage of these sort of ‘ideal content’ definitions.”

\n\n\n\n

Step two should have been this four-year-old accompanying ticket to allow users to import starter content into existing, non-fresh sites.

\n\n\n\n

Since the initial feature dropped, the theme landscape has changed. Let’s face it. WordPress might simply not be able to compete with theme companies that are pushing the limits, creating experiences that users want at much swifter speeds.

\n\n\n\n

Look at where the Brainstorm Force’s Starter Templates plugin for its Astra theme is now. Users can click a button and import a full suite of content-filled pages or even individual templates. And, the Astra theme is not alone in this. It has become an increasingly-common standard to offer some sort of onboarding to users. GoDaddy’s managed WordPress service fills a similar need on the hosting end.

\n\n\n\nAstra’s starter templates and content.\n\n\n\n

As WordPress use becomes more widespread, the more it needs a way to onboard users.

\n\n\n\n

This essentially boils down to the question: how can I make it look like the demo?

\n\n\n\n

Ah, the age-old question that theme authors have been trying to solve. Whether it has been limitations in the software or, perhaps, antiquated theme review guidelines related to demo and imported content, this has been a hurdle that has been tough to jump. But, some have sailed over it and moved on. While WordPress has seemingly been twiddling its thumbs for years, Brainstorm Force and other theme companies have solved this and continued to innovate.

\n\n\n\n

This is not necessarily a bad thing. There are plenty of ideas to steal copy and pull into the core platform.

\n\n\n\n

One of the other problems facing the WordPress starter content feature is that it is tied to the customizer. With the direction of the block system, it is easy to ask what the future holds. The customizer — originally named the theme customizer — was essentially a project to allow users to make front-end adjustments and watch those customizations happen in real time. However, new features like global styles and full-site editing are happening on their own admin screens. Most theme options will ultimately be relegated to global styles, custom templates, block styles, and block patterns. There may not be much left for the customizer to do.

\n\n\n\n

Right now, there are too many places in WordPress to edit the front-end bits of a WordPress site. My hope is that all of these things are ultimately merged into one less-confusing interface. But, I digress…

\n\n\n\n

Starter content should be rethought. Whoever takes the reins on this needs a fresh take that adopts modern methods from leading theme companies.

\n\n\n\n

The ultimate goal should be to allow theme authors to create multiple sets of templates/content that end-users can preview and import. It should not be tied to whether it is a new site. Any site owner should be able to import content and have it automagically go live. It should also be extendable to allow themes to support page builders like Elementor, Beaver Builder, and many others.

\n\n\n\n

This seems to be in line with Hou-Sandí’s thoughts. “For a future release, we should start exploring what it might look like to opt into importing starter content into existing sites, whether wholesale or piecewise,” she wrote. “Many of us who work in the WordPress development/consulting space tend not to ever deal in switching between public themes on our sites, but let’s not forget that’s a significant portion of our user audience and we want to continue to enable them to not just publish but also publish in a way that matches their vision.”

\n\n\n\n

Let’s do it right this go-round, keep a broad vision, and provide an avenue for theme authors to adopt a standardized core WordPress method instead of having everyone build in-house solutions.

\n\n\n\n

I haven’t even touched on the recent call to use starter content for WordPress.org theme previews. It will take more than ideas to excite many theme authors about the possibility. That ticket has sat for seven years with no progress, and most have had it on their wish list for much longer. It is an interesting proposal, one that has been tossed around in various team meetings for years.

\n\n\n\n

Like so many other things, theme authors have either given up hope or moved onto doing their own thing. They need to be brought into the fold, not only as third parties who are building with core WordPress tools but as developers who are contributing to those features.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 14 Oct 2020 20:07:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:116:\"WPTavern: Google Podcasts Manager Adds More Data from Search: Impressions, Top-Discovered Episodes, and Search Terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:271:\"https://wptavern.com/google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms?utm_source=rss&utm_medium=rss&utm_campaign=google-podcasts-manager-adds-more-data-from-search-impressions-top-discovered-episodes-and-search-terms\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2568:\"

Google announced an expansion of listener engagement metrics today for those using its Podcast Manager. Previously, audience insights included data about the types of devices listeners are using, where listeners tune in and drop off during a given episode, total number of listens, and listening duration, but the service lacked analytics regarding how visitors were discovering the podcast.

\n\n\n\n

Google is remedying that today by expanding the dashboard to show impressions, clicks, top-discovered episodes, and search terms that brought listeners to the podcast. This information can help podcasters understand how their content is getting discovered so they can better tailor their episodes to attract more new listeners.

\n\n\n\n

The podcasting industry has seen remarkable growth over the past five years, which previously led experts to project that marketers will spend over $1 billion in advertising by 2021. After the pandemic hit, podcast listening took a downturn in the U.S. but at the same time, podcast creators have found more time to create new shows and episodes. Businesses are turning to the medium to supplement traditional marketing methods that no longer have the same impact now that consumer spending habits heavily favor online products.

\n\n\n\n

Along with the new metrics available inside Google Podcasts Manager, the company also published a guide to optimizing podcasts for Google Search. It highlights four important items for making sure a podcast can be found:

\n\n\n\n
  • Detailed show and episode metadata
  • Ensure the podcast’s webpage and RSS data match
  • Include cover art
  • Ensure Googlebot can access your audio files
\n\n\n\n

A detailed breakdown of your audience’s listening habits isn’t worth much if you’re having trouble getting your podcast discovered. Any podcasting plugin for WordPress should handle these basic optimization recommendations, but if you are still having trouble being found via Google, you can dig deeper into the podcast setup guide for more detailed recommendations.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 22:57:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Are Block-Based Widgets Ready To Land in WordPress 5.6?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/are-block-based-widgets-ready-to-land-in-wordpress-5-6?utm_source=rss&utm_medium=rss&utm_campaign=are-block-based-widgets-ready-to-land-in-wordpress-5-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8214:\"

Two weeks ago, the Gutenberg team put out an open call for block-based widgets feedback. I had already written a lengthy review of the new system earlier in September but was asked by a member of the team to share my thoughts on the most recent iteration. With the upcoming freeze for WordPress 5.6 Beta 1 just a week away, I figured it would not hurt to do another deep dive.

\n\n\n\n

For reference, my latest testing is against version 9.2.0-alpha-172f589 of the Gutenberg plugin, which was a build from earlier today. Gutenberg development moves fast, but everything should be accurate to that point.

\n\n\n\n

Ultimately, many of the problems I pointed out over a month ago still exist. However, the team has cleaned most of the minor issues, such as pointing the open/close arrows for sidebars (block areas) in the correct direction and making it more consistent with the post-editing screen. The UI is much more polished.

\n\n\n\n

Before I dive into all the problems, I want to answer the question I am proposing. Yes, the block-based widget system will be ready for prime time when WordPress 5.6 lands. It is not there yet, but it is at a point where there is a clear finish line that is reachable in the next two months.

\n\n\n\n

I will ignore the failure of block-based widgets in the customizer, which landed in Gutenberg 8.9 and was removed in 9.1. I will also look past the recent proposal to reconstruct the widgets screen to use the Customize API, at least for now. There is a boatload of problems that block-based widgets present for the customizer, and those problems are insurmountable for WordPress 5.6. Long term, WordPress needs to have a single place for editing widget/block areas. Users will likely have to live with some inconsistencies for a while.

\n\n\n\n

Assuming the team does not try to throw a last-minute Hail Mary and implement full editing of blocks in the customizer this round, it is safe to say that block-based widgets are well on their way toward a successful WordPress 5.6 debut.

\n\n\n\n

The User Experience

\n\n\n\nBlock-based widgets screen.\n\n\n\n

As a user, I genuinely enjoy using the new Widgets admin screen. The open-ended, free-form block areas create untold possibilities for designing my WordPress sites. Traditional widgets were limited in scope. Users were buckled down to a handful of core widgets, possibly some plugin widgets, and whatever their theme author offered up. However, with blocks, the pool of choices expands to at least triple the out-of-the-box options (I am not counting embed-type blocks individually). Plus, blocks provide a far more extensive set of design options than a traditional widget.

\n\n\n\n

In comparison, traditional widgets are outdated. Blocks are superior in almost every way. However, there are still problems with this new system.

\n\n\n\n

The biggest issue right now is that end-users can exit the Widgets screen without saving their changes. There is no warning to let them know that all their work is about to be lost in the ether. This is one of those OMGBBQ-level items that need to happen before WordPress 5.6 drops.

\n\n\n\n

One nice-to-have-but-not-necessary feature would be the ability to drag blocks from one block area to another. In the old widgets system, users could move widgets from sidebar to sidebar. The current alternative is to copy a widget, paste it in a new block area, and remove the original.

\n\n\n\n

I am also not a fan of not having an option for the top toolbar, which is available on the post-editing screen. One of the reasons for using this toolbar is because I dislike the default popup toolbar on individual blocks. It is distracting and often gets in the way of my work.

\n\n\n\n

Legacy widgets seem to still be a work in progress. The Legacy Widget block did not work at all for me at times. Then, it magically began to work. However, Gutenberg does now automatically add registered third-party widgets to the block inserter just as if they were blocks.

\n\n\n\nGetting a plugin’s widget to work.\n\n\n\n

This presented its own problems. The only way I managed to make third-party plugin widgets work was to insert the widget, save, and refresh the widgets screen. At that point, the widgets appeared and became editable.

\n\n\n\n

The Theme Author Experience

\n\n\n\n

One of my biggest concerns for theme authors right now is that there does not seem to be any documentation in the block editor handbook. There is plenty of time to make that happen, but there are things theme authors need to be aware of. Having a centralized location, even while the feature is under development, would help them gear up for the 5.6 release.

\n\n\n\n

Some of these questions, which may be answered in various Make blog posts, should exist on a dedicated documentation page:

\n\n\n\n
  • How can a theme opt out of block-based widgets?
  • What are the hooks to add custom styles for the Widgets screen?
  • Can themes target specific sidebar styles on the Widgets screen?
  • Is it possible to consistently style sections like traditional widgets on the front end?
  • Can themes opt into wide and full-alignment within block areas, which could essentially be used similarly to the post content area?
\n\n\n\n

These are some of the questions I would want to be answered as a former theme author. I am no longer in the thick of the theme design game and presume that those who are would have a larger list of questions.

\n\n\n\n

One less-obvious piece of documentation should center on how to handle fallbacks or default widgets. Traditionally, themes that needed to show a default set of widgets would check if the sidebar has widgets and fall back to using the_widget() to output one or more defaults. While theme authors can still do that, we should start to transition them across the board to the block system.

\n\n\n\n

Should theme authors copy/paste block HTML as a fallback? Would the starter content system be better for this, and can starter widget content handle blocks? What is the recommended method for widget fallbacks in WordPress 5.6?

\n\n\n\n

There is still the ongoing issue of how theme authors should handle the traditional widget and widget title wrapper HTML in the new block paradigm. One patch added since the Gutenberg 9.1 release wraps every top-level block with the widget wrapper. If this lands in the 9.2 release, it will likely make the issue worse.

\n\n\n\n

In the traditional system, both the widget title and content are wrapped within a container together. However, if a user adds a Heading block (widget title) and another block (widget content), each block is wrapped separately with the theme’s widget wrappers. The only way to rectify the situation as it stands is for end-users to add a Group block for each “widget” they want, which would require an extensive amount of re-education for WordPress users. It is not an ideal scenario.

\n\n\n\nEach block is wrapped as an individual section.\n\n\n\n

Instead of attempting to directly “fix” this issue, WordPress should instead do nothing to the output. Blocks and traditional widgets are fundamentally different.

\n\n\n\n

Let theme authors take the reins on this one and explore possibilities. However, give them the tools to do so, such as supporting block patterns.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 13 Oct 2020 21:35:39 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: WordCamp Austin 2020 Finds Success with VR Experience for Sessions and Networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=106119\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking?utm_source=rss&utm_medium=rss&utm_campaign=wordcamp-austin-2020-finds-success-with-vr-experience-for-sessions-and-networking\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7246:\"

WordCamp Austin 2020 attendees are raving about their experiences attending the virtual event last Friday. It was no secret that the camp’s organizers planned to use Hubs Virtual Rooms by Mozilla to create a unique environment, but few could imagine how much more interactive and personalized the experience would be than a purely Zoom-based WordCamp.

\n\n\n\n

After selecting a custom avatar, attendees entered the venue using a VR headset or the browser to check out sessions or network in the hallway track.

\n\n\n\n
\n

Here’s a small taste of the experience at @WordCampATX today. #WordPress logos and no sponsor banners on any elevator doors. #WCATX pic.twitter.com/Nv2p2VchXf

— David Bisset (@dimensionmedia) October 9, 2020
\n
\n\n\n\n

Speaker and Q&A sessions were broadcast through Zoom but organizers can also embed YouTube videos and streams within the standalone VR environment.

\n\n\n\n

“The VR experience was the most life-like WordCamp experience I’ve had since the start of global lockdowns,” attendee and speaker David Vogelpohl said. “You could attend sessions in one of two virtual presentation halls depending on what track you wanted to see at that time. The speaker presented on a virtual stage and you could see the other attendees watching the presentation.”

\n\n\n\n

Vogelpohl said he enjoyed his experience getting to know others in the Slack and VR venue. Organizers preserved the general vibe of the “hallway track” to recreate what is arguably one of the most valuable aspects of in-person WordCamps.

\n\n\n\n
\n

So cool – checking out the Virtual Space of WordCamp Austin – love the background noise of people talking, ran into @ChrisWiegman and @Josh412 #WCATX pic.twitter.com/68EdgDN2Om

— Birgit Pauli-Haack (@bph) October 9, 2020
\n
\n\n\n\n

“In the hallway track between the virtual presentation halls was a large foyer where you could meet new people, spot a friend speaking with someone else, and virtually step aside from a group conversation to have a private conversation,” Vogelpohl said.

\n\n\n\n

“It was great to see folks like Josepha circling around speaking with attendees, Josh Pollock nerding out in a corner with a group of advanced WP developers, and having random friends drop into a conversation I was having with a group of others. While VR WordCamp doesn’t wholly replace the value of attending a WordCamp live, a lot of the best parts of meeting and collaborating with others was captured in the VR context.”

\n\n\n\n

The live music interludes, which showcased talents from around the community, also provided a way for virtual attendees to stay connected while waiting for the next session.

\n\n\n\n

Behind the Scenes with Anthony Burchell: Creative Director for WordCamp Austin’s Virtual World

\n\n\n\n

WordPress core contributor Anthony Burchell, who started a company dedicated to creating interactive XR sound and art experiences, was the creative director behind the WordCamp Austin’s VR backdrop.

\n\n\n\n

“For WordCamp Austin we wanted to give folks something to be excited about outside of the typical webcam and chat networking,” Burchell said. “I feel that virtual events are not utilizing the networking layer nearly enough to make folks feel like they are really at an event. I’ve seen many compelling formats for virtual events utilizing webcams and chat rooms, but in the end, it feels like there’s been a missing element of presence; something video games and virtual reality excel at.”

\n\n\n\n
\n

Virtual mission control for #WCATX pic.twitter.com/WyrFkIsW2Q

— Anthony Burchell (@antpb) October 9, 2020
\n
\n\n\n\n

Setting up the virtual world involves spinning up a self-hosted instance of Hubs Cloud, which Burchell said is very similar to the complexity of making a WordPress site.

\n\n\n\n

“The most time consuming part of creating a 3D world for an event is making the 3D assets for the space,” Burchell said. “In total I streamed 11 hours of video leading up to the event to give a glimpse into the process.”

\n\n\n\n

Burchell’s YouTube playlist documents the incredible amount of work that went into creating the WordCamp’s virtual venue for attendees to enjoy.

\n\n\n\n

“While it took quite a bit of time to prepare, the code and assets are completely reusable for another event,” Burchell said. “A lot of the time was spent trying to make the space purpose built for the goals of the camp. Much like a real WordCamp, I found the majority of folks packing into the theater rooms for presentations and dipping out a little early to network with friends in the hallway area. That was very much by design!”

\n\n\n\n

Burchell and the other organizers were careful to ensure that the Hubs space was not the primary viewing experience of the camp but rather an extension of the networking activities that attendees could drop in on. The event had nearly identical numbers of attendees joining the virtual space as it did for those joining the video channels. At the end of the afterparty, Burchell turned on flying for all attendees to conclude the successful event:

\n\n\n\n
\n\n
\n\n\n\n

“With Hubs we were able to give attendees the ability to express themselves within a venue vs within a camera and chat box,” Burchell said. “It was incredible to see characteristics of folks in the community shine through a virtual avatar! Just the simple act of seeing your WordCamp friends in the hallway joking and chatting just as they would at a real life event was enough to make me feel like I was transported to a real WordCamp.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 22:31:02 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Privacy-Conscious WordPress Plugin Caches and Serves Gravatar Images Locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105825\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally?utm_source=rss&utm_medium=rss&utm_campaign=privacy-conscious-wordpress-plugin-caches-and-serves-gravatar-images-locally\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5285:\"

Ari Stathopoulos released his new Local Gravatars plugin last week. The goal of the plugin is to allow site owners to take advantage of the benefits of a global avatar system while mitigating privacy concerns by hosting the images locally.

\n\n\n\n

In essence, it is a caching system that stores the images on the site owner’s server. It is an idea that Peter Shaw proposed in the comments on an earlier Tavern article covering local avatar upload. It is a middle ground that may satisfy some users’ issues with how avatars currently work in WordPress.

\n\n\n\n

“I am one of the people that blocks analytics, uses private sessions when visiting social sites, I use DuckDuckGo instead of Google, and I don’t like the ‘implied’ consents,” said Stathopoulos. “I built the plugin for my own use because I don’t know what Gravatar does, I don’t understand the privacy policies, and I am too lazy to spend two hours analyzing them. It’s faster for me to build something that is safe and doesn’t leave any room for misunderstandings.”

\n\n\n\n

He is referring to Automattic’s extensive Privacy Policy. He said it looks benign. However, he does not like the idea of any company being able to track what sites he visits without explicit consent.

\n\n\n\n

“And when I visit a site that uses Gravatar, some information is exposed to the site that serves them — including my IP,” said Stathopoulos. “Even if it’s just for analytics purposes, I don’t think the company should know that page A on site B got 1,000 visitors today with these IPs from these countries. There is absolutely no reason why any company not related to the page I’m actually visiting should have any kind of information about my visit.”

\n\n\n\n

The Local Gravatars plugin must still connect to the Gravatar service. However, the connection is made on the server rather than the client. Stathopoulos explained that the only information exposed in this case is the server’s IP and nothing from the client, which eliminates any potential privacy concerns.

\n\n\n\n

The Latest Plugin Update

\n\n\n\n

Stathopoulos updated the plugin earlier today to address some performance concerns for pages that have hundreds or more Gravatar images. In the version 1.0.1 update, he added a maximum processing time of five seconds and changed the cache cleanup process from daily to weekly. Both of these are filterable via code.

\n\n\n\n

“Now, if there are Gravatars missing in a page request, it will get as many as it can, and, after five seconds, it will stop,” said Stathopoulos. “So if there are 100 Gravatars missing and it gets the first 20, the rest will be blank (can be filtered to use a fallback URL, or even fall back to the remote URL, though that would defeat the privacy improvement). The next page request will get the next 20, and so on. At some point, all will be there, and there will be no more delays.”

\n\n\n\n

He did point out that performance could temporarily suffer when installing it on a site that has individual posts with 1,000s of comments and a lot of traffic. However, nothing would crash on the site, and the plugin should eventually lead to a performance boost in this scenario. For such large sites, owners could use the existing filter hooks to tweak the settings.

\n\n\n\n

Right now, the plugin is primarily an itch he wanted to scratch for his own purposes. However, if given enough usage and feedback, he may include a settings screen to allow users to control some of the currently-filterable defaults, such as the cleanup timeframe and the maximum process time allowed.

\n\n\n\n

The Growing List of Alternatives

\n\n\n\n

With growing concerns around privacy in the modern world, Local Gravatars is another tool that end-users can employ if they have any concerns around the Gravatar service. For those who are OK with an auto-generated avatar, Pixel Avatars may be a solution.

\n\n\n\n

“I’ve seen some of them, and they are wonderful!” Stathopoulos said of alternatives for serving avatars. “However, this plugin is slightly different in that the avatars the user already has on Gravatar.com are actually used. They can see the image they have uploaded. The user doesn’t need to upload a separate avatar, and an automatic one is not used by default.”

\n\n\n\n

He would not mind using an auto-generated avatar when commenting on blogs or news sites at times. However, Stathopoulos prefers Gravatar for community-oriented sites.

\n\n\n\n

“My Gravatar is part of my online identity, and when I see, for example, a comment from someone on WordPress.org, I know who they are by their Gravatar,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 12 Oct 2020 21:06:20 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress 5.6 to Introduce Application Passwords for REST API Authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105997\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-5-6-to-introduce-application-passwords-for-rest-api-authentication\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2604:\"

In 2015, WordPress 4.4 introduced a REST API, but one thing that has severely limited its broader use is the lack of authentication capabilities for third-party applications. After considering the benefits and drawbacks of many different types of authentication systems, George Stephanis published a proposal for integrating Application Passwords, into core.

\n\n\n\n

Stephanis highlighted a few of the major benefit that were important factors in the decision to use Application Passwords: the ease of making API requests, ease of revoking credentials, and the ease of requesting API credentials. The project is available as a standalone feature plugin, but Stephanis and his collaborators recommended WordPress merge a pull request that is based off the feature plugin’s codebase.

\n\n\n\n

After WordPress 5.6 core tech lead Helen Hou-Sandi gave the green light for Application Passwords to be merged into core, the developer community responded enthusiastically to the news.

\n\n\n\n

“I am/we are 100% in favor of this,” Joost deValk commented on the proposal. “Opening this up is like opening the dawn of a new era of WordPress based web applications. Suddenly authentication is not something you need to fix when working with the API and you can just build awesome stuff.”

\n\n\n\n

Stephanis’ proposal also mentioned how beneficial a REST API authentication system would be for the Mobile teams‘ contributors who are relying on awkward workarounds while integrating Gutenberg support.

\n\n\n\n

“This would be a first step to replace the use of XMLRPC in the mobile apps and it would allow us to add more features for self hosted users,” Automattic mobile engineer Maxime Biais said.

\n\n\n\n

After the REST API was added to WordPress five years ago, many had the expectation that WordPress-based web applications would start popping up everywhere. Without a reliable authentication system, it wasn’t easy for developers to just get inspired and build something quickly. Application Passwords in WordPress 5.6 will open up a lot of possibilities for those who were previously deterred by the lack of core methods for authenticating third-party access.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 23:01:31 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"WPTavern: WP Agency Summit Begins Its Second Annual Virtual Event October 12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105160\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:197:\"https://wptavern.com/wp-agency-summit-begins-its-second-annual-virtual-event-october-12?utm_source=rss&utm_medium=rss&utm_campaign=wp-agency-summit-begins-its-second-annual-virtual-event-october-12\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6357:\"

Jan Koch, the founder and host of WP Agency Summit, is kicking off his second annual event on October 12. The five-day event will feature 37 speakers from a wide range of backgrounds across the WordPress industry. It is a free virtual event that anyone can attend.

\n\n\n\n

“The focus for the 2020 WP Agency Summit is showing attendees how to bring back the fun into scaling their agencies,” said Koch. “It is all about reducing the daily hustle by teaching how to successfully build and manage teams, how to work with enterprises (allowing for fewer customers but bigger projects), how to build sustainable recurring revenue, and how to position your agency to dominate your niche.”

\n\n\n\n

This year’s event includes three major changes to make the content more accessible to a larger group of people. Each session will be available between October 12 – 16 instead of the previous 48-hour window that attendees had to find time for in 2019.

\n\n\n\n

After the event has concluded, access to the content will be behind a paywall. Koch reduced the price to $77 for lifetime access for those who purchase pre-launch, which will increase to $127 during the event. Last year’s prices ballooned to $497, which meant that it was simply not affordable for many who found it too late.

\n\n\n\n

Some of the proceeds this year are going toward transcribing all the videos so that hearing-impaired users can enjoy the content.

\n\n\n\n

This year’s event will also focus on a virtual networking lounge for attendees. “I’ve seen how well it worked at the WP FeedBack Summit — we even had BobWP record a podcast episode on the fly in that lounge!” said Koch. “I’ve seen many new friendships develop, people connecting with new suppliers or getting themselves booked on podcasts, and sharing experiences about their businesses.”

\n\n\n\n

The lounge will be open during the entirety of the summit, which will allow attendees to jump into the conversation on their own time.

\n\n\n\n

A More Diverse Speaker Lineup

\n\n\n\n

Koch received some backlash for the lack of gender diversity last year. The 2019 event had over 20 speakers from a diverse male lineup. However, only four women from our industry led sessions.

\n\n\n\n

When asked about this issue in 2019, Koch responded, “I recognize this as a problem with my event. The reason I have so much more male than female speakers is quite simple, the current speaker line-up is purely based on connections I had when I started planning for the event. It was a relatively short amount of time for me, so I wasn’t able to build relationships with more female WP experts beforehand.”

\n\n\n\n

The host said he paid attention to the feedback he received. While not hitting the 50/50 split goal he had for 2020’s event, 16 of the 37 speakers are women.

\n\n\n\n

Koch said he strived to get speakers from a wider range of backgrounds. He wanted to bring in both freelancers and multi-million dollar agency owners. He also focused on getting people from multiple countries to represent WordPress agencies.

\n\n\n\n

“I did reach out to around 130 people four months before the event to make new connections,” he said. “The community around the Big Orange Heart (a non-profit for mental well-being) also helped a lot with introducing me to new members of the WP community.”

\n\n\n\n

Koch said he learned two valuable lessons when branching out beyond his existing connections for this year’s event:

\n\n\n\n

Firstly, don’t hesitate to reach out to people you think will never talk to you because they’re running such big companies. For example, I immediately got confirmations from Mario Peshev from Devrix, Brad Touesnard from Delicious Brains, or Marieke van de Rakt from Yoast. When first messaging them, I had little hope they’d set aside time to jump on an interview with me – but they were super supportive and accommodating! The WordPress community really is a welcoming environment if you approach people in a humble way.

Secondly, build connections with sincerity. Do not just focus on what you can get from that connection but how you can help the other person. I know this sounds cheesy and you’ve heard this quite often — but it is true. Once I got the first response from new contacts and explained my goal of connecting fellow WordPress community members virtually, most immediately agreed because they also benefit from new connections and being positioned as a thought-leader in this event.

\n\n\n\n

WP Agency Summit? WP FeedBack Summit?

\n\n\n\n

For readers who recall the Tavern’s coverage of the WP FeedBack Summit earlier this year, the article specifically stated that the WP FeedBack Summit was a continuation of 2019’s WP Agency Summit. The official word at the time from WP FeedBack’s public relations team was the following:

\n\n\n\n

Last year’s event, the WP Agency Summit has been rebranded under the umbrella of WP FeedBack’s brand when Jan Koch the host of last’s year WP Agency Summit joined WP FeedBack as CTO.

\n\n\n\n

Koch said that it was a standalone event and not directly connected to WP Agency Summit but had the same target audience. However, the WP FeedBack Summit did use the previous WP Agency Summit’s stats and data to promote the event.

\n\n\n\n

“The WP FeedBack Summit was hosted under the WP FeedBack brand because I joined their team as CTO in March this year,” he said. “Vito [Peleg] and I had the idea to host a virtual conference around WordPress because of WordCamp Asia being canceled — we wanted to help connect the community online through our summit.

\n\n\n\n

Koch left WP FeedBack soon after the summit ended and is currently back on his own and has a goal of making WP Agency Summit a yearly event.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 09 Oct 2020 17:01:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: Navigation Screen Sidelined for WordPress 5.6, Full-Site Editing Edges Closer to Public Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105839\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:247:\"https://wptavern.com/navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta?utm_source=rss&utm_medium=rss&utm_campaign=navigation-screen-sidelined-for-wordpress-5-6-full-site-editing-edges-closer-to-public-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4676:\"

The new block-based navigation screen is once again delayed after it was originally slated for WordPress 5.5 and then put on deck for 5.6. Contributors have confirmed that it will not be landing in WordPress core until 2021 at the earliest.

\n\n\n\n

“The Navigation screen is still in experimental state in the Gutenberg plugin, so it hasn’t had any significant real-world use and testing yet,” Editor Tech Lead Isabel Brison said. She made the call to remove it from the 5.6 lineup after the feature missed the deadline for bringing it out of the experimental state. It still requires a substantial amount of development work and accessibility feedback before moving forward.

\n\n\n\n

Contributors will focus instead on making sure the Widgets screen gets out the door for 5.6 and plan to pick up again on Navigation towards the end of November.

\n\n\n\n

WordPress 5.6 lead Josepha Haden gave an update this week on the progress of all the anticipated features, including the planned public beta for full-site editing (FSE).

\n\n\n\n

“I don’t expect FSE to be feature complete by the time WP5.6 is released,” Haden said. “What I expect is that FSE will be functional for simple, routine user flows, which we can start testing and iterating on. That feedback will also help us more confidently design and build our complex user flows.”

\n\n\n\n

Frank Klein, an engineer at Human Made, asked in the comments of another update why full-site editing is being tied to 5.6 progress in the first place, since it will still only be available in the plugin at the time of release.

\n\n\n\n

“The main value is that it provides a good checkpoint along the path of FSE’s development,” Kjell Reigstad said. “Full-site editing is very much in progress. It is still experimental, but the general approach is coming into view, and becoming clearer with every plugin release.”

\n\n\n\n

Reigstad posted an update on what developers can expect regarding block-based theming and the upcoming release, since the topic is closely tied to full-site editing. He emphasized that the infrastructure is already in place and that, despite it still being experimental, future block-based themes should work in a similar way to how they are working now.

\n\n\n\n

“The focus is now shifting towards polishing the user experience: using the site editor to create templates, using the query block, iterating on the post and site blocks, and implementing the Global Styles UI,” Reigstad said.

\n\n\n\n

“The main takeaway is that when 5.6 is released, the full-site editing feature set will look similar to where it is today, with added polish to the UI, and additional features in the Query block.”

\n\n\n\n

Theme authors are entering a new time of uncertainty and transition, but Reigstad reassured the community that themes as we know them today are not on track to be phased out in the immediate future.

\n\n\n\n

“There is currently no plan to deprecate the way themes are built today,” Reigstad said. “Your existing themes will continue to work as they always have for the foreseeable future.” He also encouraged contributors to get involved in an initiative to help theme authors transition to block-based themes. (This project is not targeted for the 5.6 release.)

\n\n\n\n

Developers can follow important FSE project milestones on GitHub, and subscribe to the weekly Gutenberg + Themes updates to track progress on block-based theming. A block-based version of the Twenty Twenty-One theme is in the works and should pick up steam after 5.6 beta 1, expected on October 20.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 22:57:37 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:68:\"WPTavern: EditorPlus 1.9 Adds Animation Builder for the Block Editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105678\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:181:\"https://wptavern.com/editorplus-1-9-adds-animation-builder-for-the-block-editor?utm_source=rss&utm_medium=rss&utm_campaign=editorplus-1-9-adds-animation-builder-for-the-block-editor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4535:\"

Munir Kamal shows no signs of slowing down. He continues to push forward with new features for his EditorPlus plugin, which allows end-users to customize the look of the blocks in their posts and pages. He calls it the “no-code style editor for WordPress.”

\n\n\n\n

The latest addition to his plugin? Animation styles for every core block.

\n\n\n\n

My first thought was that this would bloat the plugin with large amounts of unnecessary CSS and JavaScript for what is essentially a few bells and whistles. However, Kamal pulled it off with minimal custom CSS.

\n\n\n\n

Inspired by features from various website builders, he wanted to bring more and more of those things to the core block editor. The animations feature is just another ticked box on a seemingly never-ending checklist of features. And, so far, it’s all still free.

\n\n\n\n

Since we last covered EditorPlus in June, Kamal has added the ability to insert icons via any rich-text area (e.g., paragraphs, lists, etc.). He has also added shape divider, typography, style copying, and responsive editing options for the core WordPress blocks.

\n\n\n\n

How Do Animations Work?

\n\n\n\n

In the version 1.9 release of EditorPlus, Kamal added “entrance” animations. These types of animations happen when a visitor sees the block for the first time on the screen. For example, users could set the Image block to fade into visibility as a reader views the block.

\n\n\n\n

Currently, the plugin adds seven animations:

\n\n\n\n
  • Fade
  • Slide
  • Bounce
  • Zoom
  • Flip
  • Fold
  • Roll
\n\n\n\nAdding a Slide animation for the Cover block text.\n\n\n\n

Each animation has its own subset of options to control how it behaves on the page. The bounce animation, for example, allows users to select the bounce direction. Other options include duration, delay, speed curve, delay, and repeat. There are enough choices to spend an inordinate amount of time tinkering with the output.

\n\n\n\n

One of the best features of this new feature is that Kamal has included an Animation Player under the block options. By clicking the play button, users can view the animation in action without previewing the post.

\n\n\n\n

Watch a quick video of the Animations feature:

\n\n\n\n
\n\n
\n\n\n\n

After testing and using each animation, everything seemed to work well. The one downside — and this is not limited to animations — is that applying styles on the block level sometimes does not make sense. In many cases, it would help users to have options to style or animate the items within the block, such as the images in the Gallery block. When I broached the subject with Kamal, he was open to the idea of finding a solution to this in the future.

\n\n\n\n

What Is Next for EditorPlus?

\n\n\n\n

At a certain point, too many block options can almost feel like overkill and become unwieldy. EditorPlus does allow users to disable specific features from its settings screen, which can help get rid of some unwanted options. Kamal said he would like to continue making it more modular so that users can use only the features they need.

\n\n\n\n

“What I plan is to have micro-level feature control for this extension so that a user can switch off individual styling panels like, Typography, Background, etc.,” he said. “Even further, I plan to bring these controls based on the user role as well. So an admin can disable these features for the editor, author, etc.”

\n\n\n\n

That may be a bit down the road though. For now, he wants to focus on adding new features that he already has planned.

\n\n\n\n

“I do plan to add more animation features,” said Kamal. “I got too many ideas, such as scroll-controlled animation, hover animation, text animation, Lottie animation, background animation, animated shape dividers, and more. But, having said that, I will be careful adding only those features that don’t affect page performance much.”

\n\n\n\n

Outside of extra styles and animations for existing blocks, he plans to jump on the block-building train in future releases. EditorPlus users could see accordion, toggle, slider, star rating, and other blocks in an upcoming release.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:53:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"Donncha: Hide featured image if it’s in the post\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://odd.blog/?p=89503242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"https://odd.blog/2020/10/08/hide-featured-image-if-its-in-the-post/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3885:\"

I’ve been running a photoblog at inphotos.org since 2005 on WordPress. (And thanks to writing this I noticed it’s 15 years old today!)

\n\n\n\n
\n\n\n\n

In that time WordPress has changed dramatically. At first I used Flickr to host my images, but after a short time I hosted the images myself. (Good thing too since Flickr limited free user accounts to 1000 images, so I wrote a script to download the Flickr images I used in posts.)

\n\n\n\n
\n\n\n\n

For quite a long time I used the featured image instead of inserting the image into the post content, but then about two years ago I went back to inserting the photo into the post. Unfortunately that meant the photo was shown twice, once as a featured image, and once in the post content.

\n\n\n\n

The last theme I used supported custom post types, one of which was a photo type that displayed the featured image but hid the post content. It was an ok compromise, but not perfect.

\n\n\n\n
\n\n\n\n

Recently I started using Twenty Twenty, but after 15 years I had a mixture of posts with:

\n\n\n\n
  • Featured image with no image in the post.
  • Featured image with the same image in the post.
\n\n\n\n

I knew I needed something more flexible. I wanted to hide the featured image if it also appeared in the post content. I procrastinated and never got around to it until this evening when I discovered it was actually quite easy.

\n\n\n\n\n\n\n\n

Copy the following code into the function.php of your child theme and you’ll be all set! It relies on you having unique filenames for your images. If you don’t then remove the call to basename(), and that may help.

\n\n\n
\nfunction maybe_remove_featured_image( $html ) {\n        if ( $html == \'\' ) {\n                return \'\';\n        }\n        $post = get_post();\n        $post_thumbnail_id = get_post_thumbnail_id( $post );\n        if ( ! $post_thumbnail_id ) {\n                return $html;\n        }\n\n        $image_url = wp_get_attachment_image_src( $post_thumbnail_id );\n        if ( ! $image_url ) {\n                return $html;\n        }\n\n        $image_filename = basename( parse_url( $image_url[0], PHP_URL_PATH ) );\n        if ( strpos( $post->post_content, $image_filename ) ) {\n                return \'\';\n        } else {\n                return $html;\n        }\n}\nadd_filter( \'post_thumbnail_html\', \'maybe_remove_featured_image\' );\n
\n\n\n

The post_thumbnail_html filter acts on the html generated to display the featured image. My code above gets the filename of the featured image, checks if it’s in the current post and if it is returns a blank string. Feedback welcome if you have a better way of doing this!

\n\n\n\n
\n\n\n\n

\n\n

Related Posts

\n

Source

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 20:43:35 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Donncha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches Automatic Platform Optimization for WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105641\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-automatic-platform-optimization-for-wordpress?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-automatic-platform-optimization-for-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6128:\"

Just a day after launching its new privacy-first web analytics product last week, Cloudflare announced Automatic Platform Optimization (APO) for WordPress. The new service boasts staggering performance improvements for sites that might otherwise be slowed down by shared hosting, slow database lookups, or sluggish plugins:

\n\n\n\n

Our testing… showed a 72% reduction in Time to First Byte (TTFB), 23% reduction to First Contentful Paint, and 13% reduction in Speed Index for desktop users at the 90th percentile, by serving nearly all of your website’s content from Cloudflare’s network. 

\n\n\n\n

APO uses Cloudflare Workers to cache dynamic content and serve the website from its edge network. In most cases this eliminates origin requests and origin processing time. That means visitors requesting your website will get near instant load times. Cloudflare reports that its testing shows APO delivers consistent load times of under 400ms for HTML Time to First Byte (TTFB).

\n\n\n\n

The effects of using APO are similar to hosting static files on a CDN, but without the need to manage a complicated tech stack. Content creators retain their ability to create dynamic websites without any changes to their workflow for the sake of performance.

\n\n\n\n

Version 3.8 of Cloudflare’s official WordPress plugin was recently updated to include support for APO. It detects when users make changes to their content and purges the content stored on Cloudflare’s edge.

\n\n\n\n

The new service is available to Cloudflare users with a single click of a button. APO is included at no cost for existing Cloudflare customers on the Professional, Business, and Enterprise plans. Users on the Free plan can add it to their sites for $5/month. The service is a flat fee and is not metered.

\n\n\n\n

Cloudflare’s announcement has so far been well-received by WordPress professionals and hosting companies and many have already begun testing it.

\n\n\n\n
\n

So the week after @Cloudflare Birthday Week I try and play with as many of the new products as possible. Today was the WordPress APO on my simple demo site. You can see TTFB dropped from ~350ms to ~75ms! https://t.co/zg976EjrZI pic.twitter.com/KuaHqtHLom

— Matt Bullock (@mibullock) October 6, 2020
\n
\n\n\n\n

WordPress lead developer Mark Jaquith called APO “incredible news for the WordPress world.”

\n\n\n\n

“On sites I manage this is going to lower hosting complexity and easily save hundreds of dollars a month in hosting costs,” Jaquith said.

\n\n\n\n

After running several speed tests from six different locations around the world, early testers at Kinsta got remarkable results using APO:

\n\n\n\n

“By caching static HTML on Cloudflare’s edge network, we saw a 70-300% performance increase. As expected, the testing locations furthest away from Tokyo saw the biggest reduction in load time.

“If your WordPress site uses a traditional CDN that only caches CSS, JS, and images, upgrading to Cloudflare’s WordPress APO is a no-brainer and will help you stay competitive with modern Jamstack and static sites that live on the edge by default.”

\n\n\n\n

George Liu, a “self-confessed page speed addict” and Cloudflare Community MVP, performed a series of detailed tests on the new APO product with his blog. After many comparisons, he found that Cloudoflare’s WordPress plugin with APO turned on delivers results similar to his heavily optimized WordPress blog that uses a custom Cloudflare Worker caching configuration.

\n\n\n\n

“You’ll find that Cloudflare WordPress plugin’s one click Automatic Platform Optimization button does wonders for page speed for the average WordPress user not well versed in page speed optimizations,” Liu said.

\n\n\n\n

“Cloudflare’s WordPress plugin Automatic Platform Optimization will in theory beat all other WordPress caching solutions other than you rolling out your own Cloudflare Worker based caching like I did. So you get a good bang for your buck at US$5/month for Cloudflare’s WordPress plugin APO.”

\n\n\n\n

Liu also warned of some speed bumps with the initial rollout, as Cloudflare’s APO supports a limited set of WordPress cookies for bypassing the Cloudflare CDN cache, leaving certain use cases unsupported. APO does not seem to work on subdomains and users are also reporting that it’s not compatible with other caching plugins. It also disables real visitor IP address detection.

\n\n\n\n

Cloudflare is aware of many of these issues, which have been raised in the comments of the announcement, and is in the process of adding more cookies to the list to bypass caching. Due to some plugin conflicts, APO may not be as plug-and-play as it sounds for some users right now, but the product is very promising and should improve over time with more feedback.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 08 Oct 2020 04:18:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: Kick off Block-Based WordPress Theme Development With the Theme.json Creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105832\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:217:\"https://wptavern.com/kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator?utm_source=rss&utm_medium=rss&utm_campaign=kick-off-block-based-wordpress-theme-development-with-the-theme-json-creator\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4674:\"

Gutenberg 9.1 made a backward-incompatible change to its theme.json file (experimental-theme.json while full-site editing is under the experimental flag). This is the configuration file that theme developers will need to create as part of their block-based themes. Staying up to date with such changes can be a challenge for theme authors, but Ari Stathopoulos, a Themes Team representative, wrote a full guide for developers.

\n\n\n\n

Jon Quach, a Principal Designer at Automattic, has also been busy creating a tool to help theme authors transition to block-based themes. He recently built a UI-based project called Theme.json Creator that builds out the JSON code for theme authors. Plus, it is up to date with the most recent changes in the Gutenberg plugin.

\n\n\n\n

Tools like these will be what the development community needs as it gets over the inevitable hump of moving away from the traditional theme development paradigm and into a new era where themes are made almost entirely of blocks and a config file.

\n\n\n\n

While plugin development is becoming more complex with the addition of JavaScript, theme development is taking a sharp turn toward its roots of HTML and CSS. We are barreling toward a future in which far more people will be able to create WordPress themes. Even the possibility of sharing pieces of themes (e.g., template parts and patterns) is on the table. This could not only empower theme designers by lowering the barrier to entry, it could also empower some end-users to make the jump into theme building.

\n\n\n\n

However, the theme.json file is one aspect of future theme authorship that is extremely developer-oriented. JSON is a universal format shared between various programming languages. It is meant to be read by machines and is not quite as human-friendly as other formats. As the theme.json file grows to accommodate more configuration options over time, the less friendly it will become to simply typing keys and values in.

\n\n\n\n

It makes sense to build tools to simplify this part of the theme building process.

\n\n\n\n

That is where the Theme.json Creator tool comes in. Theme authors pick and choose the options they want to support and input custom values. Then, the tool spits out everything in properly-formatted JSON.

\n\n\n\nUsing the Theme.json Creator tool.\n\n\n\n

One big thing the tool does not yet cover is custom CSS variables. This feature is a recent addition to the theme.json specification. It allows theme authors to create any custom property that WordPress will automatically output as CSS. In his announcement post, Stathopoulos covered how to create a typographic scale with custom properties and use those variables for editor features, such as line-height and font-size values.

\n\n\n\n

Currently, Theme.json Creator’s primary focus is on global styles. However, Gutenberg allows theme authors to configure default styles on the block level. For example, theme designers can set the color or typography options for the core Heading block to be different from the default global styles. This provides theme authors with fine-tuned control over every block.

\n\n\n\n

Theme.json Creator does not yet support configuration at this level. However, it would be interesting to see if Quach adds it in the future.

\n\n\n\n

The focus on setting up global styles is a good start for now. This is still an experimental feature. The great thing about it is that it can help theme authors begin to see how one piece of the block-based themes puzzle fits in. It is a starting point for an entirely new method of adding theme support for features when most are accustomed to adding multiple add_theme_support() PHP function calls.

\n\n\n\n

With the direction that theme development seems to be heading, it is easy to imagine that it could evolve into a completely UI-based affair at some point down the line. If templates are made up of blocks and patterns, which anyone can already build with the block editor, and if styles will essentially boil down to a config file, there will be little-to-no programming required to build a basic WordPress theme.

\n\n\n\n

If someone is not already at least jotting down notes for a plugin that allows users to create and package a block-based theme, I would be surprised. For now, Theme.json Creator is removing the need to write code for at least one part of the theme design process.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 07 Oct 2020 20:53:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"WPTavern: Jetpack 9.0 Introduces Loom Block, Twitter Threads Feature, and Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105743\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-introduces-loom-block-twitter-threads-feature-and-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4033:\"
\n\n\n\n

Jetpack’s highly anticipated 9.0 release has landed, introducing some of the new features the team has previewed over the past week. Users can now publish WordPress posts to Twitter as threads. This new feature is available as part of the Publicize module when you have connected a Twitter account.

\n\n\n\n

Posting Twitter threads is a feature that only works with the block editor, as it takes advantage of how content is naturally split into chunks (blocks).

\n\n\n\n

In the comments on his demo post, Automattic engineer Gary Pendergast gave a more detailed breakdown of the logic Jetpack uses to ensure full sentences aren’t broken up in the tweets.

\n\n\n\n

“With the mental model now being focused on mapping blocks to tweets, it’s much easier to make logical decisions about how to handle each block,” Pendergast said. “So, a paragraph block is the text of a tweet, if the paragraph is too long for a single tweet, it tries to split the paragraph up by sentences. If a sentence is too long, then it resorts to splitting by words. Then, if there’s an embed/image/video/gallery block following that paragraph, we can attach it to the tweet containing that paragraph. There are additional rules for other blocks, but that’s the basic process. It then just iterates over all of the supported blocks in the post.”

\n\n\n\n

Pendergast published his post as thread to demonstrate the new feature in action. The advantage of posting a thread from your WordPress site is that it doesn’t end up getting lost in Twitter’s fast-moving timeline. Most important Twitter threads evaporate from public consciousness almost as soon as they are published. Publishing threads from your website ensures they are better indexed and easier to reference in the future.

\n\n\n\n

Jetpack Adds Loom Block for Embedding Screen Recordings

\n\n\n\n

Loom was added to Jetpack as a new oEmbed provider three weeks ago. The video recording service allows for recording camera, microphone, and desktop simultaneously. The service is especially popular in educational settings. Jetpack 9.0 introduces a new Loom block for embedding recordings.

\n\n\n\n\n\n\n\n

“Loom is growing in popularity as it is being recommended more and more to assist in distance learning efforts,” Jetpack Director of Innovation Jesse Friedman said. “Now more than ever we want to be able to help those working, learning, and teaching from home. The Loom block was a natural addition to join the other Jetpack video blocks which now include YouTube, TikTok, DailyMotion, and Vimeo.”

\n\n\n\n

Loom’s free tier allows users to record up to 25 videos, but the Pro plan is free for educators. Friedman confirmed that Jetpack does not have any kind of partnership with Loom. The team decided to support the product to assist professionals, educators, and students. Having it available as a block also makes it more convenient for those using P2 for communication.

\n\n\n\n

As anticipated, Jetpack 9.0 also provides a seamless transition necessary to ensure Instagram and Facebook embeds will continue working after Facebook drops unauthenticated oEmbed support on October 24. The Jetpack team reports that it “partnered with Facebook” to make sure these embeds continue to work with the WordPress.com REST API.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 23:28:38 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:51:\"Post Status: Joost de Valk on WordPress marketshare\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"https://poststatus.com/?p=79914\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:62:\"https://poststatus.com/joost-de-valk-on-wordpress-marketshare/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1193:\"

David Bisset makes his podcast debut for Post Status, as he interviews Joost de Valk, Founder and Chief Product Officer of Yoast, and discusses all things WordPress marketshare related.

\n\n\n\n\n\n\n\n

Links

\n\n\n\n\n\n\n\n

Partner: Jilt

\n\n\n\n

Jilt offers powerful email marketing built for eCommerce. From newsletters to highly segmented automations, Jilt is your one-stop show for eCommerce email. Join thousands of stores that have already earned tens of millions of dollars in extra sales using Jilt. Try Jilt for free

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 22:28:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Brian Krogsgard\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"WPTavern: iThemes Buys WPComplete, Complementing Its Recent Restrict Content Pro Acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105631\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:227:\"https://wptavern.com/ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition?utm_source=rss&utm_medium=rss&utm_campaign=ithemes-buys-wpcomplete-complementing-its-recent-restrict-content-pro-acquisition\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4395:\"

Just one month after publicly announcing its acquisition of Restrict Content Pro (RCP), iThemes purchased WPComplete for an undisclosed amount. The acquisition is for the product, website, and customers only.

\n\n\n\n

Paul Jarvis and Zack Gilbert created the WPComplete plugin in 2016. However, it has outgrown what the duo could maintain and support alone. After the transition period in which the new owners take over, the two will step away from the project.

\n\n\n\n

In essence, WPComplete is a “course completion” plugin. Site owners can create online courses while allowing students/users to mark their work as completed. It also gives students a way to track their progress through courses, which can often boost the potential for them to finish.

\n\n\n\n

“Paul and Jack believe a key to their success has been their ability to keep their team small and manageable,” wrote Matt Danner, the COO at iThemes, in the announcement. “The growth of WPComplete has presented a number of challenges for a team of two people, so the decision was made to start looking towards alternative ownership solutions that could continue to grow WPComplete and provide it with a stable team. iThemes is a perfect fit.”

\n\n\n\n

iThemes customers who have a Plugin Suite or Toolkit membership will get automatic access to the pro version of the WPComplete plugin. For current WPComplete users, Danner said everything should be “business as usual.” However, iThemes has assigned a few of its team members to work on the product and site, so customers should see some new faces.

\n\n\n\n

RCP and WPComplete are obviously complementary products. RCP is a membership plugin that allows site owners to restrict content based on that membership. WPComplete allows site members to mark lessons or coursework as completed. “We’ll be rolling out a new bundle later this month that combines both RCP and WPComplete for course and membership creators to take advantage of these two plugins,” said AJ Morris, the Product Innovation and Marketing Manager at iThemes.

\n\n\n\n

WPComplete is still a young product. The free version of the plugin currently has 2,000+ active installs and a solid 4.7 rating on WordPress.org. If marketed as an extension of the RCP plugin, it automatically puts it in front of the eyes of 1,000s of more potential customers. It should be much easier to grow the plugin as part of a membership bundle.

\n\n\n\n

iThemes is making some bold moves in the membership space. It will be interesting to see if the company makes any other acquisitions that could strengthen its product line and help it become more dominant. There is still a ton of room for growth in the membership segment of the market. There is also the potential for integrations with other major plugins.

\n\n\n\n

“Adding WPComplete to the iThemes product lineup also allows us to move more quickly on some plans we have for Restrict Content Pro,” said Danner in the initial announcement. He also vaguely mentioned a couple of ideas the team had in the works but did not go into detail.

\n\n\n\n

With a little prodding, Morris provided some insight into what they are planning for the immediate future. The biggest first step is tackling integration with the block editor. Currently, WPComplete uses shortcodes. The team’s next step is likely to begin with creating block equivalents for those shortcodes.

\n\n\n\n

“After that, we’ve touched on a few deeper integrations with Restrict Content Pro, like the possibility to restrict courses to memberships,” said Morris.

\n\n\n\n

The iThemes team does not plan to stop with WPComplete as part of its product lineup. One of the goals is to use the plugin for the iThemes website itself.

\n\n\n\n

“We always try to eat our own dogfood when we can,” said Morris. “You’ll see that with RCP and WPComplete early next year as we look to integrate them into our iThemes Training membership.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 06 Oct 2020 20:59:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Exploring Full-Site Editing With the Q WordPress Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105676\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:173:\"https://wptavern.com/exploring-full-site-editing-with-the-q-wordpress-theme?utm_source=rss&utm_medium=rss&utm_campaign=exploring-full-site-editing-with-the-q-wordpress-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7492:\"

I have been eagerly awaiting the moment when I could install a theme and truly test Gutenberg’s full-site editing feature. By and large, each time I have tested it over the past few months, the experience has felt utterly broken. This is why I have remained skeptical of seeing the feature land in WordPress 5.6 this December.

\n\n\n\n

The Q theme by Ari Stathopoulos is the first theme that seems to be a decent working example. Whether that is a stroke of luck with timing or that this particular theme is simply built correctly is hard to tell — Stathopoulos is a team rep for the Themes Team. Gutenberg 9.1 dropped last week with continued work toward site editing.

\n\n\n\n

Q is as experimental as it gets. The Themes Team put out an open call for experimental, block-based themes as far back as March this year. However, not many have taken the team up on this offer. If approved, Q stands to be the first block-based theme to go live in the official WordPress directory. It still has to work its way through the standard review process, awaiting its turn in the coming weeks.

\n\n\n\n

On the whole, full-site editing remains a frustrating and confusing experience. I still remain skeptical about its readiness, even in beta form, to show off to the world in WordPress 5.6.

\n\n\n\n

However, Q is an interesting theme to explore at this point for both end-users and theme developers. Users can install it and start tinkering with the site editing screen via the Gutenberg plugin. Developers can learn how global styles, templates, and template parts fit together from a working theme.

\n\n\n\n

Using the Site Editor

\n\n\n\nEditing a single post in the site editor.\n\n\n\n

The Q theme requires the Gutenberg plugin and its full-site editing mode to be enabled. Generally, requiring a plugin is not allowed for themes in the directory. However, experimental Gutenberg themes are allowed to bypass this guideline.

\n\n\n\n

Stathopoulos pointed out that the theme is highly experimental and should not be used on a production site. However, he is hopeful that it will get more eyes focused on full-site editing.

\n\n\n\n

He mentioned that several items are broken, such as category archives not showing the correct posts. This is a current limitation of the Query block in Gutenberg. However, one of the best ways to find and recognize these types of issues is to have a theme that stays up with the pace of development.

\n\n\n\n

Currently, the site editor feels like it is biting off more than it can chew. Not only can users edit the layout and design of the page, but they can also directly edit existing post content — don’t try this at home unless you are willing for your post titles to get switched to the hyphenated slug. Should the site editor be handling the double-duty of design and content editing? If so, should design and content editing be handled in separate locations in the long term or be merged into one feature?

\n\n\n\n

It feels raw. It is not geared toward users at this point.

\n\n\n\n

The bright spot with the site editor is the current progress on template parts in the editor. Template parts are essentially “modules” that handle one part of the page. For example, the typical theme will have a header and footer template part. Currently, end-users can insert custom template parts or switch one template part for another. This opens a world of possibilities, such as users choosing between multiple header designs (template parts) for their sites.

\n\n\n\nSwitching the header template part.\n\n\n\n

The downside to the entire template system is that it seems so divorced from the site editor that it is hard to believe the average user would understand what is going on. Templates and template parts reside under the Appearance menu in the admin. The Site Editor is a separate, top-level menu item. Without any preexisting knowledge of how these pieces work together, it can be confusing.

\n\n\n\n

Template parts worked for me in the site editor from the outset. However, they did not work on the front end at first. I continually received the “template part not found” message for hours. Then, at some point — whether through magic or a random save that pulled everything together — the feature began to output the previously-missing header and footer template parts.

\n\n\n\n

Glimpse Into the Future of Theme Development

\n\n\n\n

The Q theme has a scant few style rules, which it loads directly in the <head> section of the site in lieu of adding an extra stylesheet. It relies on the stock Gutenberg block styles on the front end with a few minor overrides. Most other custom styles are handled via the global styles system, which pulls from the theme’s experimental-theme.json config file (will be theme.json in the future).

\n\n\n\n

It begs the question of whether themes will necessarily need much in the way of CSS when full-site editing lands.

\n\n\n\n

If WordPress allows users to configure most styles via block options and global styles overrides, themes may not need much more than their config files. After that, it would come down to registering custom block styles and patterns.

\n\n\n\n

If this is the future that we are headed toward, anyone could essentially create a WordPress theme. And, those pieces, such as template parts and patterns, could all be shared between any site. In that future, themes may simply not matter anymore.

\n\n\n\n

Last year, Mike Schinkel proposed deprecating the theme system altogether and replacing it with web components.

\n\n\n\n

“Rather than look for a theme that has all the features one needs — which I have found always limits the choices to zero — a site owner could look for the components and modules they need and then assemble their site from those modules,” he said. “They could pick a header, a footer, a home-page hero, a set of article cards, a pricing module, and so on.”

\n\n\n\n

The more I tinker with full-site editing, the more it feels like that is the lane that it will ultimately merge into. Imagine a future where end-users could pick and choose the pieces they wanted and simply have it look right on the front end.

\n\n\n\n

It is exciting to think about that possibility. Both Schinkel and I have more of a background in programming than we do in design. It makes sense from that sort of analytical mindset to put everything into neat, reusable boxes because reuse is a cornerstone of smart programming.

\n\n\n\n

However, I worry about the state of design in such a system with so many replaceable parts. Will designers be able to take holistic approaches to theme development, creating truly intricate pieces of art? Will that system essentially create a web of cookie-cutter sites? Or, will designers simply find ways to think outside the box while within the constraints of the block system?

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 21:21:13 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Virtual Jamstack Conf to Feature Fireside Chat with Matt Mullenweg and Matt Biilmann, October 6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105680\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6?utm_source=rss&utm_medium=rss&utm_campaign=virtual-jamstack-conf-to-feature-fireside-chat-with-matt-mullenweg-and-matt-biilmann-october-6\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2618:\"
image credit: Jamstack Conf
\n\n\n\n

The greater Jamstack community is coming together on October 6-7, 2020, for a virtual conference. Organizers expect more than 15,000 attendees from around the globe over a two-day span that includes keynotes, sessions, interactive topic tables, workshops, speaker Q&As, and networking opportunities.

\n\n\n\n

Matt Mullenweg will be joining Netlify CEO Matt Biilmann on day 1 at 12PM PDT for a fireside chat moderated by CSS-Tricks Creator Chris Coyier. The chat will go deeper on recent topics of contention, including developer sentiment, complexity, security, and performance. Coyier also plans to discuss how the Jamstack and WordPress communities intersect through headless implementations of the CMS.

\n\n\n\n

A provocative post from TheNewStack at the end of August quoted Mullenweg as saying that “JAMstack is a regression for the vast majority of the people adopting it.” This sparked multiple heated exchanges across blogs and social media. Biilimann, who originally coined the term “Jamstack,” wrote a response to Mullenweg’s remarks, hailing “the end of the WordPress era.”

\n\n\n\n

Live conversations tend to be more cordial than shots fired across the blogosphere. It will be interesting to see if Biilimann cares to join Stackbit CEO Ohad Eder-Pressman in his wager that Jamstack will become the predominant architecture for the web by 2025. The fireside chat should be recorded, in case you cannot catch the live session. Recordings of talks from the previous virtual Jamstack event held in May are available on YouTube.

\n\n\n\n

Today is the last call for registration. Many of the workshops have already sold out, but tickets to the regular sessions on October 6 are still available. Sign up on the event website to get your free ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 05 Oct 2020 20:12:50 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Gutenberg 9.1 Adds Patterns Category Dropdown and Reverts Block-Based Widgets in the Customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105629\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-9-1-adds-patterns-category-dropdown-and-reverts-block-based-widgets-in-the-customizer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5615:\"

Gutenberg 9.1 was released to the public on Wednesday. The team announced over 200 commits from 77 contributors in its release post yesterday. One of the biggest changes to the interface was the addition of a new dropdown selector for block pattern categories. The team also reverted the block-based widgets section in the customizer and added an image size control to the Media & Text block.

\n\n\n\n

One of the main focuses of this release was improving the block-based widgets editor. The feature was taken out of the experimental stage in Gutenberg 8.9 and continues to improve. The widgets screen now uses the same inserter UI as the post-editing screen. However, users can currently only insert regular blocks. Patterns and reusable blocks are still not included.

\n\n\n\n

Theme authors can now control aspects of the block editor via a custom theme.json file. This is part of the ongoing Global Styles project, which will allow theme authors to configure features for their users.

\n\n\n\n

The development team has also added an explicit box-sizing style rule to the Cover and Group blocks. This is to avoid any potential issues with the new padding/spacing options. Theme authors who rely on the block editor styles should test their themes to make sure this change does not break anything.

\n\n\n\n

Better Pattern Organization

\n\n\n\nNew block patterns UI in the inserter.\n\n\n\n

I have been calling for the return of the tabbed pattern categories since Gutenberg 8.0, which was a regression from previous versions. For 11 versions, users have had to scroll and scroll and scroll through every block pattern just to find the one they wanted. The development team has sought to address this issue by using a category dropdown selector. When selecting a specific category, its patterns will appear.

\n\n\n\n

At first, I was unsure about this method over the old tabbed method. However, after some use, it feels like the right direction.

\n\n\n\n

As more and more theme and plugin authors add block pattern categories to users’ sites, the dropdown is a more sensible route. Even tabs could become unwieldy over time. The dropdown better organizes the list of categories and makes the UI cleaner. More than anything, I am enjoying the experience and look forward to this eventually landing in WordPress 5.6 later this year.

\n\n\n\n

Customizer Widgets Reverted

\n\n\n\nReverted widgets panel in the customizer.\n\n\n\n

On the subject of WordPress 5.6, one of its flagship features has been hitting some roadblocks. Block-based widgets are expected to land in core with the December release, but the team just reverted part of the feature. They had to remove the widgets block editor from the customizer they added just two major releases ago.

\n\n\n\n

It was for the best. The customizer’s block-based widgets editor was fundamentally broken. It was not ready for primetime and should have remained in the experimental stage until it was somewhat usable.

\n\n\n\n

“I will approve this since the current state of the customizer in the Gutenberg plugin is broken, and there is no clear path forward about how to fix that,” wrote Andrei Draganescu in the reversion ticket. “With this patch, the normal widgets can still be edited in the customizer and the block ones don’t break it anymore. This is NOT to mean that we won’t proceed with fixing the block editor in the customizer, that is still an ongoing discussion.”

\n\n\n\n

The current state of editing widgets via the customizer is at least workable with this change. If end-users add a block via the admin-side widgets editor, it will merely appear as an uneditable, faux widget named “Block” in the customizer. They will need to edit blocks via the normal widgets screen.

\n\n\n\n

There is no way that WordPress can ship the current solution when 5.6 rolls out. However, we are still two months out. This leaves plenty of time for a fix, but Draganescu’s note that “there is no clear path forward” may make some people a bit uneasy at this stage of development.

\n\n\n\n

Control Image Size for Media & Text

\n\n\n\nImage size dropdown selector for the Media & Text block.\n\n\n\n

One of the bright spots in this update is the addition of an image size control to the Media & Text block. Like the normal Image block, end-users can choose from any registered image size created for their uploaded image.

\n\n\n\n

This is a feature I have been looking forward to in particular. Previously, using the full-sized image often made the page weight a bit heftier than necessary. It is also nice to go along with themes that register sizes for both landscape and portrait orientations, giving users more options.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 20:56:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"WordPress.org blog: The Month in WordPress: September 2020\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://wordpress.org/news/?p=9026\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://wordpress.org/news/2020/10/the-month-in-wordpress-september-2020/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8711:\"

This month was characterized by some exciting announcements from the WordPress core team! Read on to catch up with all the WordPress news and updates from September. 

\n\n\n\n
\n\n\n\n

WordPress 5.5.1 Launch

\n\n\n\n

On September 1, the  Core team released WordPress 5.5.1. This maintenance release included several bug fixes for both core and the editor, and many other enhancements. You can update to the latest version directly from your WordPress dashboard or download it directly from WordPress.org. The next major release will be version 5.6.

\n\n\n\n

Want to be involved in the next release?  You can help to build WordPress Core by following the Core team blog, and joining the #core channel in the Making WordPress Slack group.

\n\n\n\n

Gutenberg 9.1, 9.0, and 8.9 are out

\n\n\n\n

The core team launched version 9.0 of the Gutenberg plugin on September 16, and version 9.1 on September 30. Version 9.0 features some useful enhancements — like a new look for the navigation screen (with drag and drop support in the list view) and modifications to the query block (including search, filtering by author, and support for tags). Version 9.1 adds improvements to global styles, along with improvements for the UI and several blocks. Version 8.9 of Gutenberg, which came out earlier in September, enables the block-based widgets feature (also known as block areas, and was previously available in the experiments section) by default — replacing the default WordPress widgets to the plugin. You can find out more about the Gutenberg roadmap in the What’s next in Gutenberg blog post.

\n\n\n\n

Want to get involved in building Gutenberg? Follow the Core team blog, contribute to Gutenberg on GitHub, and join the #core-editor channel in the Making WordPress Slack group.

\n\n\n\n

Twenty Twenty One is the WordPress 5.6 default theme

\n\n\n\n

Twenty Twenty One, the brand new default theme for WordPress 5.6, has been announced! Twenty Twenty One is designed to be a blank canvas for the block editor, and will adopt a straightforward, yet refined, design. The theme has a limited color palette: a pastel green background color, two shades of dark grey for text, and a native set of system fonts. Twenty Twenty One will use a modified version of the Seedlet theme as its base. It will have a comprehensive system of nested CSS variables to make child theming easier, a native support for global styles, and full site editing. 

\n\n\n\n

Follow the Make/Core blog if you wish to contribute to Twenty Twenty One. There will be weekly meetings every Monday at 15:00 UTC and triage sessions every Friday at 15:00 UTC in the #core-themes Slack channel. Theme development will happen on GitHub

\n\n\n\n
\n\n\n\n

Further Reading:

\n\n\n\n\n\n\n\n

Have a story that we should include in the next “Month in WordPress” post? Please submit it here.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 09:34:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Hari Shanker R\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: Cloudflare Launches New Web Analytics Product Focusing on Privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105446\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/cloudflare-launches-new-web-analytics-product-focusing-on-privacy?utm_source=rss&utm_medium=rss&utm_campaign=cloudflare-launches-new-web-analytics-product-focusing-on-privacy\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2448:\"

In pursuit of “democratizing web analytics,” Cloudflare announced it is launching privacy-first analytics as a new standalone product. The company is entering a market that has been dominated by Google Analytics for years but with a major differentiating feature – it will not track individual users by a cookie or IP address to show unique visits.

\n\n\n\n

Cloudflare Web Analytics defines a visit as “a successful page view that has an HTTP referer that doesn’t match the hostname of the request.” It’s not the same as Google’s “unique” metric, and Cloudflare says it may differ from other reporting tools. Weeding out bots from the total traffic numbers is a nascent feature that Cloudflare is improving as part of its Bot Management product.

\n\n\n\n
\n\n\n\n

Cloudflare Web Analytics is launching with features that are largely similar to Google Analytics but with some unique ways of zooming into different traffic segments and time ranges to see where traffic is originating from.

\n\n\n\n

“The most popular analytics services available were built to help ad-supported sites sell more ads,” Cloudflare product manager Jon Levine said. “But, a lot of websites don’t have ads. So if you use those services, you’re giving up the privacy of your users in order to understand how what you’ve put online is performing.

\n\n\n\n

“Cloudflare’s business has never been built around tracking users or selling advertising. We don’t want to know what you do on the Internet — it’s not our business.”

\n\n\n\n

Paying customers on the Pro, Biz, and Enterprise plans can access their analytics from their dashboards immediately. Cloudflare is also offering the product for free as JavaScript-based analytics for users who are not currently customers. Those who want access to the free plan can sign up for the waitlist.

\n\n\n\n

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 02 Oct 2020 04:03:01 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:67:\"WPTavern: Virtual WordPress Page Builder Summit Kicks Off October 5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105570\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:179:\"https://wptavern.com/virtual-wordpress-page-builder-summit-kicks-off-october-5?utm_source=rss&utm_medium=rss&utm_campaign=virtual-wordpress-page-builder-summit-kicks-off-october-5\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6348:\"

From October 5 through October 9, the first Page Builder Summit will open its virtual doors to all attendees for free. Nathan Wrigley, the podcaster behind WP Builds, and Anchen le Roux, the founder and lead developer of Simply Digital Design, are hosting the five-day online event that focuses on the vast ecosystem of page builders for WordPress.

\n\n\n\n

The summit will include 35 sessions spread out over the event schedule. Each session will last around 30 minutes, so it will be easy to pop in and watch one in your downtime. Sessions will cover a range of builders, including the default WordPress block editor, Elementor, Beaver Builder, Oxygen, Brizy, and Divi.

\n\n\n\n

“It’s an event specifically for users of WordPress page builders, or those curious about what they can do,” said Wrigley. “I feel like a page builder style interface for creating websites is the future for our industry. WordPress itself is moving in this direction with the block editor (a.k.a. Gutenberg). With that in mind, it seemed like a good idea to create a dedicated event to share knowledge about this side of WordPress. We’ve tried to include presentations from as many page builders as we could.”

\n\n\n\n

Wrigley made sure to point out that it is not all geared toward developers, discussing the inner-workings of builders. Some of the sessions focus on marketing, optimization, and conversion, which provides a wider range of topics for potential attendees.

\n\n\n\n

The summit hosts created an online quiz for those who are unsure about which sessions to watch.

\n\n\n\n

There is a small catch. The sessions will be freely available only from the time they begin and the following 24 hours. After that, accessing the videos will come at a premium. Attendees can gain lifetime access to the PowerPack for $47 if they purchase within 15 minutes of signing up. Then, prices will rise to $97 until the event kicks off on October 5. Beyond, the price jumps to $147. The lifetime access includes access to the presentations, transcripts, a workbook, and other bonuses from the speakers.

\n\n\n\n

For those unsure about forking over the cash, they can still watch the sessions during the 24-hour window.

\n\n\n\n

The proceeds from the event will go out to paying affiliate commissions to speakers and partners. Some of it will go into planning and investing in a second summit down the road.

\n\n\n\n

“Both myself and Nathan have specific charities that we want to donate to after the event,” said le Roux. “It was part of our goals to be able to do this, but we didn’t want to make this an official contribution.”

\n\n\n\n

Why a Page Builder Summit?

\n\n\n\n

Both Wrigley and le Roux have their preferred builders. But, the goal of the summit is to offer a wide look at the tools available and help freelancers and agencies better streamline their businesses and create happier clients.

\n\n\n\n

“I’ve been a user of page builders for many years, but only at the point where they truly showed in the editing interface something that almost perfectly reflected what the end-user would see did I get really immersed,” said Wrigley. “Having come from a background in which I built entire websites from a collection of text files (HTML, CSS, PHP, etc.), I was fascinated that we’d reached a point where the learning curve for building a good website was significantly reduced.”

\n\n\n\n

He pointed out that it is not always so simple though. While the same level of coding skills may not be necessary, people must figure out how to navigate their preferred page builder, which can come with its own learning curve.

\n\n\n\n

“You need to learn their way of doing things and how to achieve your design choices,” he said. “It’s always going to work out better if you know the code, but the WordPress mission of democratizing publishing certainly seems to align quite nicely with the adoption of tools, like page builders, which mean that once-difficult tasks are now easier.”

\n\n\n\n

For le Roux, her interest in hosting the Page Builder Summit falls back to her design studio.

\n\n\n\n

“As a developer, my main reason for switching to page builders was around streamlining and creating more efficient but quality websites in the shortest amount of time,” she said. “Especially now that we focus on day rates, creating the best possible website that clients would love fast would not have been possible without page builders.”

\n\n\n\n

The Hosts’ Go-To Builders

\n\n\n\n

“We prefer using Beaver Builder with Themer at Simply Digital Design,” said le Roux. “We use Gutenberg for blog posts or where possible with custom post types or LMS software. However, we’ve also taken on a few Elementor projects where that’s the client’s preferred option.”

\n\n\n\n

Wrigley uses some of the same tools. His main work is on the WP Builds website where he hosts podcasts.

\n\n\n\n

“I have used Beaver Builder’s Themer to create templates for specific layouts, but for content creation within those layouts I’m using the block editor,” said Wrigley. “My content is mainly text and the WordPress editor is utterly remarkable in this situation. I kept the classic editor installed for a few months after WordPress 5.0 came about, but I soon realized that this was folly and that the editing interface of Gutenberg is superior. The ability to insert and move text, buttons, etc. is such a joy to work with, and the iterations that have been made in the last two years make it, in my opinion, the best text editing experience on the web.”

\n\n\n\n

Wrigley sees a future in which the WordPress block editor takes over much of the work that page builders are currently handling. However, that future is “still over the horizon.”

\n\n\n\n

“I’m excited about this future though, and we’ve got a few crystal ball-gazing presentations; trying to work out what that future might look like,” he said.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 20:31:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"WPTavern: Jetpack 9.0 to Introduce New Feature for Publishing WordPress Posts to Twitter as Threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105448\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads?utm_source=rss&utm_medium=rss&utm_campaign=jetpack-9-0-to-introduce-new-feature-for-publishing-wordpress-posts-to-twitter-as-threads\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3318:\"

Jetpack 9.0, coming on October 6, will debut a new feature that allows users to share blog posts as Twitter threads in multiples tweets. A recent version of Jetpack introduced the ability to import and unroll tweetstorms for publishing inside a post. The 9.0 release will run it back the other way so the content originates in WordPress, yet still reaps all the same benefits of circulation on Twitter as a thread.

\n\n\n\n

The new Twitter threads feature is being added as part of Jetpack’s Publicize module under the Twitter settings. After linking up a Twitter account, the Jetpack sidebar options for Publicize allow users to publish to Twitter as a link to the blog or a set of threaded tweets. It’s not just limited to text content – the threads feature will also upload and attach any images and videos included in the post.

\n\n\n\n\n\n\n\n

When first introduced to the idea of publishing a Twitter thread from WordPress, I wondered if threads might lose their trademark pithy punch, since users aren’t forced to keep each segment to the standard length of a tweet. Would each tweet be separated in an odd, unreadable way? The Jetpack team anticipated this, so the thread option adds more information to the block editor to show where the paragraphs will be split into multiple tweets.

\n\n\n\n

“Threads are wildly underused on Twitter,” Gary Pendergast said in a post introducing the feature. “I think a big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.” The tool Pendergast has been working on for Jetpack gives users the best of both worlds.

\n\n\n\n

In response to a comment requesting Automattic “concentrate on tools to get people off social media,” Pendergast said, “If we’re also able to improve the quality of conversations on social media, I think it’d be remiss of us to not do so.” He also credits IndieWeb discussions on Tweetstorms and POSSE (Publish (on your) Own Site, Syndicate Elsewhere) as inspirations for the feature.

\n\n\n\n

For years, blogging advocates have tried to convince those who post lengthy tweetstorms to switch to a publishing medium that is more suitable to the length of their thoughts. The problem is that Twitter users lose so much of the immediate feedback and momentum that their thoughts would have generated when composed as a tweetstorm.

\n\n\n\n

Instead of lecturing people about how they should really be blogging instead of tweetstorming, Jetpack is taking a fresh approach by enabling full content ownership with effortless social syndication. You can test out the experience for yourself by adding the Jetpack Beta Testers plugin and running the 9.0 RC version on your site.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Oct 2020 02:56:46 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: Ask the Bartender: How To WordPress in a Block World?\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105491\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:167:\"https://wptavern.com/ask-the-bartender-how-to-wordpress-in-a-block-world?utm_source=rss&utm_medium=rss&utm_campaign=ask-the-bartender-how-to-wordpress-in-a-block-world\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9755:\"

I love your articles. And now, in the middle of the WordPress revolution, I realized I’m constantly searching for an answer regarding WP these days.

So many things are being said, so many previsions of the future, problems, etc., but, right now, I think I, as a designer, just want to understand one thing that seemed answered already but it’s never clear:

Is WordPress a good choice to build a client’s template where he just has to insert the info that will show in the frontend where I want to? And he doesn’t have to worry about formatting blocks? I love blocks, don’t get me wrong, but will normal templating end?

I just think that having a super CMS, HTML, CSS, and being able to play with a database with ACF is so powerful, that I’m wondering if it’s lost. After so much reading, I still don’t understand if this paradigm is going to disappear.

Right now, I don’t know if it’s best to stop making websites as I used to and adopt block patterns instead.

Ricardo
\n\n\n\n

WordPress is definitely changing. Over the past two years, we have seen much of it reshaped into something different from the previous decade and more. However, this is not new. WordPress has always been a constantly-changing platform. It just feels far too different this time around, almost foreign to many. The platform had to make a leap. Otherwise, it would have started falling behind.

\n\n\n\n

And, it is a big ask of the existing community to come along with it, to take that leap together.

\n\n\n\n

It can be scary as a developer whose livelihood has depended on things working a certain way or who has built tools and systems around pre-block WordPress. Many freelancers and agencies had their world turned upside down with the launch of the block editor. It is perfectly OK to feel a bit lost.

\n\n\n\n

Now, it is time for a little tough love. It has been two years. As a professional, you need to have a plan in place already. Whether that is an educational plan for yourself or a transitional plan for your clients, you should already be tackling projects that leverage the block editor. If you are at a point where you have not been building with blocks, you are now behind. However, you can still catch up and continue advancing in your WordPress career.

\n\n\n\n

There are so many changes coming down the pipeline that anyone who plans to develop for WordPress will be in continual education mode for years to come.

\n\n\n\n

When building for clients, the biggest thing to remember is that it is not about you. It is about getting something into the hands of your clients that addresses their specific needs. Freelancers and agencies need to often be the Jacks and Jills of all trades. Sometimes, this even means having a backup CMS or two that you can use that are not named WordPress. It helps to be well-rounded enough to jump around when needed, especially if you are not at a point in your career where you can demand specific work and pass on jobs that would put food on the table.

\n\n\n\n

It is also easy to look at every job as a nail and WordPress as the hammer. Or, even specific plugins as the tool that will always get the job done. I have seen developers in the past rely on tools like ACF, CMB2, or Meta Box but could not code a custom metadata solution when necessary to save their life. Sometimes a bigger toolbox is necessary.

\n\n\n\n

Every WordPress developer needs a solid, foundational understanding of the languages that WordPress uses. Gone are the days of skating by on HTML, CSS, and PHP knowledge. You need to learn JavaScript deeply. Matt Mullenweg, the co-founder of WordPress, was not joking around when he said this back in 2015. It holds true more and more each day. In another five years, it will tough to be a developer in the WordPress world without knowing JavaScript, at least for backend work.

\n\n\n\n

It also depends on what types of sites you are building. If you are primarily handling front-end design, you will likely be able to get by with a lower skill level. You will just need to know the “WordPress way” of building themes.

\n\n\n\n

Within the next year, you should be able to build just about any theme design with decent CSS and HTML knowledge along with an understanding of how the block system works. Full-site editing and block-based themes will change how we build the front end of the web. It is going to be a challenging transition at first, especially for those of us who are steeped in traditional theme development, but client sites will often be far easier to build. I highly recommend the twice-monthly block-based themes meetings if your focus is on the front end.

\n\n\n\n

Block Templates

\n\n\n\n

Based on your question, I am going to make some assumptions. You have a history of essentially building out meta boxes via ACF where the client just pops in their data. Then, you format that data on the front end. You are likely mixing this with custom post types (CPTs). This is a fairly common scenario.

\n\n\n\n

One of the great things about the block system is that you can lock the post editor for individual CPTs. WordPress already has you covered with its block templates feature, which allows you to define just what a post should look like. You can set up which blocks you want to appear and have the client drop their content in. At the moment, this feature is limited to the post type level. However, it should grow more robust over time, particularly when it works alongside the traditional “page templates” system.

\n\n\n\n

Block templates are a powerful tool in the ol’ toolbox that will come in handy when building client sites.

\n\n\n\n

Block Patterns

\n\n\n\n

You do not have to stop making websites as you are accustomed to at the moment. However, you should start leveraging new block features as they become available and make sense for a specific project. I am a fanatic when it comes to block patterns, so my bias will definitely show.

\n\n\n\n

The biggest thing with block patterns and clients is education. For the uninitiated, you will need to spend some time teaching them how to insert a pattern and how it can be used to their advantage. That is the hurdle you must jump.

\n\n\n\n

For many of the users that I have seen introduced to well-designed patterns, they have fallen in love with the feature. Even many who were reluctant to switch to the block editor became far more comfortable working with it after learning how patterns worked. This is not the case for every user or client, but it has been a good introduction point to the block editor for many.

\n\n\n\n

To answer your question regarding patterns: yes, you should absolutely begin to adopt them.

\n\n\n\n

ACF Is Evolving

\n\n\n\n

Because you are accustomed to ACF, you should be aware that the framework is evolving to keep up with the block editor. Version 5.8.0 introduced a PHP framework for creating custom blocks over a year ago. And, it has been improving ever since. There are even projects like ACF Blocks, which will provide even more tools for your arsenal.

\n\n\n\n

It is important to learn from what some of the larger agencies are doing. Read up on how WebDevStudios is tackling block development. The company also has an open-source block library for ACF.

\n\n\n\n

Solving Problems

\n\n\n\n

Your job as a developer is to be a problem solver. Whatever system you are building with is merely a part of your toolset. You need to be able to solve issues regardless of what tool you are using. At the end of the day, it is just code. If you can learn HTML, you can learn CSS. If you can learn those, you can learn PHP. And, if you can manage PHP, you can certainly pick up JavaScript.

\n\n\n\n

A decade or two from now, you will need to learn something else to stay relevant in your career. Web technology changes. You must change with it. Always consider yourself a student and continue your education. Surround yourself and learn from those who are more advanced than you. Emulate, borrow, and steal good ideas. Use what you have learned to make them great.

\n\n\n\n

There is no answer I can give that will be perfect for every scenario. Each client is unique, and you will need to decide the best direction for each.

\n\n\n\n

However, yes, you should already be on the path to building with a block-first mindset if you plan to continue working with WordPress for the long haul. Immerse yourself in the system. Read, study, and build something any chance you get.

\n\n\n\n

This is the first post in the Ask the Bartender series. Have a question of your own? Shoot it over.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Sep 2020 20:35:25 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"WPTavern: Supercharge the Default WordPress Theme With Twentig, a Toolbox for Twenty Twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:225:\"https://wptavern.com/supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty?utm_source=rss&utm_medium=rss&utm_campaign=supercharge-the-default-wordpress-theme-with-twentig-a-toolbox-for-twenty-twenty\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6455:\"Custom page pattern from the Twentig plugin.\n\n\n\n

I am often on the hunt for those hidden gems when it comes to block-related plugins. I like to see the interesting places that plugin authors venture. That is why it came as a surprise when someone recommended I check out the Twentig plugin a few days ago. Somehow, it has flown under my radar for months. And, it has managed to do this while being one of the more interesting plugins for WordPress I have seen in the past year.

\n\n\n\n

Twentig is a plugin that essentially gives superpowers to the default Twenty Twenty theme. Diane and Yann Collet are the sibling co-founders and brains behind the plugin.

\n\n\n\n

While I have been generally a fan of Twenty Twenty since it was first bundled in core, it was almost a bit of a letdown in some ways. It was supposed to be the theme that truly showcased what the block editor could do — and it does a fine job of styling the default blocks — but there was a lot of potential left on the table. The Twentig plugin turns Twenty Twenty into something worthier of a showcase for the block editor. It is that missing piece, that extra mile in which WordPress should be marching its default themes.

\n\n\n\n

While the new Twenty Twenty-One default theme is just around the corner, Twentig is breathing new life into the past year’s theme. The developers behind the plugin are still fixing bugs and bringing new features users.

\n\n\n\n

Of its 34 reviews on WordPress.org, Twentig has earned a solid five-star rating. That is a nice score for a plugin with only 4,000 active installations. As I said, it has flown under the radar a bit, but the users who have found it have obviously discovered something that adds those extra touches to their sites they need.

\n\n\n\n

What Does Twentig Do?

\n\n\n\n

It is a toolbox for Twenty Twenty. The headline feature is its block editor features, such as custom patterns and page layouts. It also offers a slew of customizer options that allow end-users to put their own design spin on the default theme. However, my interest is primarily in how it extends the block editor.

\n\n\n\n

Let’s get this out of the way up front. Twentig’s one downside is that it adds a significant amount of additional CSS on top of the already-heavy Twenty Twenty and block editor styles. I will blame the current lack of a full design system from WordPress on most of this. Styling for the block editor can easily bloat a stylesheet. Adding an extra 100+ kb per page load might be a blocker for some who would like to try the plugin. Users will need to weigh the trade-offs between the additional features and the added page size.

\n\n\n\n

The thing that makes Twentig special is its extensive patterns and pages library, which offers one-click access to hundreds of layouts specifically catered to the Twenty Twenty theme.

\n\n\n\nInserting one of the hero patterns.\n\n\n\n

It took me a few minutes to figure out how to access the patterns — mainly because I did not read the manual. I expected to find them mixed in with the core patterns inserter. However, the plugin adds a new sidebar panel to the editor, which users can access by clicking the “tw” icon. After seeing the list of options, I can understand why they probably would not fit into WordPress’s limited block and patterns inserter UI.

\n\n\n\n

It would be easier to list what the plugin does not have than to go through each of the custom patterns and pages.

\n\n\n\n

The one thing that truly sets this plugin apart from the dozens of other block-library types of plugins is that there are no hiccups with the design. Almost every similar plugin or tool I have tested has had CSS conflicts with themes because they are trying to be a tool for every user. Twentig specifically targets the Twenty Twenty theme, which means it does not have to worry about whether it looks good with the other thousands of themes out there. It has one job, which is to extend its preferred theme, and it does it with well-designed block output.

\n\n\n\n

The other aspect of this is that it does not introduce new blocks. Every pattern and page layout option uses the core WordPress blocks, which includes everything from hero sections to testimonials to pricing tables to event listings. And more.

\n\n\n\n

Twentig does not stop adding features to the block editor with custom patterns. The useful and sometimes fun bits are on the individual block level, and I have yet to explore everything. I continue to discover new settings each time I open my editor.

\n\n\n\n

Whether it is custom pullquote styles, a photo image frame, or an inner border tweak to the Cover block (shown below), the plugin adds little extras that push what users can do with their content.

\n\n\n\nInner border style for the Cover block.\n\n\n\n

Each block also gets some basic top and bottom margin options, which comes in handy when laying out a page. At this point, I am simply looking forward to discovering features I have yet to find.

\n\n\n\n

Areas Themes Should Explore

\n\n\n\n

One of the things I dislike about many of these features being within the Twentig plugin is that I would like to see them within the Twenty Twenty theme instead. Obviously not every feature belongs in the theme — some features firmly land in plugin territory. The default WordPress themes should also leave some room for plugin authors to explore. But, shipping some of the more prominent patterns and styles with Twenty Twenty would make a more robust experience for the average end-user looking to get the most out of blocks.

\n\n\n\n

Block patterns were not a core WordPress feature when Twenty Twenty landed. However, for the upcoming Twenty Twenty-One theme, which is expected to bundle some unique patterns, the design team should explore what the Twentig plugin has brought to the current default. That is the direction that theme development should be heading, and theme developers can learn a lot by stealing borrowing from this plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 22:00:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:105:\"WPTavern: Coming in Jetpack 9.0: Shortcode Embeds Module Updated to Handle Facebook and Instagram oEmbeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105381\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:253:\"https://wptavern.com/coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds?utm_source=rss&utm_medium=rss&utm_campaign=coming-in-jetpack-9-0-shortcode-embeds-module-updated-to-handle-facebook-and-instagram-oembeds\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2938:\"

Facebook and Instagram are dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers in an upcoming release. After evaluating third-party solutions, WordPress VIP is recommending its partners enable Jetpack’s Shortcode Embeds module. Jetpack will be shipping the update in its 9.0 release, which is anticipated to land prior to the October 24th deadline.

\n\n\n\n

The module is being updated to provide a seamless transition for users who might otherwise be negatively impacted by Facebook’s upcoming API change. WordPress contributors have run some simulations but are not yet sure what will happen to the display for previously embedded content.

\n\n\n\n

“It is possible that they change the contents of the JS file to manipulate cached embeds, perhaps to display a warning that the site is using an old method to embed content or that the request is not properly authenticated,” Jonathan Desrosiers commented on the trac ticket for removing the oEmbed providers.

\n\n\n\n

WordPress.com VIP roughly outlined what users can expect if they do not enable a solution to begin authenticating oEmbeds:

\n\n\n\n

By default, WordPress caches oEmbed contents in post metadata. These embeds will continue to display in previously-published content. If you edit older posts in the Block Editor, regardless of whether you update the post by saving changes, the embeds in the post will no longer be cached and will stop displaying. If you view these older posts using the Classic Editor, so long as the post is not re-saved, the embeds will continue to function and display properly. If you update the post content, the embed will cease functioning unless you have a mitigation installed.

\n\n\n\n

Although WordPress VIP recommends using the Jetpack module as the best solution, self-hosted WordPress users may want to investigate other options if they are not already using Jetpack. oEmbed Plus is a free plugin created specifically for solving the problem of WordPress dropping Facebook and Instagram as oEmbed providers but it is more work to set up and configure. It requires users to register as a Facebook developer and create an app to get API credentials.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 21:18:52 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"WPTavern: W3C Selects Craft CMS for Redesign Project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105265\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:149:\"https://wptavern.com/w3c-selects-craft-cms-for-redesign-project?utm_source=rss&utm_medium=rss&utm_campaign=w3c-selects-craft-cms-for-redesign-project\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9407:\"

W3C has selected Craft CMS over Statamic for its redesign project, after dropping WordPress from consideration in an earlier round of elimination:

\n\n\n\n

In the end, our decision mostly came down to available resources. Craft had already committed to reach AA compliance in Craft 4 (it is currently on version 3.5, the release of version 4 is planned for April 2021). They had also arranged for an external agency to provide them with accessibility issues to tackle weekly. In the end, they decided instead to hire an in-house accessibility specialist to perform assessments and assist the development team in adopting accessibility patterns in the long run.

W3C CMS Selection Report
\n\n\n\n

Last week we published a post urging W3C to revisit Gutenberg for a fair shake against the proprietary CMS’s or consider adopting another open source option. During the selection process, Studio 24, the agency contracted for the redesign, cited its extensive experience with WordPress as the reason for not performing any accessibility testing on more recent versions of Gutenberg.

\n\n\n\n

When asked if the team contacted anyone from WordPress’ Accessibility Team during the process or put Gutenberg through the same tests as the proprietary CMS’s, Studio 24 founder Simon Jones confirmed they had not.

\n\n\n\n

“No, we only reached out to the two shortlisted CMS’s” Jones said. “I’m afraid we didn’t have time to do more. We did test GB a few months ago based on editing content – though it wasn’t the only factor in our choice. As an agency we do plan to keep reviewing GB in the future.”

\n\n\n\n

In response to our concerns regarding licensing, Jones penned an update titled “On not choosing WordPress,” which further elaborated on the reasons why the agency was not inclined towards using or evaluating the new editor:

\n\n\n\n

From a business perspective I also believe Gutenberg creates a complexity issue that makes it challenging for use by many agencies who create custom websites for clients; where we have a need to create lots of bespoke blocks and page elements for individual client projects.

The use of React complicates front-end build. We have very talented front-end developers, however, they are not React experts – nor should they need to be. I believe front-end should be built as standards-compliant HTML/CSS with JavaScript used to enrich functionality where necessary and appropriate.

As of yet, we have not found a satisfactory (and profitable) way to build custom Gutenberg blocks for commercial projects. 

\n\n\n\n

The CMS selection report also stated that W3C needs the CMS to be “usable by non-sighted users” by the launch date, since some members of the staff who contribute to the website are non-sighted.

\n\n\n\n

Since the most recent version of WordPress was not tested in comparison with the proprietary CMS’s, it’s unclear how much better they handle accessibility. Ultimately, W3C and Studio 24 were more comfortable moving forward with a proprietary vendor that was able to make certain assurances about the future accessibility of its authoring tool, despite having a smaller pool of contributors.

\n\n\n\n

“[I’m] also deeply curious since the cursory notes on accessibility for both of the reviewed CMSes seem to highlight a ton of issues like ‘Buttons and Checkboxes are built using div elements’ or most inputs lacking clear focus styles,” Gutenberg technical lead Matías Ventura said. “An element like the Calendar for choosing a post date seems entirely inoperable with keyboard on Craft, for example, while WordPress’ has had significant effort and rounds of feedback poured into that element alone to make it fully operable.”

\n\n\n\n

WordPress developer Anthony Burchell commented on how using a relatively new proprietary CMS seemed counter to W3C’s stated goal to select an option on the basis of longevity. Craft CMS’s continued success is contingent upon its business model and the company’s ability to remain profitable.

\n\n\n\n

“FOSS have the same opportunity of direct access to developers,” Burchell said. “I recognize there are many accessibility shortcomings in popular software, but I think it’s more constructive to rally behind and contribute, not use a proprietary CMS that boasts beer budget in their guidelines.”

\n\n\n\n

On the other side of the issue, accessibility advocates took the W3C’s decision as a referendum on Gutenberg’s continued struggles to meet WCAG AA standards. WordPress accessibility specialist Amanda Rush said it was “nice to see the W3C flip tables over this.”

\n\n\n\n

“Gutenberg is not mature software,” accessibility consultant and WordPress contributor Joe Dolson said in a post elaborating on his comments at WPCampus 2020 Online. He emphasized the lack of stability in the project that Studio 24 alluded to when documenting the reasons against using WordPress.

\n\n\n\n

“It is still undergoing rapid changes, and has grand goals to add a full-site editing experience for WordPress that almost guarantees that it will continue to undergo rapid changes for the next few years,” Dolson said. “Why would any organization that is investing a large amount into a site that they presumably hope will last another 10 years want to invest in something this uncertain?”

\n\n\n\n

Dolson also said the accessibility improvements he referenced regarding the audit were only a small part of the whole picture.

\n\n\n\n

“They only encompass issues that existed in the spring of 2019,” he said. “Since then, many features have been added and changed, and those features both resolve issues and have created new ones. The accessibility team is constantly playing catch up to try and provide enough support to improve Gutenberg. And even now, while it is more or less accessible, there are critical features that are not yet implemented. There are entirely new interface patterns introduced on a regular basis that break prior accessibility expectations.”

\n\n\n\n

WordPress is also being used by millions of people who are constantly reporting issues to fuel the software’s continued refinement, which increases the backlog of issues. Unfortunately, Studio 24 did not properly evaluate Gutenberg against the proprietary CMS’s in order to determine if these software projects are in any better shape.

\n\n\n\n

Instead, they decided that Craft CMS’s community was more receptive to collaborating on issues without reaching out to WordPress. Given the W3C’s stated preference for open source software, WordPress, as the only CMS under consideration with an OSD-compliant license, should have received the same accessibility evaluation.

\n\n\n\n

“I can’t make any statements that would be meaningful about the other content management systems under consideration; but if WordPress wants to be taken seriously in environments where accessibility is a legal, ethical, and mission imperative, there’s still a lot of work to be done,” Dolson said.

\n\n\n\n

Studio 24’s evaluation may not have been equitable to the only open source CMS under consideration, but the situation serves to highlight a unique quandary: when using open source software becomes the impractical choice for organizations requiring a high level of accessibility in their authoring tools.

\n\n\n\n

“Studio 24 ultimately determined that working with a CMS to make it better was more possible with a smaller, proprietary vendor than with a large open-source project,” accessibility advocate Brian DeConinck said. “Project leadership would be more receptive, and the smaller community means changes can be made more quickly. That should prompt a lot of soul-searching for…well, everyone. What does that say about the future of open source?”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 04:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"Gary: More than 280 characters\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"https://pento.net/?p=5405\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"https://pento.net/2020/09/29/more-than-280-characters/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5187:\"

It’s hard to be nuanced in 280 characters.

\n\n\n\n

The Twitter character limit is a major factor of what can make it so much fun to use: you can read, publish, and interact, in extremely short, digestible chunks. But, it doesn’t fit every topic, ever time. Sometimes you want to talk about complex topics, having honest, thoughtful discussions. In an environment that encourages hot takes, however, it’s often easier to just avoid having those discussions. I can’t blame people for doing that, either: I find myself taking extended breaks from Twitter, as it can easily become overwhelming.

\n\n\n\n

For me, the exception is Twitter threads.

\n\n\n\n

Twitter threads encourage nuance and creativity.

\n\n\n\n

Creative masterpieces like this Choose Your Own Adventure are not just possible, they rely on Twitter threads being the way they are.

\n\n\n\n
\n

Being Beyoncé’s assistant for the day: DONT GET FIRED THREAD pic.twitter.com/26ix05Hkhp

— green chyna (@CORNYASSBITCH) June 23, 2019
\n
\n\n\n\n

Publishing a short essay about your experiences in your job can bring attention to inequality.

\n\n\n\n
\n

DOWNTOWN BROOKLYN: I\'m working arraignments tonight, representing poor New Yorkers who were arrested yesterday on Thanksgiving.

It was the coldest Thanksgiving in more than a century. Tonight\'s also bitterly cold, even in the courtroom. I\'m wearing my scarf & coat.

— Rebecca Kavanagh (@DrRJKavanagh) November 24, 2018
\n
\n\n\n\n

And Tumblr screenshot threads are always fun to read, even when they take a turn for the epic (over 4000 tweets in this thread, and it isn’t slowing down!)

\n\n\n\n
\n

Tumblr textposts thread, probably?

— we are a family forged in bureaucracy (@ex_aItiora) August 26, 2019
\n
\n\n\n\n

Everyone can think of threads that they’ve loved reading.

\n\n\n\n

My point is, threads are wildly underused on Twitter. I think I big part of that is the UI for writing threads: while it’s suited to writing a thread as a series of related tweet-sized chunks, it doesn’t lend itself to writing, revising, and editing anything more complex.

\n\n\n\n

To help make this easier, I’ve been working on a tool that will help you publish an entire post to Twitter from your WordPress site, as a thread. It takes care of transforming your post into Twitter-friendly content, you can just… write. \"?\"

\n\n\n\n

It doesn’t just handle the tweet embeds from earlier in the thread: it handles handle uploading and attaching any images and videos you’ve included in your post.

\n\n\n\n\n\n\n\n

All sorts of embeds work, too. \"?\"

\n\n\n\n
\n
\n
\n\n\n\n

It’ll be coming in Jetpack 9.0 (due out October 6), but you can try it now in the latest Jetpack Beta! Check it out and tell me what you think. \"?\"

\n\n\n\n

This might not fix all of Twitter’s problems, but I hope it’ll help you enjoy reading and writing on Twitter a little more. \"?\"

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 29 Sep 2020 02:33:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Gary\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"WPTavern: Themes Team Releases a Web Fonts Loader, Likely To Prohibit Hotlinking Any Off-Site Assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105363\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:243:\"https://wptavern.com/themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets?utm_source=rss&utm_medium=rss&utm_campaign=themes-team-releases-a-web-fonts-loader-likely-to-prohibit-hotlinking-any-off-site-assets\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5815:\"

Last Friday, the WordPress Themes Team announced the release of its new Webfonts Loader project. It is a drop-in script that allows theme authors to load web fonts from the user’s site instead of a third-party CDN. The secondary message included in the team’s announcement is that it no longer plans to allow themes to hotlink Google Fonts in the future.

\n\n\n\n

Throughout most of the team’s history, it has not allowed themes to hotlink or use CDNs for hosting theme assets, such as CSS, JavaScript, and fonts. The one exception to this rule was the use of Google Fonts. This allowed themes to have richer typography options at their disposal from what the team has generally declared a reliable source.

\n\n\n\n

“The exception was made because there was no practical way to not have the exception at the time,” said Aria Stathopoulos, a Themes Team representative and developer behind the Webfonts Loader project. “The exception for Google Fonts was made out of necessity. Now that there is another way, the exception will not be necessary.”

\n\n\n\n

In effect, disallowing the Google Fonts CDN would not be a new ban. It would be a removal of an exception to the existing ban.

\n\n\n\n

Google Fonts has become so embedded into the theme developer toolset over the years, there was no way the team could simply pull the plug and prohibit the use of the CDN overnight. If the Themes Team members wanted to focus more on privacy, they would need to build a tool that made it dead simple for theme authors to use.

\n\n\n\n

There is no hard deadline for when the team will remove the exception for Google Fonts, and it is not set in stone at this point. Stathopoulos said removing it has been the goal from the beginning, disallowing all CDNs. However, it took a while to find an efficient way to handle this. With a viable alternative in place, they can discuss moving forward.

\n\n\n\n

Webfonts Loader for Themes

\n\n\n\n

The Webfonts Loader project keeps it simple for theme authors. It introduces a new wptt_get_webfont_styles() function that developers can plug in a stylesheet URL. Once a page is loaded with that function call, it will download the fonts locally to a /fonts folder in the user’s /wp-content directory. This way, fonts will always be served from the user’s site.

\n\n\n\n

The system is not limited to Google Fonts either. Any URL that serves CSS with an @font-face {} rule will work. It does not currently include authentication for CDNs that require API keys, such as Adobe Fonts. However, that is something the team might add in the future.

\n\n\n\n

“For end-users, moving away from CDNs and locally hosting web fonts will improve performance (fewer handshake roundtrips for SSL), and is the privacy-conscious choice,” said Stathopoulos. “The only ‘valid privacy concern’ is that the web fonts’ CDN does not disclose information that is fundamental to the GDPR: what information gets logged, for how long these logs remain, how they are processed, if there is any cross-referencing with all the other wealth of information the company has from users, etc. The concern is a lack of disclosure and information. If a site owner doesn’t know what kind of information a third-party logs for its visitors, then they should ethically not enforce that on their visitors. With this package, the CDN is removed from the equation and the font still gets served fast — if not faster.”

\n\n\n\n

A Path to Core WordPress

\n\n\n\n

Today, there is now a broader focus on privacy concerns related to third-party resources, particularly with tech giants like Google. Such concerns extend to whether third parties are tracking users or collecting data. Additional concerns are around whether sites are disclosing the use of third-party resources, which may be required in some jurisdictions. Site owners who are often unable to work through the web of potential issues are stuck in the middle.

\n\n\n\n

Jono Alderson opened a ticket to create an API for loading web fonts locally in core WordPress in February 2019. It is a lengthy and detailed proposal, but it has yet to see much buy-in outside of a handful of developers.

\n\n\n\n

“If such a script is standardized and included in WordPress core, one of the main benefits would be more respect for the end-user’s privacy,” said Stathopoulos. “In the end, that’s all privacy is about: respecting users.”

\n\n\n\n

A standard API like Alderson proposes could solve some issues. Namely, it would virtually eliminate any privacy concerns. However, loading fonts locally could allow WordPress to optimize font loading and would create a shared system where plugins and themes do not load duplicate assets because of the current limitations of the enqueuing system. A standard API would also put the responsibility of efficiently loading fonts on WordPress’s shoulders instead of theme and plugin developers.

\n\n\n\n

The Themes Team’s new project is a solid start and strengthens the current proposal.

\n\n\n\n

“If we’re serious about WordPress becoming a fast, privacy-friendly platform, we can’t rely on theme developers to add and manage fonts without providing a framework to support them,” wrote Alderson in the ticket.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 28 Sep 2020 20:58:48 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:87:\"WPTavern: Fuxia Scholz First to Pass 100K Reputation Points on WordPress Stack Exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105282\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:219:\"https://wptavern.com/fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange?utm_source=rss&utm_medium=rss&utm_campaign=fuxia-scholz-first-to-pass-100k-reputation-points-on-wordpress-stack-exchange\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5096:\"

Fuxia Scholz, a prolific WordPress Stack Exchange (WPSE) contributor, is the first member to reach 100,000 reputation points. The popular Q&A community site rewards expert advice by floating the highest quality answers to the top, allowing users to earn reputation points. The gamified help community has proven to be more motivating for developers than many traditional forums, since the upvotes communicate how useful their answers are to others.

\n\n\n\n
\n\n\n\n

Scholz started on Stack Overflow a few months before WordPress had its own site. She wrote around 50 answers and made connections with other WordPress developers ahead of the site’s beta phase in June 2010. Once the site graduated and got its own logo and design, Scholz started writing more.

\n\n\n\n

“One core idea for all Stack Exchange sites is gamification: You earn reputation, and you get access to certain privileges,” Scholz said.

\n\n\n\n

“You can say I got a bit addicted. My favorite questions were about problems for which I didn’t know the answer, and couldn’t find one with a search engine, because no one else had solved that before. I used my answers to teach myself, and I learned a lot this way! In May 2011 my reputation on WPSE was already higher than on Stack Overflow, and for the next years it went up in a steep curve.” Ten years after WPSE launched, Scholz has become the first to reach 100,000 reputation points.

\n\n\n\n

“What reputation and karma do is send a message that this is a community with norms, it’s not just a place to type words onto the internet. (That would be 4chan.)” Stack Overflow co-creator Joel Spolsky said. “We don’t really exist for the purpose of letting you exercise your freedom of speech. You can get your freedom of speech somewhere else. Our goal is to get the best answers to questions. All the voting makes it clear that we have standards, that some posts are better than others, and that the community itself has some norms about what’s good and bad that they express through the vote.”

\n\n\n\n

The reputation points were originally inspired by Reddit Karma. Spolsky admits that the points not a perfect system but they do tend to “drive a tremendous amount of good behavior.” Gamification can shape and encourage certain behaviors but Spolsky said it’s a weak force that cannot motivate people to do things they are not already interested in doing. For Scholz, it was the community aspect and an earned sense of ownership and responsibility that kept her hooked.

\n\n\n\n

“In 2012, the community elected me as a moderator, and that changed a lot,” she said. “Now it wasn’t just a game anymore, it was a duty. I felt responsible for the site. I still do. I also found some friends on there. We met at WordCamps and in private, and worked together on different projects.”

\n\n\n\n

Scholz no longer works in development and said she doesn’t care about WordPress anymore, but she is still a regular contributor on the WPSE.

\n\n\n\n

“I switched careers and work as a writer, translator, and community manager for Chess24.com now,” she said. “But I still care about the site WordPress Stack Exchange! I keep an eye on new tags, handle flagged posts and comments, try to make every new user feel welcome, and I search for people who are abusing the system — vote fraud and spam. And, very rarely, I even write an answer, because I still know all this stuff.

\n\n\n\n

“Checking the site has become a part of my daily routine, like feeding the cat.”

\n\n\n\n

This daily habit has snowballed into Scholz racking up more than 2,000 answers. She is getting upvotes on many of her old answers nearly every day, which is what pushed her over the 100k milestone.

\n\n\n\n

“There is a lot to say about the way our site developed over the years,” Scholz said. “I’m not happy about some things. The enthusiasm of the early days is gone. We don’t have enough regulars, there is no discussion about the site on WordPress Development Meta Stack Exchange, and our chat, once very active, funny, and friendly, is now almost dead.

\n\n\n\n

“Maybe that’s normal, I don’t know. But it’s still ‘my’ site. Reputation and badges don’t really mean anything for a long time now, but keeping the site working, useful and friendly is more important.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 26 Sep 2020 15:27:03 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: PhotoPress Plugin Seeks to Revolutionize Photography for WordPress Users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=104770\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:209:\"https://wptavern.com/photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users?utm_source=rss&utm_medium=rss&utm_campaign=photopress-plugin-seeks-to-revolutionize-photography-for-wordpress-users\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5638:\"

Peter Adams, the owner of the PhotoPress plugin, announced a couple of weeks ago that now is the time for his project to take center stage. “It’s Time for PhotoPress,” read the title of his post in which he laid out a four-phase plan for the future of his project.

\n\n\n\n

Adams is no stranger to manipulating WordPress to suit the needs of photographers. He described photography as his first love and second career. He initially found the art of taking photos in high school and set off to college to become a professional photographer in the early ’90s.

\n\n\n\n

As his university graduation loomed, he was recruited to run web development for an internet ad agency that built websites for Netscape, Bill Clinton’s White House, and dozens of Fortune 500 companies. He spent the next 15 years starting or running tech companies before returning to his roots as a photographer.

\n\n\n\n

Today, he photographs for various magazines and companies. And, that’s where his PhotoPress project comes in.

\n\n\n\n

“As far as WordPress has come, it is at risk of losing an entire generation of photographers to photo website services such as Photoshelter, SmugMug, Squarespace, and PhotoFolio,” he said. Adams wants to change that, making WordPress the go-to platform for photographers around the world.

\n\n\n\n

The Jetpack of Photography Plugins

\n\n\n\n

If you dig into the history of the PhotoPress plugin on WordPress.org, it seems to have a 15-year history. However, this is not the same plugin that was published a decade and a half ago by a different developer. The original plugin is now defunct, and Adams took over when the name was freed up on the directory.

\n\n\n\n

Adams wrote in his announcement post that WordPress has done a great job of delivering several media features over the years. “Yet despite that, there are still many rough edges and missing features that keep WordPress from being the first choice for a photographer that needs to publish a beautiful portfolio of their work, put their image catalog/archive online, or showcase a photo editorial/project.”

\n\n\n\n

He outlined a list of 10 specific problem areas that he wants to address in a “Jetpack-like” plugin for photographers. This is the bread and butter of the first of the planned four phases, which he said is about 80% finished. He had originally planned to develop PhotoPress as a series of separate plugins, each addressing a specific problem. Now, it is a single plugin with modules than can be enabled or disabled.

\n\n\n\n

When asked why the “right time” is now, Adams explained it is because the Gutenberg (block editor) project is a giant leap forward in usability in terms of creating photography blogs.

\n\n\n\nPhotoPress Gallery block in the editor.\n\n\n\n

“Photogs are a rare breed of non-technical users with high design sense,” he said. “Things that I used to have to teach photographers to do using shortcode syntax and custom CSS can now be simple controls with live feedback inside a Gutenberg block. It’s really a game-changer for getting people comfortable with customizing things like gallery styling — which is the number one thing photographers need to do.”

\n\n\n\n

The primary piece of the PhotoPress plugin is its custom PhotoPress Gallery block. It allows users to choose between a range of gallery styles, such as columns, masonry, justified, and mosaic. Each style has its own options. Images can also be launched into a slideshow when one is clicked.

\n\n\n\n

Based on some quick tests, the block’s front-end output will go farther with some themes than others. This is mainly because of conflicting CSS and issues which can be solved by testing against more themes.

\n\n\n\n

Aside from the block, the plugin can automatically extract image metadata and group that data through custom taxonomies, such as cameras, lenses, locations, keywords, and more. WordPress stores this information out of the box, but it is hidden away as post meta. The plugin uses the taxonomy system to make it manageable for end-users.

\n\n\n\n

Ultimately, Adams set out to create a photography plugin that fits in with the WordPress admin user interface and experience, which he has accomplished.

\n\n\n\n

The Future of PhotoPress

\n\n\n\n

The project is still a work in progress. Adams is still moving through Phase I of the four-phase plan. Once it is complete, he can move on to the next steps in the process.

\n\n\n\n

Phase II is to create themes that are designed specifically to work with the PhotoPress plugin. He has three planned thus far. One for handling portfolio sites. Another for creating a stock photo archive. And the last for photojournalism and exhibits. Each will be built on top of his photography theme framework.

\n\n\n\n

The themes in Phase II will likely be commercial products. Adams said he needs a way to fund the next phases of the project. He hopes to have this step underway by the end of the year.

\n\n\n\n

For 2021, he wants to begin tackling Phases III and IV. The former will be a website-as-a-service (WaaS) similar to WordPress.com but for photographers. It will begin as a paid project but could have some free options for emerging photographers and students. The final phase is to build an onboarding system.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 25 Sep 2020 19:08:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: Google Officially Releases Its Web Stories for WordPress Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105227\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:191:\"https://wptavern.com/google-officially-releases-its-web-stories-for-wordpress-plugin?utm_source=rss&utm_medium=rss&utm_campaign=google-officially-releases-its-web-stories-for-wordpress-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5593:\"Web Stories for WordPress dashboard.\n\n\n\n

Two and a half months after the launch of its public beta, Google released its Web Stories for WordPress plugin. So far, the plugin has over 10,000 active installations and has garnered a solid five-star rating from four reviews.

\n\n\n\n

Google created the Web Stories format through its AMP Project to allow publishers to create visually-rich stories. It is primarily geared toward mobile site visitors, allowing them to quickly jump through story pages with small chunks of content.

\n\n\n\n

The Web Stories plugin creates a visual interface within WordPress for creating Stories. It breaks away from the traditional WordPress interface and introduces users to an almost Photoshop-like experience for building out individual Stories. The Stories editor is completely drag-and-drop.

\n\n\n\n

The plugin also offers eight predesigned templates out of the box that cover a small range of niches. However, according to Google’s announcement, the company plans to add more templates in future updates.

\n\n\n\n

Web Stories Are for Storytelling

\n\n\n\n

“Firstly…the power of Stories,” wrote Jamie Marsland, founder of Pootlepress, in a Twitter thread. “Stories are how we (humans) see the world and share our experiences. Up to now the platforms that we have to tell stories have been limited to books/films/tv/websites/blogs/instagram stories etc.”

\n\n\n\n

“Websites are ok for telling stories but in many ways the format doesn’t really fit the linear arc of storytelling. When Marshall McLuhan said ‘the medium is the message’ in 1964 he was talking about how the medium itself has a social impact, and change the communication itself…and the possibilities for what is communicated and how it is perceived. But we should keep coming back to Stories. Stories are the key here imo. Now we have an open format to tell Stories, and we have an open platform (WordPress) where those Stories can be told easily.”

\n\n\n\n

Marsland finished his thread by saying that using Stories as a replacement for a brochure or website is a missed opportunity. He said that it was a platform for storytelling and should be used as such.

\n\n\n\n

It is far too early to tell if Web Stories will simply be a fad or still in wide use years from now. The technology certainly lends itself well to telling stories, particularly in mobile format, but I doubt we have seen the best of what is possible on the web. The format feels too limited to be the end-all-be-all of storytelling. It is merely one medium that will live and die by its popularity with users.

\n\n\n\n

With the right design skills, some people will craft beautiful Web Stories. And, that is just what Marsland has done with the first Story he shared:

\n\n\n\nPage from the Wilson and Pootle Web Story by Jamie Marsland.\n\n\n\n

I agree with his conclusion. Web Stories should be about storytelling. When you move outside of that zone, the technology feels out of place.

\n\n\n\n

Where I disagree is that websites are not ideal for storytelling. Ultimately, the WordPress block editor will allow artistic end-users to craft intricate stories, mixing content and design in ways that we have not seen. We are just now scratching the surface. I expect our community of developers to build more intricate tools than what the Web Stories plugin currently allows, and we can do so in a way that revolutionizes storytelling on the web.

\n\n\n\n

New Features

\n\n\n\nStory editor with Unsplash photo integration.\n\n\n\n

The Web Stories plugin now adds support for Unsplash images and Coverr videos out of the box. The plugin adds a new tab with a “media” icon. For users of the first beta version of the plugin, this may be a bit confusing. The previous media icon was for a tab that displayed the user’s media. Now, the user’s media is under the tab with the “upload” icon.

\n\n\n\n

It is also not immediately clear that the Unsplash images and Coverr videos are not hosted on the site itself. There is a “powered by” notice at the bottom of the tab, but it can be easy to miss because it blends in with the media in the background.

\n\n\n\n

Media from Unsplash and Coverr is hosted off-site and not downloaded to the user’s WordPress media library. I could find no mention of this in the plugin’s documentation. Such hotlinking was a cause for debate over the recent official release of the Unsplash plugin.

\n\n\n\n

Google also announced it planned to add more “stock media integrations” in the near future. According to a document shared via a GitHub ticket, such future integrations may include Google Photos and GIF-sharing site Tenor.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 21:13:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: W3C Drops WordPress from Consideration for Redesign, Narrows CMS Shortlist to Statamic and Craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105108\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:255:\"https://wptavern.com/w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft?utm_source=rss&utm_medium=rss&utm_campaign=w3c-drops-wordpress-from-consideration-for-redesign-narrows-cms-shortlist-to-statamic-and-craft\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:11563:\"

The World Wide Web Consortium (W3C), the international standards organization for the web, is redesigning its website and will soon be selecting a new CMS. Although WordPress is already used to manage W3C’s blog and news sections of the website, the organization is open to adopting a new CMS to meet its list of preferences and requirements.

\n\n\n\n

Studio 24, the digital agency selected for the redesign project, narrowed their consideration to three CMS candidates:

\n\n\n\n
  1. Statamic
  2. Craft CMS
  3. WordPress
\n\n\n\n

Studio 24 was aiming to finalize their recommendations in July but found that none of them complied with the W3C’s authoring tool accessibility guidelines. The CMS’s that were better at compliance with the guidelines were not as well suited to the other project requirements.

\n\n\n\n

In the most recent project update posted to the site, Studio 24 reported they have shortlisted two CMS platforms. Coralie Mercier, Head of Marketing and Communications at W3C, confirmed that these include Statamic and Craft CMS.

\n\n\n\n

WordPress was not submitted to the same review process as the Studio 24 team claims to have extensive experience working with it. In the summary of their concerns, Studio 24 cited Gutenberg, accessibility issues, and the fact that the Classic Editor plugin will stop being officially maintained on December 31st, 2021:

\n\n\n\n

First of all, we have concerns about the longevity of WordPress as we use it. WordPress released a new version of their editor in 2018: Gutenberg. We have already rejected the use of Gutenberg in the context of this project due to accessibility issues.

If we choose to do away with Gutenberg now, we cannot go back to it at a later date. This would amount to starting from scratch with the whole CMS setup and theming.

Gutenberg is the future of WordPress. The WordPress core development team keeps pushing it forward and wants to roll it out to all areas of the content management system (navigation, sidebar, options etc.) as opposed to limiting its use to the main content editor as is currently the case.

This means that if we want to use WordPress long term, we will need to circumvent Gutenberg and keep circumventing it for a long time and in more areas of the CMS as time goes by. 

\n\n\n\n

Another major factor in the decision to remove WordPress from consideration was that they found “no elegant solution to content localization and translation.”

\n\n\n\n

Studio 24 also expressed concerns that tools like ACF, Fewbricks, and other plugins might not being maintained for the Classic Editor experience “in the context of a widespread adoption of Gutenberg by users and developers.”

\n\n\n\n

“More generally, we think this push to expand Gutenberg is an indication of WordPress focusing on the requirements of their non-technical user base as opposed to their audience of web developers building custom solutions for their clients.”

\n\n\n\n

It seems that the digital agency W3C selected for the project is less optimistic about the future of Gutenberg and may not have reviewed recent improvements to the overall editing experience since 2018, including those related to accessibility.

\n\n\n\n

Accessibility consultant and WordPress contributor Joe Dolson recently gave an update on Gutenberg accessibility audit at WPCampus 2020 Online. He reported that while there are still challenges remaining, many issues raised in the audit have been addressed across the whole interface and 2/3 of them have been solved. “Overall accessibility of Gutenberg is vastly improved today over what it was at release,” Dolson said.

\n\n\n\n

Unfortunately, Studio 24 didn’t put WordPress through the same content creation and accessibility tests that it used for Statamic and Craft CMS. This may be because they had already planned to use a Classic Editor implementation and didn’t see the necessity of putting Gutenberg through the paces.

\n\n\n\n

These tests involved creating pages with “flexible components” which they referred to as “blocks of layout,” for things like titles, WYSIWYG text input, and videos. It also involved creating a template for news items where all the content input by the user would be displayed (without formatting).

\n\n\n\n

Gutenberg would lend itself well to these uses cases but was not formally tested with the other candidates, due to the team citing their “extensive experience” with WordPress. I would like to see the W3C team revisit Gutenberg for a fair shake against the proprietary CMS’s.

\n\n\n\n

W3C Is Prioritizing Accessibility Over Its Open Source Licensing Preferences

\n\n\n\n

The document outlining the CMS requirements for the project states that “W3C has a strong preference for an open-source license for the CMS platform” as well as “a CMS that is long-lived and easy to maintain.” This preference may be due to the economic benefits of using a stable, widely adopted CMS, or it may be inspired by the undeniable symbiosis between open source and open standards.

\n\n\n\n

“The industry has learned by experience that the only software-related standards to fully achieve [their] goals are those which not only permit but encourage open source implementations. Open source implementations are a quality and honesty check for any open standard that might be implemented in software…”

Open Source Initiative
\n\n\n\n

WordPress is the only one of the three original candidates to be distributed under an OSD-compliant license. (CMS code available on GitHub isn’t the same.)

\n\n\n\n

Using proprietary software to publish the open standards that underpin the web isn’t a good look. While proprietary software makers are certainly capable of implementing open standards, regardless of licensing, there are a myriad of benefits for open standards in the context of open source usage:

\n\n\n\n

“The community of participants working with OSS may promote open debate resulting in an increased recognition of the benefits of various solutions and such debate may accelerate the adoption of solutions that are popular among the OSS participants. These characteristics of OSS support evolution of robust solutions are often a significant boost to the market adoption of open standards, in addition to the customer-driven incentives for interoperability and open standards.”

International Journal of Software Engineering & Applications
\n\n\n\n

Although both Craft CMS and Statamic have their code bases available on GitHub, they share similarly restrictive licensing models. The Craft CMS contributing document states:

\n\n\n\n

Craft isn’t FOSS
Let’s get one thing out of the way: Craft CMS is proprietary software. Everything in this repo, including community-contributed code, is the property of Pixel & Tonic.

That comes with some limitations on what you can do with the code:

– You can’t change anything related to licensing, purchasing, edition/feature-targeting, or anything else that could mess with our alcohol budget.
– You can’t publicly maintain a long-term fork of Craft. There is only One True Craft.

\n\n\n\n

Statamic’s contributing docs have similar restrictions:

\n\n\n\n

Statamic is not Free Open Source Software. It is proprietary. Everything in this and our other repos on Github — including community-contributed code — is the property of Wilderborn. For that reason there are a few limitations on how you can use the code:

\n\n\n\n

Projects with this kind of restrictive licensing often fail to attract much contribution or adoption, because the freedoms are not clear.

\n\n\n\n

In a GitHub issue requesting Craft CMS go open source, Craft founder and CEO Brandon Kelly said, “Craft isn’t closed source – all the source code is right here on GitHub,” and claims the license is relatively unrestrictive as far as proprietary software goes, that contributing functions in a similar way to FOSS projects. This rationale is not convincing enough for some developers commenting on the thread.

\n\n\n\n

“I am a little hesitant to recommend Craft with a custom open source license,” Frank Anderson said. “Even if this was a MIT+ license that added the license and payment, much like React used to have. I am hesitant because the standard open source licenses have been tested.”

\n\n\n\n

When asked about the licensing concerns of Studio 24 narrowing its candidates to two proprietary software options, Coralie Mercier told me, “we are prioritizing accessibility.” A recent project update also reports that both CMS suppliers W3C is reviewing “have engaged positively with authoring tool accessibility needs and have made progress in this area.”

\n\n\n\n

Even if you have cooperative teams at proprietary CMS’s that are working on accessibility improvements as the result of this high profile client, it cannot compare to the massive community of contributors that OSD-compliant licensing enables.

\n\n\n\n

It’s unfortunate that the state of open source CMS accessibility has forced the organization to narrow its selections to proprietary software options for its first redesign in more than a decade.

\n\n\n\n

Open standards go hand in hand with open source. There is a mutually beneficial connection between the two that has caused the web to flourish. I don’t see using a proprietary CMS as an extension of W3C values, and it’s not clear how much more benefit to accessibility the proprietary options offer in comparison. W3C may be neutral on licensing debates, but in the spirit of openness, I think the organization should adopt an open source CMS, even if it is not WordPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 24 Sep 2020 20:13:24 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WPTavern: First Look at Twenty Twenty-One, WordPress’s Upcoming Default Theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105166\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:195:\"https://wptavern.com/first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme?utm_source=rss&utm_medium=rss&utm_campaign=first-look-at-twenty-twenty-one-wordpresss-upcoming-default-theme\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6907:\"

Fashion is ephemeral. Art is eternal. Indeed what is a fashion really? A fashion is merely a form of ugliness so absolutely unbearable that we have to alter it every six months!

\n\n\n\n

Thus wrote Oscar Wilde on Victorian-era fashion in an article titled “The Philosophy of Dress” for the New-York Tribune in 1885.

\n\n\n\n

In many ways, WordPress theming is the same as the ever-changing landscape of fashion. Rounded corners are in one day and out the next. Box shadows are in one year after being frowned up just months earlier. Perhaps web design is so intolerable that we must change it every six months. Or, at least freshen it up every year in the case of WordPress.

\n\n\n\n

If art is eternal, there are only two default, Twenty* themes that I can truly recall from past years: Twenty Ten and Twenty Fourteen — yes, Twenty Twenty is memorable, but it is also still the current default. Twenty Ten was a classic that paid homage to WordPress’s past. Twenty Fourteen was such a leap away from tradition that it is hard to forget. Everything else has seemed to fade to varying degrees.

\n\n\n\n

With WordPress 5.6 and the end of the year looming, it is time to look forward to the latest trend. As Mel Choyce-Dwan noted in the announcement of Twenty Twenty-One, the next default theme, “Pastels and muted colors are pretty in right now.”

\n\n\n\n

She is not wrong. The colors are a refreshing change of pace. Now that we are into the second day of autumn, I am getting the good kind of vibes from some of the more earthy-tones from a couple of the color palettes expected to ship with the theme.

\n\n\n\nPotential color palette options for Twenty Twenty-One.\n\n\n\n

Whether Twenty Twenty-One will be a fashionable theme for the year or art that we can remember a decade from now, only history will be able to judge. For now, let’s enjoy the creation and take a look at what we should expect from the next default WordPress theme.

\n\n\n\n

The Current Twenty Twenty-One

\n\n\n\n

The new default theme is a fork of Automattic’s Seedlet, a project in which I lauded as the next step in the evolution of theming. It is a theme that is focused on WordPress’s future of being completely comprised of blocks. It gives us an ideal insight into where theme development is heading. It makes sense as the foundation for the new default. Few other themes would make for a good starting point right now. With WordPress theme development in flux, Seedlet is simply ahead of the pack in terms of foundational elements.

\n\n\n\nSeedlet WordPress theme screenshot.\n\n\n\n

“This provides us with a thorough system of nested CSS variables to make child theming easier, and to help integrate with the global styles functionality that’s under development for full-site editing,” wrote Choyce-Dwan of using Seedlet as a starting point.

\n\n\n\n

There are no plans to spin up a Google Web Font for this theme. The design team is going native and sticking with the default system font stack. Choyce-Dwan listed several reasons for the choice, such as keeping a neutral font that allows broad use, speed, and customizability via a child theme.

\n\n\n\n

Despite the neutral font, the default pastel green is a fairly opinionated design decision. It will not be used broadly across industries. However, the team plans to create multiple color palettes that will give it more range. Presumably, these palettes can also be overwritten.

\n\n\n\nPastel green color scheme on single post view.\n\n\n\n

Other than the colors, the design is relatively simple. Choyce-Dwan said that the theme’s block patterns support is where it will be truly unique.

\n\n\n\n

I was initially unhappy with the patterns that were going to ship with WordPress 5.5. However, an 11th-hour update improved the situation so that they did not feel entirely experimental. The foundational Seedlet theme for Twenty Twenty-One has some unique patterns that begin to scratch the surface of what’s possible with this WordPress feature. My hope is that the new default theme steps it up a notch.

\n\n\n\n

Currently, the theme does not register any custom patterns. However, it has a placeholder file and a note that they are a work in progress. Choyce-Dwan shared some patterns the team has already designed in the announcement.

\n\n\n\nCurrently-designed block patterns.\n\n\n\n

“We’ll be relying on our talented community designers for more ideas,” she wrote. The team has also created a GitHub template for anyone to contribute pattern design ideas.

\n\n\n\n

Currently, the theme does not support the upcoming full-site editing feature of WordPress. After the Beta 1 release of WordPress 5.6, the team plans to begin exploring the addition of this support. WordPress is expected to ship a public beta of full-site editing in its next major release, but it is unclear whether it will be far enough along to be a headline feature for the Twenty Twenty-One theme.

\n\n\n\n

The team and volunteers have less than a month before the October 20th deadline for committing the new theme to trunk, the core WordPress development branch. At that stage, the theme should be nearly complete and ready for production. Of course, there will be several rounds of patches, bug fixes, and updates before WordPress 5.6 lands in December. Right now is the best time for anyone who wants to get involved with Twenty Twenty-One to do so.

\n\n\n\n

Useful links with more information:

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 20:01:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:37:\"HeroPress: Hello World – Hevo Nyika\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=3308\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:176:\"https://heropress.com/essays/hello-world-discovering-the-world-through-wordpress/#utm_source=rss&utm_medium=rss&utm_campaign=hello-world-discovering-the-world-through-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:14438:\"\"Pull

Unokwanisa kuverenga rondedzero iyi muChiShona

\n

So I chose a career in Web Development!!

\n

To be honest it’s kind of funny when I think about it and quite surreal to be here talking about my story. It has been a journey and I would like to share my story with you.

\n

I have been lucky in the Dad department. My Dad encouraged me to work hard and dream big from a very young age. I remember occasionally having ‘when I grow up’ talks.

\n

For quite some time I wanted to be a Judge, however awesome this dream sounds it was not very inspired. After binge-watching Judge Judy for a whole weekend, I started calling myself Judge Thelma. Though I don’t remember much of this my sister says that I used to say I would arrest all the men in the World if I ever became a Judge. HAHAHA! (clearly I didn’t understand how the World works)

\n

I did not understand what being a Judge meant or what was required for me to start banging that gavel to my heart’s desire. Eventually, I learnt that I had to become a lawyer first then magistrate before I could be nominated to be a Judge and let us just say that is how I sentenced that dream to a lifetime down the drain.

\n

See what I did there? hahaha!

\nWith Daddy Dearest\n

A few years later, I was in High School and that is when I decided to pursue a career in Computer Science. I did not know what I would be doing or how I would get there but I just knew that I was going to pursue a career in ICT. I wrote my first line of code when I was 16 years old.

\n

This was after I had joined the school’s computer class, initially, I thought I would be learning about Excel Sheets and Word Documents until I was assigned to write my first program in C (talk about a double-take!!). It was not easy but it was very exciting, l remember writing up simple code for a Video Club – a simple check-in/out for VHS tapes and CDs. Dear World, thus began my fascination with computers.

\n

Seven years later, I was now in university studying ICT as I had always wanted. I was doing a Bachelors in Business Management & Information Technology. In my third year, I was interning at a local Webdesign and hosting company. This was never my plan, I only took on that job after I had failed to get a job with local banks or telecommunications companies. Before I was introduced to Website Design I envisioned myself suiting up and working in IT Audit or offering IT support. Even though things did not go as I had planned, I am glad they did not exactly go my way in that aspect. So in 2017, I was designing websites using HTML, CSS, PHP, JavaScripts and Joomla which was the prefered content management system at that company. I knew about WordPress but I was not using it for anything. People have this misconception that WordPress is not for real developers and it is not secure and at that time I was one of those people.

\n

Finding my tribe

\n

One day when I was working at the front desk Thabo Tswana came to give a colleague of mine a purple WooCommerce pen. I did not know what WooCommerce was at that time but I was taken by the purple shirt and pen he was carrying. I asked him about it and he explained what WooCommerce was and that what he was carrying was called ‘swag’. So the love of freebies led me to the WordCamp Harare website, instead of buying a ticket I applied to volunteer. I learnt more about WordPress, I was a volunteer, without any knowledge on WordPress.org or WordPress.com. I only started using WordPress because of the awesome people that l had met at that Wordcamp.

\n

Everyone was so welcoming, a week later with help from Thabo I designed my first ever WP website.

\n

Soon after I was part of the community and a bit more involved in the meetups. We had our first-ever Women Who WordPress meetup in 2018. So many ladies came on board bloggers and developers alike. We were free to talk and discuss a lot of things. We had more time to discuss the difference between WordPress.com and WordPress.org we shared views on how to handle discrimination at work, how to promote your website and a whole lot of other things.

\n

\n

Establishing roots

\n

In 2018, Harare had its first-ever female Lead Organiser Tapiwanashe Manhobo whoop whoop! I was also part of the organising team that year, I was assigned to handle Harare’s first Kids Camp. The planning process was stressful because the economic crisis in Zimbabwe was getting worse, luckily we had over 8 months to plan and with help from sponsors, we managed to pull through. In the end, everything turned out great. I wrote an article about the Kids Camp here.

\n

After the first Kids Camp, we had several WordPressors that were enthusiasts about encouraging kids to embrace ICT. In 2019 we had not planned to have a Kids Camp because of financial constraints but to our surprise, we had some anonymous donations and we managed to have a WordPress Community outreach to a youth centre a week after our WordCamp. We had the outreach at the Centre for Total Transformation which is a non-formal school that caters for underprivileged and vulnerable children. We taught them about WordPress, Computer Hardware and Software.

\n

Here is a small video I took with Ellen when we were about to leave. Did l mention that I am terrible on camera? hahaha!

\n\n

Kids Camp 2019 – Centre for Total Transformation

\n

I have fallen deeply for WordPress because of the Community, I enjoy attending WordCamps, meeting new people and just learning new stuff. I have a huge list of WordCamps I need to attend before l kick the bucket, hopefully. Last year I managed to cross WordCamp

\n

Johannesburg off my bucket list. This year I was going to attend WordCamp Capetown but unfortunately, 2020 had other plans for the whole world. Anyway when everything is back to normal my plan to travel to WordCamps will proceed. (fingers crossed)

\n

Reaping Fruits

\n

Meanwhile, my plan to improve my developing skills has not been on hold. Even though I can still cook up code in C and Java, for now, I have also included WordPress PHP functions to the mix. It was not easy to get to this point, daring myself got me to this slightly better stage. My IQ is not way up there, however, I try to do my best where I can and I am happy to say it has paid off so far.

\n

Around November last year, I was designing as a freelancer while job hunting. Out of the blue l got a call for a job offer from Trust Nhokovenzo who is big on Digital marketing and also part of the WordPress Community. He had asked someone in the community about developers and my name happened to come up. So since February, I have been part of his team at Calmlock Digital Marketing Agency.

\n

There is so much more in the world of WordPress that l am yet to tap into so even though I am ending my write up here, for now, my story is going to continue …

\n

Until next time…

\n

Hevo Nyika

\n

Saka ini ndakasarudza kugadzira mawebhusayiti.

\n

Ndakaita rombo rakanaka pana baba vandakapihwa naMwari. Baba vangu vaindikurudzira kuti ndishande nesimba. Ndinoyeuka pano neapo tichiita hurukuro dzedu dzekuti ‘kana ndakura ndoda kuveyi’.

\n

Kwenguva yakati rebei ndaida kuve Mutongi. Kunyangwe ini ndisingazvirangariri mukoma wangu anotaura kuti ndaiti ndaizosunga varume vese vari pasi rino kana ndikangoita mutongi HAHAHA zveshuwa handaiziva kuti mitemo yenyika inofambiswa seyi.
\nNdanga ndisinga nzwisisi kuti kuva mutongi kwairevei kana zvaidikanwa kwandiri kuti nditange kurova iro ghavheu kuchishuwo chemoyo wangu. Pakupedzisira, ndakadzidza kuti ndaifanirwa kuzoita gweta ipapo magistrate ndisati ndasarudzwa kuita Mutongi naizvozvo ndokupera kwakaita chiroto chekuva Mutongi.

\nNa Baba Vangu\n

Gare gare papfura makore mashoma pandakanga ndave kuHigh School ndakanga ndakuda kuita basa rema kombiyuta. Ndakanyora mutsara wekutanga wekodhi pandaive nemakore gumi nematanhatu. Izvi zvakaitika mushure mekunge ndapinda mukirasi yemakombiyuta, pakutanga ndaifunga kuti ndinenge ndichidzidza nezveExcel Sheets neWord zvisineyi ndakaona ndakunyora kodhi yangu yekutanga muC. Zvaisave nyore kunyora kodhi asi zvainakidza kwazvo, ndorangarira ndichinyora kodhi yeVhidhiyo Kirabhu.

\n

Makore manomwe apfura, ndakanga ndava kuyunivhesiti ndichidzidza ICT zvandakagara ndakaronga. Ndaiita Bachelors muBusiness Management & Information Technology. Mugore rangu rechitatu ndainge ndave kushanda kune imwe kambani yaita zvekugadzira mawebhusaiti. Ndakawana basa iri mushure mekunge ndatadza kuwana basa kumabhanga. Kunyangwe hazvo zvinhu zvisina kuenda sezvandaive ndakaronga, ndinofara kuti hazvina kunyatso enda nenzira yangu. Saka muna 2017 ndaigadzira mawebhusaiti ndichishandisa HTML, CSS, PHP, JavaScript uye Joomla iyo yaive iyo inokurudzirwa kukambani kwandaive. Panguva iyi ndaiziva nezve WordPress asi ndakanga ndisingaishandisi.

\n

Kuwanana neWordPress

\n

Rimwe zuva pandakanga ndichishanda ndakaona Thabo Tswana akauya kuzopa mumwe mukomana wandayishanda naye chinyoreso cheWooCommerce. Ndakanga ndisingazive kuti WooCommerce yaive chii asi ndakafarira chinyoreso nehembe ye WooCommerce yaanga akapfeka. Ndakamubvunza nezvazvo akatsanangura kuti WooCommerce yaive chii. Saka nekudawo zvakanaka, zvemahara ndakaenda pawebhusaiti yeWordCamp Harare ndikabata zvimbo zvegore iroro. Ndakazvipira kubatsirawo vamwe vekuWordPress kuWordCamp Harare. Nerubatsiro kubva kunaThabo ndakagadzira webhusaiti yangu yekutanga yeWordPress vhiki rakatevera .

\n

Mushure mekunge ndaitawo chipato cheavo vanoshandisa WordPress ndakanga ndakuenda kumisangano yeWordPress yaitwa muHarare. Takaita musangano wevakadzi chete muna 2018. Vakadzi vazhinji vakauya kumusangano uyu. Tainga takasununguka kukurukura zvinhu zvakawanda. Takakurukura pamusoro pemutsauko uripo pakati peWordPress.com neWordPress.org takagovana maonero ekugadzirisa rusarura kubasa nezvimwewo.

\n

\n

Nguva yandakatanga kushandisa WordPress

\n

Muna 2018, kurongwa kweWordCamp Harare kwakatungamirwa kekutanga nemusikana ainzi Tapiwanashe Manhobo (waiva mufaro mukuru). Ndakanga ndiri mumwe wevairongawo naye. Hurongwa hwekuronga WordCamp Harare mugore iri hwainetsa pamusaka pekuoma kwehupfumi wemuZimbabwe, zvisineyi takaita rombo rakanaka nokuti takawana rubatsiro kubva kunevamwewo vanhu vakatiwedzera mari. Pakupedzisira, zvese zvakabudirira zvakanaka. Takarongawo WordCamp yevana varipasi pemakore gumi nechishanu, munokwanisa kuverenga pamusoro pezuva iri pawebhisaiti yangu apa.

\n

Mushure mekuita WordCamp yevana, takave nevamwe vanhu veWordPress aifarira kukurudzira vana kuti vagamuchire ICT. Muna 2019 takanga tisina kuronga kuve neWordCamo yeVana nekuda kwezvimhingamupinyi zvemari asi chakatishamisa ndechekuti takawana mari kubvawo kune vamwe. Takaita Camp iyi paCentre for Total Transformation chinova chikoro chisiri chepamutemo chinodzidzisa vana vanotambura. Tadzidzisa vana ava pamusoro peWordPress, Computer Hardware uye Software.

\n\n

Ndofarira WordPress zvakanyanya nekuda kweavo varimu nharaunda yacho, ini ndinonakidzwa nekuenda kumaWordCampi, kusangana nevanhu vatsva uye kungo dzidza zvinhu zvitsva. Gore rakapera ndakakwanisa kuyambuka muganhu weZimbabwe ndichienda kuWordCamp Johannesburg, dai pasina kuti 2020 nyika dzepasi rino dzakawirwa nedenda reCOVID 19 zvimwe ndingadayi ndakaenda kuWordCamp Capetown. Zvisinei hazvo kana denda ranani zvimwe ndichakwanisa kufamba ndichienda kumaWordCamp edzimwe nyika.

\n

Kukowa zvandakadyara

\n

Zvichakadaro, chirongwa changu chekuvandudza hunyanzvi hwangu hachina kumira. Kunyangwe ini ndichiri kukwanisa kubika kodhi muC uye Java, ikozvino, ndasanganisirawo WordPress PHP. Zvaive zvisiri nyore kusvika apa, zvakatora kuzvishingisa nekushanda nesimba. Ndinofara mwari aiva neni pamufambo wangu uyu.

\n

Muna Mbudzi gore rakapera, ndaive ndichigadzira mawebhusayiti apo nditsvaga basa. Pasina nguva ndakataura naTrust Nhokovenzo uyo akaandipa basa mukambani yake, kambani iyi inonzi Calmlock Digital Marketing Agency.

\n

Pane zvimwe zvakawanda kuWordPress zvandisati ndapinda mazviri. Nhaizvozvo kunyangwe ndiri kupedzisa kunyora kwangu apa, nyaya yehupenyu wangu ichaenderera mberi…

\n

Kusvikira nguva inotevera …

\n

…. tsvaga chinangwa chako, chiite mushe mushe ..

\n

The post Hello World – Hevo Nyika appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 06:00:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Thelma Mutete\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n\n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:102:\"WPTavern: WordPress Contributors Debate Dashboard Notice for Upcoming Facebook oEmbed Provider Removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105132\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:249:\"https://wptavern.com/wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-contributors-debate-dashboard-notice-for-upcoming-facebook-oembed-provider-removal\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5885:\"

WordPress contributors are discussing different strategies for responding to Facebook and Instagram dropping unauthenticated oEmbed support on October 24. WordPress will be removing both Facebook and Instagram as oEmbed providers. When a user attempts to embed content by pasting a URL as they have in the past, they may not understand why it no longer works. They may assume that WordPress broke embeds, causing an increase in the support burden for this change.

\n\n\n\n

A few participants on the trac ticket for this issue have suggested WordPress detect users who will be impacted and attempt to warn them with a notice.

\n\n\n\n

“Since this may impact users unknowingly, it is possible to push a dashboard notice to users who have Facebook/Instagram embeds in their content, showing for site admins, as a one-off that can be dismissed,” Marius Jensen said.

\n\n\n\n

“We’ve previously done post-update-processing to clean up comments, so the idea of looking over content for an embed isn’t completely outlandish, and would help with those who don’t follow WordPress’ usual channels to learn of this.”

\n\n\n\n

Others don’t see the necessity. “Why should we make exception here?” Milan Dinić said. “It’s not the first time oEmbed support was discontinued for a provider, and I don’t remember anything specific was done then.”

\n\n\n\n

There is still some uncertainty about what will happen with existing oEmbeds after Facebook updates its API. During a recent core developer meeting, Helen Helen Hou-Sandí confirmed that WordPress does not clear oEmbed caches regularly. “Technically oEmbed caches are cleared if you save and a valid response is returned, we do not do cron-based garbage collection,” Hou-Sandí said.

\n\n\n\n

In a post today on the core development blog, Jake Spurlock assured users and developers that the existing embeds added before Facebook’s API change should still work:

\n\n\n\n

Because oEmbed responses are cached in the database using the hidden oembed_cache post type, any embed added prior to the October 24th deadline will be preserved past the deprecation date. These posts are not purged by default in WordPress Core, so the contents of the embed will persist unless manually deleted.

\n\n\n\n

Marius Jensen cautioned that there is still the possibility that existing embeds may not work going, depending on what Facebook does.

\n\n\n\n

“We don’t know how they plan on implementing the use of unauthorized embed attempts,” Jensen said. “It could not return an embed code and your link would remain a plain link, or maybe they decide to return some kind of embedded ‘unauthorized’ content. I don’t think anyone has heard any specifics on how Facebook plans on doing this, so we’re all just kinda waiting to either hear more, or see what happens.”

\n\n\n\n

Jensen said WordPress doesn’t re-check the cached results except when something changes with the post, but there may be plugins that clean up temporary data that may create an unpredictable outcome.

\n\n\n\n

“The reliability of the caches are hard to determine (and being caches, it’s sort of in the term that it’s not guaranteed to always be there, but rather fetched and saved for a while when needed),” Jensen said.

\n\n\n\n

Ideally WordPress’ oEmbed caches will prevent millions of embeds from breaking, but it’s still unknown how Facebook and third party plugins could change things.

\n\n\n\n

Coming off a rocky 5.5 core update that deprecated jQuery Migrate and flooded official support forums with reports of broken sites, some contributors are wary of having another situation where users are left in the dark.

\n\n\n\n

“I think a dashboard notice is desirable,” Jon Brown said. “Otherwise we’re not preemptively warning people in a way they can prepare and transition to another solution. We’re letting them know the same instant it’s going to break (when editing a specific post). I don’t think we can safely assume cached data is going to persist forever either, plenty of routines out there purge transient data before its stated expiration date.

\n\n\n\n

“I see this as potentially being similar to the problems seen in dropping JQM. It’ll cause avoidable and silent breakage client side without even any error logging for a site developer to pick up on. In hindsight, what ideally would have happened with JQM would have been incorporating the detection code from Enable jQuery Migrate Helper into core temporarily, or simply installing that plugin automatically on behalf of users.”

\n\n\n\n

Brown suggested WordPress detect calls to the cached embeds and warn users before the calls have the chance to fail so they can consider enabling a plugin to keep their embeds working more reliably.

\n\n\n\n

The discussion remains open in the make.wordpress.org/core post and the corresponding trac ticket. Spurlock said WordPress will likely remove Facebook and Instagram oEmbed providers in the upcoming 5.6 release (scheduled for December 8) but it could also be shipped in a 5.x minor release that happens after October 24.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 23 Sep 2020 04:28:56 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n\n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:65:\"WPTavern: Gutenberg Hub Launches Landing Page Templates Directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105009\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:175:\"https://wptavern.com/gutenberg-hub-launches-landing-page-templates-directory?utm_source=rss&utm_medium=rss&utm_campaign=gutenberg-hub-launches-landing-page-templates-directory\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7657:\"\n\n\n\n

Munir Kamal has created copy-and-paste blocks. He has built sections or “patterns” from those blocks. He has created a plugin that allows users to completely customize the two features via block options. Yesterday, he released an initial offering of 22 landing page templates that build upon his earlier work.

\n\n\n\n

Gutenberg Hub can almost be called his magnum opus, at least at this stage of his career. It is a continually growing library of free tools for WordPress’s block editor.

\n\n\n\n

Like previous projects, Gutenberg Hub’s landing templates require the EditorPlus plugin. This plugin is essentially a suite of design controls for the core WordPress blocks. The templates make use of these options by default. Given the limitations of the block editor’s current design controls, the use of such a plugin is necessary. Otherwise, there would be few other ways to realistically create a template system like this.

\n\n\n\n

Currently, users must copy the block code — via a convenient “copy” button — from the Gutenberg Hub website and then paste it in the editor. It is not an ideal situation, and I have been asking Kamal whether he would consider building a template inserter for months now.

\n\n\n\n

This time around, he preemptively said, “And, by the way, I am already working on adding a Template Inserter in my EditorPlus plugin. That will allow users to browse and insert these templates directly from Gutenberg without leaving the website.”

\n\n\n\n

He knew the question was coming. No need for me to ask again. He was unable to share a current screenshot of what the inserter looks like, but he is asking for feedback on what people expect of the user experience and interface.

\n\n\n\n

“Earlier, I created a template inserter similar to other blocks plugins, but later I changed my mind and thought that I should integrate with the Gutenberg Patterns API and load the templates into the ‘patterns’ panel in the block inserter,” he said. “But, I am having a few issues and thinking about going back to the original idea to have a Templates button on the top toolbar that opens a popup window to browse and filter templates that users can insert on a click.”

\n\n\n\n

For now, it is still early. However, at least it is on the long-term roadmap and being worked on.

\n\n\n\n

The Landing Page Templates

\n\n\n\nTesting the photography template (with minor adjustments).\n\n\n\n

At the moment, Gutenberg Hub offers 22 landing page templates. The “page” terminology may not mean “full page.” It simply depends on the active theme. Some themes have an open-canvas type of template that allows users to create the entire page via the editor. However, that is not a common feature, so these page templates will be confined to the post content area in most cases.

\n\n\n\n

The templates also work better with themes that have at least a full-width or no-sidebar option. End-users will want a lot of breathing room to use the templates and tinker with their designs.

\n\n\n\n

Kamal has built templates that stretch across a variety of industries. From restaurants to gyms to education to fashion, there is a lot to choose from right now. He promises more are on the way and at least a 23rd template in the next few days.

\n\n\n\n

“For the niches, I did some research from the top WordPress and HTML marketplaces and found the following most common or popular niches,” he said. “I think I will stick with these niches unless I get some more recommendations.”

\n\n\n\n

In comparison, Redux Templates offers access to over 1,000 sections and templates. Of course, there are trade-offs, such as some of those being commercial and the plugin typically requiring other third-party plugins. While quantity is not the only thing to look at, it proves there are miles of landscape that Gutenberg Hub’s templates have not yet explored. But, it is merely the beginning.

\n\n\n\n

Gutenberg Hub’s full-page templates are not quite as plug-and-play as its blocks and section templates. This is not so much a fault from the developer’s end. It is an issue of the platform, which is constantly being updated, and the range of support from current themes. End-users will start seeing some of the current limitations of the system when a layout does not quite look right with one theme but does with another. Or, if their theme has not been updated to support a new feature, such as the Social Links block, the typical horizontal menu design will likely be a normal vertical list of links instead.

\n\n\n\n

These are not insurmountable issues. Gutenberg and themes need more time to mature before projects like Gutenberg Hub’s landing templates are perfect or at least as close to perfect as can be expected.

\n\n\n\n

There are some things that Gutenberg Hub could improve with its templates. With several that I tested, I needed to switch specific blocks to be full width. This should be set up as the default with templates that are clearly meant to be full width in the example screenshots available on the site. It is a minor issue, but correcting this in the editor fixed several layout issues I was having when using the templates.

\n\n\n\n

Monetization Plans

\n\n\n\n

The second question that Kamal has not been prepared to answer fully over the past several months is how he will monetize Gutenberg Hub. Eventually, developers need some return on their investment when building tons of free tools. Many would do it all for free as long as their bills somehow got paid, but the reality is that there will come a tipping point where their projects need funding for long-haul maintenance.

\n\n\n\n

Kamal said he has laid the groundwork for funding but has not finalized anything yet. Currently, he is working on three ideas:

\n\n\n\n
  • Creating a pro version of his EditorPlus plugin.
  • Offering premium templates and blocks but is looking for a talented designer to work with.
  • Using ads specific to Gutenberg users, but he is not a fan of going this route or ads in general.
\n\n\n\n

He is open to feedback on how to best monetize the website and its projects. However, he said he is unwilling to compromise on giving away current and future free templates and tools.

\n\n\n\n

Future Gutenberg Projects

\n\n\n\n

Kamal said he does not have any new Gutenberg-related projects in the pipeline. The current plan is to work on what he has already created, which is a large ecosystem of Gutenberg tools that somehow work together.

\n\n\n\n

Outside of blocks, templates, and plugins, he is beginning to write more free tutorials on the Gutenberg Hub blog and focusing on creating videos around the project, including a new tutorial series for beginners.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 22 Sep 2020 21:05:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"WPTavern: WordPress Mobile Engineers Propose Dual Licensing Gutenberg under GPL v2.0 and MPL v2.0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105025\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:239:\"https://wptavern.com/wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0?utm_source=rss&utm_medium=rss&utm_campaign=wordpress-mobile-engineers-propose-dual-licensing-gutenberg-under-gpl-v2-0-and-mpl-v2-0\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6556:\"

During a Q&A session at WordCamp Europe 2020 online, Matt Mullenweg mentioned that Gutenberg contributors were considering dual licensing for embedding Gutenberg in mobile apps, along with the requirement that they would need to get an agreement from all contributors. WordPress mobile engineer Maxime Biais has just published a proposal for discussion, recommending dual licensing the editor under GPL v2.0 and MPL v2.0.

\n\n\n\n

“The GPL v2.0 license is a blocker for distributing the Gutenberg library in proprietary mobile apps,” Biais said in the corresponding GitHub issue. “Currently the only known users of Gutenberg on mobile are the WordPress mobile apps which are under GPL v2.0 (WordPress for AndroidWordPress for iOS). Mobile apps under the GPL v2.0 are not common and this limits Gutenberg usage in many apps.

\n\n\n\n

“Rich text editor libraries in the mobile space are lacking. There is no well known open source rich text editor for Android or iOS. We believe that Gutenberg could be a key library for many mobile apps, but that will never happen with the GPL v2.”

\n\n\n\n

Mobile app developers are limited by the GPL, because it requires the entire app to be distributed under the same license. The team is proposing dual licensing under MPL v2.0, a weaker copyleft license that is often considered to be more “business-friendly.” It allows users to combine the software with proprietary code. MPL v2.0 requires the source code for any changes to be available under the MPL, ensuring improvements are shared back to the community. The rest of the app can be distributed under any terms with the MPL v2.0 code included as part of a “larger work.”

\n\n\n\n

“The idea here is to keep some of the WordPress-specific modules under the GPL v2.0 only; some of them are not needed and not relevant for using Gutenberg in another software. Ideally, there would be a different way of bundling the project for being used in WordPress or in a non-GPL software,” Biais said.

\n\n\n\n

The GitHub ticket has several comments from developers who hope to be able to use the editor in their own projects. Radek Pietruszewski, tech lead for a collaborative todo app called Nozbe Teams, has been requesting a relicensing of Gutenberg since October 2019.

\n\n\n\n

“Our tech stack is essentially React on web and React Native on iOS and Android,” Pietruszewski said. “We’re a tiny company, and so we share >80% of app’s codebase between these 3 platforms.

\n\n\n\n

“Our app sorely lacks a WYSIWYG editor. We had a working implementation on web, but we decided to scrap it, because there was no way to port it on iOS and Android. There are pretty much no viable rich text editors for iOS or Android, yet alone both. But even then, shipping three completely separate, but somehow compatible editors would be a vast amount of work.”

\n\n\n\n

When Peitruszewski originally made his case to the mobile team, he identified Gutenberg/Aztec as a basic infrastructure that has the potential to enable many different apps:

\n\n\n\n

And that infrastructure is sorely lacking. There are very few rich text editor libraries on both iOS and Android — and most of them suck. And if you want an editor that has a shared API for both platforms… you’re stuck. There are no options – Gutenberg is the only game in town (and it’s really good).

And it’s very hard to create this infrastructure. WYSIWYG editors are very hard, and it takes entire teams years to develop them (and they still usually suck). Almost no-one has the resources to develop it just for themselves, and if they do, they’re unwilling to open-source it.

\n\n\n\n

Automattic’s mobile app engineers have struggled to get regular contributions to the apps, despite them being open source. Dual licensing Gutenberg could open up a new world of contributors with the editor being used more widely across the industry.

\n\n\n\n

“While we might not be big enough to be able to tackle a challenge of developing a rich text editor from scratch, we’re big enough to contribute features and bug fixes to open source projects,” Pietruszewski said.

\n\n\n\n

Matt Mullenweg was the first comment on Biais’ post in favor of the change:

\n\n\n\n

I think Gutenberg has a chance to become a cross-CMS standard, giving users a familiar interface any place they currently have a rich text box. There are hundreds and hundreds of engineers at other companies solving similar problems in a proprietary way, it would be amazing to get them working together but a huge barrier now is supporting Gutenberg in mobile apps, which every modern web service or CMS has. (Hypothetically, think of Mailchimp as a possible consumer and collaborator here, but it could be any company, SaaS, or other open source CMS.)

\n\n\n\n

Unless any major blockers come up in further discussion, this dual licensing change appears to be on track to move forward. Biais noted that a similar license change has already happened on Aztec-Android and Aztec-iOS. The last hurdle is gaining the approval of all the original code contributors or rewriting the code for those who decline to give approval.

\n\n\n\n

Once Gutenberg can be used under the MPL v2.0, the editor will gain a broader reach, with people already on deck wanting to use it. Other companies and projects that are normally outside WordPress’ open source orbit will also have the opportunity to enrich Gutenberg’s ecosystem with contributions back to the project. At the same time, the MPL 2.0 protects Gutenberg from companies that would try to re-release the code as a closed-source project.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 22:59:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"WPTavern: GitHub to Use ‘Main’ Instead of ‘Master’ as the Default Branch on All New Repositories Starting Next Month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=105014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:269:\"https://wptavern.com/github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month?utm_source=rss&utm_medium=rss&utm_campaign=github-to-use-main-instead-of-master-as-the-default-branch-on-all-new-repositories-starting-next-month\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4844:\"

In August, GitHub announced that it would change the “master” branch name for all new repositories created on the platform to “main” starting October 1. The date is less than two weeks away, and WordPress developers need to be prepared for the change if they use the service for version control or project management.

\n\n\n\n

The larger tech and web development community began conversations through various venues in June, a time in which the Black Lives Matter was gaining more traction in the U.S. and worldwide. The discussion centered on removing any terminology that could be discriminatory or oppressive to specific groups of people. This ongoing discussion has shown that there is a deep division over whether such changes are necessary or even helpful.

\n\n\n\n

The WordPress community is dealing with this division itself. Aaron Jorbin proposed a change at the same time to rename the default branch name on WordPress-owned repositories. Through discussion on his post and elsewhere, the community landed on “trunk,” which keeps WordPress projects in line with its SVN roots.

\n\n\n\n

“To close the circle on this, a decision was made in June and earlier today (August 19),” wrote Helen Hou-Sandí, a lead WordPress developer, in the comments of the original proposal. “I updated the default branch name for new GitHub repositories under the WordPress organization to be trunk after GitHub enabled early access to that feature.”

\n\n\n\n

As evidenced by the comments on the Tavern’s coverage of the proposal and those on the original post, the WordPress development community as a whole did not support this decision.

\n\n\n\n

Jorbin has updated several of WordPress’s repositories and switched them to use trunk instead of master. However, there are still some lingering projects yet to be updated, including the primary WordPress and WordPress Develop repositories. He left a comment with an updated list in June. There is no public word on whether the existing, leftover projects will be changed.

\n\n\n\n

WordPress Developer Preparations

\n\n\n\nCustomizing the default branch for a user’s GitHub repositories.\n\n\n\n

GitHub is merely changing the default branch name for new repositories starting on October 1. This change does not affect existing repositories. Individual users, organization owners, and enterprise administrators can customize the default branch via their account settings now before the switch is made. Owners can also change the default branch name for individual repositories.

\n\n\n\n

The biggest thing that developers need to watch out for is their tooling or other integrations that might still require the master branch. There may be cases where an alternative default branch name will break workflows. If planning to use a different branch name, the best thing to do right now is to spin up the tools you use on a test repository. If something breaks, check to see whether the particular tool you are using will be getting an update. In most cases, this should not be a problem because customized default branch names will be an industry standard.

\n\n\n\n

The great thing about how GitHub is rolling out this feature is that it offers a choice. Those who believe that “master” is oppressive can change the branch name to something they feel is more inclusive. For those who believe otherwise, they can keep their master branch. But, everyone can use the branch name they prefer.

\n\n\n\n

For existing repositories, GitHub is asking that developers be patient for now. The company is investing in tools to make this a seamless experience later this year. There are a few technical hurdles to clear first.

\n\n\n\n

Developers should read the full GitHub guide on setting the default branch for more information.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 21 Sep 2020 20:39:55 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Justin Tadlock\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Thu, 22 Oct 2020 16:31:31 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:13:\"last-modified\";s:29:\"Thu, 22 Oct 2020 16:15:08 GMT\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:4:\"x-nc\";s:9:\"HIT ord 2\";s:16:\"content-encoding\";s:4:\"gzip\";}}s:5:\"build\";s:14:\"20200501142607\";}','no'),(133,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603427491','no'),(134,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1603384291','no'),(135,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1603427491','no'),(136,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(139,'theme_mods_twentytwenty','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(140,'recently_activated','a:0:{}','yes'); /*!40000 ALTER TABLE `wp55_options` ENABLE KEYS */; UNLOCK TABLES; @@ -249,7 +251,7 @@ CREATE TABLE `wp55_posts` ( LOCK TABLES `wp55_posts` WRITE; /*!40000 ALTER TABLE `wp55_posts` DISABLE KEYS */; -INSERT INTO `wp55_posts` VALUES (1,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?p=1',0,'post','',1),(2,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?page_id=2',0,'page','',0),(3,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','

Who we are

Our website address is: http://localhost:9999.

What personal data we collect and why we collect it

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Contact forms

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Analytics

Who we share your data with

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.

Your contact information

Additional information

How we protect your data

What data breach procedures we have in place

What third parties we receive data from

What automated decision making and/or profiling we do with user data

Industry regulatory disclosure requirements

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost:9999/?page_id=3',0,'page','',0),(4,1,'2020-10-22 16:31:28','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2020-10-22 16:31:28','0000-00-00 00:00:00','',0,'http://localhost:9999/?p=4',0,'post','',0),(5,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','publish','open','open','','simple_view','','','2020-10-22 16:32:47','2020-10-22 16:32:47','',0,'http://localhost:9999/?p=5',0,'post','',0),(6,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','inherit','closed','closed','','5-revision-v1','','','2020-10-22 16:32:26','2020-10-22 16:32:26','',5,'http://localhost:9999/5-revision-v1',0,'revision','',0); +INSERT INTO `wp55_posts` VALUES (1,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?p=1',0,'post','',1),(2,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?page_id=2',0,'page','',0),(3,1,'2020-10-22 16:31:15','2020-10-22 16:31:15','

Who we are

Our website address is: http://localhost.

What personal data we collect and why we collect it

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Contact forms

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Analytics

Who we share your data with

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.

Your contact information

Additional information

How we protect your data

What data breach procedures we have in place

What third parties we receive data from

What automated decision making and/or profiling we do with user data

Industry regulatory disclosure requirements

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2020-10-22 16:31:15','2020-10-22 16:31:15','',0,'http://localhost/?page_id=3',0,'page','',0),(4,1,'2020-10-22 16:31:28','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2020-10-22 16:31:28','0000-00-00 00:00:00','',0,'http://localhost/?p=4',0,'post','',0),(5,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','publish','open','open','','simple_view','','','2020-10-22 16:32:47','2020-10-22 16:32:47','',0,'http://localhost/?p=5',0,'post','',0),(6,1,'2020-10-22 16:32:26','2020-10-22 16:32:26','\n

Some content

\n','Simple View','','inherit','closed','closed','','5-revision-v1','','','2020-10-22 16:32:26','2020-10-22 16:32:26','',5,'http://localhost/5-revision-v1',0,'revision','',0); /*!40000 ALTER TABLE `wp55_posts` ENABLE KEYS */; UNLOCK TABLES; @@ -423,7 +425,7 @@ CREATE TABLE `wp55_users` ( LOCK TABLES `wp55_users` WRITE; /*!40000 ALTER TABLE `wp55_users` DISABLE KEYS */; -INSERT INTO `wp55_users` VALUES (1,'test','$P$BDzpK1XXL9P2cYWggPMUbN87GQSiI80','test','test@gmail.com','http://localhost:9999','2020-10-22 16:31:15','',0,'test'); +INSERT INTO `wp55_users` VALUES (1,'test','$P$BDzpK1XXL9P2cYWggPMUbN87GQSiI80','test','test@gmail.com','http://localhost','2020-10-22 16:31:15','',0,'test'); /*!40000 ALTER TABLE `wp55_users` ENABLE KEYS */; UNLOCK TABLES; @@ -555,7 +557,7 @@ CREATE TABLE `wp_options` ( LOCK TABLES `wp_options` WRITE; /*!40000 ALTER TABLE `wp_options` DISABLE KEYS */; -INSERT INTO `wp_options` VALUES (1,'siteurl','http://localhost:9999','yes'),(2,'home','http://localhost:9999','yes'),(3,'blogname','Datadog Test WP DB','yes'),(4,'blogdescription','','yes'),(5,'users_can_register','0','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\\\d_-]+?)-(\\\\d+?)\\\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\\\d+?)\\\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\\([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\\.?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwentythree','yes'),(41,'stylesheet','twentytwentythree','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','53496','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:0:{}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1690204371','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'auto_update_core_dev','enabled','yes'),(96,'auto_update_core_minor','enabled','yes'),(97,'auto_update_core_major','enabled','yes'),(98,'wp_force_deactivated_plugins','a:0:{}','yes'),(99,'initial_db_version','53496','yes'),(100,'wp_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(101,'fresh_site','1','yes'),(102,'user_count','1','no'),(103,'widget_block','a:6:{i:2;a:1:{s:7:\"content\";s:19:\"\";}i:3;a:1:{s:7:\"content\";s:154:\"

Recent Posts

\";}i:4;a:1:{s:7:\"content\";s:227:\"

Recent Comments

\";}i:5;a:1:{s:7:\"content\";s:146:\"

Archives

\";}i:6;a:1:{s:7:\"content\";s:150:\"

Categories

\";}s:12:\"_multiwidget\";i:1;}','yes'),(104,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:7:\"block-2\";i:1;s:7:\"block-3\";i:2;s:7:\"block-4\";}s:9:\"sidebar-2\";a:2:{i:0;s:7:\"block-5\";i:1;s:7:\"block-6\";}s:13:\"array_version\";i:3;}','yes'),(105,'cron','a:7:{i:1674663182;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1674695582;a:4:{s:18:\"wp_https_detection\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674695658;a:1:{s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674738782;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738858;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738859;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}s:7:\"version\";i:2;}','yes'),(106,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_archives','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_meta','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'widget_search','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(115,'widget_recent-posts','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(116,'widget_recent-comments','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(117,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(118,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(119,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(121,'recovery_keys','a:0:{}','yes'),(122,'https_detection_errors','a:1:{s:20:\"https_request_failed\";a:1:{i:0;s:21:\"HTTPS request failed.\";}}','yes'),(123,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-6.1.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-6.1.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-6.1.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-6.1.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"6.1.1\";s:7:\"version\";s:5:\"6.1.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"6.1\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1674652480;s:15:\"version_checked\";s:5:\"6.1.1\";s:12:\"translations\";a:0:{}}','no'),(126,'_site_transient_timeout_theme_roots','1674654196','no'),(127,'_site_transient_theme_roots','a:3:{s:15:\"twentytwentyone\";s:7:\"/themes\";s:17:\"twentytwentythree\";s:7:\"/themes\";s:15:\"twentytwentytwo\";s:7:\"/themes\";}','no'),(128,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1674652481;s:7:\"checked\";a:3:{s:15:\"twentytwentyone\";s:3:\"1.7\";s:17:\"twentytwentythree\";s:3:\"1.0\";s:15:\"twentytwentytwo\";s:3:\"1.3\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:15:\"twentytwentyone\";a:6:{s:5:\"theme\";s:15:\"twentytwentyone\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentytwentyone/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentytwentyone.1.7.zip\";s:8:\"requires\";s:3:\"5.3\";s:12:\"requires_php\";s:3:\"5.6\";}s:17:\"twentytwentythree\";a:6:{s:5:\"theme\";s:17:\"twentytwentythree\";s:11:\"new_version\";s:3:\"1.0\";s:3:\"url\";s:47:\"https://wordpress.org/themes/twentytwentythree/\";s:7:\"package\";s:63:\"https://downloads.wordpress.org/theme/twentytwentythree.1.0.zip\";s:8:\"requires\";s:3:\"6.1\";s:12:\"requires_php\";s:3:\"5.6\";}s:15:\"twentytwentytwo\";a:6:{s:5:\"theme\";s:15:\"twentytwentytwo\";s:11:\"new_version\";s:3:\"1.3\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentytwentytwo/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentytwentytwo.1.3.zip\";s:8:\"requires\";s:3:\"5.9\";s:12:\"requires_php\";s:3:\"5.6\";}}s:12:\"translations\";a:0:{}}','no'),(130,'_site_transient_timeout_browser_894dc60a4e148f4652615ed246d3e298','1675257258','no'),(131,'_site_transient_browser_894dc60a4e148f4652615ed246d3e298','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:9:\"109.0.0.0\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(132,'_site_transient_timeout_php_check_ce267f3653936506950ae9448202043a','1675257259','no'),(133,'_site_transient_php_check_ce267f3653936506950ae9448202043a','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(135,'_site_transient_timeout_community-events-1de8873aa0984c1dbee47981d08b0def','1674695662','no'),(136,'_site_transient_community-events-1de8873aa0984c1dbee47981d08b0def','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:10:\"172.21.0.0\";}s:6:\"events\";a:1:{i:0;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:15:\"WordCamp Torino\";s:3:\"url\";s:33:\"https://torino.wordcamp.org/2023/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2023-04-14 00:00:00\";s:8:\"end_date\";s:19:\"2023-04-15 00:00:00\";s:20:\"start_unix_timestamp\";i:1681423200;s:18:\"end_unix_timestamp\";i:1681509600;s:8:\"location\";a:4:{s:8:\"location\";s:12:\"Turin, Italy\";s:7:\"country\";s:2:\"IT\";s:8:\"latitude\";d:45.050238;s:9:\"longitude\";d:7.669286;}}}}','no'),(137,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1674695664','no'),(138,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:52:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:8:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"The latest news about WordPress and the WordPress community\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 11:56:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=6.2-alpha-55136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"image\";a:1:{i:0;a:6:{s:4:\"data\";s:11:\"\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:3:\"url\";a:1:{i:0;a:5:{s:4:\"data\";s:29:\"https://s.w.org/favicon.ico?2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"width\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"32\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:6:\"height\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"32\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"The Month in WordPress – December 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2023/01/the-month-in-wordpress-december-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:18:\"month in wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14191\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:339:\"Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg: “We are now, as […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:12820:\"\n

Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg:

\n\n\n\n
\n

“We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.”

\nJosepha Haden Chomphosy
\n\n\n\n

December brought with it a time for reflection—a time to look back, celebrate, and start planning new projects. Read on to find out what 2023 holds for WordPress so far.

\n\n\n\n
\n\n\n\n

WordPress is turning 20!

\n\n\n\n

2023 marks the 20th anniversary of WordPress’ launch. The project has come a long way since the first release as it continues to advance its mission to democratize publishing. From its beginnings as a blogging platform to a world-leading open source CMS powering over 40% of websites.

\n\n\n\n

Join the WordPress community in celebrating this important milestone. As the anniversary date approaches, there will be events, commemorative swag, and more.

\n\n\n\n
\n

Stay tuned for updates.

\n
\n\n\n\n
\n\n\n\n

WordPress 6.2 is scheduled for March 28, 2023

\n\n\n\n

Work on WordPress 6.2, the first major release of 2023, is already underway. It is expected to launch on March 28, 2023, and will include up to Gutenberg 15.1 for a total of 10 Gutenberg releases.

\n\n\n\n

The proposed schedule includes four Beta releases to accommodate the first WordCamp Asia and avoid having major release milestones very close to this event.

\n\n\n\n
\n

Read more about the 6.2 schedule and release team.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.8 was released on December 21, 2022. This version features a reorganized Site Editor interface with a Browse Mode that facilitates navigation through templates and template parts. In addition, it includes the ability to add custom CSS via the Style panel and a Style Book that provides an overview of all block styles in a centralized location.
  • \n\n\n\n
  • Gutenberg 14.9 became available for download on January 4, 2023. It introduces a new “Push changes to Global Styles” button in the Site Editor, which allows users to apply individual block style changes to all blocks of that type across their site. Other features include typography support for the Page List block, and the ability to import sidebar widgets into a template part when transitioning from a classic theme.
  • \n
\n\n\n\n
\n

Learn how Gutenberg’s latest releases are advancing the Site Editor experience to be more intuitive and scalable.

\n
\n\n\n\n
\n\n\n\n

Team updates: WordPress big picture goals, new Incident Response Team, and more

\n\n\n\n\n\n\n\n
\n

Check out the 2022 State of the Word Q&A post, which answers submitted questions that Matt could not address at the live event.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

Have thoughts for improving the Five for the Future contributor experience? This post calls for ideas on how this initiative can better support the project and the people behind it.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n\n\n\n\n
\n

Would you like to be a speaker at WordCamp Europe 2023? Submit your application by the first week of February.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @laurlittle, @rmartinezduque.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"WP Briefing: Episode 47: Letter from the Executive Director\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/episode-47-letter-from-the-executive-director/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14175\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:114:\"Hear from WordPress Executive Director Josepha Haden Chomphosy on her vision for the open source project in 2023. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2023/01/WP-Briefing-047.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8912:\"\n

On episode forty-seven of the WordPress Briefing podcast, Executive Director Josepha Haden Chomphosy shares her vision and current thinking for the WordPress open source project in 2023. Rather read it? The full letter is also available.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Show Notes

\n\n\n\n

make.WordPress.org/core
Join the 6.2 Release!
Submit Topics for the Community Summit!

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:40] 

\n\n\n\n

Last month at State of the Word, I shared some opening thoughts about why WordPress. For me, this is an easy question, and the hardest part is always knowing which lens to answer through. Though I always focus on the philosophical parts of the answer, I know that I often speak as an advocate for many types of WordPressers.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

So as we prepare ourselves for the start of a new year, I have a few additional thoughts that I’d like to share with you, my WordPress community, to take into the year with you. 

\n\n\n\n

Firstly, the Four Freedoms. If you have already listened to State of the Word, you have heard my take on the philosophical side of open source and the freedoms it provides.

\n\n\n\n

But if you didn’t, then the TL;DR on that is that open source provides protections and freedoms to creators on the web that I really think should just be a given. But there are a couple of other things about the Four Freedoms, and especially the way that WordPress does this kind of open source-y thing that I think are worth noting as well.

\n\n\n\n

One of those things is that WordPress entrepreneurs, those who are providing services or designing sites, building applications, they have proven that open source provides an ethical framework for conducting business. No one ever said that you aren’t allowed to build a business using free and open source software, and I am regularly heartened by the way that successful companies and freelancers make the effort to pay forward what they can.

\n\n\n\n

[Josepha Haden Chomphosy 00:02:02]

\n\n\n\n

Not always for the sole benefit of WordPress, of course, but often for the general benefit of folks who are also learning how to be entrepreneurs or how to kind of navigate our ecosystem. And the other thing that I love about the Four Freedoms and the way that WordPress does it is that leaders in the WordPress community, no matter where they are leading from, have shown that open source ideals can be applied to the way we work with one another and show up for one another.

\n\n\n\n

As a community, we tend to approach solution gathering as an us-versus-the-problem exercise, which not only makes our solutions better, it also makes our community stronger. 

\n\n\n\n

As I have witnessed all of these things work together over the years, one thing that is clear to me is this: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, but open source methodologies represent a process that can change the way we approach our work and our businesses.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:01] 

\n\n\n\n

The second big thing that I want to make sure you all take into the year with you is that we are preparing for the third phase of the Gutenberg project. We are putting our backend developer hats on and working on the APIs that power our workflows. That workflows phase will be complex. A little bit because APIs are dark magic that binds us together, but also because we’re going to get deep into the core of WordPress with that phase.

\n\n\n\n

If you want to have impactful work for future users of WordPress, though, this is the phase to get invested in. This phase will focus on the main elements of collaborative user workflows. If that doesn’t really make sense to you, I totally get it. Think of it this way, this phase will work on built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and things like programmable editorial, pre-launch checklists.

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00] 

\n\n\n\n

So phases one and two of the Gutenberg project had a very ‘blocks everywhere’ sort of vision. And phase three and, arguably, phase four will have more of a ‘works with the way you work’ vision.

\n\n\n\n

And my final thought for you all as we head into the year is this, there are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was State of the Word 2013, where Matt dreamed on stage of a true WYSIWYG editor for WordPress. Some say it was State of the Word 2016, where we were all encouraged to learn JavaScript deeply. For a lot of us though, it was at WordCamp Europe in 2018 when the Gutenberg feature plugin first made its way to the repo.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like it’s been a long time because it has been a long time. But I can also confirm that it takes many pushes to knock over a refrigerator. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

For early adopters, both to the creation of Gutenberg as well as its use, hyperfocus on daily tasks makes it really hard to get a concept of scale.

\n\n\n\n

And so I encourage everyone this year to look out toward the horizon a bit more and up toward our guiding stars a bit more as well. Because we are now, as we ever were, securing opportunity for those who come after us because of the opportunity that was secured for us by those who came before us. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:33] 

\n\n\n\n

That brings us now to our small list of big things. It’s a very small list, but two pretty big things. The first thing on the list is that the WordPress 6.2 release is on its way. If you would like to get started contributing there, you can wander over to make.WordPress.org/core. You can volunteer to be part of the release squad. You can volunteer your time just as a regular contributor, someone who can test things — any of that. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

We’ll put a link in the show notes. And the second thing that I wanted to remind you of is that today is the deadline to submit topics for the Community Summit that’s coming up in August. That comes up in the middle of August, like the 22nd and 23rd or something like that. 

\n\n\n\n

We’ll put a link to that in the show notes as well. If you already have chatted with a team rep about some things that you really want to make sure get discussed at the community summit, I think that we can all assume that your team rep has put that in. But if not, it never hurts to give it a second vote by putting a new submission into the form.

\n\n\n\n

And that, my friends, is your small list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in a couple of weeks.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:49:\"Letter from WordPress’ Executive Director, 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/letter-from-wordpress-executive-director-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14180\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:127:\"If Phases 1 and 2 had a \"blocks everywhere\" vision, think of Phase 3 with more of a “works with the way you work” vision. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:5903:\"\n

Last month at State of the Word, I shared some opening thoughts about “Why WordPress.” For me, this is an easy question, and the hardest part is knowing which lens to answer through. The reasons that a solopreneur will choose WordPress are different than the reasons a corporation would. And while artists and activists may have a similar vision for the world, their motivations change their reasons, too. That’s why I always focus on the philosophical parts of the answer because I know that I am speaking as an advocate for many types of WordPressers. I have a few other reasons, too, which you may not be aware of as you use our software every day.

\n\n\n\n

Why WordPress?

\n\n\n\n

Most importantly, the Four Freedoms of Open Source. If you have already listened to State of the Word, you have heard my thoughts on the philosophical side of open source and the freedoms it provides. If you didn’t, then the tl;dr on that is that open source provides protections and freedoms to creators on the web that should be a given. There’s an extent to which the idea of owning your content and data online is a radical idea. So radical, even, that it is hard for folks to grasp what we mean when we say “free as in speech, not free as in beer.” Securing an open web for the future is, I believe, a net win for the world especially when contrasted to the walled gardens and proprietary systems that pit us all against one another with the purpose of gaining more data to sell.

\n\n\n\n

A second reason is that WordPress entrepreneurs (those providing services, designing sites, and building applications) have proven that open source offers an ethical framework for conducting business. No one ever said that you cannot build a business using free and open source software. And I am regularly heartened by the way successful companies and freelancers make an effort to pay forward what they can. Not always for the sole benefit of WordPress, but often for the general benefit of folks learning how to be an entrepreneur in our ecosystem. Because despite our competitive streaks, at the end of the day, we know that ultimately we are the temporary caretakers of an ecosystem that has unlocked wealth and opportunity for people we may never meet but whose lives are made infinitely better because of us.

\n\n\n\n

And the final reason is that leaders in the WordPress community (team reps, component maintainers, and community builders) have shown that open source ideals can be applied to how we work with one another. As a community, we tend to approach solution gathering as an “us vs. the problem” exercise, which not only makes our solutions better and our community stronger. And our leaders—working as they are in a cross-cultural, globally-distributed project that guides or supports tens of thousands of people a year—have unparalleled generosity of spirit. Whether they are welcoming newcomers or putting out calls for last-minute volunteers, seeing the way that they collaborate every day gives me hope for our future.

\n\n\n\n

As I have witnessed these three things work together over the years, one thing is clear to me: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, open source methodologies represent a process that can change the way we approach our work and our businesses. 

\n\n\n\n

WordPress in 2023

\n\n\n\n

As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows. Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.

\n\n\n\n

If Phases 1 and 2 had a “blocks everywhere” vision, think of Phase 3 with more of a “works with the way you work” vision. 

\n\n\n\n

In addition to this halfway milestone of starting work on Phase 3, WordPress also hits the milestone of turning 20 years old. I keep thinking back to various milestones we’ve had (which you can read about in the second version of the Milestones book) and realized that almost my entire experience of full-time contributions to WordPress has been in the Gutenberg era.

\n\n\n\n

I hear some of you already thinking incredulous thoughts so, come with me briefly.

\n\n\n\n

There are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was at State of the Word 2013 when Matt dreamed of “a true WYSIWYG” editor for WordPress. Some say it was at State of the Word 2016 where we were encouraged to “learn Javascript deeply.” For many of us, it was at WordCamp Europe in 2017 when the Gutenberg demo first made its way on stage.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like a long time because it has been a long time. I can also confirm that it takes many pushes to knock over a refrigerator. For early adopters (both to the creation of Gutenberg and its use), hyper-focus on daily tasks makes it hard to get a concept of scale.

\n\n\n\n

So I encourage you this year to look out toward the horizon and up toward our guiding stars. We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.

\n\n\n\n

Rather listen? The abbreviated spoken letter is also available.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:43:\"WordPress is Turning 20: Let’s Celebrate!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wordpress.org/news/2023/01/wordpress-is-turning-20-lets-celebrate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 21:38:49 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:6:\"Events\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:4:\"WP20\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14155\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"2023 marks the 20th year of WordPress. Read on to learn about how WordPress is celebrating this milestone.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Dan Soschin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:1476:\"\n

2023 marks the 20th year of WordPress. Where would we all be without WordPress? Just think of that! While many technologies, software stacks, and fashion trends have come and gone throughout the past two decades, WordPress has thrived. This is due to the fantastic work and contributions of the WordPress community, comprised of thousands of contributors; and millions of users who have embraced the four freedoms of WordPress and the mission to democratize publishing.

\n\n\n\n

Let’s celebrate!

\n\n\n\n

Throughout the beginning of 2023, leading up to the official anniversary date of WordPress’s launch (May 27, 2003), a number of different events will celebrate this important milestone, reflect on the journey, and look toward the future.

\n\n\n\n

Please join in!

\n\n\n\n

Over the next few months, be sure to check WordPress’s official social media accounts along with the official anniversary website for updates on how you can be involved in this exciting celebration by contributing content, collecting cool anniversary swag, and much more. 

\n\n\n\n

Use the hashtag #WP20 on social media so the community can follow along.

\n\n\n\n

If you have something planned to celebrate that you would like to be considered for inclusion on the official website, please use this form to share the details.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14155\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"WP Briefing: Episode 46: The WP Bloopers Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2022/12/episode-46-the-wp-bloopers-podcast/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 31 Dec 2022 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14123\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:115:\"This episode of the WP Briefing features all the Josepha bloopers our little elves have stored away over the year. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2022/12/WP-Briefing-046.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9636:\"\n

This episode of the WP Briefing features all the Josepha bloopers our little elves have stored away over the year.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can normally catch quick explanations of the ideas behind the WordPress open source project with the hope that deeper understanding creates deeper appreciation.

\n\n\n\n

But on today’s bonus episode, instead of catching quick explanations, you’ll catch some quick bloopers. 

\n\n\n\n

The end of the year is a time when many people and many cultures gather together, and whether you observe traditions of light or faith, compassion, or celebration from everyone here at the WordPress Briefing Podcast, we’re wishing you a happy, festive season and a very happy New Year.

\n\n\n\n

Sit back, relax, and enjoy some of the laughs and outtakes from recording the WP Briefing over the year.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress. This is the thing I’ve done 25 times, and I know how to do it for reals.

\n\n\n\n

Welcome to WordPress Briefing, episode 20. Oh no, 7? 27? 26? Episode 27. I know how many things I’ve done.

\n\n\n\n

Ooh, neat. This is Josepha recording episode 46 of the WP Bonus Briefings. Not because we’ve had 46 bonus Briefings, but because this is the 46th one and it is a bonus, it will also have a fancy name. But right now, I’m just calling it the bonus. It’s gonna be quick. Here I go. 

\n\n\n\n

Group them into two big buckets, themes, uh, themes and tools. Mmm, I’m gonna have to redo the whole thing! No! I thought I could save it, and I didn’t save it. I had a typo in my script, and then I messed it up. I, it said into you big buckets instead of into two big buckets. 

\n\n\n\n

[Josepha Haden Chomphosy 00:02:00] 

\n\n\n\n

I’m gonna start over from the target release date because I kind of smeared it all together, um, despite what I intended to do.

\n\n\n\n

And gives everyone, no. What is this ringing of phones? Oh, I was doing so well. Where was I? Let’s see if I can just pick it up.

\n\n\n\n

All righty, live from my closet. It’s episode 20, the WordPress Briefing, WP Briefing. So I have a title for this, and when I started writing it, I really had every intention of writing it to the title. And then what I wrote doesn’t fit the title at all, but does really hang together well. And so we’re gonna have to come up with a new title, but at the moment, it’s called So Many Ways to WordPress.

\n\n\n\n

Here in a minute, you will see why it doesn’t fit. Also, at the end, I feel like I get very, like, angry nerd leader.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:00]  

\n\n\n\n

And so I may, I may at the end, give that a second go and see if there’s a way that I can soften it a little bit, but, I, I don’t know that I can soften it. I feel very strongly about it. So, maybe I am just an angry nerd leader.

\n\n\n\n

Oh, okay. I’ll get us started now that I apparently have filled the room with apologies, not the room, the closet. 

\n\n\n\n

We’ll figure out something very catchy as a title or as an alternative. Very descriptive, and people will click on it because they must know, but we’ll figure out the title later.

\n\n\n\n

@wordpress.org. However, I don’t know why I decided to do an invitation to email me in the middle of that. I’m gonna start from the top of that paragraph. I just got too excited by the opportunity to get mail.

\n\n\n\n

I gotta slow it down. I’m like the fastest talker, had too much coffee. Okay, slowing it down now. 

\n\n\n\n

Huh? What am I saying? No, no, that’s what I’m saying. It’s fine. I, I can do this. 

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00]

\n\n\n\n

Hold on. Oww. Sorry. I was adjusting my microphone, and then it fell down. I happened to be holding it at the time, so it didn’t, like, slam down, I think, and hurt your ears and so I apologize. Good thing I stopped so it didn’t just, like, slam down in the middle of a recording.

\n\n\n\n

That’s all right. I’m gonna give myself that win, even though it’s a hollow one. All right. Trying again. Starting right there, at now since.

\n\n\n\n

This year, it starts on October 18th, 2001. That’s the year? No, 2021. That’s the year. Oh man. I’m doing such a great job of this.

\n\n\n\n

Um, I’m recording this slightly before, um, you’re hearing it? What, how am I gonna start this? Hold on. I don’t know how to start this. All right. I’m, I can do it.

\n\n\n\n

Oh, I’m so glad I remembered. We had guests that could have been so embarrassing.

\n\n\n\n

Now for me, the trade-offs work well. How many times can I say now?

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

Do I just start every sentence with now now? Is this just how I do things? Uh, now, now, now, now. I’m gonna start all over again because I’m in my head about the words in my mouth now. So.

\n\n\n\n

In some near timeframe, some near timeframe. This is not a thing that people say, Dustin, I’m sorry. That’s not a thing people say. I’m just gonna retry that one sentence to sound like I speak with other human beings sometimes.

\n\n\n\n

Today is the start of… I can do these things.

\n\n\n\n

This was a terrible ending. I need to just finish that last part. I’m gonna redo the part where I started with my name and not the name of the podcast. Um, and we’ll do that.

\n\n\n\n

And if you’re supporting or building anything to hand off to clients, you know that timely, easy to ship changes on a site are considered a vital part of any overarching brand and marketing strategy. Wow. It’s like, I don’t know what words are right there. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

I tripped over my own tongue a lot. I’m gonna sit, I’m gonna do that paragraph again because I didn’t do a very good job of it.

\n\n\n\n

I’ll do a better job.

\n\n\n\n

I literally digress, and now I don’t know. I am in my thing. What was I saying? Oh, there we go. 

\n\n\n\n

Topher DeRosia, who founded Word not WordPress. Holy moly. That was a, I knew I was gonna say that, and I was like, don’t say that when you actually get around to saying this, but here I am, and I did it. Even though I knew I was gonna do it and I told myself not to. Doing it again. Right from there.

\n\n\n\n

Not which audiench segment. Oh man. Audiench is not a word, folks. I was on a roll. I’m gonna start right from the primary thing.

\n\n\n\n

I don’t even remember how I started this podcast. What is the last thing I said? I said, here we go. All right. 

\n\n\n\n

Kind of covered some interesting ground, and so, oh no, this is not where I’m gonna start it. I know exactly where I’m gonna start it. Okay. I’m really ready now. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:07:00] 

\n\n\n\n

I suddenly, I’m gonna pause right here because I suddenly got really worried that I didn’t actually hit record. Oh my gosh. I did. Woo. I’m all over the place. Okay. We’ll now continue. Wait, did I? Oh my goodness. I did, super sorry.

\n\n\n\n

Of the WordPress Briefing. I’m gonna do some singing in the middle of some talking, but I keep trying to talk myself out of the singing, so I’m gonna go ahead and do the singing, and then I’ll do the talking before I talk myself out of the singing. Here I go, probably.

\n\n\n\n

I added a word. That was so good. I’m gonna start again. I’m gonna get some water, and then I’m gonna start again. Not again. Again. Just from the ‘and finally.’

\n\n\n\n

I don’t know how I finish my show. Y’all, I do this literally every week. I never know how to finish my show. Here we go.

\n\n\n\n

I don’t know why I shouted at you from the other side of the tiny closet. I apologize. I’m gonna start again from ‘and finally.’

\n\n\n\n

Tada we did it.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:00] 

\n\n\n\n

Ha. I hate it. I hate the whole podcast. It’s gonna be fine. 

\n\n\n\n

Done. Nailed it.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

With that, I’m your host, Josepha Haden Chomphosy. Merry Christmas from me. Happy holidays to you, and we’ll see you again in the new year.

\n\n\n\n

Done.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14123\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"WP Briefing: Episode 45: State of the Word Reflections\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://wordpress.org/news/2022/12/episode-45-state-of-the-word-reflections/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Dec 2022 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14070\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"Josepha reflects on this year\'s State of the Word address here on the WP Briefing podcast. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2022/12/WP-Briefing-045.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:14564:\"\n

In the forty-fifth episode of the WordPress Briefing, WordPress Executive Director Josepha Haden Chomphosy discusses highlights from this year’s State of the Word address.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

References

\n\n\n\n

LearnWP
WordPress Playground
ICYMI: State of the Word Recap
Take the 2022 WordPress Survey!
Exploring WordPress Certifications
Community Summit WordCamp Site
Submit Topics for the 2023 Community Summit
20th Anniversary– Stay Tuned for Updates
Check Out Style Variations and the 2023 Theme

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello, everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:39]

\n\n\n\n

Last week, WordPress hosted its annual State of the Word. As usual, this was delivered by our project co-founder Matt Mullenweg and represented a year-long labor of love from the WordPress community as a whole. There are many things I love about State of the Word, but consistently the thing I love the most is being able to shine spotlights on the great work of our global network of contributors.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:02] 

\n\n\n\n

Since that presentation goes by at the speed of light, I wanted to highlight a few things as well. First things first, I wanted to highlight that we had nearly 1,400 contributors, and by nearly, I mean just one too few. We had 1,399 contributors. So that is a big deal in general, but it’s an especially big deal to me because that’s before we start looking at any contributions that aren’t specifically tied to a release. 

\n\n\n\n

You may be wondering what those non-release contributions are. An incomplete list of those contributions would include organizing WordPress events, training others how to use WordPress, the myriad podcasts, articles, and newsletters that make up the WordPress media community, and any participant in a call for testing. Not to mention the unglamorous ways to contribute, like reviewing themes or reviewing plugins.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:58] 

\n\n\n\n

Things like patching security vulnerabilities and the bazillion things that Meta does to make sure that our community has all the tools that it needs to function. So I want to echo, once again, the huge, huge thanks that Matt already shared in State of the Word, and thank all of you for showing up for our project and for each other this way.

\n\n\n\n

The next thing I wanted to be sure to highlight was LearnWP. It was briefly noted that 12,000 learners had found their way to courses on learn.wordpress.org, and then during the Q&A, there was a related question about certifications in WordPress. 

\n\n\n\n

The need for certifications has been a regular topic in our project, and I mentioned that there are two different ongoing discussions at the moment. One of those discussions is happening directly on the make.wordpress.org/training site, so I’ll share a link in the show notes for that.

\n\n\n\n

But I’ve also been personally chatting on and off with Training team reps and other members of the community about what makes that so hard. In case you have not heard my whole spiel about what makes it difficult, it’s the logistics and our speed of iteration, and public perception. 

\n\n\n\n

[Josepha Haden Chomphosy 00:03:05]

\n\n\n\n

So not exactly a small set of hurdles. I’ll be doing a more complete post on this in the New Year so that we can get some solid documentation of the state of things and not let it be lost forever in this podcast. But I do know that it is something that we are very interested in as a community and something that, historically, I have really been resistant to.

\n\n\n\n

Not because I think it’s a bad idea, but because as someone who’s looking out for our operations side of things and our logistics side of things, it is not clear how we’re gonna get that done. Like I said, in the New Year, keep an eye out for a big, big post that takes a look at the benefits versus the costs and everything that we can do to help make those match each other a bit better.

\n\n\n\n

And then the last thing I wanted to highlight was the WordPress Playground. Okay, so this was the last thing that Matt mentioned, and I want to be sure that it’s clear what’s going on with this project because when I first heard about it, I very nearly lept from my chair! 

\n\n\n\n

[Josepha Haden Chomphosy 00:04:03] 

\n\n\n\n

It was such a remarkably big deal. Okay, so the WordPress Playground uses technological magic called ‘web assembly.’ I don’t know what it is, but it’s magic. And when I say magic, I mean that this tool makes it possible to run WordPress, an instance of WordPress, including a theme and a handful of plug-ins entirely inside your browser as a logged-in admin.

\n\n\n\n

You don’t need a server. You don’t need to select a host. You don’t need to download anything at all. You don’t need to know what your domain’s going to be. You simply select the theme you want to test. Add some dummy content and see how all of the posts and pages function as though we’re a real live WordPress site running on your favorite top-tier host.

\n\n\n\n

Then when you close the tab, it’s gone forever. Poof. Just like that. Now, this is a brand new project. It’s brand new to us and has a long way to go. So if working on that sounds cool, stop by the Meta Playground channel in the Making WordPress Slack. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:09] 

\n\n\n\n

But this, in my mind, changes the way that we stage sites.

\n\n\n\n

It could change the way we determine whether a theme or plugin is right for us. And arguably, it can become a stress-free way to introduce new or undecided users to WordPress’s admin area so that they can tell what they’re getting into. So when I say that this is a mind-blowing thing, and when I say that it is powered by magic, like it is astounding, it is astounding.

\n\n\n\n

And the applications for our users as a whole, I think, are untapped yet, and potentially even the applications for our learners and future learners of WordPress– equally untapped. I’m very excited to see what we can do with this project in the future. So stop by the Meta channel. Stop by Meta Playground.

\n\n\n\n

See what’s going on over there. We would love to have you. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

So those are my highlights of the day for State of the Word. Like I said, there are a few things I want to do more of a deep dive on in the text, so keep an eye out on make.wordpress.org/projects for most of those. But right now, let’s make some time for the small list of big things.

\n\n\n\n

[Josepha Haden Chomphosy 00:06:17] 

\n\n\n\n

Today I actually have kind of like a big list of big things. But I pretended it was small, so you didn’t turn off the podcast. So the first thing that I have is that in case you missed State of the Word, if you didn’t have a Watch Party to go to, or you didn’t know it was happening and so you didn’t really tune in at the time, I’m going to drop in a link of the recording.

\n\n\n\n

It’s gonna probably start right when everything gets going. And so you shouldn’t have to scrub through anything. If you end up on one of the recordings that includes like the whole live stream, there is jazz for the first 30 minutes, and just, you know, skip through that.

\n\n\n\n

[Josepha Haden Chomphosy 00:07:00]

\n\n\n\n

The second thing on my big list of big things is our annual community survey. So Matt mentioned this in State of the Word, and he pointed out that one of the things that makes WordPress and open source in general so effective is that we have a way to communicate with people who are using our software and we make every effort to be responsive to it.

\n\n\n\n

So the annual survey that we send out, it used to be quite big, and we’ve cut it down to 20 questions. If you want, you can think of it as like a census, so have your type of work and how long you’ve been working in WordPress, and what you wish to do with WordPress– have all those things be counted so we have a good idea of the type of person who’s currently using WordPress, and we can account for your needs and wants.

\n\n\n\n

But also, if you want to think of it more as an opportunity to share the things that were especially useful for you in the project this year or especially valuable for you as a contributor, this is also an excellent place to do that.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:01] 

\n\n\n\n

There’s a QR code running around on the internet somewhere, but I’ll also put a link in the show notes. If you do not know where the show notes are, by the way, they are at wordpress.org/news/podcast, and you’ll be able to get to the survey.

\n\n\n\n

The third thing on my big list of big things is that next year we’re hosting a community summit. So if you’ve never been to a community summit, Matt mentioned that it is an opportunity for the best and most prolific contributors that we have to show up and discuss the things that are the biggest problems for the WordPress project right now.

\n\n\n\n

But we also want to make sure that we are making space for the voices that we know that we are missing from the community as well as contributors who look like they are probably excellent future stewards of this open source project that we are taking care of together. And so there is a whole website for that.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:55] 

\n\n\n\n

I believe it’s communitysummit.wordcamp.org. Right now, there is a form up asking for topics that you want to be able to discuss while we are there, but it’s taking place, if I recall correctly, on August 22nd and 23rd of 2023.

\n\n\n\n

Number four on my big list of big things is that next year is WordPress’s 20th anniversary. So on May 27th of next year, WordPress will officially be 20 years old. So on our 10th birthday, anniversary rather, and our 15th anniversary, we pulled together some parties all across the world. 

\n\n\n\n

We had some images, some logos, and things that were specific to the celebration that we printed into stickers and that folks put on, like, mugs and backpacks and cakes and stuff. So if you want to learn more about that, keep an eye out in the community channel in making WordPress Slack. They will keep you posted on how to one, find any of those logos and designs so that your local community can join in the celebrations.

\n\n\n\n

[Josepha Haden Chomphosy 00:10:03] 

\n\n\n\n

But they will also help you learn how to have any sort of WordPress celebration party that we’re doing there in May of 2023. 

\n\n\n\n

And then the final thing on my big list of big things, it was mentioned that on the 2023 theme that was shipped with a bunch of style variations and there was this really, I think, excellent illustrative video that Rich Tabor put together for us that shows that you can switch through style variations on a single theme and have a site that looks totally different.

\n\n\n\n

Now, that feels like that’s just a thing that should always have been in WordPress, but it is new this year. And so, if you have not yet had a chance to look at the 2023 theme, it is the default theme that shipped with 6.1. And so, if you have it on your website and just haven’t had a look at it yet, I encourage you to do that.

\n\n\n\n

[Josepha Haden Chomphosy 00:11:00]

\n\n\n\n

It’s a really interesting implementation that makes a single theme potentially look like an infinite number of other themes, and those style variations can be specific to the theme or can just kind of be around and about in the patterns that are also available in Core. 

\n\n\n\n

Give that a look. I think it’s super worthwhile.

\n\n\n\n

And that, my friends, is your big list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in the New Year.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14070\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"The Month in WordPress – November 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2022/12/the-month-in-wordpress-november-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Dec 2022 12:05:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:18:\"month in wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14124\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:317:\"WordPress enthusiasts tuned in last week for the State of the Word address to celebrate the project\'s yearly accomplishments and explore what 2023 holds. But that’s not the only exciting update from the past month. New proposals and ideas are already emerging with an eye on the year ahead—let’s dive into them!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:13931:\"\n

WordPress enthusiasts tuned in last week for the State of the Word address to celebrate the project’s yearly accomplishments and explore what 2023 holds. But that’s not the only exciting update from the past month. New proposals and ideas are already emerging with an eye on the year ahead—let’s dive into them!

\n\n\n\n
\n\n\n\n

Highlights from State of the Word 2022

\n\n\n\n

WordPress co-founder Matt Mullenweg delivered the annual State of the Word address on December 15, 2022, before a live audience in New York City. Most attendees joined the event via livestream or one of the 33 watch parties held across 11 countries.

\n\n\n\n

Josepha Haden Chomphosy, Executive Director of WordPress, kicked off this year’s event with an introduction to the Four Freedoms of open source and the importance of WordPress in ensuring “a free, open and interconnected web for the future.”

\n\n\n\n

Similar to past State of the Word events, Matt reflected on the project’s achievements over the past year, including Gutenberg’s adoption beyond WordPress, the steady progress in advancing the site editing experience, and the return to in-person events. In addition, he took the opportunity to remind everyone of the 2023 Community Summit and the 20th anniversary of WordPress coming up next year.

\n\n\n\n

Ahead of 2023, Matt announced new taxonomies in the WordPress.org theme and plugin directories to help users identify the extensions that best fit their needs and plans for Phase 3 of Gutenberg—Collaboration—among other notable updates.

\n\n\n\n

People who watched the State of the Word enjoyed a demo of WordPress Playground, an experimental project to explore, experiment, and build apps with a WordPress instance that runs entirely in the browser.

\n\n\n\n
\n

Missed the event? Read the recap or watch the State of the Word recording and Q&A session on WordPress.tv.

\n
\n\n\n\n
\n\n\n\n

The 2022 WordPress Survey is open

\n\n\n\n

The annual WordPress survey helps project leadership and those who build WordPress understand more about the contributor experience, how the software is used, and by whom.

\n\n\n\n

This year’s survey will remain open through the end of 2022 and is available in English, French, German, Italian, Japanese, Russian, and Spanish.

\n\n\n\n
\n

Take the 2022 WordPress Survey to help make an impact on the project.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.6, released on November 23, 2022, came with many refinements to core blocks. Notable highlights include a variation picker that allows users to choose a desired layout when a Group block is inserted on a page, a new list view for editing the Navigation block, and a keyboard shortcut to transform paragraph blocks into headings.
  • \n\n\n\n
  • Gutenberg 14.7, released on December 7, 2022, introduced an experimental tabbed sidebar, colors to help identify some block types in list view, and improvements to the Page List block to make it easier to manage page links in the content.
  • \n
\n\n\n\n
\n

Follow the “What’s new in Gutenberg” posts to stay on top of the latest enhancements.

\n
\n\n\n\n
\n\n\n\n

Team updates: Introducing the block editor in the support forums, a revamped Showcase page, and more

\n\n\n\n\n\n\n\n
\n

Curious about why WordPress has so many releases? Tune in to Episode 44 of WP Briefing to learn about the role of major and minor releases in the project.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

The Community Team is calling on WordPress contributor teams to suggest topics for the 2023 Community Summit by January 16, 2023.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n
    \n
  • The #WPDiversity working group organized several workshops during the past few months. Among other highlights, attendees of the Speaker Workshop for Women Voices in Latin America reported a 52% increase in self-confidence to speak in public. Stay tuned for the next events.
  • \n\n\n\n
  • The WordCamp Europe 2023 organizing team shared their content vision for next year’s flagship event in Athens, Greece.
  • \n\n\n\n
  • WordCamp Asia 2023 is just a few months away, scheduled for February 17-19, 2023, in Bangkok, Thailand. Organizers have announced the first recipient of the WordCamp Asia Diversity Scholarship, Awais Arfan.
  • \n\n\n\n
  • Three more WordCamps are happening in the next few months:\n\n
  • \n
\n\n\n\n
\n

WordCamp Europe 2023 is calling for sponsors and speakers.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @webcommsat, @sereedmedia, and @rmartinezduque.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14124\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"State of the Word 2022: A Celebration of the Four Freedoms of Open Source\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"https://wordpress.org/news/2022/12/state-of-the-word-2022-recap/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Dec 2022 22:11:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:6:\"Events\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:17:\"state of the word\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14110\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:355:\"WordPress belongs to all of us, but really we’re taking care of it for the next generation.” Matt Mullenweg A small audience of WordPress contributors, developers, and extenders gathered on December 15 for the annual State of the Word keynote from WordPress co-founder Matt Mullenweg. Those who could not join in person joined via livestream […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Chloe Bringmann\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:5677:\"\n
\n

WordPress belongs to all of us, but really we’re taking care of it for the next generation.”

\nMatt Mullenweg
\n\n\n\n
\n\n
\n\n\n\n

A small audience of WordPress contributors, developers, and extenders gathered on December 15 for the annual State of the Word keynote from WordPress co-founder Matt Mullenweg. Those who could not join in person joined via livestream or one of 33 watch parties held across 11 countries, with more than 500 RSVPs.

\n\n\n\n
\"The
\n\n\n\n

Executive Director, Josepha Haden Chomphosy, introduced the event with a reminder of why so many of those gathered choose WordPress—the Four Freedoms of open source. As Haden Chomphosy noted, open source is an idea that can change our generation, and WordPress is one of the most consistent and impactful stewards of those freedoms.

\n\n\n\n

As with past State of the Word events, Matt reflected on the year’s accomplishments, learnings, and aspirations as the project moves into 2023. From Gutenberg concluding its second phase of site editing in preparation for phase three—Collaborative Workflows, to the reactivation of meetups and global WordCamps, to the introduction of a new theme and plugin taxonomy, to musings on the potential of machine learning, WordPress enters its 20th year continuing to define bleeding edge technology in thanks to the ecosystem’s vibrant community. 

\n\n\n\n

The one-hour multimedia presentation was followed by an interactive question and answer session where Matt fielded questions from the livestream and studio audience. All questions will be responded to in a follow-up post on Make.WordPress.org/project

\n\n\n\n

Discover everything that was covered by watching the official event recording and join the ongoing #StateOfTheWord conversation on Tumblr, Instagram, Facebook, Linkedin, and Twitter. For another way to get involved, consider sharing your experience with WordPress in the 2022 WordPress Community Survey.

\n\n\n\n\n\n\n\n

Referenced Resources 

\n\n\n\n\n\n\n\n

Special thanks to @laurlittle and @eidolonnight for review and collaboration.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14110\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"Share Your Experience: The 2022 WordPress Survey is Open\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:57:\"https://wordpress.org/news/2022/12/2022-wordpress-survey/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Dec 2022 16:00:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:6:\"survey\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14062\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"The 2022 WordPress survey is open for your input and available in English, French, German, Italian, Japanese, Russian, and Spanish.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Chloe Bringmann\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:4584:\"\n

Each year, members of the WordPress community (users, site builders, extenders, and contributors) provide valuable feedback through an annual survey. Key takeaways and trends that emerge from this survey often find their way into the annual State of the Word address, are shared in the public project blogs, and can influence the direction and strategy for the WordPress project.

\n\n\n\n

Simply put: this survey helps those who build WordPress understand more about how the software is used, and by whom. The survey also helps leaders in the WordPress open source project learn more about our contributors’ experiences.  

\n\n\n\n

To ensure that your WordPress experience is represented in the 2022 survey results, take the 2022 annual survey now.

\n\n\n\n\n\n\n\n

You may also take the survey in French, German, Italian, Japanese, Russian, or Spanish, thanks to the efforts of WordPress polyglot contributors. These are the most frequently installed languages based on the number of WordPress downloads. 

\n\n\n\n

The survey will be open through the end of 2022, and then WordPress plans to publish the results sometime in 2023. This year, the survey questions have been refreshed for more effortless survey flow, completion, and analysis. Some questions have been removed, while a few new ones are now present, reflecting the present and future of WordPress. If you’re looking for the analysis of the 2021 survey results, those will also be shared in early 2023.

\n\n\n\n

Spread the word

\n\n\n\n

Help spread the word about the survey by sharing it with your network, through Slack, or within your social media accounts. The more people who complete the survey and share their experience with WordPress, the more the project as a whole will benefit in the future.

\n\n\n\n

Security and privacy

\n\n\n\n

Data security and privacy are paramount to the WordPress project and community. With this in mind, all data will be anonymized: no email addresses nor IP addresses will be associated with published results. To learn more about WordPress.org’s privacy practices, view the privacy policy.

\n\n\n\n

Thank you

\n\n\n\n

Thank you to the following WordPress contributors for assisting with the annual survey project, including question creation, strategy, survey build-out, and translation:

\n\n\n\n

dansoschin, _dorsvenabili, angelasjin, arkangel, audrasjb, atachibana, bjmcsherry, chanthaboune, eidolonnight, fernandot, fierevere, fxbenard, jdy68, jpantani, laurlittle, nao, nielslange, peiraisotta, piermario, rmartinezduque, santanainniss.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14062\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:72:\"\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"People of WordPress: Huanyi Chuang\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2022/11/people-of-wordpress-huanyi-chuang/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Nov 2022 20:09:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:6:{i:0;a:5:{s:4:\"data\";s:9:\"Community\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:3;a:5:{s:4:\"data\";s:10:\"Interviews\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:4;a:5:{s:4:\"data\";s:9:\"HeroPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:5;a:5:{s:4:\"data\";s:19:\"People of WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=13562\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:144:\"The latest People of WordPress story features Huanyi Chuang, from #Taiwan, on his journey to become a digital marketer and front end developer. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Abha Thakor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11689:\"\n

This month we feature Huanyi (Eric) Chuang, a front end developer from Taiwan, who helps connect local groups to WordPress and the worldwide open source community. He is part of the team helping to make the first WordCamp Asia a success in 2023.

\n\n\n\n

The People of WordPress series shares some of the inspiring stories of how people’s lives can change for the better through WordPress and its global network of contributors.

\n\n\n\n
\"Huanyi
\n\n\n\n

Discovering WordPress and the benefit of child themes

\n\n\n\n

Huanyi’s first footsteps in WordPress began in 2017 when he worked for a firm that built blogs and developed ad content for clients.

\n\n\n\n

After building a few sites using the platform, he discovered child themes and through them opened up a world of possibilities for his clients. To this day, he uses child themes to deliver truly custom designs and functionality for clients.

\n\n\n\n

Later in his career, Huanyi moved into digital marketing, integrating sites with massive ad platforms like Google and Facebook. This led him to learn to work with tracking code and JavaScript. He also began his learning journey in HTML, CSS, and PHP, to be able to improve his development skills and customize child themes.

\n\n\n\n

Meetups bring together software users to learn together

\n\n\n\n
\"Huanyi
Huanyi pictured in Australia during one of his travels meeting a koala bear.
\n\n\n\n

When Huanyi had a problem with a client’s site, he looked to WordPress meetups near where he lived in Taipei to help find the solutions.

\n\n\n\n
\n

“When I encountered an issue with the custom archive pages, a local meetup announcement showed up on my WordPress dashboard.”

\nHuanyi Chuang
\n\n\n\n

At the meetup, he met more experienced WordPress users and developers there, who answered his questions and helped him learn.

\n\n\n\n

“When I encountered an issue with the custom archive pages, a local meetup announcement showed up on my WordPress dashboard. That was my original connection with the local community,” Huanyi said.

\n\n\n\n

The WordPress community gave Huanyi a chance to connect with people, feed his curiosity about the software, and join a circle of people he could share this interest.

\n\n\n\n

At first, he thought meetups were an opportunity to source new clients, and he took his business cards to every event. However, he soon found that these events offered him the opportunity to make friends and share knowledge.

\n\n\n\n

From then on, Huanyi started focusing more on what he could give to these events and networks, making new friends, and listening to people. This led him to share as a meetup speaker his own commercial website management experience.

\n\n\n\n

The road to WordCamp

\n\n\n\n

It was going to his first meetup and then getting involved with WordCamps that changed Huanyi’s whole relationship with WordPress.

\n\n\n\n
\"Huanyi
\n\n\n\n

In 2018, he took the step to help as an organizer, having joined the Taoyuan Meetup in Taiwan. He played several parts across the organizing team, and the welcoming feeling he got in every situation encouraged him to get more involved.

\n\n\n\n

He recalls meeting new friends from different fields and other countries, which gave him a great sense of achievement and strengthened his passion for participating in the community.

\n\n\n\n

When the team started this meetup, numbers were much lower than in the group in the city of Taipei, but they were not disheartened and gradually grew the local WordPress community.

\n\n\n\n

They created a pattern of ‘multiple organizers,’ which spread the workload and grew friendships. 

\n\n\n\n
\n

“Being connected to and from meetups is the most valuable part of the community. Having these friends makes me gather more information. We share information and benefit from others’ information, and thus we gain more trust in each other. With such credibility, we share more deeply and build deeper relations.”

\nHuanyi Chuang
\n\n\n\n

Before the pandemic, the meetup met every month and grew to become the second largest meetup in Taiwan. Huanyi also contributed to the WordPress community as an organizer of WordCamp Taipei 2018 in the speaker team and lead organizer of WordCamp Taiwan 2021.

\n\n\n\n

So why should you join the community?

\n\n\n\n

According to Huanyi, you will always have something to take home with you. It might be new information or experiences. It might be plugins or theme ideas. But most of all, it is the chance to meet fascinating people and make new friends.

\n\n\n\n
\n

Huanyi’s message to other contributors:
“Keep participating, and you will find more you can achieve than you expect.”

\n
\n\n\n\n

He added that long-term participation will ‘let you feel the humanity behind the project’.

\n\n\n\n

Localize: the road ahead for WordPress

\n\n\n\n
\"Huanyi
\n\n\n\n

Huanyi believes WordPress has the power to break down the barriers between designers, project managers, developers, marketers, writers, and publishers. In Taiwan, he said WordPress is ‘a common protocol’ that lets people from all of these disciplines work and communicate together more easily than they ever have before.

\n\n\n\n

That is why he works on and encourages others to localize plugins today. He believes localization of the software is the foundation for the extension of the WordPress community as it enables people to ‘Flex their Freedom’ in a language they speak!

\n\n\n\n

He has helped to organize online events around previous WordPress Translation Day events.

\n\n\n\n

Huanyi said: “I think it’s important to localize WordPress because its very concept of ‘open source’ means that people can access it freely. In another way, free from the monopoly of knowledge and speech. To achieve it, it’s important that people can access it with their own language.

\n\n\n\n

“Localization is the foundation of the extension of WordPress community because it helps people using different languages to access the project and lowers the hurdle to understand how things work.”

\n\n\n\n

Share the stories

\n\n\n\n

Help share these stories of open source contributors and continue to grow the community. Meet more WordPressers in the People of WordPress series.

\n\n\n\n

Contributors

\n\n\n\n

Thank you to @no249a002 for sharing his adventures in WordPress.

\n\n\n\n

Thank you to Abha Thakor (@webcommsat), Mary Baum (@marybaum), Meher Bala (@meher), Chloe Bringmann (@cbringmann), Surendra Thakor (@sthakor), Adeeb Malik (@adeebmalik) for research, interviews, and contributing to this feature article.

\n\n\n\n

The People of WordPress series thanks Josepha Haden (@chanthaboune) and Topher DeRosia (@topher1kenobe) for their support.

\n\n\n\n
\"HeroPress
\n

This People of WordPress feature is inspired by an essay originally published on HeroPress.com, a community initiative created by Topher DeRosia. It highlights people in the WordPress community who have overcome barriers and whose stories might otherwise go unheard. #HeroPress

\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"13562\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:11:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Wed, 25 Jan 2023 13:14:24 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Thu, 19 Jan 2023 12:00:00 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:16:\"content-encoding\";s:4:\"gzip\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20211220193300\";}','no'),(139,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1674695664','no'),(140,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1674652464','no'),(141,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1674695670','no'),(142,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:169:\"HeroPress: Becoming A Better Me with Core Contribution – কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায়\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5055\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:160:\"https://heropress.com/essays/becoming-a-better-me-with-core-contribution/#utm_source=rss&utm_medium=rss&utm_campaign=becoming-a-better-me-with-core-contribution\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:25361:\"\"Pull\n

এই নিবন্ধটি বাংলায় পাওয়া যায়

\n\n\n\nHere is Robin reading his own story aloud.\n\n\n\n
\n\n\n\n

Few years back, my daily life started with 10am waking up and going to the office without having breakfast (lazy me). Then doing a 9 hours job with a pretty simple routine and without any major engagement with others.

\n\n\n\n

At present, I wake up with tons of Slack messages and end my day with various in person short/long meetings with my fellow colleagues / mates around the world.

\n\n\n\n

I used to scroll Facebook, you know. But now WordPress Slack has become Facebook to me. How things got changed and became more enjoyable. 

\n\n\n\n

Lucky Me \"😊\"

\n\n\n\n

Hello World : How it all started

\n\n\n\n

I wasn’t supposed to be an engineer in the first place. I was brought up in Cumilla, Bangladesh. Finished my School and College in my hometown. Everyone wanted me to be a Doctor. It is very common here in our country that parents want their child to be a doctor. I completed my 3 months preparation for the Medical exam but later I ended up in Engineering.

\n\n\n\n
\n\n\n\n

I have spent 5 years in Sylhet, a heavenly place to live in. Oh! How I miss Sylhet these days. It has been a few years since I had breakfast (khichuri) in Pach Bhai restaurant (a very popular restaurant in Sylhet) and had tea in chachar tong (a famous tea stall in Modina Market, Sylhet). These days I don’t go out at night but during my Sylhet life, midnight tea was a much desired thing for us and of course that tea from a tong (small tea stall in the roads).

\n\n\n\n\n\n\n\n

My five years at SUST (Shahjalal University of Science and Technology) was a blessing to me. It helped me to become a better person and better me. Sust was full of energy. Seniors and Juniors. Lal Tong (tea stall in our campus). There were almost 300 plus students in our department and we knew personally almost 90 percent of our seniors and juniors. That bond is still alive in Dhaka (most of us living here with our job). Everyone helps each other to get a job or with the recommendation for the best jobs. Almost in every software farm I see SUST CSE seniors or juniors.

\n\n\n\n

Thanks God I got a chance to live those fine memorable years in SUST and Sylhet.

\n\n\n\n

Hello Dolly : Meeting WordPress

\n\n\n\n

My first meeting with WordPress was in my 2nd job. I was facing difficulties with my earlier professional career but as soon as I met WordPress, I just fell for her (WordPress). I found it really easy to adopt and it has a pretty huge community I must say. There were tons of documentation in Codex (but frankly I couldn’t understand at first). Now the documentation (https://developer.wordpress.org/) is much better and much more user friendly. I was amazed with the term Code is Poetry. It felt like I was writing poems instead of doing jobs.

\n\n\n\n

I really enjoyed my early career with WordPress. I wanted to do all by myself (that’s what we call Full Stack these days, LOL).

\n\n\n\n

I used to write markups from design (PSD to Html, that’s write). Then converting that into WordPress. And the training phase which was given to me was really a learning experience. I still keep in my mind that, “You can take unlimited divs. It won’t cost you money”, LOL. I was struggling with CSS opacity. But as soon as I started using it It became Pani(water, means easy) later.

\n\n\n\n

In my earlier life with WordPress I wasn’t aware of the active community and contribution to the project. I did many theme and site customization. Fixed bugs for clients. Built features as per their needs. But I was missing something.

\n\n\n\n

I was missing the large community of WordPress and the inner beauty of the Open Source project.

\n\n\n\n

Code is Poetry : WordPress Core Contribution

\n\n\n\n

My life at WPDeveloper was a blessing to me. It is where I started meeting the large community and the exciting activities of this wonderful community of WordPress. It feels like I truly belong to this community. Everyone is so close and so helpful to each other. 

\n\n\n\n

I have started joining meetups. Taking meetups, yes that’s correct. Started networking with similar minded people. It felt great to see so many people who love the same thing that you love. Such a blessing community.

\n\n\n\n

After joining WordPress slack and attending a few meetings, I found it is actually helping me to improve my skills. I saw how they manage their projects, how they think, how they fix. So many things to learn. I got addicted \"😀\" I started browsing channels often. 

\n\n\n\n

I started attending all the meetings of almost all the Make WordPress teams (that’s funny but I did). I was enjoying my life. 

\n\n\n\n

Slowly I started contributing to the Core WordPress. I do complex tasks in my regular job life but at core a simple task accomplishment gives so much pleasure. 

\n\n\n\n

Everytime I see my name in the commit description it feels good.

I didn’t stop after doing my first contribution to the core. I continued and I checked almost all tickets and figured out what I can fix or help to fix. I got PR reviews from WordPress experts. Their every single suggestion helped me to know the WordPress and Coding standards better. Now I do practice those coding standards in my regular job tasks.

\n\n\n\n

In WordPress 6.1 I contributed to 20 plus core tickets and that was a pretty good number in Bangladesh. These days I take online workshops in the Make Learn team, in person workshops in our Dhaka community. Also taking in house (within company) workshops to show how to join Release Parties and attend meetings and write team meeting notes. 

\n\n\n\n

By the way, I am Marketing Team Representative for the year of 2023. I am excited and looking forward to it. Also a Training Team Faculty member. 

\n\n\n\n

I don’t think all of these would be possible without being an active contributor to the project. Thank you everyone who helped me in this wonderful journey \"😊\"

\n\n\n\n

Life Is Beautiful : Living Success

\n\n\n\n

When I was writing this essay, I became one of the Release Leads of WordPress 6.2 (Test Co-Lead).

\n\n\n\n

It is unbelievable for me even after the declaration. I keep checking that P2 blog post just to make sure I am truly there, funny I know. 

\n\n\n\n

Recently I took contributor days in our office and it felt like there was only one topic in the town and that is “Let’s Do Core Contribution”. It became trending here, loving it \"😊\"

\n\n\n\n

Thanks to WordPress and the community. Due to my outstanding contribution in Core, I recently got selected for the prestigious #YoastCareFund and here I am sharing my stories with our HeroPress friends.

\n\n\n\n

Oh! I am living my dream life. Just one thing is missing. Ronaldo isn’t in UCL and is getting older. I know \"😀\"

\n\n\n\n

WordPress Core Contribution helped me to become a better developer, a better me. It removes your fear of losing your job and instead you will fall in love with your job and definitely you will enjoy every minute of your coding life.

\n\n\n\n

Thank You WordPress.
Code is Poetry and you are the book full of Poems.
I can’t stop reading you \"😊\"

\n\n\n\n

কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায়

\n\n\n\n

এইতো কয়েক বছর আগেও, আমার ডেইলি রুটিন ছিল সকাল ১০ টায় ঘুম থেকে ওঠা এবং নাস্তা না করে অফিসে যাওয়া (আলসেমির কারণে দেরি হয়ে যেত এবং বাসায় নাস্তা করা হত না)। তারপর ৯ ঘণ্টার অফিস শেষ হত গতানুগতিক কাজ দিয়ে।

\n\n\n\n

বর্তমানে, আমার ঘুম থেকে উঠেই দেখি স্ল্যাক ভর্তি মেসেজ এবং দিন শেষ হয় ছোট বড় বেশ কিছু টিম কোলাবোরেশান এবং মিটিং এর মাধ্যমে। 

\n\n\n\n

আমি ছিলাম ফেসবুক পাগল, ইংরেজিতে এডিক্টেড \"😀\"। কিন্তু এখন WordPress Slack হয়ে গেছে ফেসবুক আমার কাছে। কীভাবে ইন্টারেস্ট পরিবর্তিত হয় এবং পরিবর্তনটা উপভোগও করছি।

\n\n\n\n

Lucky Me \"😊\"

\n\n\n\n

Hello World : যেভাবে পথচলা শুরু

\n\n\n\n

প্রথমত আমার ইঞ্জিনিয়ার হবার কথাই ছিল না। আমার শৈশব কাটে কুমিল্লায়। স্কুল এবং কলেজ এলাকাতেই ছিল। সবার চাইছিল আমি যেন ডাক্তার হই।আমাদের দেশে এটা খুব কমন যে বাবা মা চায় তাদের ছেলেমেয়েরা যেন ডাক্তার হয়। আমি মেডিকেলের জন্য তিন মাস প্রিপারেশান নেয়ার পরেও ভাগ্যক্রমে চান্স পেয়ে যাই ইঞ্জিনিয়ারিং এর জন্য।

\n\n\n\n

সিলেটে ছিলাম পাঁচ বছর। আহা সিলেট, Where Heaven touches the Earth <3  

\n\n\n\n
\n\n\n\n

সিলেট নাম শুনলেই থমকে যাই।সে কবে গেলাম।কতদিন পাঁচ ভাইয়ের খিচুরি খাই না, কতদিন মদিনা মার্কেটের চাচার টং দেখি না। কতদিন মাঝ রাতে বের হয়ে টং এর চা খাই না। 

\n\n\n\n

আহা সিলেট!  

\n\n\n\n\n\n\n\n

SUST (Shahjalal University of Science and Technology) এর ৫ বছর ছিল আমার জন্য ব্লেসিং। আমাকে পরিণত করেছিল সাস্ট। সাস্ট ছিল এনার্জিতে ভরপুর।সিনিয়র জুনিয়রদের সম্পর্ক। লাল টং। ৩০০ এর বেশি স্টুডেন্ট ছিল আমাদের ডিপার্টমেন্টে। যাদের মধ্যে ৯০ ভাগই ছিল আমাদের ভাই ব্রাদার। অলমোস্ট সবাইকেই চিনতাম আমরা। বর্তমানে আমরা সবাই ঢাকায় কোন না কোন জবে আছি। দেখা কম হলেও সম্পর্ক এখনও আগের মতই। সবাই সবাইকে জবে হেল্প করছে। জবের বাইরে হেল্প করছে।ঢাকার মোটামোটি সব ফার্মে গেলেই দেখা যায় SUST CSE থেকে কেউ না কেউ আছে।

\n\n\n\n

আল্লাহর কাছে শুকরিয়া সিলেট এবং সাস্টে পরার সুযোগ হয়েছিল।

\n\n\n\n

Hello Dolly : WordPress এর সাথে পরিচয়

\n\n\n\n

WordPress এর সাথে আমার প্রথম পরিচয় যখন আমি আমার দ্বিতীয় জবে জয়েন করি। ক্যারিয়ারের শুরুতে আমার খাপ খাওয়াতে একটু সমস্যা হচ্ছিল। যখনই WordPress এর সাথে পরিচয় তখন থেকেই ফিদা হয়ে গেলাম।এটার ব্যবহার বিগিনার হিসাবে তখন আমার কাছে অনেক সহজ এবং উপকারী ছিল।অনেক বড় একটা কমিউনিটি। রিসোর্স অনেক। যদি Codex ছিল বেশ কঠিন বুঝার জন্য। কিন্তু বর্তমানে ডকুমেন্টেশান (https://developer.wordpress.org/) অনেক ভাল এবং সহজ হয়েছে। প্রথম যখন Code is Poetry শুনেছি এবং দেখেছি আমার অনেক পছন্দ হয়েছিল। মনে হচ্ছিল কোড না যেন কবিতা লিখতেসি।

\n\n\n\n

ক্যারিয়ারের শুরুতে আমি WordPress বেশ উপভোগ করেছি। চাইতাম সব নিজে নিজে করব (যাকে আমরা বলি এখন Full Stack, লোল)। 

\n\n\n\n

শুরু হয়েছিল PSD to Html দিয়ে যা আসলে আমাদের অনেকের ক্ষেত্রেই মিলে যাবে। তারপর তা WordPress এ কনভার্ট করতাম। শুরুতে আমাকে একটা ট্রেনিং দেয়া হয়েছিল যা ছিল খুবী কার্যকর।

\n\n\n\n

আমার এখনও একটা কথা মনে আছে “যত বেশি div নিবা। div নিতে টাকা লাগে না”, লোল।  

\n\n\n\n

আমার CSS opacity নিয়ে সমস্যা হচ্ছিল। কিন্তু যখনই কাজ শুরু করে দিয়েছি আস্তে আস্তে সব পানি (ইংরেজিতে Water, মানে সহজ) হয়ে গেসে। 

\n\n\n\n

প্রথমদিকে আমি WordPress কমিউনিটি নিয়ে ততটা অবগত ছিলাম না। অনেক থিম কাস্টমাইজেশান এবং সাইট কাস্টমাইজেশান করেছি। Bug ফিক্স করেছি অনেক ক্লায়েন্টদের জন্য। ফিচার তৈরি করেছি তাদের চাহিদা অনুযায়ী। কিন্তু কি যেন একটা মিসিং ছিল। 

\n\n\n\n

WordPress Open Source Project এবং WordPress এর বড় একটা কমিউনিটির সাথে যে তখনও আমার পরিচয় হয়ে উঠেনি। 

\n\n\n\n

Code is Poetry : WordPress Core Contribution

\n\n\n\n

WPDeveloper ছিল আমার জন্য ব্লেসিং। এখানে আসার পর থেকেই আমি WordPress এর বড় কমিউনিটির সাথে পরিচিত হই এবং দেখতে থাকি তাদের একের পর এক চমৎকার উদ্যোগ।

\n\n\n\n

মনে হচ্ছিল যেন এটাই এতদিন মিসিং ছিল। সবাই এত আন্তরিক এবং সাহায্য করার জন্য কতটা উদগ্রীব। 

\n\n\n\n

আমি meetup জয়েন করা শুরু করলাম। meetup নেয়াও শুরু করলাম, হা ঠিক শুনেছেন।লোল। 

\n\n\n\n

সবার সাথে নেটওয়ার্কিং হল।দেখে খুব ভাল লাগল যে একই চিন্তা ধারার সবাই একসাথে।

\n\n\n\n

Such a blessing community.

\n\n\n\n

WordPress স্ল্যাক জয়েন করি এবং মিটিং এটেন্ড করা শুরু করি। দেখি যে এটা আসলেই আমাকে সাহায্য করছে আমার স্কিল বাড়াতে।দেখতে পেলাম কিভাবে তারা প্রজেক্ট মেনেজ করে, কিভাবে চিন্তা করে, কিভাবে বাগ ফিক্স করে। কত কিছু শিখার। এডিক্টেড হয়ে গেলাম \"😀\"। চ্যানেলগুলো প্রায়ই ব্রাউজ করতে থাকতাম।

\n\n\n\n

সব টিমের মিটিং জয়েন করতে শুরু করলাম (ফানি বাট সত্য)। সবকিছু ভালই লাগছিল। 

\n\n\n\n

আস্তে আস্তে কোর কন্ট্রিবিউশান শুরু করলাম। যদিও অফিসে কমপ্লেক্স কাজগুলাই আমরা করতাম। কিন্তু যখন একটা ছোট খাটো কোর কন্ট্রিবিউশান করি তখন মনে অনেক আনন্দ কাজ করে। যতবার কমিটে আমার নাম দেখি ততবারই ভাল লাগে। আহা।

\n\n\n\n

প্রথম কন্ট্রিবিউশানের পর আমি থেকে থাকি নাই। কন্টিনিউ করেছি। প্রতিদিন টিকেট গুলো ব্রাউজ করতাম। খুঁজে দেখতাম কোনটা করতে পারব। WordPress expert দের কাছ থেকে রিভিউ পেতে থাকলাম যখনই PR দিতাম।তাদের প্রতিটা সাজেশান আমার পরবর্তিতে বেশ কাজে দিয়েছে। নিজের অফিসের কাজেও তখন সেগুলো ব্যবহার করতে থাকলাম।

\n\n\n\n

WordPress 6.1 এ আমি ২০ এর অধিক টিকেট ফিক্স করতে সাহায্য করেছি। যা বাংলাদেশের জন্য বেশ ভাল একটা নাম্বার। এখন আমি Make Learn টিমের জন্য অনলাইন ওয়ার্কশপ বানাই। ইন পারসন ওয়ার্কশপ নেই আমাদের ঢাকা কমিউনিটির জন্য। ইন হাউজ ওয়ার্কশপ নেই কলিগদের জন্য। দেখাতে সাহায্য করি কিভাবে রিলিজ পার্টিতে জয়েন করতে হয়, কিভাবে টেস্ট রিপোর্ট লিখতে হয়, কিভাবে মিটিং নোট নিতে হয়।

\n\n\n\n

ভালো কথা, আমি এখন Marketing Team Representative ২০২৩ সালের জন্য। এটা আমি বেশ উপভোগ করছি। এবং সাথে আমি Training Team Faculty মেম্বারও। 

\n\n\n\n

আমার মনে হয় না কোর কন্ট্রিবিউশান ছাড়া আমার এই দায়িত্বগুলো পাওয়া পসিবল হত । সবাইকে অনেক ধন্যবাদ আমাকে সাহায্য করার জন্য \"😊\"। 

\n\n\n\n

Life Is Beautiful : সফলতা

\n\n\n\n

যখন আমি এটি লিখছি ততদিনে আরেকটি সুখবর পেয়ে গেছি। আমি এখন WordPress 6.2 এর একজন Release Lead (Test Co-Lead).

\n\n\n\n

একদম অবিশ্বাস্য। প্রায়ই P2 blog post গিয়ে চেক করে দেখি আমার নামটা আছে কিনা, হাস্যকর শুনাবে জানি। 

\n\n\n\n

কিছুদিন আগে কন্ট্রিবিউটর ডে আয়োজন করেছি। মনে হচ্ছিল যেন শহরজুড়ে একটাই ডায়লগ,

\n\n\n\n

“Let’s Do Core Contribution”। ট্রেন্ডিং হতে দেখে বেশ ভালই লাগে \"😊\"

\n\n\n\n

WordPress এবং কমিউনিটিকে অনেক ধন্যবাদ। কিছুদিন আগে #YoastCareFund পাই করে আউটস্ট্যান্ডিং কন্ট্রিবিউশানের জন্য। এবং আজ HeroPress বন্ধুদের সাথে সব শেয়ার করছি।

\n\n\n\n

একেই বুঝে বলে লিভিং ড্রিম লাইফ। একটা জিনিসই শুধু মিসিং। রোনাদোকে আর হয়ত ইউসিএলে দেখা যাবে না \"😀\"

\n\n\n\n

WordPress Core Contribution আমাকে ভাল ডেভেলপার হতে সাহায্য করেছে।জব হারানোর ভয় বাদ দিয়ে জবকে এঞ্জয় করা এবং কোডিং এর প্রতিটা মুহুর্ত উপভোগ করতে সাহায্য করে কোর কন্ট্রিবিউশান। 

\n\n\n\n

Thank You WordPress.
Code is Poetry and you are the book full of Poems.
I can’t stop reading you \"😊\"

\n

The post Becoming A Better Me with Core Contribution – কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায় appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 25 Jan 2023 02:00:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"A H M Nazmul Hasan Monshi\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:55:\"WPTavern: Yoast SEO 20.0 Introduces New Admin Interface\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141380\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:66:\"https://wptavern.com/yoast-seo-20-0-introduces-new-admin-interface\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2764:\"

Yoast SEO version 20.0 was released today with a new admin settings interface that also reorganizes the menu to into four main sections: General, Content types, Categories and Tags, and Advanced.

\n\n\n\n\n\n\n\n

In this update, the plugin did not add new features and settings but rather moved them to better match user workflows. The new sidebar menu should result in fewer clicks in accessing the most used settings.

\n\n\n\n

The individual settings pages are also sporting the new design, which is lighter and brighter than the previous screens. With such a large number of settings to re-learn, Yoast SEO has also added a quick search to assist users in finding settings pages faster.

\n\n\n\n\n\n\n\n

“We felt that the default WordPress admin design no longer suited us,” Yoast founder Joost de Valk said. “Our product team was itching to take our experience to the next level. WordPress’ interface was holding us back a bit, as the admin interface outside Gutenberg hasn’t progressed for years.”

\n\n\n\n

The new settings UI was built with Yoast SEO’s React component library, which the company has open sourced and made available on its website.

\n\n\n\n

Reaction to the new design was mostly positive, although some users are not keen on plugins building their own UI in the admin. If all plugins did this, the WordPress admin would become a wild buffet of disparate interfaces that add cognitive load to site management.

\n\n\n\n

“It was… surprising so I’ll reserve real judgement until I use it a while,” WordPress developer Jon Brown said. “First impression though was ‘this needs an advanced mode that hides all the useless banner images and text and just goes back to a list with toggles.’ It’s pretty, but feels overwhelming.”

\n\n\n\n

 The Yoast SEO plugin and the new settings UI work with WordPress version 6.0 or higher. Users who are struggling to adapt to the new settings pages can reference Yoast SEO’s documentation, which has a video and guide to navigating the new interface.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 21:43:57 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"Do The Woo Community: The WP Community Collective with Sé Reed and Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74360\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://dothewoo.io/the-wp-community-collective/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:422:\"

Sé and Courtney share all things to do with the new WP Community Collective, a source for supporting contributions and initiatives.

\n

>> The post The WP Community Collective with Sé Reed and Courtney Robertson appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 10:36:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WPTavern: Awesome Motive Acquires Thrive Themes\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141347\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"https://wptavern.com/awesome-motive-acquires-thrive-themes\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1769:\"

Awesome Motive has acquired Thrive Themes, its second acquisition of 2023 following the Duplicator plugin deal that was announced earlier this month.

\n\n\n\n

Thrive’s premium plugin suite reports more than 200,000 users. This includes Thrive Architect, a visual drag and drop page builder, an LMS course builder, and other marketing-focused plugins for generating leads, creating quizzes and testimonials, and doing A/B testing.

\n\n\n\n

In 2013, Thrive Themes co-founders Shane Melaugh and Paul McCarthy began their company with early products Hybrid Connect, Viral Quiz Builder, and WP Sharely. Ten years later the product suite has grown to nearly a dozen conversion-focused tools that Thrive Themes sells for $299/year.

\n\n\n\n

Although the co-founders will not be joining Awesome Motive, the team that is currently maintaining and supporting the plugin is being acquired. In the Thrive Themes announcement, Melaugh said the company’s products will not be rebranded or replaced. No price hikes are planned for existing customers and Awesome Motive plans to honor legacy memberships.

\n\n\n\n

“It has always been our policy to reward loyal customers and that will not change,” Melaugh said.

\n\n\n\n

“I’ve been watching Thrive Themes from the sidelines for a long time anyway. So my stepping away changes nothing on that front.

\n\n\n\n

“It will still be the same people building the products, and the roadmap we laid out for 2023 and beyond won’t change because of this acquisition.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 02:57:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: WP Migrate 2.6 Introduces Full-Site Exports and Import to Local\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141320\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:84:\"https://wptavern.com/wp-migrate-2-6-introduces-full-site-exports-and-import-to-local\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3672:\"

WP Migrate, formerly known as WP Migrate DB and recently acquired by WP Engine, has long since expanded beyond its initial release as a database migration tool. Users may be familiar with the push/pull workflow of installing the plugin on two sites and migrating database, media, themes, and plugin changes back and forth. The most recent 2.6 release expands the plugin’s capabilities to include full-site exports for integration with Local, a popular free WordPress development tool, also owned by WP Engine.

\n\n\n\n

This new remote-to-local workflow is included in both the free WP Migrate plugin and the pro version. The full-site exports bundle the database, media, themes, plugins, and other files into a ZIP archive, which can be seamlessly imported into Local.

\n\n\n\n\n\n\n\n

After clicking Export inside WP Migrate, users are taken to the next screen where they can configure what is included in the export file. This ZIP archive can be dragged and dropped into the Import screen in Local.

\n\n\n\n\n\n\n\n

The WP Migrate team collaborated with the Local team to match environments as closely as possible when exporting for Local import.

\n\n\n\n

“Each site exported with WP Migrate includes a wpmigrate-export.json file which contains metadata such as the PHP and MySQL versions that were last used on the site,” WP Migrate Product Manager Kevin Hoffman said. “During the import, Local reads this file and attempts to match the environment to that of the exported site, so the local site works (and breaks!) just like its remote counterpart.”

\n\n\n\n

In this migration scenario, the WP Migrate plugin can be included in the list of plugins so it is activated on the Local site, speeding up the workflow for setting up a local development site. Previously this required configuring plugins, add-ons, and license keys across both environments.

\n\n\n\n

“In the last year, we really embraced our new identity as a full-site migration solution,” Hoffman said. “One of the goals we set for ourselves was to handle the migration of an entire site from within WP Admin without ever having to touch cPanel, phpMyAdmin, or FTP. This new workflow is the culmination of those efforts delivered as a free end-to-end solution for the WordPress community.”

\n\n\n\n

Customers who have purchased the pro version may still opt for pushing and pulling directly between sites, but this new workflow makes it easier for users (both free and paid) to set up a local development environment for the first time.

\n\n\n\n

“When we realized how much simpler we could make the remote-to-local workflow by embracing full-site exports, we reached out to the Local team who helped make it happen,” Hoffman said.

\n\n\n\n

The WP Migrate team is looking at expanding the integration beyond matching the WordPress, PHP, and MySQL versions to give users the ability to predefine migration profiles for pushing local sites back to the remote host.

\n\n\n\n

“When configuring an export, we could also let users set up one-click admin access in Local,” he said. “Imagine dropping a ZIP into Local and landing in WP Admin without ever having to log in. There are lots of possibilities, and I’m sure more will pop up as the community starts to use it.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 23 Jan 2023 22:39:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:84:\"WPTavern: WordPress Community Team Proposes Adopting GitHub to Improve Collaboration\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141302\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"https://wptavern.com/wordpress-community-team-proposes-adopting-github-for-improved-collaboration\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4432:\"

Although GitHub is primarily used for code collaboration, WordPress’ Community team is considering adopting the platform to standardize their project management tools.

\n\n\n\n

Contributing to open source can already be challenging but when it requires signing up for multiple services in order to access the team’s many spreadsheets, trello boards, Slack groups, and other modes of communication, onboarding new contributors becomes needlessly difficult.

\n\n\n\n

A new proposal, authored by Community team rep Leo Gopal, outlines the benefits of using GitHub as a central communication tool. These benefits include improved collaboration and communication using the platform’s commenting system and the ability to track progress and assign tasks.

\n\n\n\n

Gopal contends that standardizing on GitHub would increase transparency and accountability while supporting better organization with tools like issues, labels, milestones, and project boards.

\n\n\n\n

“By adopting GitHub for project management and issue tracking, the Community Team will standardize our way of working, making it easier for new team members to get up to speed and enabling more effective cross-team collaboration,” Gopal said. “This standardization also makes it easier for Community Team members to track progress, identify issues and make data-driven decisions.”

\n\n\n\n

Other Make teams, such as Learn, Hosting, Meta, Marketing and more, are already successfully using GitHub to manage communication and prioritize projects. Gopal proposes the Community team learn from their efforts and adopt these tooling methods for a quarter as an experiment.

\n\n\n\n

“If after the first Quarter the consensus is that this does not suit our team, we will revert back to initial project and tracking practices and explore more,” Gopal said.

\n\n\n\n

A few participants in the resulting discussion have concerns about transparency and losing track of conversations, as they would not be linked to WordPress.org profiles.

\n\n\n\n

“The truth is that I am unsure about it,” Weglot-sponsored Community team contributor Juan Hernando said. “I think the community team is not particularly technical, and using GitHub may pose certain barriers we didn’t have so far. Maybe for many people opening an issue, requesting a pull request, or similar is their everyday life, but for others, it can be a bit blocking.

\n\n\n\n

“I’m also afraid that discussions will move from this Make site to GitHub, and we shouldn’t lose the spirit of owning our content (linked to our .org profile) and lose the use of this space for decision-making and public discussions like this one.”

\n\n\n\n

Gopal addressed this concern stating that there would be no code and that users who can work with Trello boards will have no problem adopting GitHub’s tools for planning.

\n\n\n\n

“Trello was used for planning and often forgotten until time for reviews or recaps,” Gopal said. “There was no way other teams would know what we are working on or add to the conversation unless they dug up our trello boards AND if we took their suggestion and weighed it in.”

\n\n\n\n

Gopal said using GitHub would allow the team to incorporate advantages like automations, assignments, and inter-team collaboration with advanced reporting capabilities. Overall, GitHub has the potential to increase the visibility of their work for those collaborating across teams.

\n\n\n\n

Milana Cap, who uses GitHub to help organize the Documentation team for reporting issues and automating tasks, recommended adopting the platform and shared how the Docs team is using it.

\n\n\n\n

“All the other benefits: version control, precise contribution tracking, all sorts of project management tools etc., can not be found all in one tool other than GitHub, and I can not recommend it enough – for everything,” Cap said.

\n\n\n\n

Discussion is still open on the proposal and Gopal has published a Proposal Poll for Community Team members to give their feedback on standardizing communications on GitHub.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 21 Jan 2023 04:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:103:\"WPTavern: Gutenberg 15.0 Introduces “Sticky” Position Block Support, Adds “Paste Styles” Option\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141268\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"https://wptavern.com/gutenberg-15-0-introduces-sticky-position-block-support-adds-paste-styles-option\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3623:\"

Gutenberg 15.0 was released this week with some exciting new features for working with blocks and an improved UI for managing controls in the inspector panel. This release marks the end of the block inspector tabs experiment, which is now stabilized in the plugin.

\n\n\n\n

Users will notice that some blocks will now have separate tabs in the inspector for displaying settings and design controls, and optionally a list view tab that is included in the “off canvas navigation editor” experiment. Taking the block inspector tabs out of experimentation paves the way for the Navigation block’s off-canvas editor to become the default experience.

\n\n\n\nimage credit: Gutenberg 15.0 release post\n\n\n\n

Version 15.0 introduces a new “paste styles” feature that works in a similar way to the “paste” or “paint” formatting function in Microsoft Word or Google Docs. Users can click on any block, select “Copy block” from the menu in the block settings panel and then paste those styles onto another block using the “Paste Styles” menu item.

\n\n\n\n\n\n\n\n

When using this feature, users may have to give the browser additional permissions in order to read from the clipboard. If permissions are denied, Gutenberg will display a warning snackbar to notify the user.

\n\n\n\n

Another major feature in this release is the ability for users to give blocks “sticky” positioning on the page. This will keep the block in the viewport even when scrolling down the page. The sticky/fixed positioning sticks the block to the top of the direct parent block. It can be previewed on the frontend and equally as well inside the editor.

\n\n\n\nvideo credit: Follow-up tasks for Sticky positioning\n\n\n\n

Gutenberg contributors concluded that although sticky positioning will be valuable for headers, footers, and creative instances, it is not likely to be used frequently. For this reason, it is de-emphasized in the UI. This is the first iteration of the sticky positioning feature, and contributors are tracking a list of follow-up tasks to improve it.

\n\n\n\n

A few other important changes in this release include the following:

\n\n\n\n
    \n
  • Edit block style variations from global styles (46343)
  • \n\n\n\n
  • Constrain image sizing to the width of the container (45775)
  • \n\n\n\n
  • Allow resizing the Site Editor’s sidebar and frame (46903)
  • \n\n\n\n
  • Activate copy/cut shortcut in the site editor (45752)
  • \n
\n\n\n\n

If you want to take advantage of these new features before they land in WordPress core, you will need to have the Gutenberg plugin installed. Check out the 15.0 release post to visually explore the highlights with more videos and links to all the pull requests for the release.

\n\n\n\n


\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 21 Jan 2023 00:37:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"Post Status: Launching a WordPress Product in Public: Session 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146618\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://poststatus.com/launching-a-wordpress-product-in-public-session-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:77120:\"

Corey Maass and Cory Miller go live to discuss the creation and launch of a WordPress product they have partnered to build. Crop.Express originated as a solution to a common problem Maass experienced. Miller loved the idea and wondered how to build this into a plugin to solve problems within the WordPress workflow. This is a candid conversation about the evolution of partnering to develop a WordPress product.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

In this episode, Corey Maass and Cory Miller discuss the origin of the WordPress product they are creating. Together they explore the benefits of partnership, the challenges of being a creator, and what it takes to build viable solutions. This is only the beginning of their process and partnership, but it’s loaded with experience and insight from the journeys they have had within WordPress that brought them to this moment, as well as takeaways they’ve discovered with their new undertaking.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • The Power of Partnering: Many entrepreneurs aren’t interested in partnership. But they create an opportunity to own and contribute the things you do well alongside someone who has other skill sets, strengths, and experiences. Partnerships offer space to practice open dialogue while showing respect and gaining perspective. They are a great solution for all the things you can’t do, don’t want to do, or shouldn’t do. 
  • \n\n\n\n
  • Build for a Need: Sometimes we create things believing we have brilliant ideas that will attract an audience. But where problems exist, so do the needs for solutions. You can trust if you have a problem, other people likely have the same problem and need a solution.
  • \n\n\n\n
  • Look to Make Things Easier: When you have to go out of your workflow to do a task, things feel frustrating and clunky. Finding ways to integrate tools within our natural workflow adds tremendous value to the user experience.
  • \n\n\n\n
  • Products Require Passion and Capacity: Yes, you may have the ability to create really cool, helpful things. But if you lack a sincere passion for the products you build or truly don’t have the time they require, they tend to fall flat somewhere along the way. You tap out at the end of your skillset or energy, and even though there may be real potential, the passion and time to carry things forward are missing.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Sponsor: GoDaddy Pro

\n\n\n\n

Manage your clients, websites, and tasks from a single dashboard with GoDaddy Pro. Perform security scans, backups, and remote updates to many sites on any host. Check up on site performance, monitor uptime and analytics, and then send reports to your clients. GoDaddy Pro is free — and designed to make your life better.

\n
\n\n\n\n
\n\"GoDaddy\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Cory and Corey Episode 1
Cory Miller: [00:00:00] Hey everybody. Welcome to a cool series. Uh, my friend Corey and I have been talking about it for a couple months, a project, and we said, Hey, why don\'t we just broadcast this out, do it in public. And so this series is kind of called Launching a WordPress product in Public. This is session one we\'re gonna talk about.
First. I\'m gonna let Corey introduce himself in just a second, but we\'re gonna talk about the agenda is, um, kind of where we\'ve been, just to catch everybody up. And then second part, we\'re gonna talk about next steps for what we\'re doing. And we\'ll of course describe the project, uh, as we go. So, Corey, I think people know you, but let\'s, let\'s, uh, go ahead and share it.
Tell us more about, uh, who you are, what you\'ve done with WordPress.
Corey Maass: Of course. Uh, so I\'m Corey Moss, currently [00:01:00] residing in the northeast of the United States. Um, I\'ve been a developer and an entrepreneur for 25 years or so, and largely locked into the WordPress space for 10 years or more. It was the day job for a very long time, and I was pushing SaaS apps or BU building and pushing SaaS apps, uh, in evenings and weekends.
And then, I don\'t know, years ago at this point, I went to, uh, WordCamp in Atlanta, Georgia, and met a few WordPress entrepreneurs, including the, um, specifically the Ninja Forms guys down there. And suddenly a light bulb went off of like, oh, there\'s, you know, there\'s a lot more to WordPress products and the WordPress ecosystem than I realized.
And. It can be used to build SaaS apps, which I also do. Um, but [00:02:00] also these plugins that can be grown and built into pot, you know, sometimes, or potentially into, into businesses under themselves. So that really kind of got me started. And so, uh, around that time, I, I learned about the Post Status community.
Uh, I\'m, I am wearing the Post Status t-shirt underneath. It\'s just too cold. Um, being up here in the northeast. But, um, yeah, so it\'s been, you know, fun to be part of the community and fun to grow. Uh, I\'ve now grown and sold a couple of businesses or a couple of WordPress plugins. Um, and here we are about to launch.
Cory Miller: Yeah, I, I\'m trying to remember back when we actually met Corey, but I knew you were like this developer who loved to like launch stuff and you had the kbo, uh, plugin at that time. Mm-hmm. , and I remember talking through that and how passionate you were, you were about it. Um, so, and then we chatted the last year or so comparing notes and I\'m like, man,[00:03:00]
Corey and Cor, sorry, the broadcast system went off on my ears. Excuse me. Just one second. Okay. Whew. That was weird. I\'ve got hearing aids and my phone comes through and I was like, emergency broadcast system. Mm-hmm. Um, but anyway, um, so it was fun. We\'ve gotten to kind of get to know each other over the last year or so and member huddles and you shared this thing you were doing and I\'ve followed up and I was like, I need this, I want this.
Um, and it\'s funny too in parallel is how much stuff that we\'ve got in common or things were stages of life we\'re, we\'re going through. And so I think it was a couple months ago you mentioned on the huddle or, and then we started talking about it in Post Status dms, the project that we\'re launching in public today called Crop Express.
But um, you wanna share a little bit about that, how you came to it? And I can add a little, my perspective on it. Yeah, of course. This was your idea. Um, and I was like, oh my God, this has [00:04:00] to exist in the WordPress. Um, I need it because I need it. And that\'s a typically if I try to keep at the user level and I\'m like, if I like something and use something, I\'m like, maybe there\'s more people out there that would need it too.
But talk about the start of Crop Express.
Corey Maass: Well, before that, I want to fill in a couple of blanks. One, yeah. Uh, you and I met when you were the keynote speaker at, uh, what was it? Word? WordCamp, y\'all. The, the WordCamp in Bur Birmingham, Alabama. I have lots of friends in. Birmingham, England spelled the same, but pronounced very different.
So I have a hard time pronouncing Birmingham . Um, but anyway, um, I was living in Nashville at the time and drove down and uh, that\'s you And I went to lunch with a couple of other people and I, I, I must have had too much of the free coffee, cuz I remember talking your ear off while we were waiting for like barbecue or something [00:05:00] and then, You turned to me at one point you were being a very good listener, I have to say.
And then at one point you turned to me and are like, aren\'t you speaking in like four minutes ? And I looked down and realized that yes, indeed, my session was starting in minutes and I still hadn\'t gotten my food. Um, and so you and the folks we were with were nice enough to bring me my food halfway through the session.
Oh, chicken and waffles. I got chicken and waffles, the weird things you remember. Anyway, . Um, but yeah, I, you and I have, uh, kept in touch over the years and then, um, I think mostly caught up over on the huddles. Um, but I, I mean, I tell that cuz it\'s sort of a fun story and a little background, but I also, I think it\'s.
It\'s a great ex, uh, example of the longevity of a lot of the relationships that I\'ve had in WordPress, in the WordPress ecosystem, the [00:06:00] WordPress community. Um, you know, once in a while I, I get approached, I know you do too, of people who are like, you know, let\'s partner, or, I see you\'re doing a thing, let\'s do a thing together with no background, no context.
Um, and I, I\'m definitely not saying that people shouldn\'t reach out, always reach out. You know, you never know what good is gonna come from, from reaching out. Um, I love that people messaged me directly on Twitter and um, and in Post Status and stuff like that, but also, you know, the long-term. Being part of any, uh, any, uh, being part of the WordPress community and culminating these relationships and staying in touch with people over years.
Cuz at this point, I lived in Nashville like eight years ago, so you and I met eight years ago and I don\'t think talked really for five years Anyway, so that was one of the things that jumped out at me. So getting onto Crop Express. So yeah, I. I built a, [00:07:00] a conbon plug in a few years ago, sold that, um, have launched and been running a couple of others.
One I\'m about to sell. Um, and, and that might actually be something to talk about at another time because I, I built it because I could, um, very typical developer. I built it because I could, but I was never really passionate about it. And so at this point, I\'m, I\'m talking to some folks about, um, selling it because I\'ve just never been able to, man, I\'ve never been able to market it, meaning I\'ve never been able to make myself market it.
Um, and plugins and these businesses, to me are still side hustles. I\'ve never been able to grow them large enough to be the, you know, my primary source of income. And so I have clients and. Right now, I\'ve, I\'ve got clients who run, uh, a couple of pretty big sort of magazine style, pretty traditional blogs, but they\'re, you know, magazine style, full, beautiful, well-written, professionally written articles and [00:08:00] stuff like that.
And they are not technical at all. So they\'re, they\'re entrepreneurs, they\'re writers, they\'re content people. Um, but they. It\'s not that they don\'t understand, they\'re very smart people, but they\'re not experienced with, or they don\'t think in terms of like, oh, all images need to be squares, or all images need to be 16, nine, so that the site looks uniform and consistently good.
Um, and no matter what I did, I, I couldn\'t make it easy enough for them to crop their images consistently. I didn\'t want to get them into Photoshop, you know, other, and that cost of Fortune. Other free editors cost money, da, da da. So anyway, um, almost on a whim, over a weekend, I bought crop.express, the domain.
Um, Here\'s a industry secret. One of, one of my best kept secrets is the.express, um, what is it? Top level domain, [00:09:00] TLD. Um, there\'s so many words that have not been bought yet, so I actually own poll.express, crop.express reply.express. Um, screenshot.express is another project I\'m building out. Um, so if you, anybody listening, if you\'re looking for a good domain, I, I highly recommend it.
I keep wondering what I\'m doing wrong or like, are there companies that can\'t access this or something, you know? Yeah. But
Cory Miller: anyway, um, I think it\'s a hallmark of any, uh, tech entrepreneur in particular is to have like a too big of a. Portfolio that you have. That\'s very continuing. Well, that\'s too, yes. Um, I, I\'ve got way too many, um, my wife is always like, you should put some parking pages on this.
And I go, yeah, but it\'s a cool domain. What happens? I think there\'s two things. Uh, we definitely should, and we\'ll be talking about partnership along this whole way. Um, I\'ve had a good amount of experience with partners and like having [00:10:00] partners. Um, it\'s an anomaly in, in, I in a lot of the entrepreneurs I\'ve talked to is a lot of successful entrepreneurs go, no way.
I\'m not gonna partner with anybody. And I go, well, I kind of need to and want to. Um, but then, so I know we\'re gonna be. Some thoughts about the partnership and that\'s another thing is partnering in public is probably the subtext to this too on. Um, but as we\'ve talked, just real quick before we get back to the product, is, um, I\'m not a developer.
I should get the shirt. I\'m not a developer. Um, but I love products and I\'ve had a product business. Um, tried a bunch of products. I told you, I think yesterday I was like, my, my win rate is probably like in the one hundreds, uh, percentile. Um, we talked about baseball and I was like, you know, I\'m probably a strikeout king because I feel like I failed quite a bit.
But coming to someone, like it\'s an ideal match for me because I can, [00:11:00] you know, business and marketing, but it\'s not one you have to own in this partnership. I can own that and you contribute and obviously I can\'t even try to write code. Um, but I can contribute with product and, and experience and thoughts like that.
So now to the crop express. . Um, so when you shared this, I was like, yes. Because my experience in just talking about the user profile, I\'m so keen to the user profile cuz sometimes I think we come at it artificially and go, I have an idea. Let\'s go find a person for it. And I think some of the best ones come out of just, there\'s a need, and we talked about this, it\'s like, um, you hear the story is build it for your own itch or build it for yourself and all that kind of stuff.
We talked about Pi, PIP and Williamson yesterday, like he\'s a, he\'s the one I think of it\'s like, build it, build something for a need. Mm-hmm. for himself and grew into this great, uh, business called [00:12:00] EDD. Um, what struck me about this is I go, I have a. Like trying to find software that will crop, you know, I used to use, I was an early user of Photoshop, but I don\'t have Photoshop on my computer.
And I\'m like, well, I go to Mac preview and crop and export it out and then try to upload it to WordPress. So instantly I go, I need this. And then I thought, and we started having these discussions. I think other people do too. You know, the classic example I have just like your clients is my mom built a her own site about 10 years ago or so.
And we had a theme, don\'t cringe too much, but a theme that had rotating images in it at the top. Sure. And I tried to load the site . It was like, oh my God. She had 15 images all at like hop resolution. And this is something real quick. Uh, we both were like, this isn\'t something easy. It may be in WordPress, but it\'s not easy in WordPress.
And [00:13:00] my natural question was, If I have this problem, I bet you a lot of people have this problem. We talked, talked about images, we talked about agencies that turn sites over to clients and end up, why is this so slow? Or why isn\'t, you know, why doesn\'t this work? Right? And it\'s like, well, you loaded it native from your phone, , uh, the pick.
And so that was the thesis for me, for the, for the product is you already had the SaaS solution. I was like, yes. My question was, can I get it into a plugin where it\'s inside WordPress in my workflow?
Corey Maass: Yeah. And, and you helped, helped me turn that corner, honestly, cuz I, in a weekend I built. crop.express, which right now the website is the website.
It\'s exactly the first version that I built. Um, it\'s, it\'s not complicated. It\'s not well thought out, too well thought out. Like I have a, I\'ve been also working in product for years, and so I, I do [00:14:00] okay with going, oh, well, this, this will be intuitive enough that somebody could muddle through it. Um, but I really wanted to just solve the problem initially for my clients and yeah, threw it online.
I love doing this anyway. Start showing it to people, showed it to you, um, and you kept, you, you nudged me a couple of times in Post Status, like, how can we make this easier? And originally I was not thinking WordPress plugin, surprisingly. Um, I was thinking more. This is just a, a great little tool that people will use and it will hopefully, you know, maybe I could throw some ads on it or I, it will refer them to my other products.
Um, and so I was building a little Chrome extension and, and you\'re like, okay, that\'s a start. But you know what, if we really start to explore this and yeah, the conversations kind of flowed from there.
Cory Miller: And my premise with products, [00:15:00] particularly with WordPress or any tool is this, there\'s a workflow we all kind of have and you get in this system and when you have to veer out of that workflow, cropping an image, finding, cropping an image.
Yeah. So clunky within WordPress, and you have to go outside of that experience. You just added unnecessary time and energy for something frustration. When most times when I\'m creating content, I go, I want to get this out and edit it and press publish and put it out in the world. And anything that slows me down is a problem.
Um, So, you know, there\'s , our featured image on Post Status. I\'m not happy with it. We\'re still working on, on some of our design on the Post Status website. Uh, my personal side, I don\'t typically use images because of this. And so I think that was some of the, my, my perspective is like, there\'s enough use case here to say let\'s try it.
And I think what you and I go is like, we want to have, we wanna do something that is practical and useful [00:16:00] and then see where it goes. Um, we\'re not looking to get like mega rich on this or anything, but like, it\'s something we both have an interest in. Let\'s see where it, I\'m counting on it, man. . Hey, it would be nice to get me wrong.
Corey Maass: We, uh, we bought the Mega Millions ticket last night. You know, it\'s over a billion, but, uh, it hasn\'t been announced that we won this morning. So, you know, this is, this is the, the next best
Cory Miller: thing. Right. Yet, you haven\'t won yet. When we get some of that, carve off a little bit of that lottery money and we\'ll throw some, we\'ll do some cool, cool products.
Um, yeah. I, I\'m really addicted to products. I\'ve loved it for the longest time. Um, you said something earlier, you said I could build this and you did build things. Mm-hmm. , but the second part I wrote down was so interesting because it\'s, my experience too is I wasn\'t passionate about it. And I know when I\'ve gotten, um, those, that equation wrong is where I\'ve really failed miserably.
Um, the project I think about at Ithe was [00:17:00] called Exchange. It was e-commerce. I was passionate about a user experience that anybody could use, but I wasn\'t as passionate about the field. We just saw a big. I saw a big market potential there. WooCommerce was out there. It was the big, still is, the big behemoth.
And I go, man, it\'s really tough to like just create a new product in WordPress or, or in WooCommerce. Let\'s create an easier path to do that. Um, that didn\'t work. We didn\'t do it. And I think part of it was, I wasn\'t supremely passionate about the, the domain we\'re in. When we talk about this, I go, I have a, I have a lot of experience with images and cropping and content that\'s bulk of my career before I, themes and Post Status was, and communications work and newspapers, journalism.
And I\'m like, you know, it\'s a factor. Everybody wants an image on the site. And so what we decided was to start with the featured image [00:18:00] cropping, that making that experience, um, really smooth and easy.
Corey Maass: So that\'s the, yeah, I think the other thing to talk about here is as a developer, as a human being, I\'ve learned this lesson.
It\'s, it\'s just cuz you can doesn\'t mean you should. Um, and for I think people like you and I, I\'m speaking for you, but I, I hope I\'m right. We, we get excited about a lot of things. It\'s easy to, to dip a toe into a lot of things. Um, but then we end up taking on too much and we get overwhelmed and everything is, you know, what is it?
Do two things and you\'re doing two things half-assed instead of one thing, whole ass. Or, you know, and we\'re never gonna limit ourselves to one thing, let\'s be honest. But having, definitely having too many things. Um, and like I\'ve. Epic trips, um, you know, which is, I, [00:19:00] I was lucky enough to do, but I came home and people were like, was this amazing?
And I was like, I don\'t know why, but it wasn\'t. And I realized that it was like, just because I had the opportunity to take the trip, like I didn\'t, I, I wasn\'t in the right mindset. All I wanted to do was be home, you know? And so just cuz I could, um, doesn\'t mean I should have. And I, I keep trying, I try to think about that when I\'m taking classes or, you know, reading books or things like that.
Um, because time is precious, right? And, um, and we can only experience so much. So anyway, all that to say, um, yeah, with other products, I\'ve definitely built them, um, just because I could. And as a developer it\'s really dangerous because like, I look at that and I\'m like, oh, that\'d be really interesting to solve those problems.
Um, and then, uh, even as soon as you mentioned a WordPress plugin, uh, I was like, okay, well we need. X, y, z we need, you know, big da da da, and, [00:20:00] and that\'s great. Like a year from now, let\'s have all those bells and whistles and let\'s have all those features and, and, you know, and expand. Um, but of course, I\'m, again, I, I, I work, I have client work and w client work and family and obligations and stuff just as you do.
Um, and so you did a really good thing where we were chatting, we scratched our heads, and you were like, well, what if we, you know, what is the MVP here? And, and even that, I couldn\'t, I was like, well, da, da, da. And you were like, okay, featured image, one thing. Let\'s just start with that. Can we, and I, as soon as you said it, it clicked for me.
I was like, that\'s, that\'s the place to start. It\'s the one simple feature that, but it will solve the problem for a lot of people, and it will exemplify the problem we are trying to solve. . And so, and, and again, for me, it, it is tough at times as a developer, all [00:21:00] things are possible. Mm-hmm. , I mean, not literally, but, um, and that\'s, it\'s powerful but dangerous and I\'m, I\'m trying, you\'re, you are being, uh, non a not a developer and having a history of using this kind of thing is immensely valuable.
Um, keeping my feet grounded. And I\'m trying to do the same with thinking from the perspective of my clients, because again, they were the ones that inspired this, so what\'s gonna solve the problem for them? And that\'s where we, that\'s kind of where we\'ve landed and what we\'re getting pretty close to being able to launch.
Cory Miller: I, I think, um, the experience you talked about is like, everything is when , another shirt we should do, when everything is possible, everything sucks. Because when you have, when you\'re in the experience, I know this and I\'ve been. Uh, led teams of developers. I get it. Like, and I have the, I guess I\'ll say a gift in this sense of going, I don\'t know what all [00:22:00] is possible and it helps focus, but I think that\'s where, again, a partner comes in.
I struggle with this in different areas, um, where I\'m like, well, everything is possible. Everything sucks. And I, I lose focus in that. Um, and that\'s something I really enjoy being able to do is like, you worry about everything is possible and I can help just to ask questions. Um, and when we\'re, we talked about the MVP, I think about that iconic, um, like cartoon of this, the stages of an v mvp, how, how you start with an MVP and grow it.
And the one I like best, it feels a lot of theory and cool, like to try to plan this out like this, but it\'s like, what\'s the skateboard version of the. Bike or whatever, you know, the product becomes and it\'s not, uh, a skateboard. And then you add a seat and then you add handle bars to the skateboard and you try to build out.
And I\'m like, that\'s cool in theory. But [00:23:00] I think what this does is, the way we thought about this was what is a, a toe in the market that does solve that problem that can grow? Um, and, you know, marketing and technical and business questions come out of this. And I just saw one yesterday, uh, I can\'t remember his name on Twitter, but I replied to him.
He was trying to think like, where does this thing go? You know, like you start with the skateboard, but well, what if we want to do this with Crop Express and that with crop, you know? And, um, a lot of times, I think some of the best products have been part of grew organically instead of trying to say this is the end product, it was responding to customer needs and opportunities and grow out.
And sometimes maybe it grew into a little bit of a mess out here that we kind of had to make some hard decisions, um, with our ITM security product there for sure. And then backup Buddy over time. Um, we saw that, but it, I think it stays close to the customer [00:24:00] when somebody goes, I will pay money for this.
You go, oh, there\'s magic there because we, we might have something here. Um, and I, we decided, and we should talk about this decision too, we decided to release Crop Express as a free plug in first on the.org repo. We\'ll be talking about that experience as we go. We\'re not there yet, but we\'re really close to releasing the v mvp V1 in the repo.
Uh, and then, but what I like Corey, is we\'ve done this in a way to give us options or paths to go. We\'re not, we didn\'t try to build the bicycle and launch that as a premium product. We said, what time resources do we have? And that mvp all that went into this conversation you and I had of like this.
Okay, let\'s come down to if we can get this point, and that\'s in the stream of people\'s workflow. You know, you\'re firing and proposed headline, okay, I need my future. You\'re gonna go over here, click feature damage. And that\'s where [00:25:00] Crop Crop Express is gonna help you. And I don\'t, you know, you\'ve been great to navigate us technically, where we\'re not gonna hit a dead end on something.
Um, but that\'s the part of this adventure. You never know where you\'re gonna go with it. Right.
Corey Maass: And I\'ve, uh, you know, we\'ve already touched on a, a bunch of things that I see questions about all the time, like part of the MVP. Uh, I\'m, I\'m a, I\'m a good developer, but I have very limited experience with Gutenberg, um, excuse me, the block editor.
Um, and even, and so we, we are looking at doing a custom block down the road, version 1.2 or whatever. Um, but even to get, uh, just the, to, to work with just the featured image. Like I didn\'t have experience with the panels, uh, inside the block editor. And so I looked at it, I hacked at it for a [00:26:00] little while, and then I said, okay, you know what, I\'ve got a buddy who can help me out with this.
So, hired him for a couple of hours to get me over the hump. Um, you know, and so. There\'s that, there\'s again, the partnering, uh, you and I working together, um, which we haven\'t really flushed out, but we\'re kind of excited to do, um, launching something, putting something in, in the plugin directory is, is its own experience.
Um, and so yeah, I think there\'s, there\'s a lot of different things here that if nothing else, just getting that, you know, the tip of the iceberg. Um, or I\'m mixing metaphors here. But anyway, you know, just getting this thing out the door and, and starting, um, is, is where a lot of, uh, a lot of questions arise and there\'s, there\'s a lot of hurdles, you know, unto itself.
But, um, you know, I think the, one of the things that I really like about WordPress is that. It does require, or [00:27:00] WordPress plugins, WordPress products, it does require development, no question. Um, I don\'t think there\'s a big overlap yet enough of an overlap yet with like, no code products, um, services out there that, you know, people are building products against to then somehow get that into WordPress.
Um, but it doesn\'t have to be a huge lift. It doesn\'t have to be like, some of the best, um, plugins out. There are one single feature or, you know, single file, um, the, the plugin that we have so far that, that gets the featured image. Cropped and, and injected into a post is, is still basically just two files.
You know, it\'s not complicated. It\'s not this big convoluted thing. Um, I\'ve got, uh, from, you know, from a nerd perspective, like there\'s a couple of developer patterns that I\'m using, but there, there [00:28:00] aren\'t frameworks. We\'re using a library that, you know, does the cropping for us, cuz there\'s no way I\'m stepping into that quagmire.
Um, you know, but we\'ll grow from there. I mean, and I think that that\'s, that\'s the big difference. It\'s like, yes, we wanna launch something that is useful, um, and complete unto itself, but it can be, it can start as a feature and grow.
Cory Miller: How, how has this experience differed from your past product experiences?
Um, you know, you, you released, let\'s say the CommonBond different plugins on your own. I think, um, were, were similar problems and questions. That we\'ve talked about just in this, I don\'t know, month or so we\'ve actually gotten real serious about it. No, it\'s probably what, three, four weeks I think. . Yeah. Um, but like did you have similar things like that as a developer when you were doing like the combine?
Or did you just go, okay, this is what I want to build and you knew like the N V P V one V two kind of sorted [00:29:00] it out. How did those experience go in comparison to this one?
Corey Maass: Yeah, the con bond, I really, I wanted the name space. That\'s the thing that sticks in my mind. This was, you know, eight years ago now.
Um, so I don\'t, I don\'t remember everything, but we, same sort of experience. I was working at a startup and we needed a conbon solution. Um, Trello has. Rubbed me the wrong way. I don\'t know why. Um, and, and it was then that I was first starting to look at, so another, I\'ll give away another one of my secrets here is honestly, I often look for a, um, blue o, well, red Ocean SaaS solution or SaaS app that I can put into WordPress.
Um, and so with something like Trello, I was like, you know, we are, we are working in [00:30:00] WordPress, um, but we have to go over to Trello and, and do stuff. And for whatever reason, I didn\'t like Trello anyway. Um, and so that\'s part of what made me go, oh yes, if we had a CONBON board built into WordPress, so like posts were your cards or whatever, like, this makes sense anyway.
And so I cranked out a first version, very clunky and. Mostly just because I, I wanted to, I\'m trying to think if I had actually put a plugin in the repo before that. I don\'t, no. I had, I had, but years before. And so it was, it was really a new experience for me. Um, and I made all sorts of mistakes and I was listening to, like, one of the biggest ones was, um, I kept going back and forth.
Coming from, coming from a tra a, um, a, an a developer perspective outside of WordPress, [00:31:00] I wanted to do custom tables. And I was like, no. The word pressy way is you have to use the post tables. And I swear, the week after I released it, I heard an episode of, um, back when Pippin and Brad Ard had their podcast pippin\'s, like one of the greatest regrets of my life was using the post post table for e d D.
And that was like the beginning of when they were trying to release version three, which took them years to, to untangle, basically. I was like, crap. So right away I had to untangle my own thing, which thankfully only had 50 users or something, but I had to, you know, build a migration there and stuff like that.
Um, and then I think there\'s Go ahead. Go ahead, go ahead. Well just, you know, and, but there were, I I think maybe part of your question is like, There was, there were, I was solving bigger problems, you know? Um, whereas this, I think is like, I, I like, I mean, part of why [00:32:00] the, the light bulb went off when you were like, no, just featured image to start with.
So it just, it kept it focused, you know? And that\'s so much easier. Again, like I, I hacked away for a month or two months, you know, to get a working Now conbon board is a more complicated problem than, than what we\'re talking about. But, um, you know, but it, it, it was a much bigger lift to get it out the door, which I don\'t, I don\'t think is the right thing to do.
You know, you, you need, you need, especially talking about customers and clients and users, you need something. You need to get people using it as fast as possible.
Cory Miller: I, I think they\'re, I\'m seeing two paths that when you\'re launching a product, there\'s the technical path and the business path. Um, particularly if you want to monetize from it.
Um, but technical, I saw my teams for years. It was like, I, I always describe development as a, uh, an adventure and territory. You don\'t always know like, what\'s, what\'s gonna [00:33:00] come over the next hill. You could hit a swamp and end up drudging through a swamp or get sidetracked totally off on a minor bug. And so some of the things I started watching over the years is like, it, it\'s, it\'s a tough gig with the technical cuz you got a roadmap for potential.
You don\'t know where all the terrain\'s going cause you don\'t know where the business case is gonna come from, the use case. Um, and I just think it\'s like a blind expedition oftentimes. Like, so what we would do is, and we\'re doing this now too, is just kind of check in and see how we\'re going. And I valued having someone else external watching to at least kind of keep track.
And then I\'ll say this on the business side. Same thing. There\'s potential here. I see potential here from a business, business case. I don\'t know what it is. I\'m not even gonna be foolish enough to try to predict, but there\'s something here, I think. And um, because I don\'t predict anymore, by the way, Corey, because I\'m wrong most of the times when I try to predict, [00:34:00] oh, this is gonna be $20,000 a month, you know, MRR kind of product.
Yeah. I go, there\'s maybe a hope for those things, but I never predict or promise because if I get too mired in that, I start to get too f a little bit off of focus. Because some of the questions we\'ve talked about is, okay, free plugin, what do we do there? We felt it was, at least for our collaboration here, partnership, we want to do this.
We want this in the world, you know? Um, we think though putting it in the world has the potential for something that could grow into. Something We don\'t even, but I, I say this cuz we, we said, I love every time you say something like, Hey, I think we should do this. I\'m like, right on. We should be honest. We should be authentic and share the experience.
I think too, oftentimes in business and stuff, it\'s like, this is the way I felt when I left eye themes is like the pressure real or unreal. Hey, [00:35:00] Corey did this, oh, what\'s his next thing gonna be? And I was like, she, uh, let\'s see here. Um, I don\'t know. I followed the trail, um, and kept following that trail and trying to keep going on that trail for as long as we could.
Um, th this, I just like the fact that. One of the questions I try to ask myself before I begin any new venture or partnership is, what if it fails? What\'s the worst that can happen? You know? And what\'s great is we\'ve been talking about those things along where we manage it. I know when you hired the, the friend to help with some of that stuff, I was like, well, how much is that?
And, you know, do you need me to share it? And you\'re like, Hey, for now, let\'s just, I\'m gonna keep track of it. But, uh, to see where it goes and, um, I think that\'s healthy. That open dialogue and conversation where you respect each other, what each other knows. And know just because you\'re a developer doesn\'t mean you, you have a ton of insight and feedback [00:36:00] and perspectives to share on both business and marketing.
And, but it, it, it, I don\'t know. I see those two pasts. This is the one I\'ll tell you ahead of time, Corey is I\'ll struggle with, is when we get to the point we\'re like, okay, how much should we charge for? , it\'s oftentimes feels like this meandering thing of like, okay, and I\'ll need the same for you to go.
Sure. Hey, what if we do this? Um, because if everything\'s an option, everything sucks. .
Corey Maass: Yeah. I, so a couple of things that you touched on, like, it, this needs to exist in the world. I haven\'t found a better solution. So hiring somebody to get us over the hill immediately was worth it. And just like you said, if it, if it fails, if it never makes, uh, A dollar if you and I af after this call are like, yeah, I don\'t like you in the end it turns out, let\'s just call it, it\'s like, no, it was still money well spent.
You know, and I, I understand that I, I am in [00:37:00] the very fortunate position to have a, a little money that I can throw towards a project like this, but it\'s, it\'s very limited. And I, I think of this type of stuff as a hobby. Um, and there\'s been a lot of life choices that have gone into inclu, especially with, with my, my wife talking about like, okay, what is, if, if this is a hobby, what is an appropriate amount of money to spend on it?
Cuz there were times 20 years ago when I first started building SaaS apps that I was like, every spare dollar that I have is gonna go back into this without thinking about it. Um, because everything I ever think of is brilliant and every product I launch is undoubtedly gonna make me millions. Um, Spoiler alert.
None of it has yet, yet. Um, but uh, you know, yeah, we, we, we gotta start somewhere. Um, and, uh, I\'m with you. So I, I\'m also looking [00:38:00] forward to, like, I\'ve been, I met, it was, it was at a, it wasn\'t a WordCamp, it was like, um, what are they called? Free camp, or there\'s, there\'s conferences where it\'s like anybody can sign up to talk about anything.
Um, and it\'s sort of tech specific. But anyway, I met a young woman, uh, who was a developer and she had lucked onto a client who became a partner, um, who was an older guy who ran, I don\'t remember, an advertising agency, but he had access to an, a pool of customers, basically. And so he would tell her what to build.
and then he would sell it to his audience and they just kept cranking out products. And I was like, okay. Despite being an only child, and despite my first instinct being to do everything by myself, you know, there are things that I can\'t do. There [00:39:00] are things that I don\'t wanna do. and, and things that I shouldn\'t do.
So I\'m happy to weigh in on, you know, as, as your owning, marketing and your owning business, I, I want to weigh in, I want to have opinions, I want to make suggestions. And, you know, I think you and I have established that we, the expectation is that, you know, we, there\'s, there\'s going to be quite a bit of overlap in our concentric circles.
Um, but we, we each are gonna own a lane, which I think makes a huge difference. Um, and we\'re also able to sort of look over the cubicle wall to the other person and say, Hey, you know, like I, I touched on earlier, just cuz I can, doesn\'t mean I shouldn\'t, I\'m. Not going to want. There\'s going to be times where I, I\'m going, I\'m not going to want to build what I need to build.
Like there\'s a feature that every client is clamoring for. You are finally confident. You\'re like, they will all pay X number of dollars if you [00:40:00] just add this. And I\'m gonna be like, yeah, but we need a dark mode or some ridiculous thing that\'s just gonna be more fun to build. Um, and I think there\'s definitely going to be points where, you know, I, we\'re essentially going to need to be each other\'s bosses.
Um, and that\'s going to be interesting and going to be difficult at times. But I, but I think good, you know, you, you, you need other people. There are people out there that are, there are exceptions to this of course, but you know, I, I think we\'ve pretty well established that both you and I do better if nothing else.
Having a sounding board, having somebody else who\'s as invested, um, you know, and helps keeps us, keep us on the line we\'re supposed to be on.
Cory Miller: Yeah. On that note too, um, the partnership side of things where I, I\'ve been in circumstances where, okay, this is Mon Lane, that\'s your lane. [00:41:00] And sometimes, like you were really good to ask me what part of the development do you want to contribute to?
And I said, my strengths through trial and error. By the way, I think my contribution strengths are u UI experience, like how things flow. Um, I obsess over there cuz I want them to be as fast as possible. Mm-hmm. intuitive as possible. Knowing some of my, probably I\'m gonna have to freshen up on some things.
And the other is I said, you gotta be careful with me because I will share all of these things that I would love to see, but we\'ve like, But we gotta put \'em on a, a feature roadmap, A backlog somewhere. Because I said, and I told you this, I said, be careful cuz I\'ll come in and go, what about this, what about that?
And what I had to tell my team too, and I told you is like, please don\'t unless I go, can we get this in the next release? Please don\'t think that. Let\'s do this right now. And that\'s the [00:42:00] idea Fairy in me is mm-hmm. . Uh, but, and so an example of that was we have a square coop cropper. And I was like, okay, I\'m introducing the new customer story here, which is my own, every, the Posts newsletter has those little circles in them for all the, and I\'m like, that is a pain in the butt to do.
Now I flag that because I go, if I\'m the, uh, kind of a typical user, I don. Know how, how to crop that, you know, there\'s tools out there, right? But like I go, there\'s an experience if, if someone has that and I go, Hey, what about a circle cropper? And then I knew you were going to like chase it , and I was like, Hey, hey, hey.
Not for this one unless it\'s an easy thing. This was that back and forth I did with Right. All the developers I\'ve worked with too is just like, please don\'t say, please don\'t interpret that as, can we do this right now? Um, sometimes I\'ll be like, can we do this right now? Because I\'ll, I\'ll feel [00:43:00] like we got something here.
Um, but then you\'re like, okay. I was like, well,
Corey Maass: it\'s just cuz you can doesn\'t mean you should. Yeah. But there\'s also, you know, you and I, I, I also get the sense, we haven\'t talked about this, but I get the sense that we both trust our instincts pretty well, um, when it comes to product. You know, and I\'ve, I\'ve been, I.
Studying product, looking at product. Um, for years and years and years, I\'ve got, you know, books on architecture. And, uh, the, one of my favorite books about, about the Bowhouse School is sitting next to me. I mean, things like this and like, I nerd out about this stuff. And so, um, I\'m not saying I\'m an expert, I\'m not trained in any way, but like, I think I like a lot of people we know, you know, I, I, I love putting, I love loading an app and putting it in front of my mom.
You know, who\'s, who\'s not trained in any way. She has [00:44:00] a little bit of an artistic background. Um, but she is a power user. I mean, she, at this point, she doesn\'t even have a computer. She does everything on her iPad. Bless her heart, honestly, because. Trying to book tickets or, you know, I mean, things that she does on her iPad, I, I didn\'t think possible, um, even, which really is just in a browser and, and her fingertip, you know, but gets an unbelievable amount of stuff done.
But I love putting things in front of her and saying, you know, show me how you would muddle through this. Um, and, and anyway, so all of this to say that I, I trust my instinct a lot of the time, um, when, when somebody mentions a feature to me of like, oh, this is worth doing right now. Even if it, yes, it\'s not mission critical, you know, we haven\'t released yet, so technically any feature other than one feature is, is enough.
But I was like, not only [00:45:00] do, is there not a image cropper for WordPress the way that we want. Out there, but I really don\'t think any of \'em do circles. And again, my clients for most of their stories featured images are 16, nine or square. But for whatever reason, there\'s that, that now that browser pattern where avatars people are circles.
And so, you know, let me see if I can, I can crank this out and it\'s, and it\'s fun. Um, and sure enough, like, like you said, it, it wasn\'t a big lift, but yeah, I think, I think you and I will, we\'re just gonna have to figure that stuff out. Like everything, everything goes on a backlog. Everything gets discussed at least a little bit.
Um, but I also, you know, I don\'t, I don\'t think that there\'s harm in, you know, there\'s low hanging fruit, there\'s return on investment. There\'s lots of different ways to put it. [00:46:00] It\'s like, oh, well if we, you know, if we make all the buttons green, you know, is it, does the user benefit? No. You know, so just cuz it takes a minute isn\'t worth it.
But, you know, we\'re, we\'re just gonna have to, and, and I liked what you said too, of like, we, we are gonna have to, I guess this is the other, the other benefit of trying to get this thing out the door is like, get people using it, talk to people using it. Um, you know, being part of a, a community like Post Status, um, there\'s the great, um, advanced WordPress Facebook group.
Like there\'s, there\'s places that. You and I have been involved for a long time, kind of regardless of, of our actual position within those communities. But, you know, trying to add value or trying to Twitter to trying to just, you reply to tweets for months and then you hope that when you, you do something and you need somebody else to reply that, they will.
So it\'s like, let\'s get this thing out there. Let\'s see what people think. [00:47:00] Give it a try. Um, you know, and, and follow, follow our.
Cory Miller: This is where I struggle back and forth with product. But my typical mo, what I feel instinct is you, uh, there\'s product people that are just genius and gifted. They\'re like, here, you know?
And you\'re like, God, okay, cool. Uh, but for mere mortals, um, for me it\'s been put something enough out there, check some boxes. Okay, is this something you think we need? Like, does anybody even need it? Because I put those things out there, I\'m like, put \'em out there. Not necessarily products, but other things.
I\'m like, nobody\'s even asking for this. And a lot of the entrepreneurial books and stuff, it\'s like, okay, how you scientifically go down it? And I go, it\'s art and science. Yeah, it\'s a blend. It\'s this alchemy and magic of like, but I know the power of like putting something out there and that creates enough a ripple where you get a feedback loop and, um, [00:48:00] That was so helpful along the way when you get feedback like, I, I, we feel this is a good, this is a good V one, solve somebody\'s problem, that laser beam, you know, thing of what we\'re doing for it.
Um, but what I\'m most looking forward to the product is how people react when you hear those. Like, um, backup buddy was in development, uh, and then, I can\'t remember, 2009, 2010, and I, we were at, we had a little group thing where, and this, these two twin brothers ran an agency and I just, this wasn\'t something somebody told me.
I was just like, Hey. We\'re doing this thing and this plugin, and it helps you do, um, basically, uh, backup, restore, and migrate websites. By the way, those were not things that came from me. They came from Dustin Bolton and Christine and I themes, they\'re like, no, a backup needs to do these three things. Okay, okay, let\'s do it.
Sounds good to me. But I mentioned to them [00:49:00] the migrate, or what was it? The migrate side and just in passing, and they, their eyes lit up and they go, we pay somebody $300 to do, to do that now. Wow. Consider the time and everything. This is back in the day. And I was like, okay, I think we got something.
Because, you know, and then we just try to, okay, I think we\'re gonna keep going, keep doing, we obviously launch it, we\'re gonna launch it no matter what. But um, that\'s where I was like those moments where someone lights up and they\'re. Can I pay you now? The shut up pay, shut up. Let me pay you thing. Right? I was like, shut up.
You can take my money. Shut up and take my money. That\'s a magical moment. Um, I think times I\'ve tried to force it, um, and it\'s just, it\'s not, or create a category you hear that\'s not, and I\'m like, cool. Yeah. For those a hundred people out there that have that insane genius to create a category, most of us stumble into it.
Right. You know, um, the garage stories for startup [00:50:00] stories are always make me laugh. Cause I\'m like, what was the background? What was the context? I\'m like, that\'s a sexy headline. We started in a garage and here we are, apple. I\'m like, that\'s a sexy headline. Don\'t, and I like it. Don\'t get me wrong, but I\'m like, what Were all the actual moments, the places you got phenomenally lucky.
I know there\'s a big part of mine luck and every time I\'ve tried to time it and like, okay, I\'m gonna ride this thing, it just hasn\'t worked. And that\'s why I really like her direction with this. Um, Because we kind of had a fleeting thought of like, I think as I recall, like this could be a paid product.
Um, you know, I don\'t even know if we entertained much of starting with a paid, we\'re like, let\'s just do the free plugin. And I will say, remember actually, um, give you credit for this too, is I think I said, what about a Gutenberg block? Put it in editor. So upload image crop, boom, I\'m there. My workflow\'s fast, efficient.
And, [00:51:00] um, you, you looked into that, you chased a little bit of it and I said, Hey, there\'s some roadblocks here. And that\'s that collaboration of how we go, okay, featured image, what if we started right here? We want to grow potentially into that. You know, I think the idea in this, and we\'re, I think we\'re both verbal processors, but is the thesis is start here and it\'ll grow into.
Block, like the inline process where you\'re in the thing and you\'re having the same problem, I need to crop it, figure out right. Dimensions and all that. Um, so I don\'t know where I was going with that other than to say that was some of the background too of decisions and knowing like you could hit a dead end.
And I\'m waiting for that. I think we\'re putting ourselves out there with this to see if there\'s magic in this. Yeah. Journey.
Corey Maass: Yeah. A couple of things you said, um, stuck out to me. One is [00:52:00] like a lot, everybody builds products differently. Everybody b builds UI differently. WordPress has very soft wall, has a lot of walls, but they\'re very soft and there\'s a lot of discussion, often negative, often complaints about, um, The, the experience that a plugin provides.
And I think what\'s different about WordPress, right, is like often you\'ll, you\'ll go to Trello and you interact with Trello, and you go to Slack and you interact with Slack in WordPress, you\'re essentially interacting with numerous apps, really numerous UIs, side by side. Um, and the tolerance for terrible ui.
I mean, let\'s be honest, even WordPress is not great anymore. Um, the tolerance is high for what you can [00:53:00] get done. Uh, and so I think that that\'s, that\'s an, that\'s something that I hadn\'t really thought about, but it\'s like things you can get away with in WordPress as long as you can solve the problem. And so there\'s, there\'s a lot to be said for, bless you.
There\'s a lot to be said for. Solving the problem, um, and not getting caught up in the genius of a product. You know, cuz like you said, people, people wanna get it done and get out, you know, get on with their lives. Um, the other thing that I\'ve had a lot of luck with, so I think we should do this here, is talking about that feedback loop.
Um, with Conbon, I put myself on the homepage and had a, and, and had a nice. Response. Um, with, uh, there\'s an online game that I built during the pandemic that, that I\'ve told you about, um, called Mexican Train [00:54:00] in the web websites, Mexican train.online. So if anybody out there wants to play Mexican train, which is a Domino\'s game, but I built an online version, um, I put myself on the homepage and it\'s a game that is played by a lot of seniors and especially during the pandemic when everybody was really locked down.
And then even now a lot of seniors are still trying to stay inside, stay safe, stay more isolated than they were before. Um, and isolated being the word. They use the game to keep interacting with their friends, um, which is just amazing. Um, but they. Not only does every email that come in start with, Hey Corey, because I am on the homepage.
Um, but apparently when, like, there, there are groups of people that play every week and even every day and uh, they curse me when they get bad dominoes. They praise my name when they get good dominoes. Um, the picture is of me [00:55:00] eating cheezits cuz it\'s sort of as a joke, like, Cheezits are a guilty pleasure for me.
So a number of them actually like, go and buy Cheezits and eat Cheezits while they\'re playing because it\'s become a, you know, uh, a thing. Um, inside joke I guess is the, you know, uh, or whatever. Um, but there\'s the, that feedback loop is definitely there. Like, they talk to Corey, you know, and then even with.
Subsequent products that I\'ve built, me being on the homepage with a blurb about like why I started the Solve the Problem and stuff like that, has made a huge difference. And so I think as, at least early on, that\'s something that you and I should definitely replicate is, you know, as we\'re se I mean, we\'ll we\'ll send this to our friends and family.
Okay, that\'s easy, that\'s obvious. But, um, you know, maybe even building in a mechanism that\'s like, you know, Hey, it\'s your favorite. Corey and Corey, like, tell us what you think. What do you, you know, um, does this work for [00:56:00] you? Does this not work for you? That kind of thing. I usually don\'t think about explicitly collecting feedback until further down the road.
Um, usually wanting to focus on like paid customers and that kind of thing, but, you know, maybe it\'s something we start with sooner than later.
Cory Miller: I definitely think so, because, you know, so many times I\'ve put products out there and not really made that splash. Like, you know, they\'re like, okay, there\'s practical, they\'re doing this thing, um, that we set out to do, but I think you wanna have push, push it to have an opinion.
Mm-hmm. , you know, like the user to have a reaction to it, enough to say it sucks or it\'s awesome. Um, some, some way of that to see where you\'re at. I think both if you get it sucks and it\'s awesome. You\'ve got some validation there, you\'ve got something. Um, but putting things out there, that\'s [00:57:00] how I, my mo with products.
So 2006 or seven I think I, I launched, I did launch, I guess, uh, this is way back in Word Press was different, but I launched a theme and put my zip file. Uploaded it to.org. People downloaded it and I was like, this is crazy. I got a response from them, which I had a contact form up , you know, my website linked in the theme and stuff, and they\'re like, will you build blog for me?
And I was like, whoa. I\'m learning. I did this too because I wanted to do it and I\'m learning. But that\'s the magic that when you put something out there. Yeah. But I think there\'s this case for put something out there that kind of pushes a reaction. You know,
Corey Maass: and I think this will be an interesting point of conflict potentially, is uh, there\'s going to be a point where.
We\'re, we\'re going to see different paths and we\'re gonna want different features too. And so I think this is, that\'ll be an [00:58:00] interesting, you know, let\'s try to have that conversation on camera because it\'s there. There\'s points where I\'m dogmatic, like I\'ve got my, one of my other plugins is like, like I said, I, I often look at products that are out, out on, out in the wild and I repurposed them inside WordPress.
And so I\'ve, I\'ve got a plugin that\'s kind of like a link tree or a card or an About me where it builds very simple social focused landing pages. Like the link bio pages is kind of the, the phrase most people think of. And uh, and even like when I submitted it, the, the people reviewing the plugin were like, um, you\'ve kind of built WordPress inside WordPress.
And so I still get a lot of requests for features that are beyond. The point of the product, because it is within, like WordPress using the right theme or page builder, you can do literally anything. [00:59:00] So this is supposed to be very focused and people come in, come, come in and are like, well make it do this.
And I\'m like, that makes no sense. Like, go use WordPress. Um, and so I have found myself being more and more dogmatic about like, my own vision or, you know, certain vision for a product. Um, you know, and right now, like you and I have it easy, like we know it, it it\'s a one trick pony or one and a half if we do circles.
Um, you know, so what\'s, what\'s the next thing that I think that\'ll, and, and, you know, in a year down the road, I think that\'ll be interesting. Um, again, that, that backlog, you\'re probably gonna end up hearing more feedback than I am. Um, you know, uh, Product ownership might ha end up being a thing that we, we actually have to sort out.
So, and it\'ll be an interesting ride.
Cory Miller: Well, that\'s been a lot of the background, um, that we wanted to share and kind of catch you all up since we were, were launching [01:00:00] this live or in public. Um, but catching you up on some of the background, some of those key conversations. I hope people can use some of this to, uh, inform their own product journey.
Um, where we are today, where are we today, Corey, with the actual product? Sure. Um,
Corey Maass: yeah, and I just to add to what you just said, like as people watch this, there are a few people watching live. Um, my expectation, like most things recorded is, you know, more people are going to watch it on the playback. Um, but we are going to.
Looking at comments, and I think both of us are pretty easy to find. Um, you know, so, so as, as the, as the conversation gets started, you know, I encourage anybody listening, please ask us questions, you know, give including hard questions. You know, what do you want us to talk about? What do you want? What questions do you want our answers to?[01:01:00]
Um, not that we have the answers to all these problems, but you know, this is, we\'re doing this out loud, recorded on the internet, you know, so we\'re happy to talk about it. Um, and we\'re both pretty candid out, outspoken kind of people. So we\'re, we\'re happy to talk about prayer, pretty much anything. Um, but anyway, where are we at now?
Um, so I, with, again, with the, the help of a freelancer built, uh, a first version, I did the p h P. Um, he helped get the. JavaScript and React part of the, um, panel inside of the block editor integrated. Um, and then I took the, the cropping library that we\'re using, stuck that in. Um, and we\'ve, we\'ve gotten pretty far with that.
The, what, what we had been limited to for the last couple of weeks [01:02:00] is the selecting of an image. So, you know, nobody\'s, nobody\'s seen this yet. So talking through the flow real quick, you\'re opening up a, a new post in WordPress. There\'s, you know, the built-in featured image panel on the right. Um, we\'re essentially replacing.
It looks very similar to the built-in one intentionally, but when you click on it, instead of it opening the media library where you upload an image or select an image, it uploads a, uh, or excuse me, it opens a modal where it says What shape do you want a crop? Um, it does say, do you want a circle? Um, you select an image from your hard drive, it then opens the crop.
And one of the nice things about this kind of tech is that that image is not uploaded yet. And so it\'s all just in the browser until you say, okay, set this, you know, I\'ve moved the crop. I want it this part. Set that as the featured image and that\'s what gets uploaded. [01:03:00] Um, as of today, I got a poll request again from my freelancer who helped me get started with the media library, cuz this is the one thing.
I\'m, I\'m undermining you here, but you said, I really want circles. To me, I was like, that\'s a differentiator. We need circles. Um, to, from my perspective, I\'m saying also we need very basic media library integration. I think you originally suggested this as a nice to have, and I was like, no, you\'re right like this.
To launch with, you need to be able to select an image that has already been uploaded or select an image from your hard drive, crop it and set it. Um, and so we\'re, we\'re pretty much there. The media library is opening and you can select an image. Um, so I need to do a, a couple more hours of development, I think, to get it so that it\'ll save that essentially re cropped version of what is in your media library.
Um, [01:04:00] and then from a d a product standpoint, we\'re pretty much ready to go, um, on, on your list. Um, I know we have the readme.
Cory Miller: That\'s, it was like, Hey, Corey, you have 15 minutes of work to do. .
Corey Maass: That\'s not true. I mean, it, it is to get it in the repo because it\'s one of those, you know, no, nobody does it if a tree falls in the wo if a plugin gets committed to the repo and there\'s nobody there to hear it. Yeah. Um, you know, or, or security by obfuscation kind of thing.
But, you know, there\'s, it\'s the beginning of the marketing. How do we describe this thing? What do we even really, what do we call it? You know, is it, is it crop express? Is it crop express image cropper? Is it image, crop express, da da da da da. Like, just, we have the domain, but that\'s it. So there\'s,
Cory Miller: uh, it presents a lot of questions.
[01:05:00] Um, and I know we\'ve run outta time, um, but it presents a lot of questions because you go, there\'s wordpress.org plugin search that is, Pretty big, right? Um, the, these are some of the things coming outta my mind with the readme because it does turn into that plug-in repo section. Um, I\'ve seen a bunch throughout the years how people like, enough there to go.
Here it is. And then my balancing act is, let\'s get enough to show this is the value proposition, this is what it can do for you. Uh, and then just like everything iterate over time. Um, but I can\'t help but tell and admit to you. I think, oh, it\'s gotta be like side bki put a plugin on the repo. Like he knows he\'s a marketer, he\'s got all these talents, but he, he understands how to put a plugin, um, and showcase it, right?
And so I\'m battling that a little bit, but I go, okay, get enough to, so here\'s the value prop and that this is an active development and we want that [01:06:00] feedback loop back about what\'s next. But I think the read me is showing. Telling enough of what we\'re trying to do where someone goes, that is a problem. I have this plugin, will will solve it.
Now getting to that is gonna be, is gonna be fun, but I started on the Readme file from the Generate WP site you gave me. And um, that\'s where I\'ll honestly spin some wheels a little bit, cuz I\'ll try to be perfect. But I think the two outcomes there really are, you know, clearly understanding what this does.
So someone, mm-hmm can go, oh, I\'ve got this problem, or my client\'s got this problem. And then second is, we need a loop. We need to know these things. Even the things you go, we\'re never gonna do. I still want to have \'em up there. I still want to have \'em in our visibility because it just allows us to make better informed decisions as we over time hone in on, you know, A lot of the products we [01:07:00] released at I themes, it was years before we go, oh, that group right there, because you get enough of big sample size and you go, okay, convert Kit had a very similar, uh, fault, Nathan Berry.
He started out with one thought in mine, and then he saw it was this creators, you know, um, economy. And then he just, when he got that bead, he just, you know, doubled down on that. And I, I see, I see that similar here. I think we have pretty good profiles, like anyone that wants to make image cropping easier, um, and faster from a blogger to an agency doing work for clients, um, that\'s a big use case for me.
And I\'m like, there\'s, that\'s why I have some faith that there\'s something here that we can do in an advanced case, but it\'s just discovery to me, you know, so.
Corey Maass: Yeah. Well, and I think that\'s part of, I, I think you should take notes on your experience and then tell me about it. The next time we have a call, like [01:08:00] mm-hmm.
you are a, apparently you launched a pro a theme many years ago, , uh, but have it since. And so when I was like, okay, you go, go and do the read me. You were like, uh, I need some guidance. Like I, yes, I can write words, but tell me more about the Read me and what are the consequences of, you know, the, what I put in the read Me.
Um, and I think that that\'s, you know, you, here\'s a prime example of your experiencing something for the first time. You know, tell us about that experience and, and, and the thinking, some of the thinking that goes into it, like, it is, it is something that gets iterated on often, but there are consequences of, uh, you know, when we submit the plugin, the slug, the u r l is going to be locked.
You can. ask them to change it [01:09:00] once within, I don\'t know, the first couple of days or something. But then that\'s it. So, you know, cuz and you\'ll, and you\'ll see that with plugins on the repo that the U R L is W P S E O, but the product is Yost, you know? Right. Or things like that. Um, things that they\'ve had to change over time, but you can\'t change the slug.
Cory Miller: I know that firsthand too . Right. I sure think security was better WP security and, and it still is. I think. I don\'t think we That\'s right. Get there\'s, yeah. So that\'s right. Yeah. There are some foundational things that can\'t change over time, which is tough when you\'re doing new products as you don\'t.
Always know where it\'s gonna go or what the right, you know, do we need to say image cropping, you know, kind of thing. Whatever the, the kind of keywords are.
Corey Maass: Yep. So, yep. So, but I, I definitely think that\'s, that\'ll be a great experience for you to talk about and, and also a lot of the, the thinking that, that it makes you do will subsequently guide at least some of our early work [01:10:00] when we do put up a marketing site.
Cory Miller: Absolutely. Well, okay, so last question. We\'ll wrap this up since we, since we got over time. Um, but it\'s hard not to stop talking with you. I enjoyed this. Um, so by next Wednesday, um, what do you think is realistic for us to make progress on and we can start talking about that next. Because we\'re gonna be doing this, by the way, for the next five, six weeks, I think.
Um, there\'s a webinar, um, that was in the newsletter, the link to that. And then of course, if you\'re watching on YouTube, you can just come back to Post Status on YouTube. But Corey, what do you think, um, our next steps are, the progress we wanna make in this week interval?
Corey Maass: Yeah. I think the goal should be either we get this across the first finish line or past the first milestone or whatever of it.
Either we submit it to the [01:11:00] plug-in repo or it\'s, or it\'s ready to go and we can talk about that. But, you know, feature, feature complete as far as version one is concerned, um, and, and that, that read me, basically it\'s the whole zip file ready to go and be submitted and then we can either, Maybe we even, we could even submit it while we\'re on the, uh, you know, on the call and kind of talk about like that.
And then I think we\'ll end up talking about like, you know, whenever I\'ve submitted plugins, um, I\'ve, I\'ve never just had one like stamp done. Like there were questions asked or there were, um, code revisions that I needed to make based on, I know that they use a programmatic, um, I can\'t think of what it\'s called, but basically code sniffer, um, to, you [01:12:00] know, it basically some little AI that, that will flag variables that aren\'t escaped or things like that.
And, um, and then I\'ve also usually wound up having a conversation with a human being who\'s like, you know, what are your intents? What, what\'s your intention of this? Or, you know, why do you think we need this? Or whatever. And so if, you know, I think that\'ll be worth talking about too.
Cory Miller: Because the submission to the repo takes some time because it\'s gotta go in the review and all that stuff too.
So, um, I think about timing wise as well as like, once it\'s there, it\'s, we\'re gonna have just by nature of the review process, which is good. I, I, I get it. Um, it\'s gonna push us out some to actual, to actual launch. That\'s something to consider too.
Corey Maass: So, you know, so we can, I think let\'s, you know, let\'s regroup, um, today\'s Wednesday, you know, end of the week, beginning of the week kind of thing.
Um, and we can. basically just hit submit. Um, and [01:13:00] I th the last I heard the review process takes a couple of days and I, that, that fits with my experience. Mm-hmm. , um, you know, so maybe we\'ve heard if we submit Friday or Monday, we might have heard by Wednesday. Um, and then we\'ll have that to talk about, you know, or we can just submit on Wednesday and then the following week we definitely should have something to talk about.
We might not be live in the repo, but um, you know, we should have heard back. I know we\'ll hear back within a week. Yeah.
Cory Miller: Okay. Well, my intention is to carve out some time today. I think I\'ve got some buckets of time to finish, to read me at least get a draft that you can review and we can go back and forth, um, to have that, at least you not be waiting on that or me, so that sounds great.
Corey Maass: Yeah, I\'m.
Cory Miller: All right, Corey. Thanks, man. It\'s always fun talking through this stuff. Yeah, having a partner and a collaborator. And, uh, thanks everybody else for, uh, joining in as you can. Um, we\'re gonna be here Wednesdays 11:00 AM Central Standard time, um, [01:14:00] for the next five, six weeks throughout January and February.
As we talk, just share the progress we\'re making for this WordPress product called Crop Express. Thanks everybody. Thanks Corey. See ya. See ya.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 20:00:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:6:\"Emilee\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"Do The Woo Community: AI Text, Art, and Code\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:41:\"https://dothewoo.io/ai-text-art-and-code/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:397:\"

I chat with Mark Westguard from WS Form about how we have both used AI with content, art and even WordPress. With some added thoughts of AI and WooCommerce.

\n

>> The post AI Text, Art, and Code appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 12:12:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: WooCommerce Seeks to Improve Cart and Checkout Blocks Performance\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"https://wptavern.com/woocommerce-seeks-to-improve-cart-and-checkout-blocks-performance\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3517:\"

WooCommerce Blocks maintainers are asking the developer community to share feedback on any performance issues they are experiencing with the Cart and Checkout blocks.

\n\n\n\n

“We’re aware there is work to be done in this area and we want to improve,” WooCommerce developer Alex Florisca said.

\n\n\n\n

“We’re specifically interested in any performance related issues that may be stopping merchants or developers from adopting the Cart and Checkout blocks over the shortcode version.”

\n\n\n\n

The plugin’s repository has nine open issues categorized as related to performance. Most of them are not straight forward and require more research and testing. For example, an issue with running multiple blocks of product grids was reported as having increased response times of 4+ seconds. Contributors have proposed a few different ideas to address performance issues, such as experimenting with useSuspenseSelect to improve the perceived loading experience for various blocks and finding a way to track the performance of the Cart and Checkout blocks. Neither of these tickets have seen much movement yet.

\n\n\n\n

Store owners will not be eager to switch over to a checkout experience that is slower, so the WooCommerce team is seeking feedback that will help them make the cart and checkout blocks faster. So far, one user reported that due to a bug in a third-party plugin, he got a glimpse of what the block-based checkout adds to the JS asset payload.

\n\n\n\n

“I think this adds at least ~300 kB (compressed) JS payload (initial numbers, my measurement process is still ongoing),” Leho Kraav said.

\n\n\n\n

“We don’t plan to convert our classic theme to a block theme any time soon, but still, I feel uneasy about this direction.”

\n\n\n\n

Florisca followed up on this feedback with a few cursory benchmarks comparing the legacy shortcode checkout with blocks checkout and Shopify:

\n\n\n\n
Blocks CheckoutShortcode CheckoutShopify
Total Payload2.9MB935kb6.1MB
Total Transferred2.1MB1.3MB*3MB
Number of requests14477146
\n\n\n\n

“The number of requests has almost doubled for Blocks, which isn’t great so this is something that we can look into,” Florisca said. “I suspect the reason is because we rely on a few layers of abstraction on top – WooCommerce and WordPress, each with their packages and set ways of doing certain things. We can investigate if we can simply this.”

\n\n\n\n

The discussion on how to improve cart and checkout block performance is still open for more developers to give feedback, and investigations are ongoing. The good news is that WooCommerce maintainers are aware of how much weight the block-based checkout adds and are actively looking for ways to improve it for users.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 03:53:58 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: WordCamp Europe 2023 Tickets Now on Sale\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141212\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:61:\"https://wptavern.com/wordcamp-europe-2023-tickets-now-on-sale\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2405:\"

WordCamp Europe announced the first batch of tickets on sale for the 2023 event that will be hosted in Athens, Greece, June 8-10. General tickets are € 50.00, a fraction of their true cost, which is heavily subsidized by sponsors. It includes admission to the two-day event, lunches, coffee, snacks, Contributor Day, a commemorative t-shirt, and an invitation to the After Party.

\n\n\n\n

WCEU is also offering micro-sponsorship tickets at € 150.00, which organizers say is closer to the real cost of attendance.

\n\n\n\n

Speaker applications are still open but will close soon in the first week of February. Applicants will be notified by the second week of March and organizers will announce the lineup in mid-April.

\n\n\n\n

WCEU is also seeking a host city for 2024. The minimum requirements are considerably less stringent than in previous years. Hosting the event is open to any team that has organized at least one successful in-person WordCamp in a European city in the last four years with a community that has been active during 2022. Organizers have also published an update to the selection process:

\n\n\n\n
\n

For this year, we have tweaked the selection process to concentrate more on the local community and the city instead of deep knowledge about how to organise a successful WordCamp Europe.

\n\n\n\n

The selection of the WordCamp Europe 2024 host city will be based on the overall evaluation of the application, instead of ranking different parts of it. We don’t ask your team to prepare a budget for the whole event, but estimated costs for the proposed venue(s) should be available.

\n
\n\n\n\n

Contributor Day registration for this year’s event is not yet open but will be free with the purchase of a conference ticket.

\n\n\n\n

At the time of publishing, only 257 tickets remain in this first round, but more batches will be released in the future. Register now to lock in your spot or sign up for email updates on the registration page to be notified of future ticket releases.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 19:37:51 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"Post Status: Interview With Product Lead Tiffany Bridge Of Nexcess — Post Status Draft 137\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146391\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"https://poststatus.com/interview-with-product-lead-tiffany-bridge-of-nexcess-post-status-draft-137/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:57251:\"

In this episode, Tiffany Bridge joins Cory Miller to talk about the latest innovations she and her team at Nexcess have created for beginner online store owners, simplifying WordPress for users, and the ongoing battles between centralization and decentralization.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

Tiffany Bridge has been working in WordPress almost since the beginning of WordPress. She is the Product Manager for WordPress eCommerce at Nexcess and talks with Cory Miller about their hosting services and products, specifically highlighting the benefits and capabilities of Store Builder. They dive into optimizing UX in WordPress, the benefits of open source, and more.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • WooCommerce Simplified with Store Builder. As you know, WordPress and WooCommerce love to hide settings in layers of menus. Nexcess saw the struggles people had in trying to set up eCommerce sites and created StoreBuilder as an easy tool to go from zero to having an online store. This removes the initial learning curve required to get started in Woo and sets up a DIY tool for merchants.
  • \n\n\n\n
  • A Platform to Grow with You: One of the great things about setting people up on WordPress and Woo as they start businesses is the flexibility available for future growth. If their model totally shifts, they can just uninstall a plugin and add another to obtain the functionality they need to sustain their business growth without the hassle of migration or the increased fees of a platform.
  • \n\n\n\n
  • Solving for What Users Shouldn’t Have to Know. Kadence and so many WordPress and WooCommerce plugins are designed for WordPress professionals. We are working to leverage the power of Kadence by creating a top-notch user experience for people who don’t know what things like a border radius or gutter are. These tools enhance and expand the power of WordPress, so creating solutions that lower the knowledge barrier to entry is the kind of work that moves WordPress forward.
  • \n\n\n\n
  • You Can Own Your Own Platform. Often people aren’t aware that this is an option. From Etsy to Twitter, controversies tend to increase demand for alternatives. Bringing more awareness to individual ownership on the web-for blogs, stores, or anything else-empowers people to show up online and conduct business on their terms.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Sponsor: GoDaddy Pro

\n\n\n\n

Manage your clients, websites, and tasks from a single dashboard with GoDaddy Pro. Perform security scans, backups, and remote updates to many sites on any host. Check up on site performance, monitor uptime and analytics, and then send reports to your clients. GoDaddy Pro is free — and designed to make your life better.

\n
\n\n\n\n
\n\"GoDaddy\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Cory Miller: [00:00:00] Hey everybody. Welcome to back to Post Status Draft. This is an interview in the series of product people that we\'re doing with some of the great product companies in WordPress. And today I have my new friend Tiffany. Um, we get to talk a couple weeks back and I love her energy, her experience, her approach to WordPress overall. She\'s very distinguished, uh, experienced person in WordPress having done some cool stuff that I\'m gonna let her talk about. But we\'re gonna be talking about Nexus and Store builder today I think So, um, Tiffany, welcome to Draft podcast. Thanks Corey. You tell us what you do, what, what you do in WordPress now, and where, where you got to this.

\n\n\n\n

Tiffany Bridge: Okay. Well, so right now I am the product manager for WordPress e-commerce at Nexus, which is, uh, basically I kind of, uh, I have my hands in the entire experience [00:01:00] of using WordPress on our platform as a, as an e-commerce focused host. Um, that\'s a pretty wide swim lane, so I do a lot, a lot of different things.

\n\n\n\n

Um, the thing that I\'ve been focusing on is our store builder. Um, before Nexus I was, uh, I was at Automatic for a while doing, uh, I was on their special projects team, um, which works with, um, you know, interesting people and organizations to try and make sure they have a great experience on WordPress. So I did a lot of, sort of very bespoke projects there.

\n\n\n\n

Um, before that I freelanced. You know, was kind of doing what a lot of, uh, my colleagues are doing is just trying to, you know, help my clients have, um, you know, with by setting up like WordPress sites for them and things like that. And before that I was doing a lot of WordPress just kind of in personal projects.

\n\n\n\n

I started teaching myself WordPress in 2004. So, um, I\'ve been with WordPress almost as long as WordPress has been WordPress, which is, um, which is fun, like to see how far we\'ve. As a, as a community and as a, and as a piece of software. Right?

\n\n\n\n

Cory Miller: We\'re gonna have to [00:02:00] talk about that later. I\'m gonna come back to that cuz you, you predate me. I was just a blogger in 2006 on, on this cool thing called WordPress . Um, but you said this, uh, as part of you, I know you\'re so, you\'re so humble, but I want to act accentuate a part of this, like that special projects team you did at Automatic is known for doing. Big, glamorous, cool sites with potential big problems attached to them.

\n\n\n\n

And I can\'t remember what the code name for the team has called, but I knew about it for years. And then when we met a couple weeks ago, months ago, um, and you told me your background, I was like, you were on that team. Cuz it\'s very, um, I, I would say like, You know, a celebrity status in my sense, because I know I\'d go, I\'d go to this blog site of this cool site and realize it was on WordPress, or somebody would say, now this is on WordPress, and you kind of dig into the details and you go, it\'s that team at Automatic that was doing it, that you were a part of for such a long time.

\n\n\n\n

Tiffany Bridge: Yeah, I was there for, uh, well, it was just like, [00:03:00] it was a couple of years and, um, yeah, I mean I worked on some very, very cool projects and it\'s kind of like WordPress bootcamp, right? Like if you don\'t, whatever you think you know about WordPress, you will know more after, after like six months on that team.

\n\n\n\n

Um, because we solved like, Like every WordPress problem there is, right? Like you\'re, sometimes you\'re rescuing a site from a developer that maybe didn\'t do a great job. Sometimes you\'re converting a site that isn\'t on WordPress to WordPress. Um, like a, a project that I worked on that is very close to my heart that I can talk about is, um, I worked on the conversion of a list part from Expression Engine to WordPress, which was just an incredible experience.

\n\n\n\n

Um, I learned so much, and the a list part team was super great. So, um, yeah, like that was a, that was an intense couple of years. Like there\'s a lot, there\'s a lot that goes into those projects and our job was to kind of make it, it was like, you know, like the metaphor of the duck, right? Like you\'re, you\'re swimming seren except underneath, you\'re like furiously paddling

\n\n\n\n

And like that\'s, uh, [00:04:00] that\'s the special projects team.

\n\n\n\n

Cory Miller: Can you say this special code name for it? I wanna say stiff.

\n\n\n\n

Tiffany Bridge: Um, the, so, I mean, every team at Automatic has like an internal nickname, right? Like the, the, the name. Because the names of teams at Automatic have historically not been, um, they have, there, there isn\'t just like, oh, that\'s accounts payable.

\n\n\n\n

Like there\'s, that\'s not what any of the teams are called, right? They all have like clever names, , um, special projects team is, uh, the overarching team is called Team 51. There are a lot of, there are a lot of rumors about why that was chosen. Um, none of them are, all of them are more glamorous and interesting than the real reason it was chosen

\n\n\n\n

Um, but now team 51 is actually, like, when I was there it was like 13 people, but it\'s now like 40 some people and so there\'s lots of subteams and those subteams all have names and things like that as well. So, but the overarching team internally is called Team 51.

\n\n\n\n

Cory Miller: This is why I wanted to do these set of interviews cuz there\'s people behind, oftentimes behind the scenes with these vast experie.[00:05:00]

\n\n\n\n

Building the cool products that so many people use and why? I wanted to highlight your background. When we got to talk, I was like, oh, I\'ve gotta share this, because I think it\'s so compelling to see one, you\'ve been doing WordPress for a very long time. Two, you did it for with this like, very, uh, interesting team doing some cool projects that really put a great face on WordPress.

\n\n\n\n

Um, like a list apart. You know, so many people in our community know that like the back of their hands. Um, I wanna share that. Cause I think that that all formulates these compelling stories into today in your role at Nexus and what you\'re doing and formulates all this background. Like I remember at I themes, there\'s so many times we\'re building cool stuff, but people don\'t see inside the workshop, they don\'t see all this stuff.

\n\n\n\n

They don\'t know all the history and background, the care and passion that goes into this. And so that\'s one of the reasons I was doing this and why I wanted to like point it out, you know, , um, So, um, okay, so that brings us to [00:06:00] today, and now you\'re at Nexus doing store builder of many things. But I really wanna talk about store builder because I think it\'s really interesting.

\n\n\n\n

I know you\'ve been focusing on it, um, at Nexus and it, there\'s a big problem that I think it solves for my own work. , I shouldn\'t even say work, trying to use w this thing called WooCommerce, which is incredible. one I, I think I, I\'ve said at least, and you correct me, kept, but I\'m like WooCommerce is the default e-commerce software on the planet because it\'s used so broadly.

\n\n\n\n

I think it\'s growing faster still than WordPress and for good reason, but you can do anything and everything with it. And that presents a lot of complexity. Absolutely. Absolutely. What is the problem you\'re trying to solve with store builder?

\n\n\n\n

Tiffany Bridge: Sure. Oh, well. So as you say, like the more flexible and powerful something is, the more complicated it is.

\n\n\n\n

And you know, something that I learned, and this I think, especially I learned at, um, on special projects is that, [00:07:00] you know, setting up WordPress and WooCommerce, that\'s a different set of skills than just using them day-to-day. And the problem is that people who, like once you, once the, the site is set up right, people can learn to use it.

\n\n\n\n

It\'s not, it\'s not that hard to use, but getting to that point where you can just use it and run your business on it requires a ton of knowledge. And you know how WordPress. Is like, it likes to hide all of the settings, like in all of these different menus. And you have to, you have to kind of know what you\'re looking for in order to find it.

\n\n\n\n

Um, and that\'s a real, that\'s a real challenge for people. So the problem that we\'re trying to solve with store builder is this idea of like, okay, there\'s like five or six things you have to do in order to go from zero to a store. And we wanna like gather those all up in one place and just walk you through them in a very logical way.

\n\n\n\n

So, okay, first we\'re doing like what we call first time. Consider. You\'re setting like the name and address of the store and the name of the site. And then we wanna do look and feel. Um, so let\'s just get some pages into your site. Let\'s get some content into your site that you can edit and make your own.[00:08:00]

\n\n\n\n

Then we wanna, like, let\'s add a domain. We\'ve got this very cool, like we call it the Go Live wizard, where you just, um, where it like walks you through the process of, of connecting a domain right there from inside WP admin. And then we\'ve got, okay, great. Now it\'s time to add your products. Products we don\'t have a wizard for.

\n\n\n\n

We\'re just sort of surfacing a lot of help content to just help people make good choices as they\'re configuring their product, their products. And then it\'s like, great. Now let\'s connect your payment. Now let\'s set up your shipping. Hey, congratulations, you have a store. Is there more work to do on the site?

\n\n\n\n

Of course there is. There\'s always more work to do. But now we have gotten to a point where you have products and you can take payment and you can ship them, and your site has a domain name and therefore an SSL certificate. So here you are, now you\'re in business on the. And that\'s the problem that we\'re really trying to solve is just like, let\'s just get p get all of these, like things that you have to configure in front of people so they don\'t have to go hunting for.

\n\n\n\n

Cory Miller: And that\'s a huge problem I see that firsthand, um, is, you know, WordPress enabled me [00:09:00] to start a business, start a blog first, and then it evolved into a business. And that\'s the beauty of it. And I see that with, with commerce. Nearly any, uh, nuance thing you want to do, you can probably do it with WooCommerce.

\n\n\n\n

There\'s so many extensions, plug ons and addons and stuff. It from my experience, it seems like, you know, you get in and, and e-commerce just set aside from e-commerce is just complex because, okay, well you\'re selling in Europe and you need that and you need invoices or something like that. You\'re selling, you know, a digital good with a physical product and you want a free trial.

\n\n\n\n

I was just talking to somebody about that yesterday. The whole thing on e-commerce. And then you get to WooCommerce, great tool, awesome ecosystem and stuff. And I see this problem that you\'re trying to tackle over and over, uh, and I think it provides a huge need for those trying to build stores on the web.

\n\n\n\n

Um, tell me about who the product is really for. [00:10:00]

\n\n\n\n

Tiffany Bridge: So you know, this product is really for that sort of like merchant who is either setting up the site themselves or maybe they\'re working with somebody to set up, but they\'re not like hiring an agency to build them a site, right? Like they might have, they might have a buddy who\'s good with computers, or they might even have paid a freelancer, but it\'s really meant to be kind of, Right at that like level of the person who is actually gonna be running the business should be able to set up the store.

\n\n\n\n

That\'s always the goal that we\'re after, right? Is if you decide, if you\'re like knitting hats and selling them on Etsy and you decide you wanna get off of Etsy, like you should be able to do this. So it\'s, it\'s meant for people whose skill is whatever it is that their business is. Not building websites, and that\'s who we\'re really targeting with this.

\n\n\n\n

Now, that is a very complicated problem and there\'s a lot of layers to it. And so we are always in the process of trying to solve for that use case. I think, um, I don\'t know if you can ever be, you can never say. We\'ve solved it, right? Like there\'s always gonna be more to do. [00:11:00] Um, and that\'s what we\'re doing with Store Builder right now, but that\'s who, that\'s for.

\n\n\n\n

Like a lot of our other products, like we host, we have Manageable commerce hosting, manage WordPress hosting. What we like to say about those products is that we\'re the hosts that you graduate to, right? If you\'re coming to us, you\'ve probably already been somewhere else. Um, but with Store Builder, we\'re really focusing on people who probably don\'t already have a website, and that\'s, uh, that\'s who the product\'s for.

\n\n\n\n

Cory Miller: That\'s unique with Nexus, but I know Nexus is a brand company, has extensive experience with e-commerce too. And this offering is really interesting because one, you\'re tackling a big problem. Um, but two, you\'ve got a lot of experience on your team and the company that has really dealt with this, um, the e-commerce question for a long time.

\n\n\n\n

So.

\n\n\n\n

Tiffany Bridge: Yeah well, and it\'s such a privilege to be able to work with people who like really think about e-commerce, right? Like Nexus got its start doing Magento. And so like we have a lot of like all of our, you know, engineering and our operations, like, they understand like what an e-commerce site [00:12:00] needs. And so it\'s, it\'s been great to watch them kind of apply that knowledge to WordPress and w as well.

\n\n\n\n

Cory Miller: Excuse me. And I think this is. It\'s one thing to have a blog, you don\'t wanna have blog. Mm-hmm. , I didn\'t worry too much about downtime. Sure. When you have downtime or something happens and you can\'t get things done with your story, you\'re probably likely losing money. So Absolutely. I think that experience is, is key to highlight Mato Gun back to the, the days, you know, this big, big behemoth of an e-commerce platform that switched hands and

\n\n\n\n

.

\n\n\n\n

hear that background. Next is, So you, you said this, uh, just a second ago, but you talked about some of the things, like what you\'re trying to do, and you mentioned some, some key things in the last year or so, as you\'ve b led this project. Um, what are some of the things that, that stand out that you\'re, um, excited about, proud about that uh, you can share.

\n\n\n\n

Tiffany Bridge: You know, I think in terms of like actual product features, you know, I\'m so proud of that Go Live Wizard. Um, because like, [00:13:00] you know, what\'s this saying? Like it\'s always d n s, right? D n s is hard and that\'s. and that\'s such, and there\'s no way to talk about it in a way that isn\'t like technical, right? How do you connect a, a domain name to your site?

\n\n\n\n

Well, you\'ve gotta go change your name servers. Well, what\'s a name server? What\'s a cname? What\'s an a record? Um, people shouldn\'t have to know that, right? Like people shouldn\'t have to know that in order to get online, I think. Um, so it\'s been really fun to kind of build this cool tool that just walks people kind of through a decision tree.

\n\n\n\n

The first thing it asks you is, , do you have a domain name or do you need one? If you need one, it\'ll send you out to the Nexus checkout, or we\'re working on this feature where it\'ll send you out to the, the Nexus checkout. We\'re working on the feature where it brings you back, back into your store. Like right now, we can, we can send you out to our domain registration, but we, we have to rely on you to come back.

\n\n\n\n

We\'re working on a feature where we can move you out and then just bring you right back to where you left off. But you know, so that\'s the first question. And then like once you have it, it like it will actually validate whether your domain is ready to connect, right? It\'ll do all the queries to see like, [00:14:00] are your name servers set or do you have the C name set up?

\n\n\n\n

And it\'ll tell you. If not, it\'ll tell you what it is that you need to do. Um, And then, you know, you, as you proceed with it, it\'ll like set up the DNS zone in your portal and it will like do the, um, the find and replace on your database to make sure that like WordPress knows what domain it\'s supposed to be using and that all of your internal links are now referring to the correct domain.

\n\n\n\n

So like it does all of those like little things that, like on special projects, we have a whole checklist for, to make sure that a human does them well. Now we\'ve got like a. Um, so that, that does that, and that\'s, I actually tease my former coworkers sometimes and I\'m like, Hey, I\'m over here trying to replace special projects with a series of onboarding wizards.

\n\n\n\n

And they\'re like, yeah, good luck with that . I\'m like, Hey, look, I never said I like small problems. Right? . So, um, but so that, like, that feature is something that I\'m really, really proud of and, um, and excited about. And I\'m always telling people it\'s like the best single piece of store builder

\n\n\n\n

Cory Miller: is, is this different [00:15:00] from the wizard?

\n\n\n\n

You mentioned a bit ago.

\n\n\n\n

Tiffany Bridge: It\'s the same one. Okay. I mean, it\'s like the, like that\'s the, that\'s the one that I\'m most excited about. And, and I think it\'s the reason that I may, that we\'re able to do that one so beautifully is because you don\'t have to, like, there isn\'t like a third party that we\'re having to connect with.

\n\n\n\n

Um, you know, when you start getting into like payments and shipping, like suddenly you\'re dealing with other people\'s APIs and so there\'s a limit to what you can do. Um, but like where we\'re able to kind of control the experience, we\'re able to make it like really beautiful and functional.

\n\n\n\n

Cory Miller: I know I\'ve, I\'ve helped people.

\n\n\n\n

You know how it is, I\'m sure you get this too. It\'s like if they know you do WordPress or websites, you know, everybody has some kind of idea. And, um, there\'s platforms out there, but again, the power of WooCommerce and, and WordPress particularly to, to grow your business. But there\'s complexity that happens that, that I know you\'re wiring in as you think about and build, continue to build the.

\n\n\n\n

For that experience. Um, it\'s kind of [00:16:00] going back for a second. I know Nexus does. Okay. You graduate to us. Uh, store builder specifically, I think is for a different kind of, um, problem. And you might have said this, but I want to come back to it cause I, I think I might have missed sharing this part of it. So, store builder, if you, you know, want to start a store and here are, you know, 15 options.

\n\n\n\n

This is the option if you want to, um, start a store and grow it.

\n\n\n\n

Tiffany Bridge: Is that right? Yeah, I think so. I mean, I think there\'s a no better platform than WordPress and Woo for something that\'s gonna grow with your business and be flexible to your business. Like maybe you get farther down the road and you decide, you know what?

\n\n\n\n

I don\'t actually want to sell merchandise anymore. What I would rather do is do courses or events. I mean, all right, well just install another plugin. You can uninstall WooCommerce. , off you go. Um, and so, you know, having that option always available to people as well is really important. Like you can, [00:17:00] because as you know, it\'s so flexible and you can just swap in the pieces you need and take out the pieces you don\'t.

\n\n\n\n

Um, I think it\'s, it\'s really great to just get people, like, just, just get on the platform that\'s going to grow with you at the beginning instead of having. Migrate later, right? Like, nobody likes migrations, nobody likes, you know, having to convert their data and carry their, carry their orders from like their Shopify store and their commerce.

\n\n\n\n

Just start with WooCommerce. It\'s fine.

\n\n\n\n

Cory Miller: I know. Um, so we talked about in that experience, like really making that initial experience where you\'re like, I\'ve got something I want to sell. Um, you mentioned when we were talking before this too, like particularly you\'re on another platform, like an Etsy or some other platform.

\n\n\n\n

This is when, um, you\'re ready to go and there\'s this, there\'s this learning curve with WordPress WooCommerce that you\'re trying to sort out. Um, I think you said it when we were, um, prepping for this like idea to selling [00:18:00] is, is kind of that key, which I think is so awesome because I know from experience.

\n\n\n\n

People, you know, non-word, pressure related. Go, I\'m ready to do this. Lindsay and I, my wife have a, a partner, great founder who does physical products. And, and that was the question I was like, okay, well you have a couple of options. , they all have pros and cons, they have some things. Um, but having an experience like this, I think is so key because of that initial learning curve going live online.

\n\n\n\n

But there, I know there\'s other things too. Nexus happens to be in the family of LiquidWeb, which is Own, has a number of WordPress specific company outside of the Nexus brand of families that you all, um, leverage within the platform too.

\n\n\n\n

Tiffany Bridge: Yes, absolutely. Um, the biggest, uh, so you know, the liquid web family of brands is large and growing, right?

\n\n\n\n

And, and, and as our post status friends know, there are quite a lot of like WordPress plug-in businesses that are now part of the family of brands. And the one that we are leveraging most right now in store builder is [00:19:00] cadence. And cadence. For those who don\'t know, is this really great? I don\'t wanna call.

\n\n\n\n

I mean, it\'s a theme, but it\'s like so much more than a theme, right? Um, it, it is a theme. It is blocks, it is starter templates. It\'s this whole package and it\'s really geared around people who are like web designers, but just need a great, um, like way to build and customize a site that doesn\'t necessarily rely on like a third party page builder.

\n\n\n\n

Right? Something I appreciate about Cadence is the way it sort of embraces. Extends the WordPress Block editor rather than trying to replace it. Um, cadence is there, there\'s so much great stuff, right? Like right now, store Builder really leverages this Cadence starter template. So you pick one of the starter templates around, uh, around e-commerce, and we import a site for you, basically.

\n\n\n\n

Um, and then you just have to edit it and make it your own. Replace the images, replace the text. But, you know, the, the feedback that we\'re getting from our customers is that that\'s still a lot of work and it. Their feedback is that because it is, they are correct. [00:20:00] That is still a lot of work to do. And so something that we\'re kind of, the next problem we\'re trying to tackle in store builder is this idea of editing all the not store parts of your site, making sure that you have a homepage and an about page and you know, all of your policy pages and things like that.

\n\n\n\n

And making it as easy as possible for people. Because you know, cadence was kind of designed around people who are already web designers and that isn\'t who our audience is. So we\'ve been working very closely with the ca cadence team on, you know, what\'s a, how can we leverage cadence and the power and the, the, the experience that they have, but create like a really great experience for, um, people who aren\'t.

\n\n\n\n

Who aren\'t already savvy with web design, right? Who don\'t know, like, what is a gutter, what\'s a border radius like, you know, no one should have to know that. Um, so we\'re, that\'s the next problem that we\'re trying to solve and um, and it\'s been a real privilege to work with my colleagues over on that side of the house on that.

\n\n\n\n

Cory Miller: I, That\'s you just kind of like [00:21:00] highlighted one of, one of the benefits why we, our partner and, and the founder of that physical products company. Like why not just to use, let\'s say a Shopify site or something is like mm-hmm. , the stuff you said that the non-store stuff is so awesome and attractive.

\n\n\n\n

Mm-hmm. and helpful for store owners where you can blog and. NCO and different things like that. And I happen to have some inside knowledge as far as . Um, having been at Lake Web a couple years ago, sold, sold our themes to, uh, lake Web, that there\'s a suite of tools That\'s awesome. And to see, you know, post status by the way, also runs cadence and such a powerful framework, whatever we call it, you know, word critical.

\n\n\n\n

Tiffany Bridge: Yeah. It\'s a, it\'s a sweet a package. I don\'t know, it\'s like, it\'s a theme. It\'s a lot. It\'s a lot of stuff. Um, and it\'s, it\'s just great. And, um, I\'ve been really, it\'s been really nice to be able to, to work with, um, something that both kind of embraces kind of the WordPress way of doing things, but also really [00:22:00] enhances and expands it.

\n\n\n\n

Cory Miller: Okay. So help me complete this sentence. As for product lead for this, this particular. Um, there\'s probably all these things that your, your team knows in sudden and out cuz you built them and you built them based on these customer, this journey of these problems with obstacles people ran into. I wish people knew or did about what?

\n\n\n\n

As part of store builder. Is there things from like, you know, your team just goes, gosh, they\'re not taking advantage of the school teacher. They\'re not doing this one thing that would make their life easier, the business would grow better. What are, what are some of those things, part of the platform that\'s come to mind?

\n\n\n\n

Tiffany Bridge: Oh, that\'s a hard one. I mean, I think the thing that I find is that the thing that I always want customers to know is usually it\'s bec, usually they don\'t know it cuz I haven\'t adequately conveyed it to them. So it seems a little bit almost self-serving. Right. To be like, oh, I wish [00:23:00] they knew. Like, one thing that I always find myself wishing that people knew is that e-commerce is really complicated.

\n\n\n\n

Right. Um, cuz I think sometimes we get people who come to. To store builder and expect us to solve all of the complexity of the e-commerce when what we\'re really able to solve is like the complexity of the website part. Like I read our, um, One of the things I do as a product manager is I read all of our cancellation reasons.

\n\n\n\n

Um, so like anytime somebody has left the product and they wanna tell me why it\'s hard reading, sometimes , it\'s very bad for the ego, but it\'s very good for the product. And somebody once said, well, I, I can\'t believe how many things I have to log into to use this. Like, okay. Well if you\'re talking about like our Nexus portal, like I agree with you.

\n\n\n\n

I would love to reduce the need for people to have to log into a web hosting portal. Right? But if you\'re talking about payments shipping, like was there ever a future where you weren\'t gonna need a Stripe account? I know some people are [00:24:00] tackling that by like building their own payments, but then I feel like that\'s another form of lock-in that I don\'t love.

\n\n\n\n

Right. Um, so, you know, so a thing that I, I want people to know is that, um, the system ha the, this, we\'re trying to, we\'re trying to balance like that like. Opinionated versus like freedom thing, right? Like, can we be very opinionated? Like, look, you\'re just gonna use, this is the payment system you\'re gonna use.

\n\n\n\n

Just, just, you know, while also still giving people that freedom of w of, of WooCommerce, um, I think that\'s always like when I\'m reading stuff, that\'s always what I\'m wishing people knew. And so now it\'s just a question of like, well, how do I then, like how do I teach \'em that it\'s not their fault? They don\'t know that I know that they don\'t know that.

\n\n\n\n

I think about e-commerce all day. You don\'t, you, all you wanna do is just get online and like sell this thing you made,

\n\n\n\n

Cory Miller: sell your stuff. Absolutely. Well, and, and there\'s platforms out there like Shopify for instance, and it, it\'s super fast gets [00:25:00] something going, but the complexity exists of some of these things.

\n\n\n\n

Like, you gotta think through, are you selling to Europe? What do you, you know, that\'s just one that comes to mind for me. Exactly. Um, but I totally get it. Um, the space that you all are in, what the product you\'re trying to provide, um, that, that is kind of like a pro and con of the beauty of the. , you can with store builder, with WordPress, with WooCommerce, get a store up and going mm-hmm.

\n\n\n\n

Um, so you can do it. And that\'s a great freedom that we have and enjoy for sure. But that, uh, I know from having done had, obviously businesses that run e-commerce rely on e-commerce or website was our front door to our store, but it was down. We didn\'t make money. Um, and then trying to help navigate some of those complexities is, is a pretty tough job.

\n\n\n\n

Anything else that kind of comes out to. About what I wish people knew. Yeah.

\n\n\n\n

Tiffany Bridge: Oh gosh. So many things. All the [00:26:00] things. Um, , they need anything, I guess they wouldn\'t need store builder

\n\n\n\n

Cory Miller: anything about the product that we haven\'t. Mentioned that, that you want to share too? I

\n\n\n\n

Tiffany Bridge: mean, I think, like, I think we\'ve covered all the things that I\'m like most passionate about.

\n\n\n\n

Like I just, yeah. You know, well, we were, you remember that controversy several months ago about Etsy and like Etsy\'s increase in fees and people were sh closing down their Etsy stores. And, um, like I just, like, I want people to know that it doesn\'t have to be that. . Like, it doesn\'t have to be that way.

\n\n\n\n

Like you can own the plat, like you can own your platform. We\'re seeing this now with Twitter, right? The implosion of Twitter. People are like, what are we gonna do? Where are we gonna go? And I\'m like, you should have a blog is what you should do. Um, you know, I think I, it just, I want people to know that it doesn\'t have to be that way.

\n\n\n\n

We don\'t have. Like our presences on the web, which is an increasingly important way of way, way that we conduct business, the way we conduct our relationships, the way we meet new people. Like we don\'t have to, it doesn\'t have to be that way, right? You [00:27:00] can own your home on the web, whether that home is a store or just a blog.

\n\n\n\n

Or just a blog or, um, or anything else. Like it. Just like, it doesn\'t have to be this way. It can be. There are many of us who would love to help you with it. And like, I\'m not saying that just as a person who wants to sell store builders, I wanna sell store builders, but I want to sell, like the reason that I care about store builder is because what it allows people to do.

\n\n\n\n

Cory Miller: Mm-hmm. Absolutely. You backed into my question I was gonna ask you next was to, you\'ve been a workforce a long time and you know when we prop. Uh, examples, like, I don\'t want to just poo poo Shopify, but use Shopify software is a service. There\'s benefits to having a SaaS Absolutely. Solution for what you\'re doing, but there\'s also,

\n\n\n\n

Tiffany Bridge: there\'s a reason they\'re successful.

\n\n\n\n

Cory Miller: Absolutely. There\'s also downside, and you mentioned earlier it\'s like WooCommerce, WordPress, and even store builder and Nexus grows with you. Um, but I want you to share a little bit more about that. You know, Shopify, what I was telling our partner, I said, you know, [00:28:00] Shopify\'s the glamorous thing people look at.

\n\n\n\n

And I see, I see why. But I said, you\'re gonna trade some problems for a new set of problems. And one of those you\'ve mentioned a couple times is lock in. And the beauty of, I want you to share a little bit about the, what your thoughts are around WordPress, WooCommerce, and open.

\n\n\n\n

Tiffany Bridge: Yeah, I mean, I think, I mean, the number one, biggest one is that you can own it and you can go, you know, wherever you want, and you can decide the experience that you wanna have.

\n\n\n\n

Um, I think that\'s something that a lot of us are spending a lot of time thinking about right now as like various social media platforms or like the, the downsides of like, for example, kind of lock in, uh, in social media pro. Platforms is becoming apparent, right? So that\'s like one thing that I think is really important.

\n\n\n\n

Um, another thing that\'s important is that, you know, the thing about, like, there are lots of companies in WordPress and Yes, here we all are trying to sell you our solution, right? We\'re all trying to make money. We\'re all trying to, you know, everybody, we, we live in capitalism. We\'re all trying to make money here.

\n\n\n\n

[00:29:00] But at the same time, like there is no reason. That you have to have any of that, right? Like the only thing that, that you have to pay for to use WordPress is someplace to. Right. You can download it, you can use it, it\'s all free, and that you can decide what you need and then you know what\'s worth paying for versus what\'s worth not paying.

\n\n\n\n

Like you can, it\'s such a like a choose your own adventure kind of platform. And I feel like, you know, we\'ve had so much centralization and so much, um, You know, like it\'s just so much centralization, so, so much like merging and like this company buys this company that we kind of forget that like we don\'t have to be that way.

\n\n\n\n

And I think it\'s, it\'s really important. Uh, I think open source is really important to like individual autonomy in that way. Like we\'re starting to get a little of like philosophical here, but I think, you know, just knowing that. If nothing else, you can just go download WordPress and learn to use it. Like I started downloading WordPress and learning to use it because, um, [00:30:00] movable type was going to a pay a for pay model and it was more money than I could pay at that time to indulge my like personal blog habit.

\n\n\n\n

And everybody was talking about this new system, WordPress that was open source and free. And I was like, free is good cuz I am broke. And I downloaded it and I started teaching myself to use it and it completely changed my. And I know I\'m not the only one. Right. I have talked to other people who are like, great.

\n\n\n\n

WordPress was free for me to learn to use, so I learned to use it. Word camp was $20 for me to go, so I slept on somebody\'s couch and went to a Word camp. Something that I think is, is so important is, is that kind of low financial barrier to entry. I would love to see us have a lower like knowledge barrier to.

\n\n\n\n

and I think we\'re all working on that every day. Um, but um, that, that\'s just like, that barrier to entry I think is always really close to my heart because I really believe that, you know, these are things that can change people\'s lives if they just have what they need in order to take advantage of them.

\n\n\n\n

Um, and I think that the community really [00:31:00] does care about that. And that\'s something that\'s like, makes me very proud to be involved in WordPress.

\n\n\n\n

Cory Miller: Well, you, you just, there\'s a practical side to this too, and I love the philosophical because it has practical implications as well. It\'s like Absolutely. You get locked into a platform, like you\'re talking about, whether it\'s an Etsy or a Twitter or a Shopify.

\n\n\n\n

Mm-hmm. , you\'re at kind of the whims of. What they\'re doing. That\'s a little bit different in word control,

\n\n\n\n

Tiffany Bridge: like company gets bought by somebody who then does all kinds of questionable things with it, and then here you are, like, I\'ve been on Twitter for 15 years, right? Like I\'ve been on Twitter since, yeah, 2007.

\n\n\n\n

So I\'ve been on Twitter like 15 years and here I am. Like with my like 15 year old, like at Tiffany Twitter handle, because that\'s how long I\'ve been on it. I got my first name and now somebody\'s over here like running it into the ground, making all kinds of questionable decisions, messing up the experience I have.

\n\n\n\n

And then I\'m like, well, now what? Like half the people I know I met here, like now what do I do? And like here I am like. I got locked in. I said I wasn\'t gonna get [00:32:00] locked in, but here I am, locked in. Um, so yeah, I mean that has like very practical considerations. There\'s people that I\'m struggling to stay in touch with because I only knew them on Twitter and like, how do I find them now?

\n\n\n\n

Cory Miller: Well, and you know, just a real direct one-to-one is, um, Shopify and Etsy platform versus this. And you, you look at a lot of entrepreneurs, e-commerce merchants start something, it blows. It. It starts to really grow and that lock in down the stream really comes into play For sure. Like you start getting taxed on your success in a sense where you, like you said, to that own and locked in feature where you go now.

\n\n\n\n

Exactly. With WordPress, we built a tool to, I themes that stellar brand that you can move websites very easily with. Exactly. Including at Nexus Brands.

\n\n\n\n

Tiffany Bridge: Exactly. And you know, like you, you build something, you go viral, you\'re like, suddenly your Etsy store\'s [00:33:00] going crazy. Now you have like, you know, transaction fees at Etsy.

\n\n\n\n

So the bigger you are, like the more your fees grow at ets, you know, at Etsy. And um, so you have that problem, but also like maybe you never bought a domain name. So now everybody only knows where to find you on Etsy instead of getting a domain name. So now you\'ve gotta like figure out how to teach people to go somewhere else.

\n\n\n\n

Like if you wanna move, like it\'s, yeah, it\'s a real. . I see this a lot of times too with like content creators and like Instagram. They\'re like, oh my gosh. If, I mean, Instagram\'s how I reach my audience, how are people gonna find me? If inst, if Instagram goes down, y\'all, that is a problem. Like you need a website and, and it just like, it makes me nuts, like a thing that is, it just makes me like pound the table cuz I get so annoyed about it.

\n\n\n\n

Is so you don\'t have like, People, you can only have like one link on Instagram, right? It\'s in your bio link in bio. And so people will like pay money for a link in bio service and then like link to their website and a link in bio. And I\'m like, what if I told you that you could just put a page on your website with the list of all your [00:34:00] links and then put that link in your bio.

\n\n\n\n

Um, and then you wouldn\'t be locked into yet another service, right? You don\'t have to get locked into the, like, there\'s the lock into Instagram and then there\'s the lock into the, the thing that you did to like work around the limitations of Instagram. Just have websites. Y\'all just have websites.

\n\n\n\n

Cory Miller: It\'s well in this, this partner of our same thing, built a great, huge audience on Instagram.

\n\n\n\n

Mm-hmm. that you gotta have an gotta have a website, gotta have an email list that you\'re trying, you know, things have, things have evolved. There\'s other marketing opportunities. But I go for me, website, email list that you can contact them that you quote own. So if something shifts, but you know, Tiffany, I\'m interested too.

\n\n\n\n

You see all this, you know, looking, looking around Instagram for instance. Some of the people that have got huge audiences, and I click those links and I think, okay, well maybe they\'re what, you know, at some point, how do they monetize that? And I go and I wanna get your thoughts on this and this whole creator [00:35:00] economy and what, I think probably 10 years ago we thought it\'s like bloggers and , you know, we have a new name for it now, but the creator economy, where they used the platform to get some initial buzz, but then, Okay.

\n\n\n\n

What\'s the path to Monet monetization. I mean, we\'re all passionate about what we do, but at some point you also need to, you know, keep the lights on and pay the pay the bills kind of thing. Absolutely. But I\'m curious too, like seeing that you\'ve been at WordPress a long time, seen in the web, a long time, been a technologist, but like, you know, what\'s your thoughts on that creator economy?

\n\n\n\n

Just like you said, okay, hey, here\'s a good point. Build your audience here. Hey, maybe not just a link tree or whatever it\'s called, but like, here\'s your website and all that. But what kind of trends and, and themes are you seeing in, in the foreseeable future, uh, that you know, you have thoughts on and ideas for as the creator economy builds?

\n\n\n\n

Tiffany Bridge: I mean, I\'m seeing, I\'m seeing a lot of people kind of fall back to newsletters, which is very cool in like retro, right? Like this idea of [00:36:00] like email, like we\'ve all got email. We neglected our email boxes for a while, but now it\'s back email\'s back, baby. Um, I think that\'s really interesting. And, and you know, and we\'re still seeing like some consolidation there, right?

\n\n\n\n

Because then now it\'s like, oh, let\'s, let\'s have a CK and like, okay, but now you\'re like locked into ck, right? Yeah. Um, which, which is a little bit of a concern, but you can at least like export. Subscribers out from ck, like if nothing else, like you can take your list with you, which I think is really great.

\n\n\n\n

CK has put together like a really easy to use stack of things that you need to run a four page newsletter. And, um, and so they\'re, they\'re popular for a reason, even if I still think people should have websites mm-hmm. , um, you know, but, but we are seeing that and even within sub, I\'m starting to see people like branch out into.

\n\n\n\n

Having websites like ghosts, which is another open source project. I\'m seeing people do that instead. Um, I think it\'s, it\'s really interesting right now because we ha we\'re in this moment where like the, the platform, the [00:37:00] social media platforms are really starting to show the seams and, and it\'s starting to feel like maybe we\'re on the edge of something.

\n\n\n\n

And I was just talking about this with a friend of mine the other day, and cuz he was saying like, Man, like Google Reader died and it kind of killed R Ss, right? Like, and nobody\'s figured that problem out since then. I\'m like, well, no, because everybody just started aggregating through Twitter. Twitter\'s the new, your new Google reader, except now like Twitter is twittering.

\n\n\n\n

And, um, because then we all, you know, we, and, and that, and again, that\'s like that problem of consolidation. Like even Google Reader, which was aggregating sources, it was like the dominant r s s reader. And I don\'t know, I don\'t know how to solve that problem. decent, uh, of centralization. Right. But I think it\'s very interesting that we\'re seeing people kind of move to newsletters because then they at least know that they can contact you.

\n\n\n\n

Mm-hmm. , and, and you can, um, and you, and you can have more control of your audience that way. Well, and then I\'m watching people like try out, like mastered on and that\'s interesting. [00:38:00] I don\'t, I don\'t know how that\'s gonna go cuz I feel like Mastodon is still. It\'s too difficult from like an administrative perspective.

\n\n\n\n

Like it\'s too difficult to start an instance right. Still. Um, I was talking about this actually in post status Slack the other day. I feel like a big reason that I ever got as far as I did with WordPress is cuz they had that five minute install so early on. Yeah. Like even in 2004, it was easy enough to install that I could figure it out myself and that like, I tried to set up ma on like ju like just like on a Nexus test account and like, , we don\'t have a way to run that particular form of like, of SQL that it uses of S SQL L and so like, like I would immediately stop and like, well, I.

\n\n\n\n

Like this, this thing doesn\'t even, like, it has dependencies that aren\'t necessarily available everywhere. And um, and then you have to, like, there\'s all this stuff that you have to do to set it up. And I\'m like, and you all have to, and it all has to be done from the command line. Um, so I feel like, you [00:39:00] know, these kind of like federated platforms where you run under an instance are gonna have to put a lot of attention into installation and onboarding if they want to, if they really wanna take off.

\n\n\n\n

I think that\'s gonna be a big thing.

\n\n\n\n

Cory Miller: What I take from this too is really going back to if you\'re thinking about building a business, even if you\'re dancing for passion, all of a sudden you\'re back in. You go, oh my gosh, I\'m a business owner. The thought process here to me is make sure you understand. What you own and what you\'re renting or borrowing for a time.

\n\n\n\n

Yeah, and just like you said, like I think so much from the we, I think we so much, by the way, benefit from de decentralization, AK WordPress, . You can, yes, you can copy it, you can for it and do whatever you want with WordPress. And there\'s power in that. And that shift of power where another platform has the rules.

\n\n\n\n

and regulations and policies that they change like Instagram, changing from more focus on [00:40:00] video to compete what\'s, let\'s say a TikTok and you go mm-hmm. Well, and, and I\'m not looking at my analytics all the time, but I look at likes, right? And I go, well, my likes went down quite a bit. Well, because I don\'t do video, I don\'t want to do video.

\n\n\n\n

Right. And right. Then you go, there\'s a way to build, it seems like build some initial audience, but make sure you have these off-ramps into something, even like an email list, you said, much less complex to export your subscriber list and go to another platform than e-commerce, but be really choosy and picky about what you\'re doing because.

\n\n\n\n

When your business does continue to grow, you want to be able to grow with it in the right platform to do that.

\n\n\n\n

Tiffany Bridge: Absolutely. Absolutely. And also, you know, as like the thing about decentralization is that there are a lot of problems that we are accustomed to having platforms solved for us. That now we have to solve on our own a decentralized situation.

\n\n\n\n

And so those of us who\'ve been working in open source a long time and and who work in tech, kind of like we already understand that like moderation is a problem and you have to think [00:41:00] about it. But you\'ve got all these, like for example, new MA on instance, admins who\'ve never really thought about moderation is like a problem.

\n\n\n\n

They have to solve , , and, and, and you\'d better. Right? And so, and that\'s like a, I think that\'s gonna be a real adjustment for people to make as we kind of like, if we\'re, if we\'re really gonna see like the beginning of a decentralization here, like there\'s gonna be a lot of like lessons that have to get relearned.

\n\n\n\n

Cory Miller: Yes. And when you said that about the five minute install, raise my hand because I go, that\'s why I loved WordPress. I didn\'t have to, what\'s a command line? What\'s the, you know, how do I. Upload, install, extract, set up my databases. Like that kind of simple. I\'ve seen so many tools over the years that promise some decentralization.

\n\n\n\n

But it\'s great for the developers that know all those things. But for the everyday person, once that gets figured out, that five minute or click, click install, I, I think we\'re gonna see some shifts in power.

\n\n\n\n

Tiffany Bridge: Yeah, I think so too. I think, um, I think if they pay a lot of, at pay more attention to that, I think you\'ll start to see a lot more.

\n\n\n\n

Cory Miller: [00:42:00] Tiffany, thanks so much for being on, um, post draft today and sharing some of your background and obviously your vision values, and then, um, what you\'re doing over at Nexus with store Builder and the other products. Um, tell, tell people where they can find you.

\n\n\n\n

Tiffany Bridge: Well, um, my slightly less neglected these days.

\n\n\n\n

Personal blog is tiff.is so, https://tiff.is/, you can find me there as long as there\'s still a Twitter. You can find me on Twitter at Tiffany. And, uh, you can find me on Mastodon at, uh, Tiffany@theinternet.social social.

\n\n\n\n

Cory Miller: Awesome. Thanks so much, Tiffany.

\n\n\n\n

Tiffany Bridge: All right. Thank you.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 18:45:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:60:\"WordPress.org blog: The Month in WordPress – December 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2023/01/the-month-in-wordpress-december-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:12476:\"

Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg:

\n\n\n\n
\n

“We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.”

\nJosepha Haden Chomphosy
\n\n\n\n

December brought with it a time for reflection—a time to look back, celebrate, and start planning new projects. Read on to find out what 2023 holds for WordPress so far.

\n\n\n\n
\n\n\n\n

WordPress is turning 20!

\n\n\n\n

2023 marks the 20th anniversary of WordPress’ launch. The project has come a long way since the first release as it continues to advance its mission to democratize publishing. From its beginnings as a blogging platform to a world-leading open source CMS powering over 40% of websites.

\n\n\n\n

Join the WordPress community in celebrating this important milestone. As the anniversary date approaches, there will be events, commemorative swag, and more.

\n\n\n\n
\n

Stay tuned for updates.

\n
\n\n\n\n
\n\n\n\n

WordPress 6.2 is scheduled for March 28, 2023

\n\n\n\n

Work on WordPress 6.2, the first major release of 2023, is already underway. It is expected to launch on March 28, 2023, and will include up to Gutenberg 15.1 for a total of 10 Gutenberg releases.

\n\n\n\n

The proposed schedule includes four Beta releases to accommodate the first WordCamp Asia and avoid having major release milestones very close to this event.

\n\n\n\n
\n

Read more about the 6.2 schedule and release team.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.8 was released on December 21, 2022. This version features a reorganized Site Editor interface with a Browse Mode that facilitates navigation through templates and template parts. In addition, it includes the ability to add custom CSS via the Style panel and a Style Book that provides an overview of all block styles in a centralized location.
  • \n\n\n\n
  • Gutenberg 14.9 became available for download on January 4, 2023. It introduces a new “Push changes to Global Styles” button in the Site Editor, which allows users to apply individual block style changes to all blocks of that type across their site. Other features include typography support for the Page List block, and the ability to import sidebar widgets into a template part when transitioning from a classic theme.
  • \n
\n\n\n\n
\n

Learn how Gutenberg’s latest releases are advancing the Site Editor experience to be more intuitive and scalable.

\n
\n\n\n\n
\n\n\n\n

Team updates: WordPress big picture goals, new Incident Response Team, and more

\n\n\n\n\n\n\n\n
\n

Check out the 2022 State of the Word Q&A post, which answers submitted questions that Matt could not address at the live event.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

Have thoughts for improving the Five for the Future contributor experience? This post calls for ideas on how this initiative can better support the project and the people behind it.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n\n\n\n\n
\n

Would you like to be a speaker at WordCamp Europe 2023? Submit your application by the first week of February.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @laurlittle, @rmartinezduque.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:112:\"Do The Woo Community: Bringing WordPress Certification to the Community with Talisha Lewallen and Sophia DeRosia\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74322\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://dothewoo.io/wordpress-certification/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:427:\"

Talisha Lewallen & Sophia DeRosia from CertifyWP chat with us about the importance of WordPress certification.

\n

>> The post Bringing WordPress Certification to the Community with Talisha Lewallen and Sophia DeRosia appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 10:57:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: WooCommerce Blocks 9.4.0 Adds Support for Local Pickup\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141197\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"https://wptavern.com/woocommerce-blocks-9-4-0-adds-support-for-local-pickup\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1740:\"

WooCommerce Blocks version 9.4.0 was released with support for a new block-powered Local Pickup option under shipping settings. The feature plugin offers users early access to new blocks and improvements to existing blocks before they become available in WooCommerce core.

\n\n\n\n

Local Pickups introduces two new blocks: a shipping method toggle block that allows shoppers to select between regular shipping or pickup from a specified location, and a pickup location block that displays local pickup rates.

\n\n\n\nimage source: WooCommerce Blocks 9.4.0 release post\n\n\n\n

These blocks can both be enabled and configured via a new local pickup settings page. Store owners can even rename Local pickup to something else, and optionally add a price for this option.

\n\n\n\n\n\n\n\n

It’s important to note that the new Local pickup blocks can only be used with the Checkout block. WooCommerce Blocks also introduces a change with this new Local Pickup experience that will support location-based taxes based on the pickup address, improving tax reporting. Previously, WooCommerce based local pickup taxes on the store address.

\n\n\n\n

WooCommerce Blocks 9.4.0 includes a handful of other small enhancements and bug fixes. Check out the release post for a more detailed look at everything that’s new in the latest version of the plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 02:59:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:21:\"Matt: Polls on Tumblr\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75803\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"https://ma.tt/2023/01/polls-on-tumblr/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:301:\"

We just launched polls on Tumblr, and it’s been pretty fun. Cool to bring together the Crowdsignal (née Polldaddy) technology into a new world.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 01:38:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: WordPress Project Aims to Complete Customization Phase and Begin Exploring Collaboration in 2023\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141181\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:117:\"https://wptavern.com/wordpress-project-aims-to-complete-customization-phase-and-begin-exploring-collaboration-in-2023\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3124:\"

WordPress Executive Director Josepha Haden Chomphosy published a summary of the project’s “big picture” goals for 2023. The goals fall into three major categories: CMS, Community, and Ecosystem.

\n\n\n\n

WordPress development will focus on completing the remaining tasks for Phase 2 (Customization), and will move on to begin exploring Collaboration in Phase 3.

\n\n\n\n

“As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows,” Haden Chomphosy said in her recent Letter to WordPress.

\n\n\n\n

“Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.”

\n\n\n\n

The vision for the first two phases was “blocks everywhere” and Haden Chomposy said this will be updated for Phase 3 to be centered on the idea of “works with the way you work.”

\n\n\n\n

In addition to the Phase 3 APIs, Haden Chomphosy identified the following items as part of the CMS goals for 2023:

\n\n\n\n
    \n
  • Openverse search in Core
  • \n\n\n\n
  • Navigation block
  • \n\n\n\n
  • Media management
  • \n\n\n\n
  • Simplify the release process
  • \n\n\n\n
  • PHP 8.2 compatibility (Core and Gutenberg)
  • \n\n\n\n
  • Block theme development tools
  • \n
\n\n\n\n

Under the Community category, WordPress will be focusing on planning the Community Summit, which will be held at WordCamp US in 2023, contributor onboarding, improving Polyglot tools, establishing mentor programs, revamping WordPress.org designs, and keeping pace with learning content. The project is also aiming to develop a canonical plugin program, which should be helpful as some Performance team contributors recently expressed that they don’t fully understand what the process is for canonical plugins.

\n\n\n\n

The Ecosystem category will focus on the WordPress Playground, an experimental project that uses WebAssembly (WASM) to run WordPress in the browser without a PHP server with many useful applications for contributors.

\n\n\n\n

WordPress contributors also prevailed upon Matt Mullenweg to consider having the project devote some time to working through old tickets and fixing bugs. Mullenweg said he is amenable to tackling one long-standing ticket (the kind that are stuck because of missing decisions or multiple possible solutions) each month in 2023.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 22:57:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"Post Status: Big Picture Goals 2023 • WP 6.2 Planning • LearnWP Needs Analysis • Wrong Plugins\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146539\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"https://poststatus.com/big-picture-goals-2023-wp-6-2-planning-learnwp-needs-analysis-wrong-plugins/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:16840:\"

This Week at WordPress.org (January 16, 2023)

\n\n\n

Where is WordPress going in 2023? Read Josepha\'s Big Picture Goals for the year. WordPress certifications are in the planning phases, and the foundation will include LearnWP. The Training Team is conducting a Needs Analysis. Help gather the community\'s input. Plugins Team is seeking intentionally wrong plugins, and Core has the 6.2 Planning Roundup.

\n\n\n
\n\n\n\n\n\n\n\n

News

\n\n\n\n\n\n\n\n

\n\n\n\n
\n
\n

Central

\n\n\n\n\n\n\n\n

CLI

\n\n\n\n\n\n\n\n

Community

\n\n\n\n\n\n\n\n

Core

\n\n\n\n\n\n\n\n

WordPress 6.2

\n\n\n\n\n\n\n\n

Meetings

\n\n\n\n\n\n\n\n

Developer Blog

\n\n\n\n\n\n\n\n

Design

\n\n\n\n\n\n\n\n

Docs

\n\n\n\n\n\n\n\n

Hosting

\n\n\n\n\n\n\n\n

Marketing

\n\n\n\n\n\n\n\n

Meta

\n\n\n\n\n\n\n\n

Openverse

\n\n\n\n\n
\n\n\n\n
\n

Performance

\n\n\n\n\n\n\n\n

Polyglots

\n\n\n\n\n\n\n\n

Plugins

\n\n\n\n\n\n\n\n

Project

\n\n\n\n\n\n\n\n

Test

\n\n\n\n\n\n\n\n

Themes

\n\n\n\n\n\n\n\n

Training

\n\n\n\n\n\n\n\n

Online Workshops

\n\n\n\n\n\n\n\n

Tutorials

\n\n\n\n\n\n\n\n

WPTV

\n\n\n\n\n
\n
\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n

Thanks for reading our WP dot .org roundup! Each week we are highlighting the news and discussions coming from the good folks making WordPress possible. If you or your company create products or services that use WordPress, you need to be engaged with them and their work. Be sure to share this resource with your product and project managers.

Are you interested in giving back and contributing your time and skills to WordPress.org? \"🙏\" Start Here ›

Get our weekly WordPress community news digest — Post Status\' Week in Review — covering the WP/Woo news plus significant writing and podcasts. It\'s also available in our newsletter. \"💌\"

\n\n\n\n
\n\n\n\n
\"Post
\n

You — and your whole team can Join Post Status too!

\n\n\n\n

Build your network. Learn with others. Find your next job — or your next hire. Read the Post Status newsletter. \"✉\" Listen to podcasts. \"🎙\" Follow @Post_Status \"🐦\" and LinkedIn. \"💼\"

\n
\n\n\n\n
\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 20:57:41 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:80:\"WPTavern: #59 – Corey Maass on How To Use WordPress To Kickstart Your SaaS App\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://wptavern.com/?post_type=podcast&p=141113\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:94:\"https://wptavern.com/podcast/59-corey-maass-on-how-to-use-wordpress-to-kickstart-your-saas-app\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:46303:\"Transcript
\n

[00:00:00] Nathan Wrigley: Welcome to the Jukebox podcast from WP Tavern. My name is Nathan Wrigley.

\n\n\n\n

Jukebox is a podcast which is dedicated to all things WordPress. The people, the events, the plugins, the blocks, the themes, and in this case, how WordPress can be used to get your SaaS app off the ground.

\n\n\n\n

If you’d like to subscribe to the podcast, you can do that by searching for WP Tavern in your podcast player of choice, or by going to WPTavern.com forward slash feed forward slash podcast. And you can copy that URL to most podcast players.

\n\n\n\n

If you have a topic that you’d like us to feature on the podcast, I’m keen to hear from you and hopefully get you or your idea featured on the show. Head to WPTavern.com forward slash contact forward slash jukebox. And use the form there.

\n\n\n\n

So on the podcast today, we have Corey Maass.

\n\n\n\n

Corey is a full stack developer who works with agencies and businesses, large and small. He specializes in advanced WordPress functionality and building products for, and using, WordPress.

\n\n\n\n

Over the last decade or so SaaS, or software as a service, apps have become more and more popular. Not only are we using our computers more, but with the rise of smartphones, we’re connected to our services all the time. There does not appear to be any corner of life where online platforms don’t have some presence. From email to taxis, fitness to food planning and delivery. You can find it all in a SaaS app somewhere.

\n\n\n\n

Now that many people are comfortable using SaaS apps, there’s been a deluge of new players coming into the market, but it won’t surprise you to learn that most of them fail to make an impact and shut up shop.

\n\n\n\n

Corey is on the podcast today to talk about why he thinks that building an MVP, or minimum viable product, app on top of WordPress is a good way to start your product journey.

\n\n\n\n

We talk about how WordPress comes bundled with many of the features that apps require. User login, roles, permissions, and the REST API. This means that you don’t have to reinvent the wheel for the things that WordPress already does.

\n\n\n\n

On top of that, the plugin ecosystem which surrounds WordPress, might enable you to short circuit the need to build all the features that your service needs. It may be that there’s an existing plugin, which does most of what you require, and is ready to go right away.

\n\n\n\n

Corey talks about how using WordPress in this way might enable you to see if there’s really a market for your app. And if there’s not, you’ve used less resources finding that out. And if there is, then you might have some revenue to develop the app in other ways.

\n\n\n\n

If you’ve toyed with the idea of creating a SaaS app in the past, but never quite got there, this episode is for you.

\n\n\n\n

If you’re interested in finding out more, you can find all of the links in the show notes by heading to WPTavern.com forward slash podcast. Where you’ll find all the other episodes as well.

\n\n\n\n

And so without further delay, I bring you Corey Maass.

\n\n\n\n

I am joined on the podcast today by Corey Maass. Hello, Corey.

\n\n\n\n

[00:03:58] Corey Maass: Hey there.

\n\n\n\n

[00:03:58] Nathan Wrigley: Very nice to have you on. Corey, we’re going to talk today all about the capabilities of WordPress as a SaaS platform. But as we typically do on this podcast, it would be very nice if we could orientate the listeners, allow them to figure out what your credentials are, what your WordPress chops are, if you like. So would you spend a few moments just giving us a brief potted history of your relationship with tech and WordPress more specifically?

\n\n\n\n

[00:04:24] Corey Maass: Absolutely. Back in the late nineties in college, a roommate of mine introduced me to this internet thing and the first websites I saw were some of my favorite bands. And I was a aspiring musician at the time, and I said, well, I want to appear as famous as they are. How do I make one of these website things, and the rest is history.

\n\n\n\n

I taught myself basic web design, web development. That led to learning some programming, JavaScript and then ASP classic way back in the day. But around that time there was the new trend of SaaS apps. 37 Signals was popular talking about this. Forums like Joel Spolsky’s, Joel on Software. And I caught the bug because I’ve always had an entrepreneurial streak.

\n\n\n\n

So I said, oh, this internet thing, building software, but not selling a download, but selling access to a website. So, I started going down that path, building websites for clients, but also building SaaS apps to try to sell on the side. And then WordPress took off and for a number of years, WordPress was pretty much my day job. Doing development or website setup or what have you, and then building Sass apps. Not using WordPress for a number of years.

\n\n\n\n

And then suddenly the light bulb went off. One, the WordPress market was getting bigger and bigger, and I realized that there actually was money in it. So that led me to start building plugins, which I think is what had you and I talking last time. But also at some point it occurred to me that WordPress had matured enough and solved enough of the problems that I was encountering over and over building SaaS apps that I said, let me look at WordPress as a SaaS platform, and I’ve been doing it ever since. So now it’s been probably five years or something, and WordPress only continues to mature, and this conversation continues to evolve.

\n\n\n\n

[00:06:27] Nathan Wrigley: So you, in the last few years, you’ve joined together the idea of a SaaS platform, but with WordPress handling some of the basic things in the background, if you like. I say basic, I just mean some of the things that we are more familiar with in WordPress. So user management, obviously if you throw some other things like WooCommerce at it, you may be able to handle billing or subscription or whatever it might be, and getting people to the right page depending on whether they’re logged in or not. Is it basically the promise of that? You can cut out a whole body of work, which you would need to build, well potentially from scratch, each time you create your own new SaaS app?

\n\n\n\n

[00:07:04] Corey Maass: Yeah, I think that’s the way to think about it. So, when you’re solving problems for people online, these days it’s definitely more broad than it was five years ago and 10 or 15 years ago, of course. So if you’re building something that’s B2B, technically speaking. So if you’re trying to build an API or some sort of true service that other systems are going to talk to. WordPress is probably not the answer you want.

\n\n\n\n

The REST API is, has come a long way, but it’s not really what it’s meant for, right? But if you think of most B2C apps, business to consumer, most of these apps are websites that you’re signing into. Well, WordPress accommodates that. You’re clicking through from page to page. WordPress accommodates that. You’re taking billing, you’re handling subscriptions. WordPress with WooCommerce or Easy Digital Downloads, or Restricted Content Pro or any number.

\n\n\n\n

I’ve been paying more attention to the membership plugins lately, which are in some ways are specifically designed to handle exactly this problem. Users signing in and doing something, interacting. Interacting with the website. Interacting with each other, that kind of thing. One of the things that, an example that I pick up on a lot is, years ago when I was building apps regularly for clients, for friends, for myself. Over and over and over again, I had to implement some sort of user password reset. And it’s so mundane. Once you’ve solved it once, it’s boring to solve as a developer. But it’s crucial to every app.

\n\n\n\n

And I got to the point where I was like, I just don’t want to ever think about this stupid problem again. But I had to integrate the code, again every time over and over again. It’s like with WordPress, I never have to think about that. And there’s a plugin called Theme My Login, that’s one of my favorites that you drop in and users can register for your website and immediately get access to a slash dashboard, which you can change. But arguably that’s the first huge leap, you set up a basic website.

\n\n\n\n

You want users to be able to register and have exclusive access to a page that they don’t have if they haven’t signed in or haven’t paid or what have you. So, these kinds of plugins just solve all of these basic problems. The bottom of the pyramid, so to speak. So that you can get onto whatever problem, your unique problem, that your SaaS is going to solve. As opposed to spending days, weeks, months, tackling the not unique problems like user registration.

\n\n\n\n

[00:09:36] Nathan Wrigley: So what you are suggesting here, let’s just lay this out. The audience that you are suggesting this to, is people who want to get something shipped quickly. And really, if you are at the beginning of your SaaS app journey, you’re not quite sure yet whether the market even exists. You’re just trying to float a solution to something that you believe might be viable in the marketplace, but you’re not sure.

\n\n\n\n

So we’re creating a shortcut. We’re offsetting the billing, the user management and so on to WordPress, just as a, as a quick way of getting an MVP or a minimum viable product out there. Is that the idea? Just to sort of test the water? WordPress is a good bet for that, and then presumably at some point you would advise that if it turns out to be an out and out success, then maybe, at that point you might need to look at different tooling.

\n\n\n\n

[00:10:28] Corey Maass: Not necessarily. There was a time when I would’ve said that definitively, but WordPress has come a long way. Hosting has come a long way. Optimization has come a long way. So it’s definitely the scenario that I’m using WordPress the most. I’ve got a new idea, or I’m working with somebody and they’ve got a new idea and this is how I want to get it off the ground.

\n\n\n\n

But there are a number of companies, big companies, in the WordPress space that continue to work, use WordPress as the core of their SaaS app, and they’ve got plenty of customers. I think it really, when you get to that level of, if you see a, a good amount of success, then there’s going to be technical problems to overcome.

\n\n\n\n

And so it’s either ramping up hosting, server power or optimizing queries or rewriting certain aspects of your app. We can talk about that. I had to do that for one of mine, about a year ago. Or again, depending on the amount of user inactivity or user, user interactivity, how much and how often your users are using your app, you may find that it handles it just fine.

\n\n\n\n

[00:11:43] Nathan Wrigley: So right at the beginning you started talking about why you use WordPress. You mentioned a few plugins, which might assist you on this journey. So I think some of the ones that you mentioned were things like Easy Digital Downloads, WooCommerce, and so on. Whilst I don’t want to necessarily promote certain plugins, I’m just wondering if, given the experience that you’ve had, if you could give us some tips as to plugins that you have found to be helpful for particular problems that you’ve faced while you’ve been trying to build it. And then in a few moments we’ll get onto the subject of how you’ve had to amend WordPress to do things, let’s say more efficient.

\n\n\n\n

[00:12:20] Corey Maass: Sure. So these days, I actually use Beaver Builder for building pages out. Beaver Builder’s a page builder. Elementor is another good one. But I find that doubling down and knowing these tools well, helps greatly with being able to solve a variety of problems because they’re not a theme, so they’re not locked into a certain layout or that kind of thing.

\n\n\n\n

But most SaaS apps have a pattern called CRUD, create, retrieve, update, and delete. So if it’s Twitter, then you are creating tweets. You are retrieving tweets, meaning you’re viewing all of them. You can’t really update tweets, but you can update your profile, that kind of thing. And again, you can’t really delete tweets, but you could delete your account, and that kind of thing. Facebook, you can create posts, you can delete posts, your viewing posts, so your retrieving posts, that kind of thing.

\n\n\n\n

So, a lot, a lot, a lot of software comes down to that pattern, and so using something like, Advanced Custom Fields and there’s a great plugin called ACF Front End, I think it’s called, that essentially puts an ACF form on the front end. So that’s how users can create and update. You could also use Gravity Forms. Or there are a couple of other plugins, form plugins, that you can then put on the front end, for again, collecting data from users or letting users post data. Essentially insert data into the database. And then using something like Beaver Builder or Elementor that have post modules.

\n\n\n\n

So it’s like if I was recreating Twitter, I would create a form, and this obviously once I’m logged in, but I would create a form that said, what do you want to tweet? And that would insert it into the database as a post record. And then I would use Beaver Builder, me personally, but you could use Elementor or again, any number of page builders, with a posts module that says, okay, show all posts, meaning tweets, with the author of Corey. So then you’ve just created a way to create tweets and then for somebody else to go look at all of Corey’s tweets, that kind of thing.

\n\n\n\n

So thinking, breaking it down to these kinds of patterns and then looking at these different plugins on how to solve them. A lot of the time I’m able to find ways to quickly implement. And it, again, it doesn’t have to be quick, and this doesn’t have to be forever, but a lot of the time it can be where WordPress and these plugins can solve these problems so that my SaaS offers the, again, the unique problem or solves the unique problem that I’m, the whole reason I’m building it in the first place.

\n\n\n\n

To get back to your question about those other plugins in particular. If you only want users to sign in, I love the plugin called Theme My Login. Again, look at membership plugins. And then, if you want to charge, again, break down the problem. What are you actually, what do you want? Usually you want subscriptions, like that’s a SaaS pattern that most people are used to now. And what are users paying for? Usually they’re paying for access to a page or pages or content or some feature to interact with other users or something like that. And there are plenty of plugins that restrict content. Which is the way to think about that.

\n\n\n\n

And so there’s literally Restricted Content Pro as a plugin. Easy Digital Downloads, which is e-commerce, but they have an add-on for restricting content. WooCommerce is really more e-commerce, but can handle this kind of stuff. And then again, membership plugins that are, as people are setting up communities, as at least some people are trying to get away from social media and get back to more private communities without relying on Facebook groups or Twitter or what have you.

\n\n\n\n

Membership plug-ins have been mature for a while, but are, I’m seeing them become even more and more popular. And are designed exactly for this. So a user pays for access to features, pages, what have you. And that’s again, kind of the core of most SaaS apps.

\n\n\n\n

[00:16:24] Nathan Wrigley: I suppose that if you are thinking of building a SaaS app, you must have some kind of kernel of an idea of whatever it is that you are trying to solve. So, you’ve got this fabulous idea, and the most important thing at that point is to judge whether or not this idea A, can be built, and let’s assume that after sitting down and thinking it through and mapping it out, you’ve decided, yep, yeah, this has got legs. This can be built with the technology that’s currently available on the web.

\n\n\n\n

And then thinking, okay, is there an audience for this? Are there going to be enough people out there who are willing to open their wallet to make it worthwhile? And if you go down the SaaS route, you may very well be an incredibly adept developer, in which case this may be in your purview.

\n\n\n\n

But if you are not and you are just trying to figure out whether the market is there and you want to do that affordably, then WordPress seems like a fairly decent bet, just because of what you said. The fact that with 60,000 plus plugins in the WordPress repository and countless more that you can purchase, in many cases for a very small amount of money.

\n\n\n\n

It may be that you can get 90%, 80%, 70% of the features that you are trying to build, but without having to do much in the way of custom coding. It may be that you can’t get a hundred percent of the way there, and that would require some tweaking, which we’ll get into. But is that essentially it? You know, you might have to cut some corners or, on your roadmap, cut out some of the things that you really thought would be nice to have in and just go for the things which can be enabled quickly and affordably.

\n\n\n\n

[00:17:58] Corey Maass: Yeah, I think it just depends on what you’re trying to accomplish. I have a buddy who is non-technical, knows enough CSS to be dangerous, which he’s learned over times, specifically for this scenario. He wanted to create a mentor program, and so he needed scheduling for matching mentorees to mentors.

\n\n\n\n

So we found a plugin that did that, or did that well enough. And then put I think a membership plug in. I don’t remember how he handled subscriptions. But basically put WordPresses stylized user management in front of it. Limited access to features based on a user being logged in or a user paying. And then a little bit of CSS to make it look a little more integrated or little more branded or what have you.

\n\n\n\n

And that was kind of all he needed. It solved the problem. He was able to charge for it. He got some customers. And then at some point he did end up hiring a developer to add a few bells and whistles or whatever features he found that were missing. But yeah, it got him 70, 80% of the way. Arguably it got him a hundred percent of the way of solving the problem enough that at least users could start using it.

\n\n\n\n

[00:19:10] Nathan Wrigley: Yeah, I suppose that’s it, isn’t it? If he’s got a core body of users, and he’s determined that, in this case he can use a calendar plugin or whatever it may be, and it will get him the user base that he needs. Then he can start to use the revenue that’s generated from the, let’s call it the SaaS app, to invest in having something done bespoke.

\n\n\n\n

That’s really interesting. That’s kind of nice to know. I guess one concern, which I may have, and I’m sure you’ve come across this before. Is just the notion that if you did build this and you fully had the intention of it staying on WordPress for all time. Then you are of course very much dependent upon the plugins that you are using. The spaghetti of plugins being updated regularly.

\n\n\n\n

In many cases that would very much be the case. It’s updated frequently. It’s made secure, and any vulnerabilities and things like that are taken care of. But there is always that chance that the developer of a key part of your SaaS app may just decide to call it quits, and then you might be left hanging a little bit.

\n\n\n\n

[00:20:14] Corey Maass: And the scenario I’ve seen more often is a mature product. Meaning your own SaaS app evolves away from what the plugin that you purchased does. So I saw this with a very big company in the WordPress space, who long ago had built their platform on top of EDD, Easy Digital Downloads. But over time had hacked and slashed at it, so that they couldn’t update it anymore.

\n\n\n\n

And that’s just a decision they had to make at some point of whether they were going to keep going with EDD and just lean into the features that EDD had and forego the other features. Or most good, big WordPress plugins are well documented and have hooks so you can add function extra functionality, or figure out how to sort of hack around them, to a point.

\n\n\n\n

And then, yeah. They had to make the decision to just stop updating it, and there was discussion. Last I heard that they were going to maybe move to something custom altogether. But the idea being, one of my favorite phrases, we made the best decision we could with the information we had at the time, right?

\n\n\n\n

So starting out early. It solves all your problems. Go for it. And then down the road you can migrate away from it. You can code around it. You could build something custom, what have you. But yes, that is certainly a risk. I mean, it’s also a problem that a lot of apps have broadly speaking. So it’s, you know, if you’ve built an app that uses the Twitter or Facebook API, you’re putting yourself in their, their hands.

\n\n\n\n

Or if you are operating system dependent or even, something I’m seeing right now is, microchip dependent, right? If you build software for MacOS and it only works on Intel and, and they move to M1 or M2. So these are just risks that I think you assess over time.

\n\n\n\n

But what I like is, the point you keep emphasizing, that this is a, a way to solve the technical problem. What I think that a lot of SaaS founders, small and large, real and imaginary, don’t take into account and, I struggle with, and most of us struggle with, is that these days the technical lift of building an app often pales in comparison to the marketing.

\n\n\n\n

We hear about these wonderful, amazing stories, like Instagram selling for whatever it was, 8 billion after two months, and yada, yada, yada. Most SaaS apps fail. And so you, you want to build quickly with a low lift and then spend most of your time, like you said, trying to get it in front of customers, validating the idea, getting feedback from customers about what features they actually want, or now that you’ve built the features they want, does it actually solve the problem for them?

\n\n\n\n

All of that is arguably way more important than the actual platform you use. But that’s what brings me back to WordPress as a platform, is in fact often a great way to get something out the door. Even if it’s just a form to collect data and then a page builder or a theme of some kind to then show the data back to the user, if that’s what solves the problem.

\n\n\n\n

[00:23:36] Nathan Wrigley: It’s interesting because if there’s a body of people listening to this who are not building SaaS apps on WordPress, and they’re just building client websites, you’ve probably encountered that scenario where the client comes and they have incredibly grandiose expectations of what they want the website to do.

\n\n\n\n

And because you’ve been building websites for so long, you just know, you have an instinct which says, well, we could build all of that. But how about we just start here? Because I would imagine it’s quite unlikely that your staff are actually going to start using some kind of intranet solution that we build as WordPress. Or some messaging system that we build in the app. It’s much more likely that they’ll continue to use things like Facebook Messenger or WhatsApp or Slack or whatever it may be.

\n\n\n\n

And so over the years you’ve become accustomed to figuring out what is plausible, what is likely to work, and I think I feel it’s the same with SaaS apps. It’s very easy to come to the table. You’ve got your blank canvas and you throw everything at it, every idea, every permutation, every possible thing that the app could do, and then decide that’s what must be built.

\n\n\n\n

That’s it. Until that is all done, we’re not going to launch it. And I think history shows that you have to be much more agile than that. You have to be able to drill it down and say, okay, what’s the 10, 20, 30% of all of that, that we’ve decided upon, which is going to get us off the ground? And so that feels like where this goes. If you try to build everything, it’s probable that you’ll A run out of money, B run out of time, and nothing will be shipped.

\n\n\n\n

Whereas in your scenario, offset the uninteresting jobs that probably don’t need to be tackled because they’ve already been tackled by plugins or WordPress Core. And just concentrate on the things which are going to benefit your users. And frankly, you don’t know what is going to benefit your users.

\n\n\n\n

It’s always amazing to me when I open up a new SaaS app that I’ve never use before. And you think, oh, this will be perfect what I need. And you end up on support saying, does it do this? No, I wish it did that. And those companies that succeed tend to be, well in my experience, the ones who listen to their early adopters and quickly pivot their solution to satisfy them.

\n\n\n\n

[00:25:45] Corey Maass: Exactly. There’s obviously no harm in thinking through what your dream app does, all the features. You make a long, long list. But one of the things that drew me to WordPress plugins, and selling WordPress plugins early on, was a rather cynical observation that I made.

\n\n\n\n

I was building blogs for customers. I was building e-commerce websites for customers. And instead of writing another article, which is hard and work. Or instead of inserting more products, which is hard and feels like work. A lot of my clients would get in the WordPress plugin repo where all the plugins are free and go, oh, I could use a to-do list plugin and they’d install it.

\n\n\n\n

Or, it’s winter. I should install a plugin that adds snowflakes falling over my theme. And they would waste an unbelievable amount of time on what felt productive and felt free. And I was like, well, if people are people, we are all human, we are all valuable and we are all, don’t want to do the things that are hard.

\n\n\n\n

But I see all these people that are spending time just digging through the plugin repo, I’m going to start building and selling plug-ins, because the discoverability is amazing. And so I think you’ve touched on that for SaaS as well, which is, we generally shy away from the things that are hard.

\n\n\n\n

We also tend to skew towards our own genius. What we think is the best idea. Because we thought of it isn’t necessarily the features, or it isn’t ecessarily solving the problem that your actual paying customers have. The real strength, and the real challenge, comes more in that side of things. Marketing, sales, talking to customers, getting over your own ego, optimizing your own time, all that kind of stuff.

\n\n\n\n

[00:27:48] Nathan Wrigley: Yeah. It’s interesting the marketing piece you mentioned. Never ceases to amaze me how much of the overall budget needs not to be sunk into the development of the actual software, but in alerting people to its existence. A significant amount. And it’s not to be underestimated.

\n\n\n\n

And obviously if at the beginning you sink a hundred percent of your finances into the code, that’s great, but I guess you better be a really good word of mouth, somebody that can spread by word of mouth incredibly successfully. Because experience at least tells me that it’s very hard to gather an audience from a standing start.

\n\n\n\n

So we’re a WordPress podcast. We’re obviously very keen on WordPress, we think it’s amazing. But I’m guessing that there must be downsides to this. Let’s just talk about that for a moment. Any drawbacks to this system that you’ve encountered over time? Just some quick examples may be that, well, does it scale very well? Does WordPress tend to be doing a lot of things in the background that a leaner, more specifically custom-built solution may get you out the hole of? Just questions around that. Any drawbacks that you would alert people to if they do decide to go down this approach?

\n\n\n\n

[00:28:59] Corey Maass: A few years ago, I was tasked with building a food subscription website. So think Blue Apron or Freshly kind of website, if you’re familiar with those. And for better or worse was told that I had to use WooCommerce. And so I spun up a WordPress website, installed WooCommerce, got subscriptions going, customized the choose the meals that you want, and then check out. And that all was okay.

\n\n\n\n

But it turned out that, I think some of this has been changed, because this was a number of years ago but, WooCommerce was storing all of the data in a very WordPressy way, which was fine because it was a known pattern. But was not very optimal. And then for the business, because all of those meals were cooked every morning and then shipped out, all of the charges had to go through at the same time, at like two in the morning. And it turned out that WooCommerce subscriptions was built so that if you signed up for a subscription at 10:30 in the morning, it would renew at 10:30 in the morning. While we needed it to renew at two in the morning so that all of the orders went through, so then the chef knew how many dishes to make, and how many chicken dishes to make or whatever.

\n\n\n\n

And that’s the kind of risk that you run into, right? So if you are using a third party piece of software, WordPress, and then with plugins. And you are essentially building it to your, or bending it to your will, so that it’s doing things that it’s not necessarily meant to do. You’re going to run into issues.

\n\n\n\n

We found that our server didn’t have enough power to process all of these orders at the same time, because it’s essentially multiple threads need to be run at the same time. We wound up in that instance sticking with WooCommerce and WordPress for at least a little while longer.

\n\n\n\n

But switching off of a hosting company that really was most popular for blogs and delivering content and not necessarily running process, CPU power. And moving to a custom AWS set up. And we watched the CPU go from 80% all the time, to 3% all the time. So in that instance, we just needed to throw more metal at it.

\n\n\n\n

But again, we were definitely using a tool, at least slightly, in ways that it wasn’t meant to do. I also, during the pandemic, or at the beginning of the pandemic, my wife made the mistake of turning to me and saying, you know, my family plays this game called Mexican Train, in person all the time. Boy, I wish there was an online version. And she should just know better than to put that kind of idea in my head.

\n\n\n\n

So within a couple of months I had spun up the only interactive online version of Mexican Train, which was great for our family, but it’s a very popular game in retirement communities. And naturally during the pandemic a lot of people in retirement communities were isolating a lot more. The game became quite popular, because it spread word of mouth. And the first Christmas, I think I built it early in the year, and, and the first Christmas it peaked at like 2,600 concurrent games or something. Which, for me, I had never built anything that needed quite that much power.

\n\n\n\n

And it did eventually fall over. But initially I’d built it so that every time somebody played, all the other games, so four people are playing, basically all four games are sitting there pinging the server, looking for updates. That’s very inefficient because most of those pings don’t return anything, but the CPU still has to accommodate them. So I wound up switching to a pushing system. So I had to integrate with that. And originally I had built it so that the game itself, so when you’re signing into mexicantrain.online, that’s the website, the login screen you’re seeing is Theme My Login.

\n\n\n\n

All of the delivery of content, so like when you go to the My Games page and you see all of your games, that’s just Beaver Builder. And then the actual game I had to build, so it was quite a lift as far as development goes. But that was what that SaaS needed. But I built an app in a JavaScript framework called React that then talks to the server.

\n\n\n\n

Well, I built the initial version using the WordPress API. So my game talked to WordPress, functionality that was built into WordPress. And the API worked, until it didn’t. So, in that instance, again, too many people hitting the server too much. Aw, shucks, it was too successful.

\n\n\n\n

I had to revisit it after a year or two and build a custom API. Now I’m a developer. I have that luxury, right? But these are things that, I got enough of a version out the door. So, thinking about it from the perspective of a non-developer. I could have set up most of it except for the game itself.

\n\n\n\n

And the game is sponsored by donations. So I installed GiveWP, which is one of the bigger WordPress donation plugins. And I still used the free version. And so I got most of those sort of basic stuff using third party plugins out of the box. And then if I wasn’t a developer, I might have had to hire a developer.

\n\n\n\n

And so yes, I would’ve had to put some money into it. But they wouldn’t have had to build everything. And I also could conceivably hire different developers, or I could by using WordPress. So one of the things we haven’t talked about is because of the popularity of WordPress, you also have a lot more developers to choose from if you’re going to hire somebody.

\n\n\n\n

But anyway, if I wasn’t a developer, I would’ve had to hire somebody to build the game. And then down the road, presumably I would’ve proven that the platform was popular, hopefully in the form of donations, which would’ve been enough money to then hire somebody to rebuild the API, if I couldn’t have done it myself.

\n\n\n\n

You know? So there’s sort of this evolution of, as you’ve said. Try things, see if it’s popular, and then maybe hire somebody if you have to, you know, if you’re going to grow parts of the platform, parts of the app beyond WordPress.

\n\n\n\n

[00:35:40] Nathan Wrigley: It’s really interesting you mentioning about all of the very large number of WordPress developers. The developers I guess, go into different niches, don’t they? They might be experts in one field or another. Do you detect that there’s a lot of people doing this kind of thing? Building SaaS on top of WordPress. Or is it just you shouting into an empty room? What I’m basically saying is, is there a community, a subset of the WorldPress developer community who, like you, are interested in building SaaS apps on top of WordPress.

\n\n\n\n

[00:36:10] Corey Maass: There is a book called Building Web Apps with WordPress that came out from O’Reilly. So it’s popular enough that people are writing books about it. I’ve given talks on it at a few different WordCamps as far back as I think four or five years ago or more. And I’ve come across a number of people who are doing it, or are thinking about it or have done it. But it’s definitely not, and even Mullenweg has talked about it, but it’s not the most common use case.

\n\n\n\n

I think in part because people just don’t necessarily think about SaaS apps separately as much anymore. More and more websites do something. And so if they have functionality, maybe that people are paying for, and users are signing in to use the web app to do something.

\n\n\n\n

It’s a SaaS app. But that’s, again, I think more and more commonly just how people view websites. So it’s not necessarily something that people are thinking about or searching for. Except for, I think, as you’ve mentioned a few times, if you’re looking for no code now means something different. But if you’re looking for a non-developery way to spin something up quickly using third party software, then it still gets some attention. But to answer your question, no, I’ve never found a community. I’ve thought about starting one, but never have. Because I just haven’t gotten a sense that enough people are talking about it.

\n\n\n\n

Which is okay. Maybe at some point they will, or, you know, maybe some other better solution will come along and consistently solve the problems. But, right here, right now, I still find WordPress a great option.

\n\n\n\n

[00:37:57] Nathan Wrigley: It’s really interesting because curiously, there’s a great deal of overlap with something that’s going on in my world at the moment in that I have been working with a developer on a SaaS app. I won’t go into the details, but reached a point where a couple of years ago, the interest in it, from my point of view, I think probably, is best to describe it. It waned a little bit and so it went on the back burner and it’s never been revived.

\n\n\n\n

And as a couple of years have gone by, I’ve decided that, actually wouldn’t it be nice to revive this? And so with a couple of friends decided that, yeah, let’s give this another go. But actually, let’s just begin again, because I’ve noticed there’s significant things in what’s already been built that I would change.

\n\n\n\n

And guess what we’ve decided to do? We’ve decided to do the MVP inside of WordPress. Basically for pretty much all the reasons that you’ve suggested. We’re familiar with it. There are sometimes free, sometimes commercially available plugins, which will do a significant amount of the lifting. Will it be exactly what we would like from our roadmap? No. Will it be close enough to get us to measure whether there’s an audience for this? Yes, I think it will. And so, curious that this is actually playing itself out in my life at this moment.

\n\n\n\n

[00:39:19] Corey Maass: Nice, yeah. Depending on the problems you’re trying to solve, but I think that’s like most things, a bit of planning, sit down, design. I encourage everybody to do this. What is the all the bells and whistles version. We nerds are a big fan of what’s called the 80 20 rule.

\n\n\n\n

So what’s the 20% that needs to be solved now, today to prove the idea? And then see what plugins align with that. How they can get you there. Will it solve the problem? Do you need custom development? Are there features that just don’t have solutions or aren’t solved by any of the plugins you might want to use.

\n\n\n\n

And then go from there. See what you can do. The nice thing too about WordPress is you can start locally, which is free. Locally meaning on your computer, not locally in your town, although you can do that too. Most computers using software like Local WP, I’m a big fan of, and there’s a few others. Also InstaWP, which lets you spin up instances of WordPress online for free, for, you know, seven days or something, and then pay to keep them, or you can download them, I think, I don’t know.

\n\n\n\n

I definitely have been guilty of getting an idea and I needed to illustrate the idea rather than just write the idea down. So I spun up an instance of WordPress real quick. Installed a couple of plugins real quick, and then said, what do I need next? Or what would the next step be? Or, if I was a user, what would I expect to see next? All that cost me was a little bit of time. There’s kind of that advantage too, where it’s, you can use it for wire framing means something specific, but conceptually you can use it for wire framing ideas, which I think is crucial. Without it costing you anything.

\n\n\n\n

[00:41:04] Nathan Wrigley: Corey, if people listening to this, if they’re resonating with it and they’re thinking actually, do you know what, this is something that I’ve been doing for a while, or, I’m curious to get into the community that you said might need to exist. Where would be the best place to get in touch with you?

\n\n\n\n

[00:41:20] Corey Maass: Honestly, the place that I talk about this the most is Twitter. twitter.com/coreymaass, c o r e y m a a s s. Just start a conversation with me. I’d love to hear people who are interested in this. If this resonated with them, if they’ve tried it at all. Because again, I’ve run into people who have done it. I’ve heard about people doing it. A book exists. So there must be people talking about it somewhere.

\n\n\n\n

But I think it would be neat to have a community of people, or even just a network of people, helping each other out, solving some of these problems. Hey, does anybody have a good recommendation for a plugin that solves such and such a functional, or a problem that I have. Where should I start? Suggestions for hosting companies. I mean, there’s, there’s always information to be shared. And honestly, that’s one of my favorite things about the WordPress community is that it’s so open. So many people are talking to each other and willing to help each other. I definitely think there could be more conversation around using WordPress as a SaaS platform.

\n\n\n\n

[00:42:21] Nathan Wrigley: Corey Maass. Thank you for chatting to us on the podcast today.

\n\n\n\n

[00:42:25] Corey Maass: My pleasure.

\n
\n\n\n\n

On the podcast today we have Corey Maass.

\n\n\n\n

Corey is a full-stack web developer who works with agencies and businesses, large and small. He specialises in advanced WordPress functionality and building products for, and using, WordPress.

\n\n\n\n

Over the last decade or so, SaaS, or software as a service, apps have become more and more popular. Not only are we using our computers more, but with the rise of smartphones, we’re connected to our services all the time.

\n\n\n\n

There does not appear to be any corner of life where online platforms don’t have some presence. From email to taxis, fitness to food planning and delivery. You can find it all in a SaaS app somewhere.

\n\n\n\n

Now that many people are comfortable using SaaS apps, there’s been a deluge of new players coming into the market, but it won’t surprise you to learn that most of them fail to make an impact, and shut up shop.

\n\n\n\n

Corey is on the podcast today to talk about why he thinks that building a MVP, or minimum viable product, app on top of WordPress is a good way to start your product journey.

\n\n\n\n

We talk about how WordPress comes bundled with many of the features that apps require, user login, roles, permissions and the REST API. This means that you don’t have to reinvent the wheel for the things that WordPress already does.

\n\n\n\n

On top of that, the plugin ecosystem which surrounds WordPress might enable you to short circuit the need to build all the features that your service needs. It may be that there’s an existing plugin which does most of what you require, and is ready to go right away.

\n\n\n\n

Corey talks about how using WordPress in this way might enable you to see if there’s really a market for your app. If there’s not, you’ve used less resources finding that out. If there is, then you might have some revenue to develop the app in other ways.

\n\n\n\n

If you’ve toyed with the idea of creating a SaaS app in the past, but never quite got there, this episode is for you.

\n\n\n\n

Useful links.

\n\n\n\n

37 Signals

\n\n\n\n

Joel Spolsky’s, Joel on Software

\n\n\n\n

Easy Digital Downloads

\n\n\n\n

WooCommerce

\n\n\n\n

Advanced Custom Fields

\n\n\n\n

ACF Frontend

\n\n\n\n

Gravity Forms

\n\n\n\n

Beaver Builder

\n\n\n\n

Elementor

\n\n\n\n

Theme My Login

\n\n\n\n

Restrict Content Pro

\n\n\n\n

Corey’s Mexican Train website

\n\n\n\n

GiveWP

\n\n\n\n

Building Web Apps with WordPress book

\n\n\n\n

Local WP

\n\n\n\n

InstaWP

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 15:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Nathan Wrigley\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:46:\"Do The Woo Community: Looking at Code as Words\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74188\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"https://dothewoo.io/looking-at-code-as-words/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:425:\"

The hurdle is getting past looking at code and saying, \"Oh, this is code. I can\'t understand it.\" You\'re not looking at zeros and ones, you\'re looking at words you can understand.

\n

>> The post Looking at Code as Words appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 11:07:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:125:\"WPTavern: Jetpack Revamps Mobile App, WordPress.com Users Must Migrate to Keep Using Stats, Reader, and Notification Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141116\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:133:\"https://wptavern.com/jetpack-revamps-mobile-app-wordpress-com-users-must-migrate-to-keep-using-stats-reader-and-notification-features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6594:\"

When Automattic launched a mobile app for Jetpack in June 2021, it was targeted mainly at users who were on a paid Jetpack plan, as it enables access to features like backups, restores, and security scanning. Most importantly, the app gave Automattic a more direct path for monetizing Jetpack, without adding more commercial interests into the official WordPress apps.

\n\n\n\n

This week Jetpack announced that it has revamped the app and is offering a more compelling reason for those using the free plan to migrate. As part of a longterm effort to refocus the official WordPress apps, features that require Automattic’s products (the Jetpack plugin or a WordPress.com account) in order to use them, will soon be removed. This includes the Stats, Reader, and Notifications features, which have been relocated to the Jetpack app.

\n\n\n\nWordPress.com announcement for the revamped Jetpack app\n\n\n\n

WordPress.com users and Jetpack users on the free plan who previously relied on these features will need to switch to the free Jetpack mobile app. All the features that are moving over from the core WordPress app will still be free in the Jetpack app.

\n\n\n\n

While most self-hosted Jetpack users may easily understand the need for the switch, this transition may be rougher for WordPress.com users who do not understand the history of the mobile apps and see it all as “WordPress.” They may not be aware that Automattic’s integrated products have been controversial features in the official WordPress apps for nearly a decade.

\n\n\n\n

The announcement on WordPress.com is confusing, as it presents Jetpack as just a new optional app and doesn’t convey the urgency of migrating if users still want access to stats, notifications, the reader, and any additional paid features.

\n\n\n\n

The post’s FAQ section describes the Jetpack app as “the premium mobile publishing experience for our super-connected world” and states that “the Jetpack app is free to download.” WordPress.com users who commented on the post found the words “premium” and “free to download” to be suspicious and confusing. They don’t understand the reason for two apps:

\n\n\n\n
\n

“Do we have to change over? I only want to blog, I’m not technical and I don’t understand why you have done this or how to use it?”

\n
\n\n\n\n
\n

“So is WordPress now called Jetpack?”

\n
\n\n\n\n
\n

“If it ain’t broke, don’t fix it. This move is not in your users’ best interests so why is it being done? This smacks of the recent pricing debacle.”

\n
\n\n\n\n
\n

“I’m really disappointed by this decision. Why are you forcing us to use two apps? Your explanation of the differences makes no sense, and sounds like you made a decision for some reason you won’t tell us and you’re just trying to justify it. This is not user-focused at all.”

\n
\n\n\n\n

Users are also concerned about data loss, as those who are migrating to the newly revamped app are advised to delete the WordPress app after installing the Jetpack app. The announcement states that “Managing your site across both apps is currently unsupported and may lead to issues like data conflicts.”

\n\n\n\n

One user asked if there are premium features in the Jetpack app that will carry additional cost, and if there is any advertising included within the app.

\n\n\n\n

“For clarity, the Jetpack app is free to use and doesn’t include in-app advertisements,” Automattic representative Siobhan Bamber said.

\n\n\n\n

“We’re still planning our 2023 roadmap, and it’s possible in-app purchases will be a part of our plans. The driving goal would be to offer features that bring most value to users, and we’re keen to hear any ideas or feedback. Any in-app purchases would be optional, with the currently free features remaining free to use.”

\n\n\n\n

In response to those asking about the differences between the two apps, Bamber said there will be a couple more posts on the WordPress.com news blog in the following weeks.

\n\n\n\n

Users will need to have the latest version of the WordPress app installed in order to automatically migrate their data and settings to the Jetpack app. This includes locally stored content, saved posts, and in-app preferences. The FAQ states that after users download the Jetpack app, they will be “auto-magically” logged in with all their content in place.

\n\n\n\n

“One good way to confirm whether your version of the WordPress app supports ‘auto migration’ is to tap one of the in-app ‘Jetpack powered’ banners,” Bamber advised users in the comments. “You’ll find these banners at the bottom of sections including Stats and Reader. If you tap the banner, you’ll only see the ‘Switch to the new Jetpack app’ prompt in versions that support migration.”

\n\n\n\n

The revamped Jetpack app has been presented to WordPress.com users as a more feature-rich way to publish to their websites, but it also lays the burden of choice on users to try to understand the difference between the two apps and select one for all the sites they manage. Many don’t want the inconvenience of switching to a new app. Based on the users’ responses, it might have been easier for them to understand that the official WordPress apps are removing all features require the Jetpack plugin or a WordPress.com account – instead of selling it as a new, shiny publishing experience.

\n\n\n\n

Migrating to the Jetpack app is the best option if you want to continue using the Stats, Reader, and Notifications features. In order to make it easy for users to choose the best path forward, future posts on WordPress.com should make it crystal clear what features users can expect in each app and when they will need to take action.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 04:57:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"HeroPress: Reflecting on My 3 Foundational Pillars\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5037\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:152:\"https://heropress.com/essays/reflecting-on-my-3-foundational-pillars/#utm_source=rss&utm_medium=rss&utm_campaign=reflecting-on-my-3-foundational-pillars\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9650:\"\"Pull\n

I strongly believe that every experience we have up to our most current place in time shapes our identity. With that being said, as we go about living our lives it is not always obvious to see just how those compounding experiences shape us into who we are today. This is what makes all our journeys unique and worth reflecting on, because in our past often lies the tools and budding potential that influences the possibilities in our future.

\n\n\n\n

With that said, I’d like to share three pillars of my journey that have shaped me as a person and become the foundations of my current work.

\n\n\n\n

Technology

\n\n\n\n

I’ve found in one way or another that I have always lived technology-adjacent. When I was a kid my family had a Super Nintendo in the house which I always loved playing Super Mario World on– this device was essentially my first step into computers before we got our first home computer in the house when I was around 6 years old. By age 10, our computer was connected to AOL dial-up, which then allowed me to explore the early internet more deeply– MIRC, Livejournal, AOL Games, Neopets, MySpace… you name it. For the first time my world expanded beyond my immediate home of Rancho Cucamonga, CA, and El Paso, TX and into the interconnected world of the web.

\n\n\n\n
\n

Due to this opportunity of early access to computers, I became proficient in typing and navigating the internet at a very young age.

\n
\n\n\n\n

From what I’ve described so far, one would think that I was on track for a technology-related degree; however, there weren’t any people in my family or immediate network of friends who held a position in tech– so the idea that the computer could become a tool to propel my career didn’t really click until after graduating college.

\n\n\n\n

A slight detour– I’m a Social-Anthropologist by trade, having graduated from Lewis and Clark College with a Bachelor’s in East Asian Studies and a minor in Japanese. Following my passion for Japan and inter-cultural studies, I moved back to Tokyo after college and it was about one year later when I landed my first job as a Product Manager for a mobile gaming company called Cocone. This was my reintroduction to the idea that I could have a technology-driven career.

\n\n\n\n

In between working at Cocone and my return to the United States in 2016 I held a couple jobs that were not necessarily reliant on strong technical knowledge such as English Teacher, Executive Assistant, and even working at a karaoke bar. What my time as a Product Manager taught me, however, is that I do have a large thirst for working in the technology space so when I moved back to the States I applied to a Digital Agency called ASA Digital to get me back into that space.

\n\n\n\n

After a year at ASA Digital being a sort of Jack of all trades on projects that included mobile apps, web apps, websites, MR/ER/VR/AR, I knew that this was the right choice for me. Sometimes when you know you know, and when I moved on from ASA Digital to Automattic I was fully embracing my love for and need to have technology in my life.

\n\n\n\n

Diversity, Equity, Inclusive, and Belonging

\n\n\n\n

I haven’t always been aware of what the world now collectively calls DEIB, but since I was little I disliked the idea of injustice and lies. I have also faced adversity in the past due to who I am and what I look like, and it never sits right with me when others are in this kind of predicament as well. Due to this, DEIB practices deeply impacted my values and how I show up to work and with other people.

\n\n\n\n

It wasn’t until around 2019 that I became more involved in the world of DEIB in an official capacity at Automattic or at the incluu, LLC (a woman-owned and operated consultancy specializing in DEIB-thoughtful product strategy and advisement), and this is when I further developed this lens by participating in webinars on various DEIB topics, taking on leadership roles in the space, and keeping my eyes open to not only injustices that are happening but how they are being responded to.

\n\n\n\n
\n

The principles behind DEIB affect everyone and every aspect of our daily lives in some capacity, and embracing this space more fully not only allowed me to better understand the many systemic practices at play that keep us all from moving forward positively, but it also opened my mind to the real needs of people all over the world. 

\n
\n\n\n\n

Everyone deserves to live in a world or operate in a space with dignity and mutual respect.

\n\n\n\n

Community Building

\n\n\n\n

While I can understand the intent around the phrase “don’t mix friend groups”, I was never the type to follow this social role wholeheartedly. There are many times in our lives when we are put in situations where we interact with people we wouldn’t necessarily have engaged with such as school projects, clubs, sports, work, etc., and while it’s not all the time, sometimes a positive reaction can occur and we can meet someone new and interesting through these random groupings.

\n\n\n\n

I’m not quick to make friends, but when I do create a strong friendship it is because we share values and experiences which serve as the foundation for our relationship despite any other differences. Maybe it’s because of my (still ongoing) gaming days, but I tend to visualize people in the world as a character with a rich background story and something only they can bring to the table.

\n\n\n\n
\n

It has always brought me joy to bring people together and see how these chain reactions occur.

\n
\n\n\n\n

It could be that by some happenstance one of the friends is recruiting, they share a similar hobby, or come from a similar background. Facilitating safe spaces where folks can develop a sense of community has always been a passion of mine.

\n\n\n\n

I have had the pleasure of building community in the WordPress community through various outlets like BlackPress, with the Training Team, and even in Automattic’s Black employee resource group Cocoamattic.

\n\n\n\n

The Outcome

\n\n\n\n

Early last year I applied for the Community Education Manager with a basic idea based off of the job description of what I would be doing– fast forwarding to today I have found that the three pillars shared above gave me the tools I need to perform in this role successfully.

\n\n\n\n

As a Community Education Manager I work to break down perceived barriers for folks who want to contribute to the Make WordPress Training Team’s goals, and work as a close partner with the Training Team Representatives and members to empower them to excel in their leadership, goals, and strategy. I also help shepherd the Faculty Program, and therefore work to enable these folks to fully own and participate in their roles.

\n\n\n\n
\n

When working with our contributors, I focus on building relationships, encouraging engagement, and enabling contributions.

\n
\n\n\n\n

We have contributors from all over the world, so I also take care to be mindful of any language or cultural differences that may be at play and lean in with curiosity to better understand each community’s unique needs.

\n\n\n\n

When working with our Team Reps, I similarly focus on building relationships, and work with them (not for them) to create an environment where the goals of the team can be realized. 

\n\n\n\n

Lastly, I work with our Faculty Program Members by building relationships and connecting them with work related to their role, and with contributors who can benefit from their expertise and mentorship.

\n\n\n\n

Can you see how my pillars are directly impacting and influencing the work I currently do?

\n\n\n\n

Exploring Your Own Foundational Pillars

\n\n\n\n

There are probably many articles with thought-provoking exercises that can lead you in your own self-reflection, so I’ll leave you all with just a some questions from me that have worked to get me started:

\n\n\n\n
    \n
  • What have you been given positive feedback on lately?
  • \n\n\n\n
  • What actions/things bring you the most joy in life?
  • \n\n\n\n
  • What actions/things make you feel motivated?
  • \n\n\n\n
  • When was the last time you found yourself “in the zone”?
  • \n
\n\n\n\n

As you go through the questions for yourself don’t discredit or try to change your initial thoughts.

\n\n\n\n

Using these as a starting point, even if what comes up doesn’t immediately surface something that could be a pillar, you’ll surely learn or get to acknowledge something about yourself that shapes your character and how you present in the world.

\n\n\n\n

Take your time with it– the way we walk through life is a long-term journey which is constantly being updated by new experiences along the way.

\n

The post Reflecting on My 3 Foundational Pillars appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 17 Jan 2023 23:00:08 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Destiny Kanno\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"Do The Woo Community: Accepting Cryptocurrency in a WooCommerce Store with Lauren Dowling\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74258\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://dothewoo.io/cryptocurrency-woocommerce-store/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:521:\"

Lauren Dowling, lead product for Coinbase commerce joins returning guest Dave Lockie from Automattic as the conversation covers accepting cryptocurrency on WooCommerce shops, whether it be for your clients sites or your own.

\n

>> The post Accepting Cryptocurrency in a WooCommerce Store with Lauren Dowling appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 17 Jan 2023 11:06:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WordPress.org blog: WP Briefing: Episode 47: Letter from the Executive Director\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/episode-47-letter-from-the-executive-director/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8660:\"

On episode forty-seven of the WordPress Briefing podcast, Executive Director Josepha Haden Chomphosy shares her vision and current thinking for the WordPress open source project in 2023. Rather read it? The full letter is also available.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Show Notes

\n\n\n\n

make.WordPress.org/core
Join the 6.2 Release!
Submit Topics for the Community Summit!

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:40] 

\n\n\n\n

Last month at State of the Word, I shared some opening thoughts about why WordPress. For me, this is an easy question, and the hardest part is always knowing which lens to answer through. Though I always focus on the philosophical parts of the answer, I know that I often speak as an advocate for many types of WordPressers.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

So as we prepare ourselves for the start of a new year, I have a few additional thoughts that I’d like to share with you, my WordPress community, to take into the year with you. 

\n\n\n\n

Firstly, the Four Freedoms. If you have already listened to State of the Word, you have heard my take on the philosophical side of open source and the freedoms it provides.

\n\n\n\n

But if you didn’t, then the TL;DR on that is that open source provides protections and freedoms to creators on the web that I really think should just be a given. But there are a couple of other things about the Four Freedoms, and especially the way that WordPress does this kind of open source-y thing that I think are worth noting as well.

\n\n\n\n

One of those things is that WordPress entrepreneurs, those who are providing services or designing sites, building applications, they have proven that open source provides an ethical framework for conducting business. No one ever said that you aren’t allowed to build a business using free and open source software, and I am regularly heartened by the way that successful companies and freelancers make the effort to pay forward what they can.

\n\n\n\n

[Josepha Haden Chomphosy 00:02:02]

\n\n\n\n

Not always for the sole benefit of WordPress, of course, but often for the general benefit of folks who are also learning how to be entrepreneurs or how to kind of navigate our ecosystem. And the other thing that I love about the Four Freedoms and the way that WordPress does it is that leaders in the WordPress community, no matter where they are leading from, have shown that open source ideals can be applied to the way we work with one another and show up for one another.

\n\n\n\n

As a community, we tend to approach solution gathering as an us-versus-the-problem exercise, which not only makes our solutions better, it also makes our community stronger. 

\n\n\n\n

As I have witnessed all of these things work together over the years, one thing that is clear to me is this: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, but open source methodologies represent a process that can change the way we approach our work and our businesses.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:01] 

\n\n\n\n

The second big thing that I want to make sure you all take into the year with you is that we are preparing for the third phase of the Gutenberg project. We are putting our backend developer hats on and working on the APIs that power our workflows. That workflows phase will be complex. A little bit because APIs are dark magic that binds us together, but also because we’re going to get deep into the core of WordPress with that phase.

\n\n\n\n

If you want to have impactful work for future users of WordPress, though, this is the phase to get invested in. This phase will focus on the main elements of collaborative user workflows. If that doesn’t really make sense to you, I totally get it. Think of it this way, this phase will work on built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and things like programmable editorial, pre-launch checklists.

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00] 

\n\n\n\n

So phases one and two of the Gutenberg project had a very ‘blocks everywhere’ sort of vision. And phase three and, arguably, phase four will have more of a ‘works with the way you work’ vision.

\n\n\n\n

And my final thought for you all as we head into the year is this, there are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was State of the Word 2013, where Matt dreamed on stage of a true WYSIWYG editor for WordPress. Some say it was State of the Word 2016, where we were all encouraged to learn JavaScript deeply. For a lot of us though, it was at WordCamp Europe in 2018 when the Gutenberg feature plugin first made its way to the repo.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like it’s been a long time because it has been a long time. But I can also confirm that it takes many pushes to knock over a refrigerator. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

For early adopters, both to the creation of Gutenberg as well as its use, hyperfocus on daily tasks makes it really hard to get a concept of scale.

\n\n\n\n

And so I encourage everyone this year to look out toward the horizon a bit more and up toward our guiding stars a bit more as well. Because we are now, as we ever were, securing opportunity for those who come after us because of the opportunity that was secured for us by those who came before us. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:33] 

\n\n\n\n

That brings us now to our small list of big things. It’s a very small list, but two pretty big things. The first thing on the list is that the WordPress 6.2 release is on its way. If you would like to get started contributing there, you can wander over to make.WordPress.org/core. You can volunteer to be part of the release squad. You can volunteer your time just as a regular contributor, someone who can test things — any of that. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

We’ll put a link in the show notes. And the second thing that I wanted to remind you of is that today is the deadline to submit topics for the Community Summit that’s coming up in August. That comes up in the middle of August, like the 22nd and 23rd or something like that. 

\n\n\n\n

We’ll put a link to that in the show notes as well. If you already have chatted with a team rep about some things that you really want to make sure get discussed at the community summit, I think that we can all assume that your team rep has put that in. But if not, it never hurts to give it a second vote by putting a new submission into the form.

\n\n\n\n

And that, my friends, is your small list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in a couple of weeks.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"WordPress.org blog: Letter from WordPress’ Executive Director, 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/letter-from-wordpress-executive-director-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5901:\"

Last month at State of the Word, I shared some opening thoughts about “Why WordPress.” For me, this is an easy question, and the hardest part is knowing which lens to answer through. The reasons that a solopreneur will choose WordPress are different than the reasons a corporation would. And while artists and activists may have a similar vision for the world, their motivations change their reasons, too. That’s why I always focus on the philosophical parts of the answer because I know that I am speaking as an advocate for many types of WordPressers. I have a few other reasons, too, which you may not be aware of as you use our software every day.

\n\n\n\n

Why WordPress?

\n\n\n\n

Most importantly, the Four Freedoms of Open Source. If you have already listened to State of the Word, you have heard my thoughts on the philosophical side of open source and the freedoms it provides. If you didn’t, then the tl;dr on that is that open source provides protections and freedoms to creators on the web that should be a given. There’s an extent to which the idea of owning your content and data online is a radical idea. So radical, even, that it is hard for folks to grasp what we mean when we say “free as in speech, not free as in beer.” Securing an open web for the future is, I believe, a net win for the world especially when contrasted to the walled gardens and proprietary systems that pit us all against one another with the purpose of gaining more data to sell.

\n\n\n\n

A second reason is that WordPress entrepreneurs (those providing services, designing sites, and building applications) have proven that open source offers an ethical framework for conducting business. No one ever said that you cannot build a business using free and open source software. And I am regularly heartened by the way successful companies and freelancers make an effort to pay forward what they can. Not always for the sole benefit of WordPress, but often for the general benefit of folks learning how to be an entrepreneur in our ecosystem. Because despite our competitive streaks, at the end of the day, we know that ultimately we are the temporary caretakers of an ecosystem that has unlocked wealth and opportunity for people we may never meet but whose lives are made infinitely better because of us.

\n\n\n\n

And the final reason is that leaders in the WordPress community (team reps, component maintainers, and community builders) have shown that open source ideals can be applied to how we work with one another. As a community, we tend to approach solution gathering as an “us vs. the problem” exercise, which not only makes our solutions better and our community stronger. And our leaders—working as they are in a cross-cultural, globally-distributed project that guides or supports tens of thousands of people a year—have unparalleled generosity of spirit. Whether they are welcoming newcomers or putting out calls for last-minute volunteers, seeing the way that they collaborate every day gives me hope for our future.

\n\n\n\n

As I have witnessed these three things work together over the years, one thing is clear to me: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, open source methodologies represent a process that can change the way we approach our work and our businesses. 

\n\n\n\n

WordPress in 2023

\n\n\n\n

As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows. Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.

\n\n\n\n

If Phases 1 and 2 had a “blocks everywhere” vision, think of Phase 3 with more of a “works with the way you work” vision. 

\n\n\n\n

In addition to this halfway milestone of starting work on Phase 3, WordPress also hits the milestone of turning 20 years old. I keep thinking back to various milestones we’ve had (which you can read about in the second version of the Milestones book) and realized that almost my entire experience of full-time contributions to WordPress has been in the Gutenberg era.

\n\n\n\n

I hear some of you already thinking incredulous thoughts so, come with me briefly.

\n\n\n\n

There are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was at State of the Word 2013 when Matt dreamed of “a true WYSIWYG” editor for WordPress. Some say it was at State of the Word 2016 where we were encouraged to “learn Javascript deeply.” For many of us, it was at WordCamp Europe in 2017 when the Gutenberg demo first made its way on stage.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like a long time because it has been a long time. I can also confirm that it takes many pushes to knock over a refrigerator. For early adopters (both to the creation of Gutenberg and its use), hyper-focus on daily tasks makes it hard to get a concept of scale.

\n\n\n\n

So I encourage you this year to look out toward the horizon and up toward our guiding stars. We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.

\n\n\n\n

Rather listen? The abbreviated spoken letter is also available.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"Do The Woo Community: Finding Team Members to Fit Your Companies Culture\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74204\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://dothewoo.io/finding-team-members-to-fit-your-companies-culture/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:442:\"

Marius Vetrici has built a process to bring in new employees that are drawn to fit his companies values, and to grow with them as a team member.

\n

>> The post Finding Team Members to Fit Your Companies Culture appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 10:09:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:95:\"Gutenberg Times: Box Shadow, Newsletter Theme, Testing Call 20 and more – Weekend Edition 241\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://gutenbergtimes.com/?p=23190\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"https://gutenbergtimes.com/box-shadow-newsletter-theme-testing-call-20-and-more-weekend-edition-241/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:15190:\"

Howdy,

\n\n\n\n\n\n

Last week’s Live Q & A on Layout features went really well, with numerous participants. The post and the show notes are still in the works. The recording is available on YouTube, should you want to revisit parts of it or missed it entirely.

\n\n\n\n

Now that feature freeze for the major WordPress release is only three weeks away, the contributors would appreciate it if you could heed the 20th call for testing from the FSE Outreach program. You can help find quirks, bugs and annoyances, so they can be fixed before February 7th and during the round of beta version of the release.

\n\n\n\n

Have a lovely weekend!

\n\n\n\n

Yours, 💕
Birgit

\n\n\n\n\n\n\n\n\n\n\n\n

Developing Gutenberg and WordPress

\n\n\n\n

Gutenberg 15.0 release candidate is available for testing. Sticky positioning, resizable Site editor, updated to the Page List block, modify block style variations from global styles, and a lot more refinements are coming to the Gutenberg plugin

\n\n\n\n
\n

🎙️ New episode: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9 with Birgit Pauli-Haack and special guest Hector Prieto

\n
\n\n\n\n

Last April, a group of contributors started working on research on how to best implement an API for to make blocks more interactive. This week, JuanMa Garrido shared a progress report: Update on the work to make building interactive blocks easier.

\n\n\n\n

The resources linked in the post are mostly code internals, so they are definitely very technical at this point. With that said, understanding how the new API works, will not be necessary for developers to use this new standard. A standard proposal will be published the next few months. So for now, this is all bit technical and architectural. The work on the underlying framework is shared on this GitHub Repository

\n\n\n\n
\n\n\n\n

Plugins, Themes, and Tools for #nocode site builders and owners

\n\n\n\n

Munir Kamal, GutenbergHub, shows you in his latest post How to Find and Use Block Patterns in WordPress. You learn, how to find patterns in the post and site editor, how to navigate the WordPress Pattern Directory and how to install the patterns via the plugin Extendify Patterns and Templates

\n\n\n\n

If you want to create your own patterns, but don’t know how to code them, you can use the plugin Blockmeister – Block Pattern Builder.

\n\n\n\n
\n\n\n\n

Sarah Gooding reports on the Lettre Newsletter Theme Now Available on WordPress.org, It can be used with the newly release newsletter feature in Jetpack plugin or as a stand-along theme. “The theme puts the focus on the subscription form, which is the most important thing a newsletter landing page can do – make it easy for people to sign up. Beneath the form there is a link to read all the posts, followed by another subscription form. All of these elements in the home page design are blocks, making it easy for them to be removed or rearranged.” Gooding wrote.

\n\n\n\n
\n\n\n\n

Will Morris explained the three ways add a Table of Contents in WordPress in is post for the Torque Magazine. The three ways are:

\n\n\n\n
    \n
  • Install a plugin
  • \n\n\n\n
  • Use on the Custom Table of Contents blocks
  • \n\n\n\n
  • Create you Table manually in the Block Editor.
  • \n
\n\n\n\n

Soon you will be able to use the core Table of Content’s block once it comes out of the experimental stage. It’s already available via the Gutenberg plugin.

\n\n\n\n

Theme Development for Full Site Editing and Blocks

\n\n\n\n

In his post, Justin Tadlock, walks you through the layout classes in WordPress 6.1. With the latest release of WordPress, the software has now centralized its layout definitions, created semantic class names, and reduced code duplication on container blocks. “Originally, this post was intended to be a quick look at the changes to the system for theme authors. However, given the heftiness of the topic, it has evolved into a full overview of the layout framework.” Tadlock wrote.

\n\n\n\n\n\n\n\n

In his second post published on the Developer Blog, Using the box shadow feature for themes, Justin Tadlock took a look at the box shadow support, that what just released in Gutenberg 14.9. As it happens with similar features, the first iteration of box shadow support is only available via code. The interface for the site editor screens are still in the works.

\n\n\n\n\n

 “Keeping up with Gutenberg – Index 2022” 
A chronological list of the WordPress Make Blog posts from various teams involved in Gutenberg development: Design, Theme Review Team, Core Editor, Core JS, Core CSS, Test and Meta team from Jan. 2021 on. Updated by yours truly. The index 2020 is here

\n\n\n\n\n

Daisy Olsen held her inaugural live programming session on Twitch this week. The recording is now available on YouTube. In this stream, she talked about:

\n\n\n\n
    \n
  • using LocalWP for local WordPress development,
  • \n\n\n\n
  • the Create Block Theme Plugin, and
  • \n\n\n\n
  • took a look at the code from a couple of existing block themes.
  • \n
\n\n\n\n

You need a Twitch account and follow DaisyonWP to get notified when she goes live.

\n\n\n\n\n\n\n\n

In his latest post for CSS-Tricks: Styling Buttons in WordPress Block Themes, Fränk Klein, takes a detailed look markup of various buttons and how to style them via the theme.json properties.

\n\n\n\n

Building Blocks and Tools for the Block editor.

\n\n\n\n

Tom McFarlin continued his series A Backend Engineer Learns to Build Block Editor Blocks with Part 5 in which he covers adding color controls to a custom block for the use case, when you want to give the user the option to select the colors for the block themselves.

\n\n\n\n

McFarlin, recommend the previous articles first as they build on top of each other. So far, he published:

\n\n\n\n
    \n
  1. Required Tools, Plugin Structure, Dependencies, Block Metadata
  2. \n\n\n\n
  3. The Backend, The Frontend, Functionality, Styles, a Working Demo
  4. \n\n\n\n
  5. Block Attributes, Editable Content, Components, Editor Styles
  6. \n\n\n\n
  7. Saving Data, Styling the Frontend
  8. \n
\n\n\n\n
\n\n\n\n

Phil Sola create a Custom Color Picker for WordPress. Sola added some improvements to the existing color picker. It’s more an experiment rather than a full-fledged solution. His exploration might also be an inspiration for others to start experimenting with WordPress component library.

\n\n\n\n
\n\n\n\n\n

Need a plugin .zip from Gutenberg’s master branch?
Gutenberg Times provides daily build for testing and review.
Have you been using it? Hit reply and let me know.

\n\n\n\n

\"GitHub

\n\n\n\n\n

Upcoming WordPress events

\n\n\n\n

February 4 + 5, 2023
WordCamp Birmingham, AL

\n\n\n\n

February 17 – 19, 2023
WordCamp Asia 2023 

\n\n\n\n

Check the schedule of WordCamp Central of upcoming WordCamps near you.

\n\n\n\n

Learn WordPress Online Meetups

\n\n\n\n

January 17, 2023 – 3pm / 20:00 UTC
Patterns, reusable blocks and block locking

\n\n\n\n

January 19th, 2023 – 10:30 ET / 15:30 UTC
Live stream: Building an Advanced Query Loop block variation plugin w/ Ryan Welcher @ryanwelchercodes

\n\n\n\n

January 19, 2023 – 7 pm ET / 24:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 20, 2023 – 3 am ET / 8:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 20, 2023 – 10:30 am 15:30 UTC
Block Themes and WordPress: Live Stream w/ Daisy Olsen @daisyonwp

\n\n\n\n

January 23, 2023 – 10 pm ET / 1 am UTC
Patterns, reusable blocks and block locking (APAC time zone)

\n\n\n\n

January 26, 2023 – 10:30 am ET / 15:30 UTC
Live stream: Reviewing developer-focused features in Gutenberg 15.0 w/ Ryan Welcher @ryanwelchercodes

\n\n\n\n

January 31, 2023 – 3pm ET / 20:00 UTC
Creating a photography website with the block editor

\n\n\n\n
\n\n\n\n\n

Featured Image: Amit Patel: Mango Shake Orange Sweet found in WordPress.org/photos

\n\n\n\n
\n\n\n\n

Don’t want to miss the next Weekend Edition?

\n\n\n\n

We hate spam, too and won’t give your email address to anyone except Mailchimp to send out our Weekend Edition

Thanks for subscribing.
\n\n\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 22:30:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Birgit Pauli-Haack\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: WooCommerce 7.3 Introduces New Products Block in Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141097\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wptavern.com/woocommerce-7-3-introduces-new-products-block-in-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2242:\"

WooCommerce 7.3 was released this week with the new Products block now in beta. In December 2022, the Products block went into testing in WooCommerce Blocks version 9.1.0. It’s based on the Query Loop block and is intended to replace all of WooCommerce’s current product-displaying blocks.

\n\n\n\n

This first beta version of the Products block allows users to list products based on specific criteria and their layout in the list or grid.

\n\n\n\n\n\n\n\n

Version 7.3 also introduces three “commerce-adjacent” patterns for building WooCommerce store pages. These are patterns that do not tap into WooCommerce store data but allow store owners to customize the images and the links. These patterns were also tested in WooCommerce Blocks 9.1.0. They include an alternating image and text block pattern, a product hero with two columns and two rows, and a “Just Arrived” full hero pattern.

\n\n\n\nimage source: WooCommerce 7.3 release post\n\n\n\n

This release also brings store owners a new multichannel marketing experience in beta. Under the Marketing menu in the admin, users can now view a list of recommended marketing extensions without leaving the dashboard. These can be installed directly from the Marketing page.

\n\n\n\n\n\n\n\n

Other notable features in WooCommerce 7.3 include Pinterest and Codisto extensions added to the onboarding wizard, a new warning banner when the tax settings have a conflict, and an improved UI for creating product attributes and uploading product images.

\n\n\n\n

Check out the release post to see the template changes and all the new actions and filters available for developers. The full 7.3 changelog is available on GitHub.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 04:25:34 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Lettre Newsletter Theme Now Available on WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141076\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"https://wptavern.com/lettre-newsletter-theme-now-available-on-wordpress-org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2595:\"

Automattic has published its Lettre theme to WordPress.org. The company launched its newsletter product at the end of December 2022 using Lettre as the default theme. The self-hosted version of this block theme is for those who want to publish a newsletter using Jetpack.

\n\n\n\n\n\n\n\n

The theme puts the focus on the subscription form, which is the most important thing a newsletter landing page can do – make it easy for people to sign up. Beneath the form there is a link to read all the posts, followed by another subscription form. All of these elements in the home page design are blocks, making it easy for them to be removed or rearranged.

\n\n\n\n

Lettre comes with 15 block patterns for building different pages and designs, including about the author(s), a bold color signup, a two-column signup, various designs for the newsletter intro with light and dark background images, newsletter signup with media on the left, newsletter signup with logos for the background, a list of posts, an in-post article promo, three columns of text, and more.

\n\n\n\n\n\n\n\n

A live demo of the theme is available on WordPress.com. The menu items on the demo give a few examples of the different signup patterns in action.

\n\n\n\n

Lettre is designed to be used with Jetpack’s Subscription block, which uses WordPress.com’s infrastructure to manage emails and subscribers. If you like the design but are already using another newsletter service, the Jetpack Subscribe block can be replaced with any other block, including the shortcode block for newsletter services that haven’t yet made their subscription forms available via a block. Be advised, you may need to write some custom CSS to ensure that the subscribe form matches the original design.

\n\n\n\n

Lettre is one of the only themes in the WordPress Themes Directory that was made to be a newsletter landing page and certainly the only block theme dedicated to this purpose. Combined with Jetpack’s subscription feature, this is one of the most seamless ways to distribute a newsletter without all the extra steps of copying the content into a newsletter service’s editor. Lettre is available for free download from WordPress.org. I wouldn’t be surprised to see more themes like this pop up now that WordPress.com has launched its newsletter service.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 02:50:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"Do The Woo Community: Taking a Deep Dive Into the Current State of Social Media with David Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74300\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"https://dothewoo.io/current-state-of-social-media/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:422:\"

David Bisset and I share our current experiences with Twitter, Mastodon, Linked, Tumblr, the Fediverse and open source.

\n

>> The post Taking a Deep Dive Into the Current State of Social Media with David Bisset appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 13 Jan 2023 10:58:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:77:\"WPTavern: New Video Explores Site Building Progress from WordPress 5.9 to 6.2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141039\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:88:\"https://wptavern.com/new-video-explores-site-building-progress-from-wordpress-5-9-to-6-2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3346:\"

WordPress 5.9 “Josephine” was released in January 2022, but that seems like ages ago when you compare the advances made in site building over the past year. Anne McCarthy, an Automattic-sponsored contributor who heads up the Full Site Editing Outreach Program, has published a short video that tours the important changes in WordPress over the past few major releases. The video also doubles as a preview of some of the features coming in 6.2.

\n\n\n\n
\n\n
\n\n\n\n

If you are using the Gutenberg plugin and have been tracking the relentless progress of the Site Editor, you will notice how limited the design options are in 5.9 and how much more consistent and expansive they are today. In 5.9 users users can only add a Front page template, and the site building interface is disjointed and less polished.

\n\n\n\n

McCarthy demonstrates how WordPress 6.2 will introduce smoother interactions with browse mode. It will also greatly expand the template options available for users to add and includes a new colorized list view.

\n\n\n\n

The Navigation block has had a long, rocky journey but seems to be reborn in 6.2. McCarthy showed how much more intuitive it has become with the new experience of editing navigation in the sidebar, and repositioning via drag and drop with live previews.

\n\n\n\n\n\n\n\n

The instant that Style Variations were introduced in WordPress 6.0, it seemed they were always with us. Looking back at 5.9 in the video, the Site Editor appears so bare without them. WordPress 6.2 will extend this even further with improved block style previews, a style book, and a new zoomed out view that makes it easy to see changes at a glance.

\n\n\n\n

Everything coming in 6.2 is converging towards better usability and more design options for site editors. The challenge here is to continue introducing new features without the interface becoming cluttered and chaotic. Many of these features are still being ironed out. For example, McCarthy mentioned that the Edit button is still a work in progress and may soon be relocated to be more prominent in the Site Editor.

\n\n\n\n

This video gives a quick visual summary of what is being done to wrap up the full-site editing phase of the Gutenberg project before contributors move on to Collaboration. It is worth a watch to see the site building progress that contributors have made in just one year.

\n\n\n\n

If you want to get involved in making sure all these features in 6.2 are ready for prime time, check out McCarthy’s latest FSE Testing Call: Find Your Style. It will plunge you into the new features of the Site Editor to perform a few tasks. It’s essentially a guided opportunity to explore the new interface while contributing back to WordPress, and you will earn a fancy testing contributor badge that will display on your WordPress.org profile.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 13 Jan 2023 03:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:96:\"Post Status: On OpenAI And WordPress With Jannis Thuemmig Of WP-Webooks— Post Status Draft 136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146297\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"https://poststatus.com/on-openai-and-wordpress-with-jannis-thuemmig-of-wp-webooks-post-status-draft-136/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:43263:\"

Jannis Thuemmig, founder of WP Webhooks, joins Cory Miller to discuss Open AI and WordPress. Jannis is passionate about utilizing the power of technology to increase efficiency. WP Webhooks is exploring the ways Open AI can be used to revolutionize website processes and management. It seems we are only at the tip of the iceberg for what is possible when working with WordPress and Open AI.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

In this episode, Jannis Thuemmig, serial entrepreneur and founder of WP Webhooks, dives into the world of automation and Open AI with Cory Miller. Together they look at what is currently possible within the world of integration and automation within WordPress. Then they lean into what is unfolding as Open AI finds its way into the mainstream and discuss what this might mean for the WordPress community.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • Integrations & Automations to Save Time: Everyone is in need of some kind of automation. Our main goal is to save time by creating automations wherever there are pain points. Rather than doing things manually, WP Webhooks enables you to automate them within your dashboard.
  • \n\n\n\n
  • Avoiding Automation through Software: Using software as a service partner means hosting your data on their platforms. Using Webhooks for integration and automation allows you to keep things on your server and within your complete control.
  • \n\n\n\n
  • Possibilities with Open AI Integration: Webhooks is focused on using Open AI as an advantage to speed up processes by creating integrations between services and generating original content. They are working on finding cool use cases and understanding the actual power of what it makes possible.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n
\n

\"🙏\" Sponsor: WordPress VIP

\n\n\n\n

Founded in 2006, WordPress VIP is the agile content platform that empowers marketers to build content both faster and smarter so they can drive more growth. We empower content and development teams with the flexibility and ubiquity of WordPress—the agile CMS that powers more than 40% of the web—while ensuring the security and reliability organizations need to operate at scale

\n
\n\n\n\n
\n
\"WordPressWordPress VIP
\n
\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

GMT20230105-161248_Recording

\n\n\n\n

GMT20230105-161248_Recording

\n\n\n\n

Cory Miller: [00:00:00] Hey everybody. Welcome back to Post Status Draft. This is another interview and discussion in our Product People series, and I\'ve got someone I\'ve met, let\'s see, last year or the year before Giannis and doing good work, but we were talking about AI and that led to OpenAI and something they\'re doing with WP Webhooks.

\n\n\n\n

So that\'s what the conversation is gonna be about today. But Giannis, welcome to the draft podcast. Would you tell us a little bit about yourself and your work and WordPress?

\n\n\n\n

Jannis Thuemmig: Sure, totally. Thanks for having me here. Uh, it\'s always an honor. Uh, my name is Giannis. I\'m from Germany originally, but started traveling a long time ago and since then, I basically work as a digital dumper from anywhere.

\n\n\n\n

And I would say with a, with a very, very deep focus on web. And specifically in automation. This is where W P Airport comes from. So we are basically focused on connecting different services and WebPress plugging to let them talk to each other and kind of just automate the [00:01:00] system and get rid of the human error and save everyone a little bit of time and money, which is really interesting nowadays.

\n\n\n\n

Cory Miller: Yeah, I, and I love it. Uh, one part I\'ll just sidebar real quick is I know when you say digital notepad, uh, the several times we\'ve had zooms, I\'m like, where are you in the world today, y\'all? It\'s like, . Um, so I, I love that. I love to see the nude, like landscapes you\'re in every time we talk. Um, okay. So WP Webhooks, um, I know you\'ve been, so automation is key.

\n\n\n\n

It\'s about efficiency, um, like really saving that time. In the processes you\'re doing, um, what, tell me what all does WP Webhooks do?

\n\n\n\n

Jannis Thuemmig: So basically it allows you to use a set of redefine integrations to let other services and WebPress plugin specifically talk to each other. So let\'s say there\'s, um, a woo commerce shop, for example, and you have a, a custom programmed plugin that has no integrations [00:02:00] whatsoever.

\n\n\n\n

You can use our plugin as a middleman to allow sending data in between, and that works with mostly any kind of WebPress plugins as well as external data like, uh, external services, something like Zapier or make or integrated. So the, the basic main goal is to just make them compatible, which they, in a lot of cases, aren\'t from the beginning. Or if they are, they\'re often very limited, which is something we realized as well. So we just want to kind of get that interoperability to WordPress, which is something that was just lacking over the last couple of years.

\n\n\n\n

Cory Miller: Yeah, I, I love that. I know Zapier has used quite a bit uh, obviously we\'ve used it in the past at, at Post Status because of all the external services, and you\'re trying to link these and do some things that certain pieces of software doesn\'t do out of the bat.

\n\n\n\n

So I, I love the premise of web hooks for sure. Uh, WP Web Hooks, what are you seeing or finding? Customers are gravitating to webhooks [00:03:00] for, like is there specific tasks that stand out that people are using these over and over and over and going, this is what I need. I don\'t want to pay for Zapier or some other alternative, I wanna do something here with my WordPress site.

\n\n\n\n

What are you seeing from your customers?

\n\n\n\n

Jannis Thuemmig: So I\'d say that\'s not a specific use case. There\'s, uh, quite a lot. So everyone, literally, everyone who\'s in the, in the, has a web presence or has an online shop or something related and does something with website. Everyone is in in need of doing some kind of automations.

\n\n\n\n

Let it be to automatically book orders into your accounting system or synchronize your properties from a property management website with your WebPress website. Or let\'s say you have a Teachable account and you sell online courses and you want to synchronize your students with a WebPress website to give them extra features.

\n\n\n\n

This is stuff that they are using it for. So basically wherever there\'s a pain point and there\'s some time that just can be avoided by automating it through software. This is something where we are, um, jumping in [00:04:00] and it\'s specifically interesting right now for people that are very critical about privacy because especially in Europe, a lot of people don\'t want to use software as a service partners like Zapier or Integra.

\n\n\n\n

Because they are hosting their data on other platforms, right? So they have no full control over it, which comes very handy with our plugin because you have your own server, so everything runs on your own server. You are in full control where your data is, what your data does. And this is a very, very critical point that is, uh, always, always well seen at the moment.

\n\n\n\n

Yeah.

\n\n\n\n

Cory Miller: Yeah, yeah, for sure. I, you went to a subject I didn\'t even think about, which is if you don\'t want your information out there on another service, having it in in WordPress, something you control. I think that\'s a key facet. Before we start talking about, uh, AI and specifically OpenAI, what are you most excited about with webhooks this year?

\n\n\n\n

Jannis Thuemmig: Ooh, for sure. Bringing that AI space model [00:05:00] web. Because we had so much fun over the last couple of months trying these things out, seeing in which direction it goes. And it\'s just incredibly fun to, to play around with it because the possibilities are really endless. And we are, we are fully about saving time.

\n\n\n\n

Right? And this is something we can even use to leverage more time out of our daily task, which is really, really good. Okay.

\n\n\n\n

Cory Miller: Well let\'s roll into that because I think that\'s one of the most, uh, uh, Interesting themes in our community is ai. I\'ve seen a couple tweets saying AI is gonna revolutionize, um, a lot of stuff with a website by the end of 2023.

\n\n\n\n

I can\'t remember who said that online. And I was like, well, I\'ve been paying attention enough. But talk to me about ai, OpenAI and what you, you see on the horizon for, um, for WordPress particularly, and opportunities and possibilities. . Yeah,

\n\n\n\n

Jannis Thuemmig: so ai uh, specifically in our case with OpenAI, uh, there\'s, uh, a little differentiation.

\n\n\n\n

So [00:06:00] right now it\'s very much hyped, the G P T three. So the, the kind of chat ai as you can, as you can, uh, think of, which is basically you just type in something and it, it gives you like a very human answer back, which is really, really incredible. And, uh, we specifically talk about the, the OpenAI api, which kind of allows you to.

\n\n\n\n

Communicate data on a programmatic level, which means you basically don\'t even need to type something yourself, but you can already use a service to let these things run through the web automatically without ever touching this kind of data. And this is, this is just something that that works very well with, with the automation part.

\n\n\n\n

Right. So we are, we are basically looking into bringing more possibilities that AI through non-static data, and, uh, what I mean by non static data, it\'s probably interesting to, to understand what an AI actually is. So it\'s a pre-trained network, right? It has the data that was feeded to it at some point. And with OpenAI, it\'s made from [00:07:00] mostly 2021.

\n\n\n\n

So it has no actual new data. If you ask it something like what happened yesterday, for example, it could probably not give you an answer to, you could give it the information if you have it yourself. But it can never give you like the, the news and accurate information. And using things like automation, you can basically bring a whole new word to it because you can kind of give the AI the possibility through response and, uh, requests to send data through automation, uh, validate it somewhere else and send it back to the AI and tell the ai, Hey, look, there\'s new information.

\n\n\n\n

We can use that, uh, or, or learn about that and, um, send me some more information or summarize me something. So it\'s, it\'s just a very interesting time in, in regards to giving the AI actual information that you can feed it, uh, that is currently not within its its own, um, possibilities.

\n\n\n\n

Cory Miller: So you said something there.

\n\n\n\n

Um, I, I haven\'t even gotten that in depth with OpenAI, but So in [00:08:00] 2021 they fed it a ton of data you\'re saying, and then trained it to be able to, to answer questions and things.

\n\n\n\n

Jannis Thuemmig: Yeah, exactly. So basically they had a, a huge dataset or couple of datasets for sure about the information that they fitted. And the AI can basically make.

\n\n\n\n

An answer that is, uh, in a human real reform, and that seems like it is made from a human, but the data that was fed is all from 2021, right? So it is a static data if we, if we want to hear it or not. So if you\'re gonna ask the ai, what is the latest model of iPhone, for example, it\'ll probably tell you something like it\'s the iPhone 13, because I don\'t think it has information about iPhone 14.

\n\n\n\n

That would be something cool to try, but I guess it must be, uh, outdated information. And with that gap of, of using that, that automation in, in connection with ai, you can kind of close that gap and you can actually feed it real time data and use that data to, to do certain things within the AI [00:09:00] and, uh,

\n\n\n\n

Cory Miller: I see, thats a new one.

\n\n\n\n

Yeah, it does. Um, totally to me, and I\'m asking as a newbie to all of this, um, because I\'ve used it and I\'m like, this is pretty dang fast. And I\'m like, how the heck are they doing that? That makes total sense. And then from the training side, um, the model itself is, I was like, gosh, if this had access to that, and you could just ask it questions like that.

\n\n\n\n

It\'s the, it\'s the a hundred times better Google. Yeah, because, yeah, it, like, I was, I, I mean I asked what are the strengths and weaknesses of WordPress, for instance, and it came back. Um, but knowing it\'s, it\'s a little bit lag on the data side is interesting to me. Um, but I saw the potential for this to truly.

\n\n\n\n

Revolutionized some things on the web. Um, so it\'s, it, it\'s been really intriguing and I mean, I asked it all kinds of questions that I was just actually curious about and seeing what, not just from the what, [00:10:00] how the model would work, but the answer. And I was like, this is like a perfectly uh, formatted.

\n\n\n\n

Informative, um, short essay that I would\'ve gotten in college, you know, so that\'s

\n\n\n\n

Jannis Thuemmig: intriguing. It\'s actually you can, you can write like on demand stories for your kids based on your own characters. Just type in a sentence, say you run a short story and it spits you out a short story that you can read them from going to bed.

\n\n\n\n

It\'s amazing. It\'s just like incredible.

\n\n\n\n

Cory Miller: I\'m gonna have to try that today. I, I continue. This subject fascinates me and I think it\'s something we need to be thinking about and looking at and talking about in WordPress and Post Status, because this new technology coming and then how WordPress is placed in this.

\n\n\n\n

And for years, I think this is a segway to talk about OpenAI and WordPress specifically. But you know, I\'ve either built sites for people or known a bunch of people that build sites for clients. And you turn on this awesome, it\'s like you turn this car over, you pull this car up to \'em, and [00:11:00] you\'re like, here\'s your car.

\n\n\n\n

But you gotta drive it with content, with things inside the site, and it\'s such a great vehicle for that. But oftentimes people get hung up on that part of. Oh, I don\'t know what to, I don\'t know how to drive my car. Right? Like these, you know, WordPress sites with the right architecture, the right things can really drive and make a dent.

\n\n\n\n

That\'s our kind of thing with WordPress is like it\'s magic like that. But you still have to like, Drive it, you have to put gas in it and drive it, uh, with content. So that\'s a compelling angle for me with OpenAI. And I\'ve heard about all these things. Before we segue specifically to the integration you\'ve done too and some possibilities there, what, where do you see all of this and WordPress going?

\n\n\n\n

Jannis Thuemmig: Like, that\'s a very interesting question. Yeah, yeah. Uh, I think, I think it will be in relay, I mean, it\'s, right now we are specifically in the content age, right? So I, I\'ve seen a lot of people. [00:12:00] Going into the space where they try to create on demand articles using an ai, which is probably a terrible idea just through plagiarism because it\'s very easily detected if you don\'t lose like a rewriter and you use your very own wordings in between.

\n\n\n\n

So this is something that we will see switching, definitely. But what I see as an advantage in the future with WebPress is that people will use to. We, we learn to use AI for the advantage in the sense of speeding up their process. So it\'s also kind of a, a way of automating things, uh, in the sense that they don\'t need to write their content anymore from scratch, or they don\'t need to write a, think about copywriting that much.

\n\n\n\n

They just ask the ai, it\'s bit something out. They put it in, maybe adjust it, tweak it in their own way so that it has their very own style. And they probably just make the, the way of, of riding blocks 10 times a hundred times faster than it\'s right now. So we\'ll definitely see like a, a boost in performance and [00:13:00] probably block block posts over the long term.

\n\n\n\n

Cory Miller: Okay. Well, so that leads into this specific integration you have and the tutorial I, I was looking through before we started. Um, so you saw OpenAI has an api and tell us, tell us about that in WP Web Web Hook.

\n\n\n\n

Jannis Thuemmig: So, yeah, we, we basically started, um, after trying a couple of times how OpenAI works to, um, to integrate it with our plugin.

\n\n\n\n

So we, we usually go for creating integrations for different services and plugins, and, uh, in that case it\'s, it\'s once separately for OpenAI, which makes it compatible with all of the other services and, uh, plugins. We are integrated. And the main goal was just to provide the integration, right? Because it\'s so new, barely anyone understands the actual power of it and what is possible.

\n\n\n\n

So we, we just kind of created it out of the blue with a thought of, Hey, it would be cool to just have it, you know, let\'s see what, what\'s going to happen. And right now we are basically just [00:14:00] working on finding cool use cases. And, uh, there are definitely a couple, uh, like I\'ve, I\'ve, uh, showed you earlier.

\n\n\n\n

We already have a blog post on our. That basically describes how you can auto generate method descriptions using OpenAI and Yost seo. So you basically just feed it in the title and it spits your order, perfectly made method description that you can just use or adjust as you want. And these kind of things, they just now come through trial and error basically.

\n\n\n\n

And, uh, it\'s, it\'s very interesting to see where it goes. And I can see that with these kind of automat. Um, we can also provide what I mentioned earlier, that that possibility of feeding the AI new information that is not available within the AI itself. So because we can make these kind of workflows, um, if that makes sense.

\n\n\n\n

And this is, uh, this is mostly what we are trying to do right now. We basically just working on, on use cases, see what\'s possible, trying out different things and it\'s a super, super exciting. [00:15:00]

\n\n\n\n

Cory Miller: Yeah, absolutely. Because I mean, you talk about this car, you some a a site builder turns the car over and they start to use it.

\n\n\n\n

But that meta, uh, description is one thing. Like I honestly confess, I never do, you know, but it\'s, it\'s helpful, it\'s vital. And so like that one little use case in the bigger picture of what I can do, I think starts to step us into this and it is really interesting. Um,

\n\n\n\n

Jannis Thuemmig: oh, totally. Yeah. This is, this is literally just the, the tip of the iceberg.

\n\n\n\n

If you want, you can basically let the AI create a, a full schema, uh, like a shima for your, for your website. So whenever there\'s a blog post, it can write the how-tos and everything in, in kind of adjacent format and, uh, spits your order perfectly well formatted SEO description and, and everything keyboards, the, the, the whole how to, and this is just, it\'s just such a time saver.

\n\n\n\n

It\'s incredible.

\n\n\n\n

Cory Miller: Well, okay. Do you have a tutorial on that [00:16:00] too? Because that\'s really interesting. Um, , or if you don\'t, we need one. Um, but so you\'re going into OpenAI or chat GPT or whatever, and then you\'re saying you\'re asking a question or something like that, and then it\'s gonna give you back those things.

\n\n\n\n

Jannis Thuemmig: Yeah, exactly. It\'s just you literally ask it just a humanly written question, something like, give me back adjacent with each of these information. And it spits you out adjacent with each of the information. And Jason, you can always use on a technical level, right? So we can just leverage that out and use it through our plugin to use it in different automations and do different things.

\n\n\n\n

Cory Miller: Oh, that\'s super cool. Well, what do you have anything you wanna share about what you\'re doing next with this WP WebHooks?

\n\n\n\n

Jannis Thuemmig: Um. As a, as a use case, it\'s a, I mean, we, we definitely, for, for now we really try to just work on the OpenAI things mm-hmm. and try to find some cool use cases there. Uh, we had a lot of, um, a lot of actually customers reaching out about the possibilities as well and how exactly it works because the models [00:17:00] and the configuration is a bit complicated if you, if you\'re not fully aware of it.

\n\n\n\n

But, uh, yeah, we, we just follow the standards and, uh, things should be fairly easy. But yeah, for us, it, it\'s mostly, mostly OpenAI and creating new integrations. That\'s something we\'re, we are hardly focused on at the moment.

\n\n\n\n

Cory Miller: I, I really think this is, like you said, the tip of the iceberg that, um, I\'m really intrigued by our WordPress community post at Post Status to go, okay, here\'s this cool technology.

\n\n\n\n

How do we translate this into practical? Um, uses for the end client, the end user in WordPress. Um, so that, that\'s, that\'s interesting. We\'ll be excited to hear what, what you find in explorer and launch launch next.

\n\n\n\n

Jannis Thuemmig: Yeah, you should just see the block post, uh, our, our, our block. There will be a couple of more tutorials coming.

\n\n\n\n

They\'re already in the making, so in the next days we should see someone there.

\n\n\n\n

Cory Miller: Okay. Perfect. All right. Um, okay. So. You, you [00:18:00] showed me something as like this. I think this is just showing the power of what it could do when we start to get these types of integrations into WordPress. Do you mind showing me the one you were telling me about?

\n\n\n\n

Jannis Thuemmig: Totally. Yeah. Not a problem. Of course. I\'m just gonna share my screen, probably this one. Yes, so I, I was basically just fooling around the other day on. With our integration, trying to find some new cool ways we can use to, to present the OpenAI integration. And, you know, like, like I mentioned earlier, you can kind of ask the AI to create adjacent format, um, with specific data.

\n\n\n\n

So Jason is basically just a structured way of presenting data within the web that is something that the, the browser or the, the server can. And in our case, we, we, we wanted to have like, like in here, uh, just a simple field that you can write something and based on whatever you write, it updates the block post of [00:19:00] your choice.

\n\n\n\n

So in our case, we just created a quick contact form seven form as we have an integration with contact as well. And we connected that with open. To create a so-called Jason and update a block post based on a specific information. So I can just demonstrate it here. You can see I have three block posts available and let\'s just take this one.

\n\n\n\n

I just need the ID because that\'s the way we wrote it. So we have ID 97, and what I would like to do is, let\'s say I want to update the, the title of this post, right? So I can, I can write something like, Update the post with the id, let\'s say post title

\n\n\n\n

with ID 97 and change it to, um, this is a new title based on OpenAI. So it\'s, it\'s basically what we read as a, as a human tech, [00:20:00] right. But if I submit that and I let our workflow. The AI basically interprets that and, uh, changes it based on our parameters within, uh, Jason. And when I refresh here and, um, the flow ran, it should display it.

\n\n\n\n

See if it doesn\'t, no, it doesn\'t. Uh, so the thing is, because it depends what you feed the ai. So the AI basically needs to understand what you do. And, uh, in some, in some cases, that\'s, that\'s the problem with ai. It fits you out text, right? So you try to, you need to, to format. And kind of use the text in a different way so we can see.

\n\n\n\n

Okay. Just didn\'t follow it. Just what I\'m gonna do is, so to, to just, so the very same example, I just tried to type the similar thing again. Let\'s try it again. So, um, update the post with the ID 87 and change the title to, [00:21:00] um, OpenAI. Something new. Let\'s see now

\n\n\n\n

Cory Miller: I always love live demos, . Yeah, I know. When you were showing me before I was like, wow, that\'s super cool.

\n\n\n\n

Jannis Thuemmig: Yeah, it really depends on the AI, if I, if I do it right or not. Um, but it seems like you see that it\'s not completed. So basically something stopped within the AI and uh, yeah, I would need to, I would need to see what.

\n\n\n\n

Cory Miller: Yeah, so you were showing, you were showing just now the webhooks, uh, pro dashboard. Do you mind taking us for a spin around the Webhooks Pro dashboard?

\n\n\n\n

Jannis Thuemmig: Uh, yeah, totally. So it\'s basically like, you know, standard WebPress plugin and stuff. On the site menu we have, uh, our W2 Web Hooks Pro, um, menu item, and basically it\'s, it\'s separated into two main things, which is the automations, the flows, and the web. So there\'s, there\'s kind of a difference in between, because originally we came from the web website, which means it\'s kind of like a [00:22:00] one-way street for information to present.

\n\n\n\n

Let\'s say you, you update a, a post on your WebPress website and based on that post you can send data into a, a certain direction, like directly and instantly to inform another service about that there\'s a new post. But then we realized that there\'s more of a need to actually automate kind of certain work.

\n\n\n\n

And then we created something called Flows, which basically allows you to connect the, or create a consecutive order of triggers and actions. So web book triggers and actions to do certain things in a, in a specific flow as we call it. So I just head into it, uh, into one, which is the human posture. This was the example I tried to show you, which, uh, was currently not working out because of something that I need to see. Um, but what we have basically, within the floor. You can see we have a trigger, right? The trigger fires on a contact form seven. Within the settings, we basically selected the form that we created earlier, which is [00:23:00] embedded in, in the site.

\n\n\n\n

And we don\'t want to send the email as we just want to send the data to OpenAI. And it was tested. We set up some conditionals, um, stuff that\'s not really important for now, but, uh, this is, this is basically what causes the actual workflow to fire, right? So, This specific trigger comes along with all the data that was sent within this form, and we then reuse the data in the other kind of actions here.

\n\n\n\n

And as you can see, the first action is something, uh, is our OpenAI integration, where we basically sent that information that we had earlier, as you can see here, to OpenAI as a, as a text. And this is, this is what we read. So it basically says, get the posterity and the PostIt from the JSON, uh, in the JSON format.

\n\n\n\n

This is the sentence, and the sentence basically is a dynamic string that comes from the input that we sent within this form. So it makes more sense if, if we go through it logically while, while building it. But, um, [00:24:00] when you click into a field like this, you will see it shows a dropdown, and inside of the dropdown you will see all of the information that was sent within the trigger, including the question like, change the title of the post idea, ???

\n\n\n\n

So this is basically what we selected here. And this is kind of more details about the OpenAI stuff. Sure. And yeah, when you, when you continue safe, you can test the action directly within here. That\'s something I can try, um, just as an example to see what comes back. So basically right now I\'m sending a real request with the data that we got earlier.

\n\n\n\n

And this is basically the response. So we can see, we got some text back from the AI, which looks a bit weird as it\'s text. But within our plugin we have something like a formatter, which allows us to format the data and change into something readable. So I\'m just gonna quickly do that to, to give you a better example of what we get back.

\n\n\n\n

So as you can see, this is what we get back from the AI or from the formatter, which came originally from [00:25:00] the OpenAI. And this specific information we want to then use in another action to actually update the post. And this is, this is literally everything it does. You can just think it of simple steps that, like we have a trigger.

\n\n\n\n

The trigger causes, uh, runs whenever the, the specific contact form was sent, then we sent the data to OpenAI. We format it in a certain way, and then we update the post based on whatever data we got back from the OpenAI. Excellent. So, yeah, exactly. This is, this is basically it for that.

\n\n\n\n

Cory Miller: What, what are some of the automations.

\n\n\n\n

Yeah, I, I saw the create the automation. So what are the, some of the things that webhooks can do from the automation side?

\n\n\n\n

Jannis Thuemmig: Uh, you mean some examples for example? Ah-huh. Yeah. Yeah, yeah. Like I say, you can, you can, for example, connect the different services together. Let\'s, for example, say you have newcomer, right?

\n\n\n\n

So you can go to the, to the integration [00:26:00] screen. You can install any kinds of integrations that you, you are working. So we have around hundred right now. And let\'s, for example, say you have commerce installed, right? So you can then install commerce once it\'s, once it\'s available on your website and within one of those automation workflows, you can then say, whenever commerce fires, then send the data using, uh, a WebBook, for example, to mm-hmm.

\n\n\n\n

your bookkeeping system. Or send, send an email using the WordPress integration. So in here I can show you. Click send email, and then you have the possibility to send an email directly from your WebPress to the customer whenever, whenever, uh, an order was created. So it basically, it basically just allows you to do certain things that you would manually do within your dashboard.

\n\n\n\n

Mm-hmm. ? Yes. In an automated way

\n\n\n\n

Cory Miller: There\'s a bunch of those things for the Post Status setup out the way. I\'m like, oh gosh. Yeah.

\n\n\n\n

Jannis Thuemmig: I can\'t imagine. Same here.

\n\n\n\n

Cory Miller: And, and what are, what are workflows to, uh, or I\'m sorry, it\'s [00:27:00] webhooks. Oh, I thought that\'s a workflow somewhere. I read that wrong. Okay. Yes. So what

\n\n\n\n

Jannis Thuemmig: I can show you, it\'s, it\'s basically separated in two parts.

\n\n\n\n

It\'s sent data and received data. What it basically means is these are kind of the triggers available, right? So whenever a user created, or when a user was deleted or when a form was submitted, you can send data to a specific url. Let\'s say, for example, I want to send a URL to my website, um, I cannot call it demo, and I, I add my api endpoint here and I edit, and then you can see it here. Which basically means when ev, every time a user gets created, you can send a direct webhook request to this url and you can test it, you can customize it with, with more features, more setups, um, based on your needs. Gotcha. And this is, this is what I mean earlier, like a, a direct connection.

\n\n\n\n

And the receive data is basically the exact opposite. So instead of sending data out on a specific event, you send data in and to do something specific. So you can, for example, activate a plugin. As you can see, you can call a PHP [00:28:00] function, you can create a comment, a post a user. So we have basically mostly all of the options of WebPress available through, uh, web as well.

\n\n\n\n

Excellent.

\n\n\n\n

Cory Miller: You don\'t have a Slack integration, do you by chance ?

\n\n\n\n

Jannis Thuemmig: Um, that\'s the, that\'s the thing. Depends what Slack has as an api. Um, if they truly have an API and if they have an api and it can be integrated with something like an API key or a hero token, it can also be used with our plugin. Um, and that\'s an interesting point.

\n\n\n\n

That\'s good that you mentioned that we have something like,

\n\n\n\n

Cory Miller: um, it\'s a, it\'s a. Post Status specific thing, but I think a lot of membership sites, which is a big trend too. People building membership sites, course sites, you know, a lot of people like us obviously use Slack. Being able to, um, one, this is a nuance and I\'m, uh, sorry for sharing, but this is like create a private group or something like that.

\n\n\n\n

I\'ve looked in some of the Slack API and. I\'m using us as a [00:29:00] test for a second to say it is a broader thing. I think a lot of membership sites, they\'re using Circle, for instance, maybe they want to use something else. So I, I stopped you though. Keep going.

\n\n\n\n

Jannis Thuemmig: Oh, no problem. No problem. Um, yeah, but what I, what I mentioned earlier is that, like you say, with, with, uh, slack, we can kind of integrate with any service that allows, like simple API calls or web and uh, we have an integration available that is called Web itself.

\n\n\n\n

So, When you install one like that, for example, and you go to, let\'s say an automation workflow, I can, I can come within here, add a new action, and you see I have a WebBook endpoint available, which basically allows me to send data or send a request to a specific site. So if you have Slack, you would, you would uh, add your Slack U URL here, for example, right?

\n\n\n\n

Slack API or something, and then you. Select the method you want to use to send data, and you can send the data and add it here along. [00:30:00] So if there\'s a, a service out there that just follows the standard rep hook or api, um, standards, you can integrate them as well with our plugin. So there\'s not directly, uh, integration necessary. Basically.

\n\n\n\n

Cory Miller: Excellent. Well, one thing that\'s intriguing about this is for as long as I\'ve been in WordPress, I, it, it has led the way in truly democratizing publishing, but over the years, you see Facebook, Twitter, what name, whatever default. Closed wall type garden come out. And um, I just did an interview with Mattias who does activity plug plugin for the Fed averse.

\n\n\n\n

And I was like, the, you, you think about what you\'re doing here with webhooks and then the Fed averse is kind of bringing that power back to the. To the user and saying, okay, fed averse can help. To me, I just see the potential to go, let\'s, let\'s decentralize some of the social [00:31:00] networks. So when a billionaire buys the next thing, or they change their policy at one of these closed set social networks, you\'re, you know, all these people are affected by it.

\n\n\n\n

And, and taking some of that control. So that\'s where I see FE averse. And then I go, what\'s the power here is. Ground zero for what you\'re doing is your WordPress site, and with things like tools like this, then you can start, I don\'t know, it\'s just helping bridge that gap of power. There\'s so much usability and features that these closed gardens have, but tools like webhooks and potential with the Fed averse is like bringing some of that power back, and I see WordPress truly being in the space to lead and innovate and bring that power back to the users.

\n\n\n\n

Jannis Thuemmig: Totally. Yeah, I fully agree with you. The, the, an interesting point about that is actually that using our plugin, for example, you can use it kind of as a standalone on WebPress. So if you say you want to make automations, you don\'t necessarily need to use WebPress, but [00:32:00] you can just set up a WebPress environment and install our plugin and you can.

\n\n\n\n

Automate external services through WebPress. Right? So you can use it kind of as a middleman for yourself without actually using WebPress.

\n\n\n\n

Cory Miller: And you still maintain control

\n\n\n\n

Jannis Thuemmig: in a lot of ways you have full control. Yes.

\n\n\n\n

Cory Miller: Even if it\'s not a public facing site where you have content on like using that, that\'s the power, that\'s the other side of WordPress.

\n\n\n\n

Do this has been become this huge power powerhouse of a, you know, a software. I talked to a lot of people on the enterprise and they mentioned. the connections. There\'s a, um, my friend Kareem at Crowd favorite talks about WordPress being the open source hub to connect services. So, like your example there.

\n\n\n\n

I, I resonate with it cause I just talked to Kareem a couple weeks ago. I love that example. Yeah. Yep. Well, Gianni, anything else you wanna share, um, that you all have going on or you\'re excited about or anything I forgot to.

\n\n\n\n

Jannis Thuemmig: Uh, yeah, I\'m excited to make this tutorial work, so I think the next blockbuster [00:33:00] will see is probably about this example.

\n\n\n\n

Okay. .

\n\n\n\n

Cory Miller: I love it. Just to have, I love it. That\'s the beauty of being a part of this community as I get to ask cool, smart people that can do these things and see, see how they go. But I, I\'ll be playing around with open api. OpenAI\'s, API\'s, mouth, um, just cuz I was playing with that and like, wow, this is powerful and I love this kinda stuff and I love there\'s people like you experimenting with it, testing it, and giving users, um, that opportunity to do that.

\n\n\n\n

Um, so thanks so much today for being on the Post Status draft podcast. Uh, this is under our product People series. I love our innovators in our community like you, and so thanks for joining me today.

\n\n\n\n

Jannis Thuemmig: My pleasure, really. So it\'s an honor. Thank you very much as well for inviting me.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 14:44:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"WordCamp Central: WordCamp Entebbe: First Wordcamp to happen in Africa in 2023 is on!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:39:\"https://central.wordcamp.org/?p=3158482\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:108:\"https://central.wordcamp.org/news/2023/01/wordcamp-entebbe-first-wordcamp-to-happen-in-africa-in-2023-is-on/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3126:\"

\n\n\n\n\"\"\n\n\n\n

WordCamp Entebbe 2023 is set to be a major community event for WordPress developers, website designers, online publishers, students, and teachers to come together and share knowledge and experiences, network with other WordPress users, and gain inspiration for their work. Taking place on Friday, March 10th and Saturday, March 11th at the Uganda Wildlife Education Centre (UWEC) in Entebbe City, this WordCamp will be the first to happen in Africa and is poised to be a memorable event for all attendees.

\n\n\n\n

The event will feature a range of activities, including beginner’s training, inspirational talks, showcases, best practices, and the latest trends in WordPress development. In addition, there will be a Women in Tech panel discussion, aimed at inspiring and empowering women-led businesses to thrive in the technology industry. A Teacher’s Workshop will explore the integration of WordPress in the local education curriculum, providing teachers with the tools and resources they need to introduce WordPress to their students for web design projects and assessments.

\n\n\n\n

Attendees will also have the opportunity to take a free tour of the Uganda Wildlife Conservation Education Center, where they can learn about the animals of Uganda and the ecosystems in which they live. The center, which was founded in the 1950s to accommodate confiscated and injured wildlife, has grown considerably in recent years and is considered a premier facility for showcasing wildlife on the African continent.

\n\n\n\n

Accommodation options are available for those traveling to Entebbe for the first time. Attendees can find a list of hotels and guest houses through booking.com https://bit.ly/entebbehotels or by contacting the WordCamp team at entebbe@wordcamp.org for more information and guidance. The full schedule of activities will be published soon, and we look forward to welcoming you to WordCamp Entebbe 2023!

\n\n\n\n

Get Involved

\n\n\n\n

There are several ways to get involved! Check out the details below:

\n\n\n\n\n\n\n\n

Join the discussion via #WordCampEbbs hashtag on Twitter

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 11:58:51 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Kasirye Arthur\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"Do The Woo Community: Reflecting on the Past and Embracing the Future for WooCommerce with Paul Maiorana\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74261\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://dothewoo.io/2022-2023-woocommerce-paul-maiorana/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:442:\"

It\'s that time of year again when Paul Maiorana, CEO of WooCommerce, joins us for a review of the year and a looking into 2023.

\n

>> The post Reflecting on the Past and Embracing the Future for WooCommerce with Paul Maiorana appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 11:29:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:17:\"Matt: Thirty-Nine\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75200\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://ma.tt/2023/01/thirty-nine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4687:\"

The last year of my thirties! WordPress turns twenty this year. Automattic is now ~2,000 people across 98 countries. There’s so much that has happened in the past decade yet it feels very much like we’re on the cusp of something even more exciting.

\n\n\n\n

This morning started well; I pulled the hammock out of the garage (it had been hiding from the rain) and read for a bit, trying to get my 5-10 minutes of sun in the first 30 minutes like Huberman suggests.

\n\n\n\n

Candidly, the last year was a really challenging one for me personally. There were some beautiful moments, and I consider myself the most lucky in my family, friends, and colleagues, yet among that same group there was a lot of loss, existential health challenges, and that weighed heavily on me. It’s also my last year to get on 40 under 40 lists! \"😂\"

\n\n\n\n

Usually when people ask me what I want for my birthday I don’t have a good answer, but this year I do! As Heather Knight wrote about in the SF Chronicle, the beloved Bay Lights are coming down in March. This has to happen — the vibrations and corrosive environment of the Bay Bridge is taking lights out strand by strand. Fortunately it’s now been a decade since the lights first went up, and there’s much better technology both for the lights and how they’re mounted and attached to the suspension cables. Finally, the lights were not visible from Treasure Island or the East Bay before, but this new version 3.0 will be, which is why the artist behind the lights, Leo Villereal, is calling it Bay Lights 360.

\n\n\n\n\n\n\n\n

Like the Foundation series, we can’t stop the coming period of darkness from happening, but if we raise $11M we can bring the lights back. If we raise it soon we can shorten the time they’re down to just a few months, so I’m working with the 501c3 non-profit Illuminate to help fundraise. The idea is to find ten people or organizations to put one million each, and raise the final million in a broader crowdfunding campaign, to re-light the Bay Bridge and give an incredible gift to the people from every walk of life that see the bridge, and hopefully have their spirits lifted by the art. I’ve heard 25 million people see the Bay Lights every year.

\n\n\n\n

It’s a lot to raise, but every little bit helps so please donate here, and if you are interested to do a larger gift please get in touch. I’m committing a million dollars to the fundraise, and myself, Illuminate director Ben Davis, and the artist Leo Villereal are happy to personally connect with anyone considering a larger donation.

\n\n\n\n

Because of some family health reasons I’m back in lockdown, so going to try and throw an online party tonight in the “Matterverse.” We’re going to party like it’s late 2020. \"🎉\"

\n\n\n\n

All birthday posts: 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 04:37:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: Automattic Launches Blaze Ad Network for Jetpack and WordPress.com Sites\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140985\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:93:\"https://wptavern.com/automattic-launches-blaze-ad-network-for-jetpack-and-wordpress-com-sites\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5113:\"

Automattic is bringing Tumblr’s Blaze ad tool to WordPress sites with its launch today on WordPress.com and Jetpack. Blaze made its debut in April 2022, to the delight of Tumblr users who will gladly shell out cash to get people to look at their cat or promote a game they made. It’s an affordable way to attract new followers or just send out something funny into the universe, starting at $5/day.

\n\n\n\n
\n

Note To all small Twitch streamers. Tumblr Blaze is the best advertising tool out there. It has an embedding feature allowing you to embed streams. I went from 20 to 100+ live viewers rn! pic.twitter.com/aolZduCTEe

— Ian Miles Cheong\'s Prime Minister (@MToph91) January 3, 2023
\n
\n\n\n\n

WordPress.com users can now to go to wordpress.com/advertising, select a site, and promote content with Blaze. Jetpack users have access to the ad network inside the WordPress.com dashboard.

\n\n\n\n\n\n\n\n

After selecting a post, users are taken to the design wizard where they can add an image, title, a snippet, and a destination URL. The URL can be the post or page or it can direct visitors to the main website.

\n\n\n\n

When Blaze first launched on Tumblr there was no way to target the promoted content – it just displayed to random users. Now there are a few more options. When promoting content from WordPress.com or a Jetpack-enabled site, users can narrow the audience by device: mobile, desktop, or all devices, select from a few main geographic areas (continents) or serve it everywhere. There is also a dropdown with topics of interest, but they are fairly general, e.g. Arts & Entertainment, Automotive, Business, Education.

\n\n\n\n\n\n\n\n

After selecting the audience, users can set the budget for the campaign, starting at $5 with a max daily budget of $50. With a minimum of $5/day for a week users can expect an estimated 5,900 – 8,000 impressions. For $25/day, users can expect 29,700 – 40,200, and up to 59,500 – 80,500 for $50/day. Site owners can monitor the success of their ads in the Campaigns tab.

\n\n\n\n

Content sponsored by Blaze will be promoted across WordPress.com sites and Tumblr pages, an audience that accounts for an estimated 13.5 billion impressions per month.

\n\n\n\n

Blazing has become somewhat of an art in the short time it has been available on Tumblr. It will be interesting to see how ads originating from WordPress.com and Jetpack go over with the Tumblr audience.

\n\n\n\n
\n

There\'s a whole code of ethics around Blaze on Tumblr basically if you use it to do anything other than shitpost people hate you, you can also buy Tumblr merch and buy fake verification (which isn\'t seen as a serious thing, it\'s just a way to support tumblr)

— New Year New Simisear Fan \"🐀\" (@LakeUncalming) January 10, 2023
\n
\n\n\n\n\n\n\n\n

Creating advertising content that works across the disparate audiences between WordPress and Tumblr-powered pages may be a challenge for some site owners. Tumblr users can only target audiences by location for blazed posts. It’s possible that WordPress’ additional targeting options can help funnel the ads to sites where they will be most well-received, but the announcement says ads will be promoted across WordPress.com and Tumblr.

\n\n\n\n

Blaze campaigns require approval to be in compliance with Automattic’s Advertising Policy before being published. They are currently moderated in approximately 30 minutes but this may change in the future as more users try out Blaze.

\n\n\n\n

Automattic is treading new ground in creating its own ad network that any user across Tumblr and WordPress can tap into. It’s a strategic move to extend access to the world of WordPress, given that it’s such a large audience, and it will be interesting to see how the company improves the targeting options to meet the challenges of serving both audiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 22:52:36 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:107:\"Post Status: Improving 5ftF Contributor Journey • Building Interactive Blocks • Layout Classes • WP20\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146399\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"https://poststatus.com/improving-5ftf-contributor-journey-building-interactive-blocks-layout-classes-wp20/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:16911:\"

This Week at WordPress.org (January 9, 2023)

\n\n\n

Share your feedback about how to improve the Five for the Future contributor journey. Check out work underway on how to make interactive blocks easier to build, and take a walkthrough of layout classes in WordPress 6.1. It\'s time to start planning; how will you celebrate WordPress\' 20th birthday?

\n\n\n
\n\n\n\n\n\n\n\n

News

\n\n\n\n\n\n\n\n

\n\n\n\n
\n
\n

Community

\n\n\n\n\n\n\n\n

Core

\n\n\n\n\n\n\n\n

Meetings

\n\n\n\n\n\n\n\n

Developer Blog

\n\n\n\n\n\n\n\n

Docs

\n\n\n\n\n\n\n\n

Hosting

\n\n\n\n\n\n\n\n

Marketing

\n\n\n\n\n\n\n\n

Meta

\n\n\n\n\n\n\n\n

Openverse

\n\n\n\n\n\n\n\n

Performance

\n\n\n\n\n\n\n\n

Polyglots

\n\n\n\n\n\n\n\n

Plugins

\n\n\n\n\n
\n\n\n\n
\n

Project

\n\n\n\n\n\n\n\n

Support

\n\n\n\n\n\n\n\n

Test

\n\n\n\n\n\n\n\n

Themes

\n\n\n\n\n\n\n\n

Training

\n\n\n\n\n\n\n\n

Online Workshops

\n\n\n\n\n\n\n\n

Tutorials

\n\n\n\n\n\n\n\n

WPTV

\n\n\n\n\n
\n
\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n

Thanks for reading our WP dot .org roundup! Each week we are highlighting the news and discussions coming from the good folks making WordPress possible. If you or your company create products or services that use WordPress, you need to be engaged with them and their work. Be sure to share this resource with your product and project managers.

Are you interested in giving back and contributing your time and skills to WordPress.org? \"🙏\" Start Here ›

Get our weekly WordPress community news digest — Post Status\' Week in Review — covering the WP/Woo news plus significant writing and podcasts. It\'s also available in our newsletter. \"💌\"

\n\n\n\n
\n\n\n\n
\"Post
\n

You — and your whole team can Join Post Status too!

\n\n\n\n

Build your network. Learn with others. Find your next job — or your next hire. Read the Post Status newsletter. \"✉\" Listen to podcasts. \"🎙\" Follow @Post_Status \"🐦\" and LinkedIn. \"💼\"

\n
\n\n\n\n
\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 18:08:54 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"WPTavern: ClassicPress Community Votes to Re-Fork WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140878\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wptavern.com/classicpress-community-votes-to-re-fork-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3772:\"

In December 2022, the ClassicPress community voted on whether to re-fork WordPress or continue on with the project as-is. As WordPress continues to evolve, ClassicPress fell behind in pursuit of PHP 8+ compatibility. The fork is based on WordPress 4.9 and users are increasingly limited in what plugins will work with the five-year-old codebase.

\n\n\n\n

In a discussion limited to ClassicPress core contributors, Viktor Nagornyy, one of the project’s directors, announced the results of the vote: “The option to re-fork has 20 votes while continue-as-is has 18.” Nagornyy summarized previous discussions and suggested an approach that would be more realistic for the project’s limited contributors:

\n\n\n\n
\n

ClassicPress can’t be WordPress without Gutenberg, but it also can’t be its own CMS with a small core team at this time. There are simply not enough developers to make progress without backporting code from WP to move away from WP.

\n\n\n\n

An almost even split in the poll suggests the best option might be a hybrid one, find a compromise solution that will satisfy both sides.

\n\n\n\n

With a small core team, we have to find ways to be more efficient, to get more done with less. The only way to do that is to leverage all the work that’s done by WP contributors. As the core team grows, we can always explore the possibility of splitting away from WP but at this point in time, it’s simply not feasible.

\n
\n\n\n\n

Some participants in the previous discussion saw re-forking as postponing the inevitable, kicking the can down the road until the next re-fork, but it is the only option if users want to retain compatibility with the rest of the WordPress ecosystem.

\n\n\n\n

“If you read recent threads, you find out that the community expects plugin compatibility with WordPress… another reason for the re-fork option,” ClassicPress core committer Álvaro Franz said.

\n\n\n\n

Franz, who is also the author of the WP-CMS fork based on WordPress 6.0, previously said he would be unwilling to help with a continuation of the current version based on WordPress 4.9.

\n\n\n\n

“It [ClassicPress] doesn’t have to be a competition (and it never could compete with WordPress anyways), but it can be a leaner version, for people who are already disabling Gutenberg via plugins, for developers who want a different approach to the way they develop their projects (closer to ‘the classic’ experience, but yet… modern!),” Franz said.

\n\n\n\n

“Eventually, it won’t make sense to run a fresh copy of WordPress to then go and install a plugin that ‘disables’ half of it. What’s the point? Why not have a version that covers that specific use case?”

\n\n\n\n

As part of Nagornyy’s proposed hybrid approach, he suggested the project retain some changes that were introduced in ClassicPress in v1.x, such as the privacy-oriented changes (anonymizing data CP sends to APIs), the news widget, and ensure that all API endpoints use ClassicPress APIs as in v1.x.

\n\n\n\n

The discussion continues around how to proceed with the fork. ClassicPress contributors are leaning towards using Franz’s WP-CMS fork based on WordPress 6.0 but have not finalized the details yet.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 15:10:57 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: #58 – Lax Mariappan on How Headless WordPress Works\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://wptavern.com/?post_type=podcast&p=140972\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:77:\"https://wptavern.com/podcast/58-lax-mariappan-on-how-headless-wordpress-works\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:56075:\"Transcript
\n

[00:00:00] Nathan Wrigley: Welcome to the Jukebox podcast from WP Tavern. My name is Nathan Wrigley.

\n\n\n\n

Jukebox has a podcast which is dedicated to all things WordPress. The people, the events, the plugins, the blocks, the themes, and in this case, how Headless WordPress works.

\n\n\n\n

If you’d like to subscribe to the podcast, you can do that by searching for WP Tavern in your podcast player of choice, or by going to WPTavern.com forward slash feed forward slash podcast. And you can copy that URL into most podcast players. If you have a topic that you’d like us to feature on the podcast, well, I’m very keen to hear from you, and hopefully get you or your idea featured on the show. Head to WPTavern.com forward slash contact forward slash jukebox, and use the form there.

\n\n\n\n

So on the podcast today, we have Lax Mariappan. Lax is a web developer based in the Philippines. He’s an open source enthusiast and lover of all things WordPress. Lax has been tinkering with websites since high school. But it all changed when he discovered WordPress in 2010. Lax currently works as a backend engineer at WebDevStudios.

\n\n\n\n

We talked today about Headless WordPress, and it’s a complex topic. Headless is the concept of decoupling the WordPress admin from the front end of the site. WordPress will continue to work as expected, but the presentation layer will be done by a different technology. React Gatsby and Remix being some popular choices.

\n\n\n\n

This implementation of WordPress is complex, requires technical knowledge above and beyond that needed for a more typical WordPress install. But it has its benefits.

\n\n\n\n

Lax talks through all of this in great detail. How keeping on top of all the additional dependencies Headless WordPress requires can be time consuming. How it can create difficulties for content editors who don’t always get to see what their work will actually look like in real time. Why this approach to WordPress can take more time and resources during the build.

\n\n\n\n

Lex explains how these problems typically crop up, and how it’s possible to plan ahead and build in solutions for all the problems that you might encounter.

\n\n\n\n

If you’ve ever thought about going headless with WordPress, then the podcast today is for you.

\n\n\n\n

If you’re interested in finding out more, you can find all of the links in the show notes by heading to WPTavern.com forward slash podcast. Where you’ll find all the other episodes as well.

\n\n\n\n

And so without further delay, I bring you Lax Mariappan.

\n\n\n\n

I am joined on the podcast today by Lax Mariappan. Hello Lax.

\n\n\n\n

[00:03:30] Lax Mariappan: Hello, Nathan.

\n\n\n\n

[00:03:30] Nathan Wrigley: Very nice to have you with us on the show today. I have to commend you for your staying power, because Lax and I have tried to record this episode a couple of times and he’s been incredibly, incredibly thoughtful about getting his, all of his equipment and everything working. So thank you, first of all, I would like to express my gratitude for you staying the course.

\n\n\n\n

But before we get into the podcast, Lax, I wonder if you wouldn’t mind spending a moment just introduce yourself. Tell us who you are, where you are, who you work for, how long you’ve been using WordPress, all of those kind of things.

\n\n\n\n

[00:04:06] Lax Mariappan: Thank you. It’s good to be on WP Tavern, it’s one of my favorite publications, and also the favorite podcast. So I’m Lax, Lax Mariappan. I’m from India, and also I’m from Philippines. So I would say I live in both countries, and I use WordPress since my school days, like 2009. So I was looking for a platform to build a website for an event or something, and then I found out Blogger versus WordPress, and I liked WordPress more even that time.

\n\n\n\n

So since then, I’m using WordPress almost every day. And my first job I got started working as a PHP developer, I would say, and then fully focused on WordPress. And I wrote my first plugin in 2011. It’s a very simple one. It’s now kind of obsolete because Facebook changed it a lot. So I wrote a plugin for something to fetch Facebook feed. So, and then my journey goes on. Right now, I work as a backend engineer at WebDevStudios. So where I get a chance to learn and work more with headless CMS every day almost.

\n\n\n\n

[00:05:09] Nathan Wrigley: Your work at WebDevStudios, I don’t know a great deal about the company, but my impression of the company is that you work with, how should we describe it? Enterprise clients. You’re dealing with fairly large projects. I would imagine sizable budgets. Those kind of things, right?

\n\n\n\n

[00:05:27] Lax Mariappan: Yeah, yes. Enterprise level.

\n\n\n\n

[00:05:28] Nathan Wrigley: So when we decided we were going to have this conversation, Lax introduced the subject to me of headless WordPress. Now this is a word which I imagine some of you have heard before. Maybe some of you have never heard the word before. Perhaps there’s a subset of you which have experimented with it, but I’m expecting that the majority of WordPress users have not.

\n\n\n\n

So, first of all Lax, would you mind giving us a very, in depth I suppose is the right word. Give us an analysis of what headless WordPress is because I’m sure many people think they know what it is, but perhaps they don’t.

\n\n\n\n

[00:06:06] Lax Mariappan: So headless, or decoupled CMS, so first we all know content management system, right? So WordPress, we are using WordPress now as a content management system. It started out as a blogging platform. We used it mainly for blogging. And then WordPress introduced custom post types, taxonomies and all that sort of stuff.

\n\n\n\n

So we are now using WordPress to build simple to complex websites. Forums. Some people use it for their colleges, universities as a social media platform, and some of them use it for a job board and everything, right? So we have plugin for everything and we can customize it and we use it.

\n\n\n\n

So when it comes to the traditional CMS, we call that as monolithic. I hope I’m not using too much jargon here. Monolithic in the sense it has everything into it. So for example, if you go to a website, the header, footer, the sidebar, and the content that you see and the forms and everything that comes from the same CMS itself. So it is going to be, let’s say, in the case of WordPress, it’s built mostly with PHP and JavaScript.

\n\n\n\n

So everything is going to be PHP template with a bit of JavaScript and CSS to it. But when you say on the contrast, headless CMS, it means, so you can consider that as a, I would not say person. Maybe something like, you can imagine something that doesn’t have a head. So in the sense the body is the same, head is different.

\n\n\n\n

So you can imagine that as, you are going to use the same admin panel and you are going to have the same WordPress features. You can add the content, you can add menus, you can edit anything, you can add users, all that stuff. But when I view the website, so it’s not going to be your theme. So it’s not the typical way of how WordPress gets rendered.

\n\n\n\n

So instead we will be decoupling it. So that is WordPress admin will stay on another site. It can be on a subdomain or a sub folder, but the front end is going to be a different platform. So it’s going to be hosted in a, mostly a JavaScript based stuff. So you can use either React based frameworks like React itself or Gatsby, Next.js or Remix, or anything that you like.

\n\n\n\n

And also you can either go in another route as well. So you can make it like a fully static website, or you can render it on every time as a server side rendering as well. So every call will go to the server and renders.

\n\n\n\n

Okay, so now we can call that a small intro about headless. You may already know this one. It became a buzzword a couple of years ago, right? But now everyone wants to go as headless. I see that company goes headless, or my competitor goes headless. So I want to go that way. But, unpopular opinion. Maybe you might hear some other people say that too. Headless is not for everyone, or I would say not for every use case.

\n\n\n\n

It depends on how much content that you publish. What are your goals and what you want to achieve. So headless is good, it’s performant, it’s fast, secure, and it gives you more freedom and flexibility, especially in terms of performance it’s really good. But I would say it’s not the something like you should go headless. It’s not the answer.

\n\n\n\n

[00:09:10] Nathan Wrigley: So essentially you’re saying that there are scenarios where this is desirable, but there’s going to be other scenarios where WordPress, in the traditional sense of the word. The regular WordPress that you download, perhaps use a hosting company and it’s all driven by PHP. The normal way of doing WordPress. That might be the best solution for lots of people.

\n\n\n\n

Okay, so we’ve got our WordPress website, which we can interact with, and then the content that comes out of that website is pushed to something else. And probably we’ll get into what the options are there. But let’s take the use case of a company which comes to you and says, okay, we’ve heard this buzzword. We think that we want to go headless.

\n\n\n\n

What are the benefits of going headless? Let’s forget about all the problems that might be associated with it. Can we just iterate through the things that you will gain if you manage to pull off a headless WordPress website. Now, I know there’s going to be all sorts of different scenarios there, but maybe just pick out the low hanging fruit. Some of the things which you believe are really beneficial.

\n\n\n\n

[00:10:17] Lax Mariappan: Yeah. The first and foremost, or the popular one, is the performance. So WordPress uses PHP templates. We will do everything with PHP and Javascript and also a little bit of caching to render our traditional CMS like traditional pages. If you use a normal WordPress installation with a theme. So that’s how it’s get rendered.

\n\n\n\n

So there you can see it depends on the hosting company as well, and also how much plugins that you use and how you configured them. So that affects the performance of a site. But when it comes to headless everything is going to be bundled, and there will be how a normal JavaScript based application gets rendered.

\n\n\n\n

So it’s going to be a modern web application where you have control over, for example, if your page doesn’t use certain CSS classes, those CSS will not get loaded for that page. So I would say the assets that are loaded, it will be less. And the images will be more optimized. In either case, like in traditional too you can optimize images, but it’s like the performance is the first one, I would say.

\n\n\n\n

It’s going to be both developers will love it and also the site owners, and also, let’s say marketers, Everyone will like the performance aspect of it. And in terms of headless, I would say developers will like it, especially in terms of, so you can repurpose the content. So if you are having a CMS, WordPress as a headless CMS, you can use that same endpoint, get the data and display it in a different formats quickly.

\n\n\n\n

Other than a WordPress theme. So for example, if you’re using a WordPress theme, you have to create multiple templates. So this is a template for mobile, and this is something that, for example, if you want to use it for a landing page, you may have to do some small or extra changes. But when it comes to headless, you can just customize it in a way that you want to.

\n\n\n\n

For example, I want to have a landing page. I don’t want certain stuff to be there. So you can turn on, off certain components, that’s it. So it’s like you can render the blocks and render the content faster. So developers and designers will like it. And also, in terms of the security, that’s where I’m more interested in cybersecurity especially. When people say WordPress sites are not secure, that triggers me actually. Yeah, I do get angry.

\n\n\n\n

So it’s like, you don’t have to worry about that. So you don’t have to worry about changing your login page url. Adding captcha to your login form, all that stuff. Because that URL is going to be safe and secure. No one knows where you are hosted your CMS.

\n\n\n\n

[00:12:49] Nathan Wrigley: Can I just interrupt there? So could you explain that, because I imagine there’s a bunch of people scratching their head at this point. Because normally, let’s say you have a website, it’s example.com. You’re going to go to example.com/wp-admin, and there is your login page. But there’s something in between here. I’m not sure that we explained that quite. So just explain why the login is secure. Explain where it is and why it’s not normal WordPress.

\n\n\n\n

[00:13:19] Lax Mariappan: Yeah, so I mean, normal WebPress is also secure but people can guess it, right? Say example.com/wp-admin, so they know. They can see from the source code and the page source, they can see oh, this looks like a WordPress site. And then they can guess the admin url. So slash wp-admin, it’ll redirect them to the login page, right.

\n\n\n\n

But when it comes to headless, the example.com will be hosted somewhere, and the front end that you see will be different. So for example, let’s say CMS is your WordPress installation, all WP. So you can call that like wp.example.com. So that’s where your WordPress stays in. But when you go to the example.com, that’s your front end, so that’s just JavaScript and html. So it’s like, if somebody wants to hack your site or somebody wants to, just guess what will be the admin url. So they cannot.

\n\n\n\n

[00:14:10] Nathan Wrigley: It’s a difficult concept to understand if you haven’t encountered this before. But what you’ve got basically is a WordPress website, which is the container for the content, but it isn’t the website and we’re not used to that in traditional WordPress. You go to example.com/ wp-admin, get redirected, log in, do all the things, and click publish, and as soon as you click publish, it will be present on the website. That’s not the way that this is working because the WordPress website is completely decoupled from the thing which is presenting it to the world, right?

\n\n\n\n

[00:14:48] Lax Mariappan: Yeah. Yeah. Completely decoupled.

\n\n\n\n

[00:14:50] Nathan Wrigley: So given that, there’s no connection between, okay, here’s my website at example.com and where I might log in. And because of that there isn’t the capability to just guess the login page and then bruteforce an attack and so on. So in terms of security, it offers that benefit. The thing which people are most worried about, somebody getting your admin password going in and spoiling your site. That’s highly unlikely because they simply won’t know where to look.

\n\n\n\n

[00:15:23] Lax Mariappan: Yeah. And also, so for example certain normal pages like comments, so that’s where we get a lot of spam, right? So comments will go to comments.php. When you submit a form without any data, or maybe if it’s spam data, it just goes there, right? But when it comes to headless, we will be using some extra customization for the comments and everything.

\n\n\n\n

So it’s not going to be the data will store as comments in the database, and it’ll be, you can view them as comments in the admin panel. But when you are viewing it in the site, so you are reading a blog post, you have a comment form, so that form is HTML and JavaScript. So that’s not how a typical, a normal WordPress form, normal comments form.

\n\n\n\n

So that’s where you will get less spam as well. So you don’t have to worry about that too. Like people submitting spam data and also any other form. So that’s another thing. And you don’t have to worry about any other security related stuff, because it’s just static.

\n\n\n\n

So people cannot do anything or manipulate data. So it’s going to be just HTML stuff. Whatever they can do is just view the data. So I would say in the headless, so if you are viewing some pages or we are in a archive page and post archive, news archive, any archive page or any other page that does the data and fetches the data from the database, all that stuff.

\n\n\n\n

So all that stuff will be protected routes. So people cannot easily guess. Sometimes you might encounter database related attacks, right. So you may hear cross site scripting attack or any other stuff like, somebody trying to get data either they pull your data or they want to insert some other data to the database. That’s not the case.

\n\n\n\n

Everything is going to be static, like just html, and it’s only read only. So people are not going to input any data. And the input will be just maybe a comments form, contact us form, something like that. And that will be handled. It depends on what form provider you are using, or how you configure it, but still it’s more secure that way.

\n\n\n\n

[00:17:25] Nathan Wrigley: So just to reiterate the point one more time, just in case anybody hasn’t been paying attention. We have our WordPress website. It is used by the developers, by the content creators, by the editors. They do their normal work inside of WordPress, but the thing which is being viewed on the front end by the population at large is completely separate.

\n\n\n\n

You’re just sucking the data out of WordPress and putting it into whatever you like. The security’s fairly obvious, you’ve explained that really well. The performance, obviously, if all that you are showing is static html, essentially. That’s going to load really, really quickly. Nothing needs to be built at the time that the page is viewed and so on and so forth. It’s already been created.

\n\n\n\n

This all sounds amazing and of course that raises the question, why aren’t we all doing it? And you have given us, in the show notes you’ve given me, three different things which we perhaps should talk about, and some of them, you explained the problem and then we’ll get to the solution.

\n\n\n\n

So the first one that you talk about is dependency hell, you’ve described it as. And, I’m guessing that having a headless site is not straightforward. We’re very used in WordPress to, novices can install WordPress incredibly quickly. You basically upload a zip file and unpack it and connect it to a database, and these days, you know, you go to a hosting company and not even that. You just click a button and, wow, there’s your WordPress website 30 seconds later.

\n\n\n\n

I’m guessing that this is not the case for headless. There must be all sorts of complex layers of things going on in the background, and you say that in many cases it can become very difficult. Dependency hell. So describe the problem of all the dependencies.

\n\n\n\n

[00:19:13] Lax Mariappan: So when you have a WordPress installation, we will be installing plugins, right? You might be, if you are using WordPress for a while, you are already aware of the jQuery migrate plugin. All that stuff. So WordPress uses jQuery even now. So jQuery is a dependency that WordPress requires. WordPress depends on jQuery in admin panel, and also on the front end.

\n\n\n\n

So if you want to get rid of jQuery, it’s kind of, WordPress may not be the same, if you want to eliminate that. Because WordPress depends on it. So it’s something like, let’s say you cannot say that as a oxygen, but it’s something that we all need it. So we need that to survive. So WordPress needs jQuery to work normally.

\n\n\n\n

So similar case, when you are building a headless site, you will be requiring a lot of frameworks, libraries, and also packages. So for example, if I’m going to choose Next.js as my front end platform, front end framework. So Next.js is built with React. If I want to use Next.js, I may want to use some other Next.js related libraries.

\n\n\n\n

So it is something like if you are on Android, you may want to add extra apps on your phone. If you are an iPhone, you’ll be adding some more extra apps to extend, right? It’s the same case. Similar to plugins. Instead of that plugins, we will be adding packages. So that packages helps the developers to add extra features that we need.

\n\n\n\n

So the problem here comes in is, everything gets stacked in and one will be dependent on another. So, for example, if someone is installing a package like for SEO, and maybe that package will require something else. And let’s say if Nathan is maintaining SEO package and I installed it, and for example, for whatever reason, Nathan becomes a musician and he doesn’t, he is not interested in SEO anymore.

\n\n\n\n

So he may not be more active in maintaining that dependency, maintaining that plugin or that package. So what happens is I’ll be waiting for him to fix the bug or some errors. Or I will waiting for him to upgrade to the lightest version. But it’s not the case, right? So, my Next.js package will be waiting for Nathan, so it’s like I’m depending on him, but he’s not available. So in that case, I have to go and do that work as well. So that adds to our development timeline.

\n\n\n\n

And then, so this is just one package and one scenario. So this happens with multiple packages and stuff. And this is not just Node or NPM packages. It also happens to WordPress stuff as well. So, for example, let’s say we have a popular forms plugin, or we have a popular slider or any other plugin.

\n\n\n\n

So you will install that plugin and you want that plugin to work with headless. So how we are using headless, it’s the data is stored in the WordPress, and we want to get the data through either Rest API. It’s a method that we, you know, you go to a url, you ask the WordPress, hey, give me this data and it’s going to give. Or you’ll be using GraphQL. It’s the same. You go to an endpoint and you’re going to say, hey, I’m looking for this post. I want five posts from this date. So it’s going to give that data as well.

\n\n\n\n

So either you use Rest API or GraphQL. The problem is a plugin that you are using, your popular forms plugin, your popular slider, or any other plugin that you’re using. LMS plugin, E-commerce plugin or any plugin, like a payment gateway. So you have a plugin and you want to use it with headless. So that plugin should work with the Rest API or Graph QL. So if that doesn’t work, if that doesn’t give you the flexibility, and then you are still stuck there.

\n\n\n\n

Because you cannot go and create everything on your own, right? So we cannot reinvent all the wheels. We don’t have time to create everything from scratch. So that’s where it’s like that becomes a bottleneck. So you are like, hey, I found the plugin. I started working on it. It works up to this mark, but it’s not a hundred percent. So it’s like it does its job 80%. Now I have to go fill in that 20%. It adds to the budget, it adds to the development timeline. So that’s the dependency hell.

\n\n\n\n

[00:23:15] Nathan Wrigley: Yeah. So in the case of all of the technology, which is in the background if you like, which we haven’t really talked about too much, but like you said, the things which you are requiring from third party developers. There’s a dependency there, and it’s very similar to the dependency that you may have on plugins, you know, you want them to be updated and so on, but you are adding extra dependencies. And of course, the more dependencies you’ve got, the more costly, time consuming it is.

\n\n\n\n

I’m guessing that most of the things that you are depending on, in addition to WordPress and you described what a few of those were, you could, I suppose, do some due diligence and figure out which projects have been well maintained, updated frequently, and so on. And I guess in the open source world, much of the dependencies that you’re using will be open sourced, so you could fork them. But again, you are creating probably a large amount of work for yourself and your team.

\n\n\n\n

[00:24:13] Lax Mariappan: Yeah that’s true. Well said. So it’s like, since it is open source, it’s good. Like lot of reviewers. We have a lot of eyes on the code, and you can fork it. You have the freedom to do whatever you want. But still you are looking for a solution and that becomes a problem. You have to fix that as well. And that adds to the, another dependency, another dependency. It becomes a cycle that you cannot escape sometimes.

\n\n\n\n

[00:24:36] Nathan Wrigley: I guess this is a bit like a seesaw. You know, on the one hand you described all of the benefits, performance, security, and so on, of headless. And then on the other side is, is all of the things that we are now describing. You know, the dependencies and so on. You’ve got to weigh up at the beginning of the project whether one thing is worth all of the time and effort that may be required to do it.

\n\n\n\n

And I’m guessing in many cases, certainly at the enterprise level, the answer’s going to be yes, because the budget is there, we can put enough bodies to work to make all of this happen, and if we need to fork things, there’s enough people on the team that can do that and maintain the project, which has fallen into disuse. But for a little project the seasaw may tip heavily against something like headless just because of the things that you’ve described there.

\n\n\n\n

Okay. So that was our first thing, dependency hell. The second thing that you wanted to talk about was the fact that in the WordPress world, especially in the last five or six years or so, we are really used to what you see is what you get, WYSIWYG. You save something in WordPress, you publish something and you have almost a hundred percent certainty of what it’s going to look like. The backend looks like the front end, especially with things like page builders and so on. But you say that that’s not always the case with headless solutions. Why is that?

\n\n\n\n

[00:25:55] Lax Mariappan: We will be creating custom blocks. So, either there are a popular way of building now custom blocks is with ACF. So you all might be aware of and using it, even though you are not a programmer, you might be using it, right? So ACF is easy to install and create some custom fields. So you can use ACF to block, to build blocks for the site.

\n\n\n\n

So those blocks can be used or you can build your own custom blocks. You can use any block starters like, frameworks that are available now. Or you can just follow our, WordPress comes with packages that you can on build command, so you can just build your block in a matter of seconds.

\n\n\n\n

But still, all this stuff. So for example, if you are having custom blocks, I’m not talking about just normal blocks, like where you add a paragraph or image or something very simple. That is easy to build and that’s easy to see. That’s different. But I’m here talking about something complex.

\n\n\n\n

So for example, you can imagine that as an Elementor widget or, some other items that it comes with the page builders. So, let’s say a slider, maybe tabs, accordions, all that stuff, right? So that can be added through the blocks itself. But you cannot preview them, because when you add them in the admin panel and we add them in the content. Those content gets, you know, you can choose like, oh, this is the tab title, this is the content.

\n\n\n\n

And you can keep adding the content, but you don’t know how it’s going to render in the front end. But let’s say if you are using some, there are a lot of free blocks and also even premium blocks available. So if you are using a block to build them, and then using the normal WordPress installation. Or you can use WordPress with the full site editing, the modern themes, or the hybrid themes, like old plus full site editing themes.

\n\n\n\n

Still they both work well. Like you can preview, oh, okay, this is the tab I added this content. I can’t view this one. But when it comes to ACF blocks or other certain custom built blocks, you cannot preview them.

\n\n\n\n

So when a editor or a user adds content, they may get lost. So I have a slider. I want to add three, four images to it. I may get lost. Oh, what’s the third image? What I have added there, and how it looks? Is the images correct? Is the text rendered properly or should I reduce any title or text or anything, right? So all this stuff becomes a little tricky. And also sometimes it becomes a pain for the content writers, content editors, and also the site owners.

\n\n\n\n

[00:28:24] Nathan Wrigley: So in the normal, traditional WordPress, let’s say we’re creating a page, we add a page, and we use whatever tool it is that we want to use for that. We add in some blocks. We are perhaps using Elementor, whatever it may be. And we click publish and then we are able to immediately view that because WordPress is working in the traditional sense of the word. The page gets pushed through the templating engine and it’s rendered with its template and we can see it right away.

\n\n\n\n

But because that’s not happening here. And the mechanism for rendering that page is entirely different. You can’t necessarily view it immediately. Have I kind of encapsulated that? What you are doing in the backend, because it’s decoupled with the presentation layer on the front end, you can’t necessarily always see it?

\n\n\n\n

[00:29:16] Lax Mariappan: Yeah, so that’s the challenge. So the solution here is to customize the way you built. So for example, we can give them a preview button so they can preview what are the slides, and how they look. And they can see that immediately in the editor itself. Like when they are adding content in the block editor, they can see it.

\n\n\n\n

And also the other way is to have a button, a preview button. So that will preview before the content gets published. So, you can change the workflow. So if somebody hits, instead of publish, you can have like a preview button or keep it as a draft. So that way it’s like nothing goes to the front end without your approval or preview, right? So you have to preview it and see, oh, make sure everything looks correct, and then you can say, hey, I want to publish it. Yes, confirm, publish it, and then it goes to the frontend.

\n\n\n\n

[00:30:04] Nathan Wrigley: That’s fascinating. That’s really ingenious. So, because we can’t necessarily see it on the frontend, you and your team have built a custom preview system. So on a block by block basis, you can see what that block will look like when it’s rendered. So in the example of your slider, presumably where we’ve got three or four fields. We’ve uploaded maybe some text, we’ve uploaded an image, and it’s just a bunch of fields. Normally we’d click publish and we’d go to the page and preview the page and we’d see it right away. But in your scenario, you are going to hit a button inside the block to show what that block and that block alone will look like. Have I understood that?

\n\n\n\n

[00:30:48] Lax Mariappan: Yeah, that’s what we did. Because the users, they are used to the traditional WordPress. And especially that was with classic editor, I mean the old editor. So if you insert an image, they can see it’s an image. And if you insert something, you can see. And we are all used to the page builder era, right? So if you add a accordion, you can see how the accordion is going to look.

\n\n\n\n

But when it comes to headless, all this stuff is going to differ. So, the tabs, accordions, sliders, and also anything else, any other custom stuff that we built, we added a preview button, and when you click on the preview, you can see that right away.

\n\n\n\n

Then you can make sure like, oh, the colors are correct, the image is correct, and everything renders properly. Because sometimes if you are not looking at the content and adding content, you might miss some data, right? So you might have missed a small setting that says full width, or you know, boxed. So then you feel like, oh, why this looks so awful. Oh, I’ve missed this full width button. So that’s how the preview button works.

\n\n\n\n

[00:31:49] Nathan Wrigley: So if I’m looking at the block and it’s a, let’s stick with the slider just for the sake of it, and I’ve uploaded my images and whatever fields were required and I click the preview. Does it literally happen inside that block? Or is this some kind of modal which pops up and shows things? Or is it, is it literally taking over the block itself?

\n\n\n\n

[00:32:09] Lax Mariappan: Ah, it’ll be within the block. Like it will replace, so for example, if you have a block and you are adding some content to it, and when you click on the preview, it’ll replace where you are adding the content, right? It’ll replace the form. Form of the block where you are saying like, hey, this is the title, this is the subheading, this is the description. Instead of that, it’ll just render the titles, heading and description.

\n\n\n\n

[00:32:32] Nathan Wrigley: Right, and then you toggle that off again once you’re, once you’re happy. So, ah, that’s really interesting. So the workflow there is really very different. And I’m presuming that after a period of time, the people who are editing, creating this content, that just becomes part of the process? They just understand that, okay, rather than viewing the whole page or whatever it may be, post whatever, I’m just viewing this little bit, and I’ve done it several times now and I’m confident that if it looks right inside the block preview, then I can click publish, wait for everything to happen, and hopefully that page will go live. And, it’s just a different workflow that you have to get used to. But once you’ve done it several times, it’s, familiar and normal.

\n\n\n\n

[00:33:14] Lax Mariappan: Yeah, it becomes part of the workflow. And also, like we discussed earlier, your site will be like, CMS.example.com. And the front end will be on example.com. Sorry, every time you have to go to example.com/about, example.com slash contact us. Instead of that we will have a preview button. So, you can preview each block and you, if you, or feel like, hey, I want to see how the whole page looks like, you can click that preview, and that will take you, or that will show you immediately, oh, this is how the front end, like example.com/the page will look like.

\n\n\n\n

[00:33:45] Nathan Wrigley: Yeah, that’s a good point. We’re so used to the preview button being connected to the URL in question, because it’s being rendered by WordPress. You click the preview page button or whatever it may be, and it takes you to the correct place. In this case, there’s no connection between what the URL will be and where you currently are, so yeah, that’s fascinating.

\n\n\n\n

Just as a bit of an aside. We haven’t got into this, but I think it would be a good topic to discuss for a couple of minutes. If WordPress is separated from the presentation layer, this sort of headless notion. How often does the website get regenerated, if you know what I mean? So for example, if we click publish in our headless WordPress website, what is typical there? Are you going to generate the page immediately and store it as static html? Or do some clients have different expectations there? You know, for example, if you are a, a site which needs to publish things regularly, perhaps you need that capability.

\n\n\n\n

I click publish. I want that page to be live within a matter of moments. Or it may be that you’ve got a website where it doesn’t really matter if the pages are not built, I don’t know, three hours, six hours a day, whatever it may be. Do different clients have different expectations there?

\n\n\n\n

[00:34:56] Lax Mariappan: Yeah, that depends on how the publication frequency is. If you want to publish immediately, we can do. If you are okay with publishing the changes after two, three hours, still we can do. So it’s about how you want to set, how you want to build the things.

\n\n\n\n

So here, few things to consider. You can go with static, fully static website. That’s just static and only when a page gets updated. So for example, you have a hundred page. All of them are static and those pages will not be regenerated. So if you change just the about page and only that 99 pages will remain the same. Only that about page will get regenerated again. You can go that route.

\n\n\n\n

And also you can go with, every time in the page gets rendered, you can go server side rendering. So every time that’s new, so you can go that route as well. So that depends on how you want to render the data and everything has pros and cons. The normal way is like how Next.Js does now. Because it is like, keep everything static and if you want to render something, you can still regenerate the specific page.

\n\n\n\n

So this way it’s like you don’t have to build everything all the time. So you can build what has changed in the WordPress. You can see that in the headless frontend. And also you don’t have to wait for it. So, for example, if I go make some change and click update and you can see that immediately.

\n\n\n\n

[00:36:21] Nathan Wrigley: Really interesting, because there is no exact way of doing this is there? You can just build it in whichever way you think is most beneficial, or whatever the client needs. You know, if, if it’s a newspaper website where, really I need to click publish, and within a few moments I need that page to be live because the content that we’re creating is tremendously important to be fresh and new and so on. But it may be that, yeah, you don’t have that expectation and you’re quite happy to have it work in a different way and publish on a, a much less frequent basis. I can’t really imagine a scenario where anybody would say no, I’d rather it was published less frequently, but maybe there are scenarios where that’s beneficial. I don’t know.

\n\n\n\n

Okay, and the last point that you wanted to talk about was, the whole conversation has proven to be really interesting, but it’s pretty clear that there’s a lot more work involved in this kind of website. And so your first point was about the fact that the dependencies, lots of dependencies. Your second point that was that you don’t always get to see what you see is what you get in operation. And the third one is basically the amount of time it takes, the amount of resources it takes. You’ve described this as headless asks for more. Tell us about that.

\n\n\n\n

[00:37:34] Lax Mariappan: Yeah, so when it comes to creating a normal WordPress, like a standard WordPress theme. So what you do is like, you start with your prototyping tool. Like it can be Figma, Adobe XD or anything. So you have your design ready, right? You are creating mock-ups, discuss with the client, and then create a mock-up and then find the variations, all that stuff. And you are settling in, hey, this is my design. And now I’m going to create the theme.

\n\n\n\n

So, I want to create this many templates. I want to create this many menus, all that stuff. When it comes to traditional stuff, it’s like, you don’t have to consider too many things. So it’s kind of straightforward process and like designers and developers can, the engineers can work hand in hand. And it’s, you can follow Agile like, build stuff, reiterate and just deliver it.

\n\n\n\n

So that’s how that works. But when it comes to headless, so you have to consider a lot of things. I would say the first thing is the knowledge or, you know, expertise. With WebDev Studios, we are, I would say kind of one of pioneers and also experts in WordPress plus headless stuff. So we have launched, it’s a open source like we have Next.js starter template. So if you want to try out Next.js a headless frontend for your WebPress site, you can just take a look at WDS Next.js starter. It’s free and it’s in GitHub, so you can just start using it.

\n\n\n\n

So, expertise comes one, like whether you should be, have sound knowledge in that. So you can go and fix stuff. You know what you are doing and you know what to expect and all that stuff. But this requires something like, for example, I am a backend engineer. I have limited React knowledge. I’m now catching up with React, Next.js, all that stuff. But I would, I would not say I’m an expert at it. I build stuff, I still use Next.js every day, but it’s like, I won’t say I’m an expert at it.

\n\n\n\n

So expertise is one. So your team should have sound knowledge in the framework or anything that you do. Or even if you don’t have sound knowledge, let’s say if you are doing something like, something very new, like Remix got released only one or two years ago, right?

\n\n\n\n

So if you want to go use Remix, You should be an expert in React and you should play around with React. So that’s the time. So my point is like time, it asks for expertise and it asks for time. So when it comes to just normal WordPress theme, probably you might finish the theme, let’s say, in a few weeks, or at least a few days even sometimes. With page builders finish it in few days or few weeks, right?

\n\n\n\n

But maybe if you are building it from scratch and you are doing a lot of customization, it may take a while. But when it comes to headless, may take even longer. So more expertise, more time, and all this adds up to more budget.

\n\n\n\n

This may sound like, oh, well should I do all this stuff? It’s kind of worth it. So you don’t have to, for example, if you have your, the front end components ready you may be having your storybook, like where you want to see how the button should look like, how the elements, how the panels are. Let’s say how each component will look like and how they render, all that stuff, right? So when you have all these parts ready, you can go from, for example, today I’m using Next.js, sooner I can move to something else, like I can use Remix. Or I can use something else that’s going to be hot in the market in future.

\n\n\n\n

But when it comes to the typical WordPress, you are going to change everything from scratch. So if you want to add a new theme, so maybe if you want to change the look and feel, that’s different. So everything has pros and cons, but the short answer is the headless CMS ask for more.

\n\n\n\n

[00:41:13] Nathan Wrigley: Yeah. It does sound like not only do you need more time to develop all of this for the reasons you’ve just described. It’s more complicated, so it takes more time. There’s more moving parts, shall we say. And it may also be that you need to spend some of that time not just building the thing, but learning how all of this hangs together, because there’s an awful lot going on in the backend here. And if you don’t have expertise in that, presumably things could go pretty wrong.

\n\n\n\n

With that just before we end. You’ve obviously decided at WebDevStudios that this is an approach. I don’t know if you build the majority of your sites in this way or subset or a proportion of them, not sure. But, typically what is the amount of time longer it would take to get a website out? Let’s say, for example, that if you were just going to use WordPress as is a normal WordPress website, and you built an exact same website, but did it headless. And let’s imagine a site with, I don’t know, several different custom post types.

\n\n\n\n

It’s got hundreds of pages. I’m just kind of making up something off the top of my head. But typically, you know, does it take twice as long, three times as long, 50% longer? What, what are we looking at?

\n\n\n\n

[00:42:28] Lax Mariappan: I’m going to answer just like other engineers do. It depends. But it’s like, I would say it takes a long, maybe you can say, maybe you can say double, but it should not take more than double or something. So that’s where I would say start with more of research. So you should not change frameworks or libraries in between. Like once you started as React, go with React. And if your team is, they are very comfortable and they’re knowledgeable in React, use that. If you are going to use Vue.js or Astro or any other framework. When you start with something and you can go with it.

\n\n\n\n

So, it is a matter of discovering what the client needs and where the goals meet. How we can achieve it. And once we are very clear on that, you can start developing. And during the development phase itself, we can see what are the possible, you know, the bottlenecks or what causes the issue, what could be a problem, and we can figure out other different approaches and solutions.

\n\n\n\n

So, for example, you don’t have to let’s say, PayPal is not the only payment provider right now, right? The payment gateway. So we are using so many different stuff and they do the payment integration quickly. But before those days, let’s say 10, 15 years ago that case was different, so now we have more options.

\n\n\n\n

So similarly, you don’t have to create a form and you don’t have to wait for someone to, the third party or some other open source in a package or something to be ready. So either you can build something on your own if you have time and budget, or you can fork something and then you can adjust to it.

\n\n\n\n

Or the other way is, I would say you can go with some existing third party or SaaS or any other solution, which is just already there and you can see how you can use it with WordPress. So these are the stuff that can reduce your development time.

\n\n\n\n

So when you say if you are, I don’t know exact hours or something, let’s say a thousand hours. So if you say a thousand hours for a normal WebPress installation, so headless may take a little longer, 1,500 or 2000 or anything. But it depends on what the client wants and what framework you choose and your expertise, like, I mean, the whole team’s expertise. And also how well we plan, organize, and go.

\n\n\n\n

So sometimes it’s like just the client takes so long to respond, or sometimes it’s just like, even the client is clueless or what’s happening. So that adds up to some stuff. And I would like to also highlight, when you hear all this stuff, somebody listening is, they will be scratching their head like, so headless is yay or nay.

\n\n\n\n

So, recently, I cannot say the client name and stuff, but I would say, how we figured this out and how it is kind of helpful. So we had to publish more than 20 websites. That’s for a single client. And all of them are different, and all of them are headless, but that’s for a single parent company.

\n\n\n\n

So what happened is, we had the architecture ready, right? So we, we know what happens when you publish. We have everything ready. I mean, the back end and the front end ready. So things become more easier that way. The development time is actually just for one site and then other sites, it’s just like, it was fast.

\n\n\n\n

But we had enough configuration and enough options we given to the client. So every site is not going to look exactly the same. They have their own customizations. But still it’s like amount of development time is the same or is actually less when you compare to traditional. But it depends. It depends on what’s the use case? How, what you are trying to build and everything.

\n\n\n\n

[00:45:52] Nathan Wrigley: Yeah, it really does sound, there were so many good perspectives at the beginning where, you mentioned performance and so on where this is definitely going to be worth it. I guess if the client is willing and the budget is available and the expertise is there, then this sounds like an incredible option. Steep learning curve probably, but a lot of benefits on the backside of that.

\n\n\n\n

Lax, just before we round it up, if somebody has been thinking about playing with headless and they’ve listened to this and they think, okay, I’d like to take that a bit further. Couple of things, firstly, where can they get in touch with you? But also have you got any guidance about resources that they may find useful?

\n\n\n\n

So that could be a website or a book or whatever it may be. So let’s start off with resources and then we’ll turn to you to finish it off. So what resources do you recommend to learn about headless in general?

\n\n\n\n

[00:46:49] Lax Mariappan: In general it’s like you can start with WP Engine has their own blog. They have stuff about headless WordPress and they also have some of packages and stuff they maintain. They have Atlas. It’s a platform they are planning to go full fledged on headless stuff. And also you can read about GraphQL, WP GraphQL. Their team is more active and they share a ton of stuff on how to customize and maintain stuff with headless.

\n\n\n\n

And also you can, like a shameless plug. So I’d also highlight about our WebDevStudios blog. So you can see a lot of headless related articles, tips, and tricks. If you want to play around like, you know, you don’t have to spend something to test it out. So you can go with a lot of free starter templates.

\n\n\n\n

So we have, WDS has like WebDevStudios has a starter template. We have Next.js starter. So that’s a headless thing. All you need is your WordPress, and then you can install that on a locally in your laptop or machine, and then you can just test it out, how it looks, compare the performance and everything.

\n\n\n\n

And also, like other developers and writers have their own stuff. Like Colby Fayock is a popular WordPress developer. He has his own Next.js starter. So you can just simply Google WordPress headless starter, and you can find a lot of starter templates. If you are a developer, go this route or if you are a, you know, site owner or you are just hobbyist, you want to just try or understand a little bit more?

\n\n\n\n

You can still do that reading the resources, right? You can actually check our blog as well. WebDevStudios blog. We have, I would say a couple of headless related stuff. That’s one of the popular article last year. Why headless WordPress is trending. So you can see why it is trending, what to expect. You can read more details in that blog.

\n\n\n\n

[00:48:40] Nathan Wrigley: Thank you very much. And then finally, just to finish this off. Where could people get in touch with you? Are you available on social media? Maybe an email address? Whatever you’re comfortable with sharing.

\n\n\n\n

[00:48:50] Lax Mariappan: Sure. You can find me on, you know, Lax Mariappan. I’m on all the social media like Twitter, Instagram, Facebook, and everywhere you can find me. So you can reach out to me as an email as well, laxman.0903@gmail.com. Anywhere like GitHub everywhere is the same. Luckily I got my name on all the social media, so you can find it.

\n\n\n\n

[00:49:10] Nathan Wrigley: Lax Mariappan, thank you so much for chatting to me today. I really appreciate.

\n\n\n\n

[00:49:16] Lax Mariappan: Thanks Nathan. It’s been great. So I’ve been listening to WP Tavern Podcast for a while. Especially, I like to catch up with what’s going on. The new stuff with WordPress. So it’s good to be on the show,

\n\n\n\n

[00:49:28] Nathan Wrigley: Well, you are most welcome. It’s been a really interesting and informative episode. Cheers.

\n\n\n\n

[00:49:34] Lax Mariappan: Cheers. Thank you.

\n
\n\n\n\n

On the podcast today, we have Lax Mariappan.

\n\n\n\n

Lax is a web developer based in the Philippines. He’s an Open Source enthusiast, and lover of all things WordPress. Lax has been tinkering with websites since high school, but it all changed when he discovered WordPress in 2010. Lax currently works as a Backend Engineer at WebDevStudios.

\n\n\n\n

We talk today about Headless WordPress, and it’s a complex topic. Headless is the concept of decoupling the WordPress admin from the frontend of the site. WordPress will continue to work as expected, but the presentation layer will be done by a different technology. React, Gatsby and Remix being some popular choices.

\n\n\n\n

This implementation of WordPress is complex, requiring technical knowledge above and beyond that needed for a more typical WordPress install, but it has its benefits.

\n\n\n\n

Lax talks through all of this in great detail. How keeping on top of all the additional dependencies Headless WordPress requires can be time consuming. How it can create difficulties for content editors who don’t always get to see what their work will actually look like in real time. Why this approach to WordPress can take more time and resources during the build.

\n\n\n\n

Lax explains how these problems typically crop up, and how it’s possible to plan ahead and build in solutions for all the problems that you might encounter.

\n\n\n\n

If you’ve ever thought about going Headless with WordPress, then the podcast today is for you.

\n\n\n\n

Useful links.

\n\n\n\n

React Library

\n\n\n\n

Gatsby

\n\n\n\n

Remix

\n\n\n\n

WebDevStudio Next.js WordPress Starter

\n\n\n\n

GraphQL

\n\n\n\n

WPGraphQL

\n\n\n\n

WebDevStudio Blog

\n\n\n\n

Colby Fayock’s website

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 15:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Nathan Wrigley\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"Do The Woo Community: 95% of Websites Have an Issue with Color Contrast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://dothewoo.io/color-contrast/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:379:\"

Even just by getting your color contrast right, which is very easy, anyone can do it. You just use a contrast checker.

\n

>> The post 95% of Websites Have an Issue with Color Contrast appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 10:43:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress Performance Team Working Towards Unbundling Performance Lab Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140668\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"https://wptavern.com/wordpress-performance-team-working-towards-unbundling-performance-lab-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4476:\"

WordPress’ Performance Team met this week with the express purpose of responding to Matt Mullenweg’s recent request to stop adding functionality to the Performance Lab plugin which could otherwise work as a standalone plugin.

\n\n\n\n

At the end of December 2022, the Performance Team published instructions for how to test the new SQLite implementation, which was bundled into the Performance Lab plugin as a module. Mullenweg commented on the post, indicating he saw the SQLite functionality as better suited to becoming a standalone community plugin:

\n\n\n\n
\n

Can we please make this its own community plugin, hopefully to become a canonical one, and stop putting additional things like this into Performance Lab — it feels like we’re stuffing things into PL unnecessarily.

\n\n\n\n

In mid-October I have requested that we stop this unnecessary bundling before with @tweetythierry around WebP, which was put into Performance Lab, so it is disappointing that another large function like SQLite was bundled into Performance Lab plugin.

\n
\n\n\n\n

In an effort to galvanize a base of testers for upcoming performance features, the Performance Team has leaned towards bundling new performance-related functionality into the plugin. Although they are already developed as self-contained modules so they can be easily extracted as individual plugins, the concern is that their visibility would be greatly reduced. The Performance Lab plugin has more than 30,000 active installs. Any standalone plugin would take time to build up to a user base, whereas functionality added to Performance Lab has an instant audience.

\n\n\n\n

“Agreed that there are definitely valid use cases for stand alone plugins, remaining mindful of some of the advantages of a single hub plugin such as development/maintenance, adoption, promotion, developer onboarding/contribution etc. which the Performance Lab facilitates well today as a central performance focus community hub plugin,” Performance Team contributor Thierry Muller said in response to the unbundling request.

\n\n\n\n

Muller outlined three different options contributors discussed in this week’s Performance Team meeting:

\n\n\n\n
\n
    \n
  • Option 1: Keep PL as is, but additionally deploy modules as individual plugins
  • \n\n\n\n
  • Option 2: Make PL a “wrapper” focused on central infrastructure and recommendation of individual plugins
  • \n\n\n\n
  • Option 3: Deprecate PL completely in favor of individual plugins
  • \n
\n
\n\n\n\n

Option 3 seems to be the least attractive to those who participated in this week’s discussion, as it introduces more hurdles for discoverability. Performance Team contributor Felix Arntz noted that one benefit of option 1 is the plugin would continue to work as-is for the 30K people who currently have it installed and that option 2 “would require a complex migration that users likely would not understand.”

\n\n\n\n

WordPress developer Jonny Harris suggested that having each functionality in its own plugin helps with testing but also asked what defines a module.

\n\n\n\n

“Would the current Site Health checks all be together, for example?” Harris asked. “SQLite and WebP are clearly their own modules, but what about smaller things?”

\n\n\n\n

Arntz suggested contributors continue the discussion regarding the scope of how the current modules could be distributed as plugins. He suggested every module could become its own plugin where some modules become standalone plugins and others would be grouped together into a few “topic specific” plugins.

\n\n\n\n

Contributors are discussing the different approaches in more detail on a GitHub issue and will be voting on the best approach. The vote will be open until Friday, January 20, 2023.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 03:34:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:49:\"HeroPress: Why small can be just the right choice\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:150:\"https://heropress.com/essays/why-small-can-be-just-the-right-choice/#utm_source=rss&utm_medium=rss&utm_campaign=why-small-can-be-just-the-right-choice\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7334:\"\"Pull\nHere is Ellen reading her own story aloud.\n\n\n\n

I feel honoured to write an essay for HeroPress. While thinking about what I should write about, I wanted to make sure it will be helpful to others.

\n\n\n\n

Of course, everyone’s goals are different. My partner Manuel and I started to create WordPress products, because we saw the opportunity to build a small business and keep it a business we both felt comfortable to work in over the years. And that’s what we did. We love to travel and searched for a way to live the nomad lifestyle long before the term was even a thing. We travelled and worked on our blog and themes. And don’t get me wrong, it was not easy in the beginning. We had to build an audience first, so we wrote blog posts about everything we learned while keeping financially afloat with small client projects. We put endless hours of work into our blog, before even dreaming of one day earning income just with our themes. But we loved every minute of it.

\n\n\n\n
\n

We worked from Thailand, Sri Lanka and New Zealand, and we felt creative and free.

\n
\n\n\n\n

We went to WordCamps and creative conferences along the way and met so many new people with similar values and goals.

\n\n\n\n

Having these experiences formed our way of thinking about the way we wanted to work moving forward. The benefits of being a small team of two seemed so obvious to us. We could make decisions fast and react to new trends without asking anyone for permission. As long as we built something others liked, we would always be ok. So that’s what we focused on. We built one theme after the other and loved the creative freedom this work gave us. The positive feedback and listening to the stories our customers shared on how our themes helped them reach their goals kept us going.

\n\n\n\n

Living abroad

\n\n\n\n

As we could work remotely from any location. We didn’t need an office or a local team. Keeping our business so flexible allowed us to move from Germany to New Zealand in 2015. After about two years working towards it, we were able to apply for a business visa and eventually for permanent residency four years later. Living away from family is never easy, but the opportunity to live in another country surely teaches us so many valuable lessons we would never want to miss. It’s a true gift, all made possible by our small WordPress business.

\n\n\n\n

Reacting to changes

\n\n\n\n

Fast-forward to 2018 and the WordCamp Tokyo, where we first got the chance to dig deeper into the Gutenberg project during contributor day. We knew changes were coming, and we needed to react with our business. Even before, we felt that building one theme after the other felt a bit tiresome and not like the most effective way for WordPress users to build their site design. We were never convinced by the page builder solutions, as it just seemed too bloated and untrue to WordPress core to bring a wow effect to us. We love to keep things flexible and minimal, and adding an entire framework on top of WordPress never felt like a great idea to us.

\n\n\n\n
\n

So here comes this Gutenberg thing, a promise to a more flexible, component based way of creating designs for WordPress.

\n
\n\n\n\n

We felt like this is meant to be for us. So once home from the WordCamp we started to build blocks and explore how this new WordPress would work. We did not realize back then how big these changes would become and how much it would impact our work and our business.

\n\n\n\n

But it felt good to build something new and to try to find a better solution to offer for our theme customers. We struggled to gain footage for quite some time, as there were just so many new technical things to figure out and so much was unclear. But we still never doubted that we are on the right track, as with every new release the opportunities seem to get better and more stable.

\n\n\n\n

And just now we are just about to relaunch our business websites with a brand-new block theme that is solely built with our blocks, WooCommerce blocks and WordPress core blocks. It finally feels like all the work comes together and themes and the Gutenberg project are ready to be merged into one and released for production.

\n\n\n\n

Opportunity to pivot

\n\n\n\n

During all these changes, we had the time to think about the future of our WordPress business and what we want our road ahead to look like. Many others around us have sold their independent businesses or took a job at one of the big WordPress businesses. I feel like it’s also a natural path of WordPress and all of us growing up.

\n\n\n\n
\n

For us, we feel like we are just getting started again, finally having found a way to have fun creating for WordPress again.

\n
\n\n\n\n

Building one of our last classic themes, we felt like we had lost the fun in designing for WordPress. We felt like themes were stuck, being either too inflexible and or way too bloated to be any good. It felt like we were trying to build, squeeze out a solution into a product that technically was never meant to be this way.

\n\n\n\n

Block themes, the site editor, patterns, and blocks come as a chance for us to do it better. It’s a big shift and a difficult project to pull off, for sure. WordPress is used by so many people in so many ways. But block themes make WordPress lighter, and they don’t stand in the way of other add-ons as much as classic themes felt they were. It’s amazing how we can take all the components apart and mix and match them together. There are still missing pieces, but we are getting there.

\n\n\n\n

For us, we are taking this shift that we are sort of making together with WordPress, as an opportunity to make things better. We always felt like we wanted to offer more support and help to our customers. But we never found the time. So with our upcoming relaunch, we are taking the chance to change that. We will offer new services and are exploring more ways to offer our customers what they actually need. It feels like a breath of fresh air to us, and we haven’t had so much fun with WordPress in a long time.

\n\n\n\n
\n

It’s funny, who would have thought that a piece of software can impact your life in such a big way.

\n
\n\n\n\n

WordPress has impacted where we live, who our friends are and which destinations we like to visit. We feel more open-minded because of WordPress, we believe in the power of open source projects and we believe that a group of people from all over the world can build something meaningful together.

\n

The post Why small can be just the right choice appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 01:15:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Ellen Bauer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WordPress.org blog: WordPress is Turning 20: Let’s Celebrate!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14155\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wordpress.org/news/2023/01/wordpress-is-turning-20-lets-celebrate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1474:\"

2023 marks the 20th year of WordPress. Where would we all be without WordPress? Just think of that! While many technologies, software stacks, and fashion trends have come and gone throughout the past two decades, WordPress has thrived. This is due to the fantastic work and contributions of the WordPress community, comprised of thousands of contributors; and millions of users who have embraced the four freedoms of WordPress and the mission to democratize publishing.

\n\n\n\n

Let’s celebrate!

\n\n\n\n

Throughout the beginning of 2023, leading up to the official anniversary date of WordPress’s launch (May 27, 2003), a number of different events will celebrate this important milestone, reflect on the journey, and look toward the future.

\n\n\n\n

Please join in!

\n\n\n\n

Over the next few months, be sure to check WordPress’s official social media accounts along with the official anniversary website for updates on how you can be involved in this exciting celebration by contributing content, collecting cool anniversary swag, and much more. 

\n\n\n\n

Use the hashtag #WP20 on social media so the community can follow along.

\n\n\n\n

If you have something planned to celebrate that you would like to be considered for inclusion on the official website, please use this form to share the details.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 21:38:49 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Dan Soschin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:88:\"Do The Woo Community: WooCommerce, Payments and Crypto with Keala Gaines and Dave Lockie\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74249\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"https://dothewoo.io/woocommerce-payments-and-crypto/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:416:\"

Keala Gaines from WooCommerce and Dave Lockie from Automattic chat about the relationship between WooCommerce and Crypto.

\n

>> The post WooCommerce, Payments and Crypto with Keala Gaines and Dave Lockie appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 10:11:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"WPTavern: Gutenberg Times to Host Webinar on How to Use New WordPress Layout Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140874\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:96:\"https://wptavern.com/gutenberg-times-to-host-webinar-on-how-to-use-new-wordpress-layout-features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4790:\"

Gutenberg Times will be hosting a live Q&A webinar titled “Layout, Layout, Layout” on January 11, 2023, at 05:00 PM in Eastern Time (US and Canada) via Zoom. This event is open to WordPress users of all experience levels who are interested to learn more about how to use WordPress’ layout features when building sites with blocks.

\n\n\n\n

Host Birgit Pauli-Haack will be joined by WordPress veterans Isabel Brison, Andrew Serong, and Justin Tadlock. Brison will be demonstrating different layout scenarios during the presentation, and attendees will be able to participate with questions.

\n\n\n\n

Any user who has attempted to layout a design in WordPress has likely tried out container blocks that offer layout settings. These blocks include Columns, the Cover block, and the generic Group block.

\n\n\n\n

The event will cover how to manipulate layouts by defining the width of post content, arranging blocks horizontally or vertically, right or left aligned, and inside container blocks.

\n\n\n\n

“In terms of block styling, Layout is a complex feature because it affects child blocks in ways that go beyond CSS inheritance,” Pauli-Haack said.

\n\n\n\n

WordPress 6.1 introduced more layout controls and flexibility in the block editor, but Pauli-Haack said the dev note on updated layout support was written more for developers.

\n\n\n\n

“Feedback from users through the FSE program and other connections revealed that handling the layout settings for container blocks is not particularly intuitive and takes some trial and error to find the right combination,” she said. “The Live Q & A will bring a better understanding to users and #nocode site builders.”

\n\n\n\n

When Pauli-Haack started the Live Q & A’s in 2018, she routinely brought in guests who were building the block editor, with the intention of having users meet them and discuss features like full-site editing, block themes, case studies, and discuss challenges.

\n\n\n\n

“Since then, quite a few initiatives of the official WordPress project have come to life,” she said. “There is the highly successful Full Site Editing outreach program, spearheaded by Anne McCarthy, who now holds regular Hallway Hangouts with community members and contributors.”

\n\n\n\n

People are also learning the ins and outs of site editing through the efforts of the training team, which began creating courses and lesson plans and hosting workshops on Meetup.com in 2021. These are also recorded and uploaded to WordPress.tv and YouTube. WordPress.org also launched a blog for developers in November 2022. With all these new learning opportunities, Pauli-Haack is changing the focus for her live events.

\n\n\n\n

“For the Gutenberg Times Live Q & As, I am now looking at topics and discussions about more complex concepts, more case studies, and technology on the cutting edge,” she said. Most recently, the show featured the developers and digital strategies of the Pew Research Center, a high profile site that was built with a block-first approach.

\n\n\n\n

“We are also in planning phase to hold a Live Q & A with the developers of GiveWP who are using Gutenberg as a framework to build the next generation of their popular donations plugin with the components and scripts that Gutenberg uses, but outside the post or site editor,” Pauli-Haack said.

\n\n\n\n

She also has another Live Q & A planned with the WordPress VIP design team that works on design systems for companies that need a streamlined way to stay within their design standards. Pauli-Haack intends to talk with them about a plugin they created that lets designers automatically create a website’s theme.json file with all the styling pulled directly from Figma designs.

\n\n\n\n

The upcoming Layouts webinar is free but attendees need to register to get the zoom link. An archive of all the past Live Q & A events is available on the Gutenberg Times website. The best way to stay informed about future events is to subscribe to Gutenberg Times’ Weekend Edition, as subscribers get an early invitation for the next Live Q & A’s.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 03:37:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:23:\"Matt: State of the Word\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75018\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:42:\"https://ma.tt/2023/01/state-of-the-word-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:365:\"

A few weeks ago, but what feels like a lifetime ago, I was in New York City with a few dozen extra special people from around the WordPress world. Alongside Josepha and the community we presented this review of how WordPress did in 2022, and vision for what’s coming:

\n\n\n\n
\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 23:25:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"Post Status: Support Inclusion in Tech with Winstina Hughes — Post Status Draft 136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146189\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"https://poststatus.com/support-inclusion-in-tech-with-winstina-hughes-post-status-draft-136/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:52731:\"

In this episode, Winstina Hughes joins Cory Miller to talk about the  Support Inclusion in Tech project created to champion diversity, equity, and inclusion in the WordPress community by providing assistance to WordCamp speakers for travel and hotels.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

Winstina Hughes  is a long-term community member and organizer within WordPress. She joins Cory Miller to discuss Support Inclusion in Tech, an effort to increase representation of minority and underrepresented speakers at WordCamp by providing needed financial support. This offers everyone in the WordPress community the chance to share their expertise and contribute resources so everyone has the opportunity to engage.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • Creativity is our common bond. WordPress is the playground where we all came to tinker and build for fun or business. It is the software magic where we discover what new things we can do each day, how to make ideas become reality, and how we might leverage what we learn to create a better world.
  • \n\n\n\n
  • Ripple effect of inclusion: When you provide the ability for a large group of people to participate, travel, and network, the impact extends beyond the WordPress community to create the bigger changes we want to see in the world. This is our community magic.
  • \n\n\n\n
  • Fifth Freedom in WordPress: We are all familiar with The Four Freedoms of WordPress. This is the 5th – full participation. Removing the financial barrier will bring us closer to the reality of being a truly inclusive community.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Episode Partner: Gravity Forms

\n\n\n\n

Gravity Forms is a powerful form builder for WordPress and the #1 choice for businesses and web professionals across the globe. Its vast array of features, intuitive drag-and-drop form editor, and extensive ecosystem of add-ons, ensure customers can design beautiful, intelligent, and accessible forms for any project requirement.

\n
\n\n\n\n\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Welcome to back to Post Status draft. I\'m with a good friend of mine when Winstina hughes. I met with Winstina a couple years ago in the post status community. We\'ve got to meet in person, talk numerous times, and, um, I\'m excited about what we\'re gonna be talking about here. Um, she\'s got a new, a project called support inclusion in tech.com and we\'re gonna dive into that today.

\n\n\n\n

But, uh, hi, Winstina. Hi. And pumped to finally have you on,

\n\n\n\n

Winstina Hughes: I\'m excited to be with you.

\n\n\n\n

Cory Miller: Could you tell us what you do in WordPress?

\n\n\n\n

Winstina Hughes: Okay. Um, What do I do in WordPress? every time I speak with, you know, every time I have one of these, um, you know, opportunities to speak with someone in the community, I end up like re repeating the question.

\n\n\n\n

Um, cuz it really helps me. I am a community member, um, and I\'m also, you know, a, an organizer, um, a meetup organizer and a board camp organizer. I started, um, going to [00:01:00] meetups in New York City and I transitioned into, Speaking, um, at Word Camp, New York City, and then I was invited to become a meetup organizer.

\n\n\n\n

And so, um, my, you know, my participation in the community was, um, you know, like in the early, um, you know, 2010s. And then around 2015, 2016, um, I started, you know, speaking at, at New York City, and then I became an organizer. I meet up organizer. In 2018, I led my first word camp and my only word, camp , hundred twenties, um, a budget of 120,000, a team of 18.

\n\n\n\n

Uh, it was an amazing experience. They were wonderful people and it was. Really tiring .

\n\n\n\n

Cory Miller: Yeah. You know, over the years, Winstina, I\'ve had so many dear friends that have been Word Camp organizers and really I go, oh my God, I love you so much because of what you\'re doing for the community. But I also go, I hope you [00:02:00] still like word this afterwards because it\'s a such a labor of love that I think, um, so often we don\'t really give the credit and thanks to the people, That do this voluntarily.

\n\n\n\n

Yeah, like you\'re talking about all the stuff you\'re done. So anyway, I wanna say thank you because I\'ve said it so many times to dear friends over the years going, thank you for what you\'re doing. I\'ve always shied away from it because it\'s so much work and I see all the passion and energy that you and other organizers have and I\'m really thankful cuz I think that is so critical to the entire community to have these, and now we\'re talking in 2022.

\n\n\n\n

But we hear WordCamps are back. You and I got to see each other in San Diego at Word Camp US. Yes, yes, yes. So

\n\n\n\n

Winstina Hughes: anyway, so Word, word camp Us. I was a co-organizer for Word Camp US this year. Um, and so yeah, you\'re right. Like we had a chance to teach other again there, and that was like, yay. That was awesome.

\n\n\n\n

Cory Miller: It was, yeah.[00:03:00]

\n\n\n\n

Yeah. A absolutely. Well, okay, so what drew you to, okay, how did you start with WordPress? Were you using WordPress for, uh, your own website, somebody else\'s website? How\'d you get started with the actual software?

\n\n\n\n

Winstina Hughes: So I started with WordPress in 2006, 2007. Um, I had a college course that was . Yeah, I, I had a college course.

\n\n\n\n

Um, and our professor required us to add, um, you know, the work that we\'d done, uh, into a wordpress.com blog. Um, it was a geographic information systems class. And, uh, we were looking at public health data at the census block level. Um, and so we were actually, you know, looking to see. You know, where, um, there were instances of like, um, I don\'t wanna say disease, but you know, like different illnesses.

\n\n\n\n

And so what what\'s really interesting is that you can, that schools get access to that data and you can actually like, You can [00:04:00] essentially imagine, and I don\'t wanna go too far deep into it, but imagine you have like, you know, Google Maps, right? And like when you have Google Maps open, you can do street view.

\n\n\n\n

So Google Maps lets you like go from that whole, um, like that map into like street view where you jump in as a person. So, uh, this data essentially took you away from just the geographic element, um, and the typography and like really. The census, you know, track level, like essentially, um, you know, looking at neighborhoods and, you know, the instances of disease in those neighborhoods.

\n\n\n\n

And so he, you know, he gave that to us as our final assignment. Um, you know, we did some like, uh, some heat mapping to show where there were greater concentrations of a particular type of illness, , right. Um, or, um, you know, disease or, you know, Uh, I\'m not exactly sure like what, what we [00:05:00] were calling it, but that\'s what our assignment was.

\n\n\n\n

And, uh, he asked us to, you know, take like a picture of the map and to post it in wordpress.com and that\'s how it all started with that , with that assignment. Um, so we were you.

\n\n\n\n

Cory Miller: We were using WordPress at the same time. That\'s the same year I started with WordPress when you started. I did not know you went that, that far back with WordPress.

\n\n\n\n

So I love that. Uh, yeah, I do. Thank you. And then you said like in 2010 you started actually, uh, getting involved with community events. And this is relevant to us talking about support, inclusion and tech. So what drew you to start participating in volunteering and contributing to WordPress?

\n\n\n\n

Winstina Hughes: So I went to New York City Meetups, um, and, uh, WordPress, New York City, uh, is the one that\'s in closest proximity to where I lived.

\n\n\n\n

I could just take the train in. Um, and it was, it was great. Like I, I really felt, um, that the community there was, was [00:06:00] open, like the organizers were open and, and they were welcoming. Um, Dana, rendy, uh, those were organizers at the time, Steve Bruner, who was an organizer. Was he is the organizer, , he started it and he\'s, he\'s kept it, you know, um, really like strong, like, since its inception.

\n\n\n\n

Um, and so like just going to these events and meeting these, these like wonderful generous people, these kind people, um, you know, meeting Kevin Cre, Christiana there as well. Um, and you know, just that environ. Was what led me to continue attending events. Um, and they really encouraged me to submit a talk to speak at New York City, um, ward Camp, New York City.

\n\n\n\n

And I submitted a talk to speak there and, you know, since that time I\'ve been more engaged in. Event organizing component, [00:07:00] um, or part of the community. So it moved beyond just, you know, um, Like learning, you know, to use Word Pro, you know, building sites and breaking them, uh, the best, right? Yeah. Like, that\'s the best way.

\n\n\n\n

That\'s the only way you can really learn. I mean, I, I started, you know, with different hosting plans, I\'ve had like four or five, like I have multiple domains. Like I think when you\'re in our space, you got a chance to really create. And, um, and that\'s what I was able to do and what I\'m able to continue doing, and.

\n\n\n\n

Now moved from just creating and building with WordPress to assisting with supporting, you know, our community through events like meetups and, uh, word camp organizing and supporting inclusion in tech is, is an extension of, um, of this work, this contribution that I\'ve been doing. It, it, it pieces together so many different elements that I\'ve come to, like I\'ve come to see and I\'ve come to understand [00:08:00] and. It\'s, it\'s a solution that I propose to, um, some current challenges that, um, I\'ve heard being expressed. Yeah,

\n\n\n\n

Cory Miller: I, uh, I wanna scroll back for a second and say that when you\'re talking about create, I sometimes it, for as long as you and I have been in WordPress sometimes forget that magic of being able to create something on the web or in the, in the world.

\n\n\n\n

See this cool tool called WordPress, so I appreciate that. I think that\'s what we rally around in the WordPress community and particularly to post status is helping build tools and projects and things on top of this magical thing we call WordPress. So that was a, when you said create, I was like, it\'s just little tingle of magic came up of that\'s, that\'s why we, I think that\'s our common bond in

\n\n\n\n

Winstina Hughes: WordPress.

\n\n\n\n

I agree. I agree. And I think that, uh, when we create as community members, um, and not necessarily [00:09:00] just as. Business owners or, or, um, you know, those who are providing like services. That\'s a component of creating. But, you know, in the middle of doing all that, I think, you know, I mean, I like to sit down and just literally play and see, you know, what could I do with it today?

\n\n\n\n

and, um, I entered a com competition, um, held by Sustainable New Jersey, um, right around the time I completed graduate school. And there were municipalities that were seeking, um, solutions for challenges that they had. And there was the city of East Orange and they wanted like a marketplace, um, and a place for their planning department to, you know, add their documents and also something for their green team.

\n\n\n\n

And when I saw this, I was like, I could use WordPress and e-commerce. So I created like a WooCommerce marketplace for them to sell, you know, for residents that would sell their products and services. And I demoed it. Um, and then I also had a website and also a Buddy press site. Um, and the buddy press site would be for their green team members.

\n\n\n\n

And I think that [00:10:00] like, when, when we create with WordPress, like we\'re able to like see like, you know, These asks and really apply like our knowledge of what we know the c m s can do and then provide a solution. And the city was actually really happy with the solution. Um, and I made it to the finals of the competition.

\n\n\n\n

Um, but there was another, uh, but there were other teams that that won it. Um, but it was, it was really exciting to show what WordPress, you know, software and what WooCommerce can. Uh, that\'s the

\n\n\n\n

Cory Miller: dream. Um, that, that\'s so awesome. Thank you for sharing that backstory. As much as we\'ve talked and stuff, I haven\'t got the chance to ask those questions and, um, it\'s a good reminder for me about, you know, I think if you go long enough in the community, you start to, well, I, I\'ll say I start to.

\n\n\n\n

Forget some of these nuances, [00:11:00] like being able to go, here\'s a project idea, this could be done in WordPress, you know? And that the tools are mostly freely available. Yes. And you can start and build something online.

\n\n\n\n

Winstina Hughes: Exactly. Yeah. You just, you know, download . Yeah. Yeah. Well, yeah.

\n\n\n\n

Cory Miller: So that leads me to support inclusion and tech. And you mentioned you saw a problem or problems and challenges in our community that you wanted to help make some a solution to toward it that became support, inclusion and tech. But can you talk about that a little bit? Cause I know my, my understanding and you continue to help me expand my understanding of all this is it\'s not just one particular country with DEI, it\'s a global thing. But could you talk a little bit about the problems and challenges that you saw in the space.

\n\n\n\n

Okay.

\n\n\n\n

Winstina Hughes: Absolutely. Um, absolutely. So I, you know, really wanna, like, I wanna hold true to like, um, to how [00:12:00] I, um, shared it on my website. Um, but really the backstory is that. There was a conversation that erupted on Twitter, um, about the need for more diversity on Word Camp, um, organizing teams. And this started, uh, due to, um, you know, uh, some, some thoughts that were expressed about Word Camp Europe, uh, where WordCamp Europe\'s organizing team, um, not being.

\n\n\n\n

Very reflective, um, of, you know, more ethnicities or a wider range of them. Um, it was a really difficult conversation that was happening. And my take on it really is that it\'s not where camp you\'re specific, right? Like, I mean, let\'s, you know, let\'s really step back and think about the fact that, you know, there\'s so many ethnicities around the world that have a ch [00:13:00] like it\'s really.

\n\n\n\n

When you\'re in the minority as a group, Really up to the group that\'s in the majority to weave you into those experiences and those opportunities. Um, and when that doesn\'t happen, then you have groups that don\'t have an opportunity to be, to participate and to be involved and, you know, support inclusion and tech.

\n\n\n\n

I mean, considering this was a conversation about word camps and our participation in them. Support, inclusion and tech really seeks to assist us in solving the challenge of, um, not having as much, you know, ethnic or, um, or just diverse representation within the Word camp experience. It doesn\'t seek to, you know, um, it doesn\'t, it doesn\'t seek, you know, to like solve, um, Like these, you know, the world that we [00:14:00] live in.

\n\n\n\n

And it doesn\'t seek to solve like, um, you know, diversity and inclusion outside of the WordPress space. Um, but I believe that in, in providing these, um, these opportunities within our community, since we\'re so large, that the ripple effects can extend well beyond the WordPress community. I believe that when you, Absolut.

\n\n\n\n

When you provide such a large group of people, the ability to, um, to participate in work camps, um, the ability to travel to them, the ability to network to them with, when you attend, um, the ability to like, you know, seek, um, you know, out more relationships, friendships, professional relationships. Then there\'s this ripple that extends outside of our community and I think.

\n\n\n\n

That level of empowerment can extend outside of WordPress and those ripples can assist us in diversity inclusion beyond, [00:15:00] um, you know, our, our involvement in WordPress. But you know, this, this particular solution is intended to solve the challenge that I saw, you know, um, being expressed, you know, within our community.

\n\n\n\n

And so the thought is really, Since, you know, since there\'s a take on it. And there\'s, it\'s a, I mean, it\'s an, it\'s an honest one, right? We don\'t see enough people of color. We don\'t see, um, enough, you know, people of, um, other minority groups, um, you know, uh, from other parts of the world. Um, You know, we are seeing an equal, more equal balance of, um, men and women.

\n\n\n\n

Uh, you know, but when it extends beyond that into like, you know, more representation in terms of like, you know, a wide range of religions, which ties to ethnicity often. Um, and when you\'re looking at representation in terms of those of us who, um, have like neuro [00:16:00] diversion, you know, um, you know, like, uh, characteristics and those of us who, um, you know, who we choose to love, , you know, the what society, um, you know, asks of us , right?

\n\n\n\n

Like, and um, and when we choose to hold true to that or when we\'re dealing with the physical limitations, um, that, you know, that we were born with or when we\'re in minority. Groups, you know, that have a harder time, you know, uh, receiving opportunities, um, to participate and to increase, you know, their reach and even, um, you know, the professional opportunities that are available to them.

\n\n\n\n

You know, like this. What can we do to, um, to really like solve. To solve that. Mm-hmm. And I thought, what could I do within our community Yep. To, you know, to integrate, you know, like all of those of us who are, um, Either, you know, disadvantaged [00:17:00] or not as represented into WordPress programming and support, inclusion and tech, um, seeks to, you know, take away that financial barrier, which I believe is really what, you know, can limit our participation.

\n\n\n\n

We want to participate, we want to speak, but if we can\'t afford to speak , right? I mean, if we can\'t afford to travel to the conference and if we can\'t afford a place to stay at the conference, um, then. Like, why would we even think to apply to speak at the conference? Right? Like,

\n\n\n\n

Cory Miller: yeah. That\'s, that\'s really beautiful, Winston, because, um, there\'s a couple of takeaways I, I got from this.

\n\n\n\n

Number one is, I, I\'ve always believed, um, at least in my world, that WordPress has been. The, an inclusive place, ever growing inclusive community. That\'s like a mirror to my world, the way I want my physical world here in Oklahoma to be. And I have [00:18:00] so much learned from our community leadership, uh, over the years, um, that there\'s a cons.

\n\n\n\n

Consistent push and drive from the entire community and the leadership to be truly diverse, truly inclusive in all those words. And I, I learn a lot from this. Um, so the mirror I, and I do think WordPress is, our community is so powerful cuz we\'re distributed all over the world. So if we make change in our community, in our WordPress, That should be, that should be reflected.

\n\n\n\n

And I think that\'s another, we talked about the software magic. This is the community magic. Exactly. Uh, the other thing is, I, I love and I respect because I try to take too much on that, you said, Hey, here\'s something I\'m passionate about, being an organizer, being at these community events, how special and valuable they are to you and other people instead.

\n\n\n\n

I\'m gonna make this dent first. Yeah. Like, I\'m gonna, I\'m gonna take on this aspect first. You. Beautifully and clearly [00:19:00] shared. This is the thing I\'m trying to take on in this bigger, bigger, um, change that you wanna see. We wanna see in the world. Thank you. Okay, so we\'ve got this now we\'ve got a website. Um, you\'ve got a website up to kind of share this.

\n\n\n\n

Now. Take me through, if you would, I am, pretend for a second you\'re talking to someone that is in an underrepresented, uh, in, in tech. Of, um, situation. Mm-hmm. , how\'s the process to, to get on the, Hey, I want to go to these WordCamps. I want to speak, but I do need some assistance. What does that process look like for, for support inclusion and tech?

\n\n\n\n

Winstina Hughes: So support, inclusion and tech. Um, also weaves into other initiatives in order to, to assist our speakers. Um, and so when you\'re accepted, support inclusion tech, it become, moves into the position to, to assist you once you\'re accepted into [00:20:00] Word Camp. Um, as soon as you get that, you know, acceptance, you know, go to https://supportinclusionintech.com/.

\n\n\n\n

Um, and you\'re simply just, you know, gonna put in the word camp that you were accepted in. And then there are two components, um, in addition that they\'re suggested , right? Like you\'re encouraged to do this. Um, you know, uh, we\'re in the community of consent and so, um, you have, you know, um, You\'re gonna give, you know, the consent to be included in these other initiatives, um, you\'re not gonna be forced into it.

\n\n\n\n

Uh, there\'s underrepresented in tech and there\'s also the WordPress diversity, speaker channel. Um, both of those, uh, are ways of. Further supporting diversity and inclusion and representation within the WordPress space and creating, you know, um, you know, successful opportunities for us to, um, to, you know, to put together great speaker applications and then to also, um, you know, move beyond just submitting [00:21:00] them. Um, but to being accepted.

\n\n\n\n

The, the ask is that, you know, once you\'ve been accepted to camp and you\'re starting the process of, you know, receiving funding through supporting inclusion and tech, that you also participate in those other two initiatives as well. Um, because you know, in the process of doing that, it\'s further supporting the work that we\'re doing in the WordPress community. Exactly as you said, Corey, that, you know, the word WordPress leadership already has been putting in, um, you know, the work to, you know, to assist us in resolving the challenges that face society as a whole. And so there are initiatives that currently exist and those two in particular, I think.

\n\n\n\n

You know, are ways that we can continue to support underrepresented minority groups in the WordPress community. Um, and so in the process of, you know, uh, applying for the funding, uh, you\'re encouraged to, you know, to list yourself on underrepresented tech to join the, um, the, the diversity speaker channel [00:22:00] on make WordPress.

\n\n\n\n

Um, and then once you\'ve just put on, put that information in and you\'ve identified the type of support that you\'re seeking, um, you just like, and it starts from there. Like I start, um, you know, pairing you with, you know, with a partner that you know can, can step in and provide, you know, the funding for you.

\n\n\n\n

And so, you know, they\'re gonna cover your travel and they\'re gonna cover your hotel. Um, and that way in order for you to participate, you\'re not going to be paying anything really that you know, out of pocket. For that participation, um, in that WordCamp. And that\'s really the goal. Um, the goal is to remove the financial barrier to your participation.

\n\n\n\n

Cory Miller: Yeah, that\'s fantastic. By the way, I wanted to sidebar for a second and say underrepresented in tech, uh, by Allie and Michelle Frechette. If, if you\'re listening to this and, uh, you also as a be becoming a member of underrepresented in tech, get a free [00:23:00] uh, professional membership at post status?

\n\n\n\n

Winstina Hughes: Yes. Yes. I started, I and let\'s not also forget too, that like there are other opportunities as well as Post Status has been, um, you know, looking into as ways of increasing, you know, diversity and representation within the Post Status community. Um, so underrepresented tech and that membership, and I know that there\'s some other ways that you\'re working on it too, Corey.

\n\n\n\n

Um, you know, I think, I think when we can pull all our efforts together. We have a stronger community. Um, and you are, you know, you\'re, you\'re offering that and then supporting inclusion and tech, you know, encouraging, you know, speakers to, to register and to participate in those two other programs. Strengthen all our efforts. Yes. Um, and, and that\'s, you know, that\'s the process of it. And so once you\'ve submitted, you know, your. once you submitted that form, you know, just letting me know, like the speaker registration that you\'re seeking, the [00:24:00] support. Um, you\'re also gonna complete the blind directory listing and that blind directory really.

\n\n\n\n

That Blind directory listing has the word camp that you\'ll be speaking at. Um, and it has the type of support that you\'re seeking, whether it\'s just beach travel or hotel, or both, and that\'s it. Um, no one in the community, um, you know, needs to know who you are. They don\'t need to know what your need is. Um, they don\'t need to know where you come from.

\n\n\n\n

And they don\'t need to know what makes you underrepresented and what makes you a diverse speaker. Uh, it\'s simply a way for, um, for companies that are considering sponsoring to see that the need does exist. And it\'s also a way for our community to see that the need does exist, um, and that we do have members that are seeking the support.

\n\n\n\n

Um, that, that blind directory listing is, is just a way, you know, for our community to see that, um, that our need is there. Um, yeah, and it\'s also a way of, um, uh, [00:25:00] keeping everyone up to date on the work that\'s happening.

\n\n\n\n

Cory Miller: So I know we\'ll have two asks. The first ask is if, um, you need assistance, want assistance to go to a WordCamp to be sure to go to supportinclusionintech.com?

\n\n\n\n

Winstina Hughes: Yes. Once you\'ve been accepted, go to supportinclusionintech.com. Complete the form for speaker registration, and you\'ll, um, you\'ll be paired currently, um, with four companies, uh, that, um, that have partnered to work on this.

\n\n\n\n

Cory Miller: That comes to our second ask. Yeah, that\'s right. Okay. So tell me how, um, now this is very relevant for post status because we\'re a bunch of professional and business members in our community.

\n\n\n\n

So the second ask is, we need someone, one, participants, people that need and want assistance go and speak at Word Camps. And the second part of this is the sponsors and partners. Can you tell me a little bit more, more about that and [00:26:00] how that.

\n\n\n\n

Winstina Hughes: Okay, so starting off, partners are sponsors, , um, partners are the first, um, you know, companies that expressed an interest in supporting this project.

\n\n\n\n

Um, you know, this initiative. Uh, and so like that is my way of thanking you, um, by, you know, by acknowledging. that you came into this, um, wholehearted and opened armed. And so thank you to the four companies, um, that have done this, uh, that have stepped forward to say that they will support. Um, you know, it was really exciting once the call went out, um, from Word Camp US that they were seeking, uh, support for underrepresented.

\n\n\n\n

Speakers. It was really exciting because Master wp, um, stepped in at that time, you know, to say that they thought that this was a great project. And, you know, they\'re the fourth company they joined, um, GoDaddy, Post Status, and Yoast, um, you know, the original three that said that, you know, that they would love to support this initiative.

\n\n\n\n

And so, um, now we have, [00:27:00] we have four of them and there are also several companies as well that are providing in-kind donations. Um, and, you know, they\'re doing so, makes it possible for support, inclusion and tech, um, you know, to, to function, right? Because like, you have the website and then there are all these different like plugins that make it functional and make it possible, you know, for, um, for, for it to run and function the way that we need to.

\n\n\n\n

Um, so if your company that wants to. Sponsor speakers, you know, you just have to go to the site. Um, there is a section there for you to register your support, um, your register, your desire to support. It\'ll ask you, um, you know, to provide, you know, like a contact. Um, it\'ll ask you the type of, uh, How you want to provide this support.

\n\n\n\n

Um, would you prefer to reimburse speakers for their expenses or are you, um, ready and, you know, willing and able to pay for their, um, their travel and their hotel in advance of their trip? [00:28:00] Um, so, you know, once you\'ve identified your contact, you know, your contact is the type of support that you want to provide, you know, then, you know, we\'ll have an opportunity, I\'ll have an opportunity, you know, to really. Sit down with you and for us to have a conversation about like, you know what would be your process, you know, what would make it easy or for you to be a part of this initiative? Um, this isn\'t a cookie cutter means of support for, for companies, because you\'re all different.

\n\n\n\n

Um, how GoDaddy, you know, is providing support is different from how Post Status is providing support is different from how Yoss is providing support. And it\'s different from, you know, how, um, Master WP is and, uh, When I started this, and I, you know, I, and I wrote on my blog, like, really this proposal on https://winstinahughes.com/.

\n\n\n\n

I went into it, um, you know, with the understanding, personal understanding is that it\'s gonna [00:29:00] take a couple years to understand the needs of our community and the ways, you know, companies and our ecosystem can support these needs. And in the last six months, Exactly what I, you know, anticipated, um, is what I\'ve been able to, you know, to, to see.

\n\n\n\n

And, you know, currently, um, there have been three, you know, requests, um, for, you know, to participate, you know, um, for funding, for support, for camps and, um, two unique, you know, individuals have, have made those requests. Um, and you know, so right now it\'s a question of. You know, like assisting them, you know, with the process of how, you know, our four partners, you know, can support them in that way.

\n\n\n\n

Um, and I think that answers part of your question. Um, the second part of the question is like, so how is this financial component gonna work? Right? [00:30:00] Like, are companies giving me money? No, you\'re not , like, I\'m not receiving, you know, um, any of the money. Is the financial support that you\'re providing. Um, instead it\'s looking at your company\'s processes, um, you know, your, your financial processes, your accounting processes for you to, you know, step back and think like, how could we as a company provide this level of support?

\n\n\n\n

Um, you know, it could be that you already have an existing program. Yoast already has a diversity fund. Um, and so Yoast partnering with me is a way of, um, you know, kind of bringing the need that exists to them as well. Um, and so therefore they\'re able to like further serve the community, um, you know, through those who are expressing an interest through support inclusion and tech.

\n\n\n\n

Um, the way Post Status, you know, is seeking the support speakers too, is different from Yoast. Um, and, you know, uh, [00:31:00] Yoast has a budget, um, and.

\n\n\n\n

Has their own system and their own ways of support. Um, and so they also have a budget and then Master wp, they also have a budget. And so once that budget has been met, then you know the partners essentially gray out for that year. Um, and they become active the next year. Um, and so. , that is a way of making this sustainable.

\n\n\n\n

You know, you, you pledge how much you can support, um, speakers financially, and once that has been met, then your, I mean, your capacity for the year is, is, is met. And then next year, once you\'ve reallocated your budget, or not re reallocated, but once you\'ve defined, you know, your budget, um, for the year, then you would go, you know, back into the process of supporting [00:32:00] speaker.

\n\n\n\n

Cory Miller: And I wanted to say from personal experience here, that there\'s many way, there\'s, there\'s creative ways to support these speakers, uh, to go, you know, uh, you, you talked about hotel and flights. Yeah. And, um, I, I wanna, I wanted to say that one standard to say this is not an unapproachable. Opportunity to support d uh, diversity inclusion in tech.

\n\n\n\n

Um, this is very manageable for most members at Post Status, by the way. So, you know, flight costs, uh, depending on where it is in the world. Um, I think the first question you asked me was, what\'s your budget? Yeah. And that\'s a great way. So as you come in and click sponsor, just be thinking of these things with when st for how you can.

\n\n\n\n

Help support this amazing project. Um, and that there\'s creative ways to do that. And I, I think Winstina, most members, business members at Post Status can make a meaningful contribution in this way [00:33:00] through this, your project here. And I love the fact also, I know we talked about this too, you wanted to be real careful.

\n\n\n\n

You wanna say you want the support to go to the person as best as possible. A lot of nonprofits have overhead. You have graciously generated your time and your talent to this project, and I, I, I love the way you\'ve done it too, even though I go, gosh, Winston, I love that you have this passion. Um, but thank you so much for this.

\n\n\n\n

But I know you give of your own time. For this particular project, but as you talk to Ena, if you\'re listening to this now, there\'s creative options and ENA is so good at helping you, helping understand where you\'re at, and then pair it with people that need assistance.

\n\n\n\n

Winstina Hughes: Thank you. Yes, and that is, that is the goal.

\n\n\n\n

In terms of my contribution to the WordPress community, burnout is so real and because of the fact that I work full-time outside of the WordPress space, the WordPress ecosystem, um, I\'m really [00:34:00] cognizant of the fact that I need to perform well. And at a high level , right? Uhhuh , um, at work, you know, and in my personal life.

\n\n\n\n

And WordPress fits into, um, you know, into that. And so I\'ve been able to contribute in different capacities since I was in college and. Graduate school, first attending in college, um, or post-college in graduate school, moving into speaking and organizing, um, and now working, you know, professionally maintaining, you know, organizing as a meetup organizer and a WordCamp organizer, and understanding that this can really lead to burnout.

\n\n\n\n

You know, um, my ultimate decision is, you know, that for the next two years, I\'m not gonna be a WordCamp speaker, and I\'m also not gonna be a. Organizer, you know, this, these are the ways that I can, you know, I can continue to contribute. I can contribute through support, inclusion and tech. Um, you know, but really pair, pair down all the other ways that I could burn out.

\n\n\n\n

[00:35:00] And so by maintaining, Being a New York City meetup organizer and hosting at least a minimum of six meet meetups a year, and, um, really pivoting and concentrating my energy towards support, inclusion and tech. I can sustainably contribute to the community. And so this is a perfect opportunity to really share with you, um, that, you know, I want to meet with every speaker.

\n\n\n\n

You know, that expresses the interest for support. So as you submit your, you know, your speaker registration and you join the directory listing, I will, um, you know, I\'ll ask to meet with you, for us to have a conversation, for me to understand your needs and to share. what it is I understand and I\'ve learned over time, and also how our partners seek to support.

\n\n\n\n

So we\'ll have that conversation. It\'s gonna be on the weekend. I hope you graciously incorporate that into your schedule. Um, because, you know, I, I work during the week, [00:36:00] um, and so, you know, we\'ll meet once. Uh, hopefully within a week or two of your registering as soon as possible. Especially it\'s, it\'s, it\'s ideal, uh, not ideal.

\n\n\n\n

It\'s encouraged to register as soon as possible, um, because the closer you get to your ward camp, you\'re gonna. Most likely, um, be reimbursed if you apply much sooner, like a, like two or three months in advance. You know, there are companies that will be able to, you know, cover your, your, your costs, um, of participation in advance of your trip.

\n\n\n\n

If you are reaching out like three to two. You know, to the time of, of your support that the time that you need, then you\'re looking at being reimbursed for your expenses. And so like, you know, that\'s, that\'s something to, to keep in mind when it comes to registering, you know, for this is that companies will be able to assist you with removing this.

\n\n\n\n

It just might be [00:37:00] later. When your need is expressed closer to the time that you\'re speaking that it\'s more, it\'ll be a reimbursement instead. Um, and so that\'s something to keep in mind, the timing in which you submit your interest, and also the fact that, um, you know, that we\'ll be meeting on a weekend. Um, there\'s this speaker that just registered and he wanted to meet with me.

\n\n\n\n

Um, On Christmas, he\'s in another part of the world. I mean, you know, like, so yeah. Um, and so I just, you know, I just like, I think when, and I had a con, you know, I just like responded and let him know that it\'s, it\'s Christmas for me. I\'m, you know, I\'m a Christian and I\'m so celebrating my holiday today. Um, you know, and, you know, like, uh, let\'s, let\'s meet next week.

\n\n\n\n

Um, so, you know, uh, we\'ll have like, you know, we\'ll have these conversations and we\'ll, we\'ll see. And you know how. Um, you know, how you and I can, can have that conversation and [00:38:00] meet and how your need can be met. And I\'ll also meet with, you know, companies that wanna sponsor as well. And I wanna tell you, I want you to tell me what\'s realistic for you.

\n\n\n\n

Um, I want just, just to, just to give you a sense of how some of the companies are. In fact, um, you have, uh, of the four partner. You have one partner who seeks to provide support, um, you know, within the us. Um, as of our last conversation, you know, the desire is to support minority speakers, um, specifically people of color, um, specifically, you know, black Americans, um, to improve or those of black descent to improve, um, their numbers.

\n\n\n\n

WordCamps in the US. Um, our last conversation was, you know, this is the direction that they wanna go. This is the greatest impact that they think that they can achieve. Um, and [00:39:00] I\'m, I\'m so glad that I get to listen to what everyone. Hopes to do, you know? Um, because it gives me a sense too that our community is really thinking through, like, this is how we\'re gonna solve it, right?

\n\n\n\n

Like, this is how we\'re gonna make the dent that we wanna see. So this company already knows this is how we\'re gonna make the dent that we wanna see. And there, there. Process too, is that they\'re just gonna give you a blanket amount of money and they\'re not gonna micromanage how it is you spend it. Um, they just simply ask, you know, that you, not simply, the requirement is that you put it towards your WordCamp experience and that\'s where they are with it.

\n\n\n\n

Um, there\'s, you know, another company host of course, has an established diversity fund and they have processes already in place for the support. And so you\'re simply gonna go through the existing process that, um, Yoast has established and they have a generous fund. Um, and their support, um, is something [00:40:00] that they\'ve been offering the support for a long time, and they\'re very, um, they\'re really respected , you know, for that effort.

\n\n\n\n

And, um, I\'ve had an opportunity to like, you know, to speak with someone who has been a part of their support in the past or received it and they speak so highly of, of Yoast um, and that\'s, you know, Yoast has already thought it through and they\'ve already walked through. You know, Corey and I, you and I have spoken about, you know, the budget, you know, that you\'re, that post status is set aside and, and you\'ve already shared.

\n\n\n\n

You know, what is the need? Like, we\'re not micromanaging, right? Like, let us know what type of support that you need, and we\'re just gonna provide that to you. Um, and so like you\'re, you are already thinking about like, how can we make this happen? Like, you know, if you need to, you know, it\'s a flight, you know, wherever it is.

\n\n\n\n

It doesn\'t have to be domestic, right? Like, it doesn\'t have to be in the us it could be anywhere in the world. Um, and, and that\'s, you know, that\'s like, [00:41:00] Post Status is thinking, and then GoDaddy is currently working through their process. Um, and I do believe that because of the fact that they have teams around the world that GoDaddy\'s reach will also be of, um, I think GoDaddy\'s reach will also extend beyond like the domestic, you know, like within the US and they\'ll be able to provide support as well toward camps.

\n\n\n\n

you know, around the world. I\'m anticipating it\'s possible that, um, GoDaddy\'s like impact could, you know, be especially strong with, um, Uh, WordCamps, like Word Camp Asia or Word Camp US or Word Camp Europe. Um, you know, because they\'ll have team members there and when they have team members there that can help facilitate and smooth the process over for, for those that they\'re going to be supporting, but they\'re working through their processes to make this established as well.

\n\n\n\n

And so I think [00:42:00] that, you know, just by me sharing that, you can tell that, you know, each of, you know, my partners are, are working within. Um, you know, like their business processes and their financial processes and also their vision for impact. Um, and I think that\'s really important.

\n\n\n\n

Cory Miller: So to recap, here\'s what I\'ve heard.

\n\n\n\n

So support inclusion and tech.com is the bridge between those that want have the desire to share their exper experience and expertise at word camps, but need some financial assistance to get their flights and hotel. That\'s what f support inclusion and tech.com does. Second, as a participant, as someone.

\n\n\n\n

Um, if you first need to apply and get, uh, approved to speak at work camp, then come to support inclusion and tech.com and, um, sign up, have a conversation.

\n\n\n\n

Winstina Hughes: Mm-hmm. , once you\'ve been approved.

\n\n\n\n

Cory Miller: Have a conversation with we, Tina. [00:43:00] And then third, the third recap is our ask for, um, well buzzer asked in our community.

\n\n\n\n

Uh, if you\'re looking to speak to Word Camp, go to support inclu or go apply, get approved, come to support inclusion in tech. And then second for those businesses out there. You know, you have a heart, you wanna support this. That\'s our community. That\'s who WordPress is. Uh, go to support inclusion and tech.

\n\n\n\n

Click on the sponsor link and have a conversation with ena. Think about your budget. Think about what you wanna do, uh, when Cena is so creative in helping just make these connections happen so you can really make a difference in our community. Did I get it all right?

\n\n\n\n

Winstina Hughes: You did. You did get it right And, okay.

\n\n\n\n

And I think that support inclusion tech also. It goes through vetting process as well to confirm that those who are seeking assistance, you know, to participate actually have been accepted. And that\'s why, that\'s why the steps are what they are. Um, partners aren\'t gonna [00:44:00] question, oh, is this need real? You know, that vetting is gonna happen in advance.

\n\n\n\n

So when you receive a speaker interest, You know that this is someone who has been accepted a Word camp, and they understand the process and they\'re working within, you know, your, your policies and your procedures, um, in order for them to participate. So it removes all those questions. Um, you know, so that and that, yeah, that\'s a part of it.

\n\n\n\n

Cory Miller: Well, Winston, my friend, thank you so much for this important work, uh, holding the banner up. I know this takes a lot of time. I know you\'ve got a full-time gig. I know you\'ve got a life

\n\n\n\n

Winstina Hughes: more,

\n\n\n\n

Cory Miller: um, But I so much appreciate you post. I just appreciate you, our members do for doing this vitally important work and making a difference in our world that can, like we said, can be a reflection in all these thousands of communities we go out to, to say, how can I be more inclusive?

\n\n\n\n

[00:45:00] How can I make sure everybody is represented as at least an opportunity to be represented? So I really appreciate you, Winstina, and your work and also just ringing the bell with me and teaching me and sharing, um, how we can make, make that difference. So I appreciate. Thanks for being. I\'m thanks for being on.

\n\n\n\n

Winstina Hughes: Sorry. No, no. I mean, I absolutely, like, this gives me life and it makes me wanna show up in the world, you know, different and energy. I wanna exercise more like , you know, like this is, this is, this is really in a lot of ways just like giving me energy to contribute. And so, um, to like, just to be able to like, work with you, you\'re, you\'re, you know, I\'m, I think you\'re awesome

\n\n\n\n

You know that, ditto. You\'re, you have a beautiful family. You know, like your energy is like, you have such great energy and so just a chance to work with you and like the amazing people that I\'ve had a chance to, it, it just, it gives me life and it makes me want to live more, you know? So like, let\'s, let\'s [00:46:00] see what we can do to continue to support our community so that the four freedoms, you know, I think that it\'s, , it\'s creating a fifth freedom, which is, you know, for all of us to be able to participate in a truly inclusive, um, community.

\n\n\n\n

And, you know, that speaks a lot to what the co-founders of WordPress. I think, um, you know, what, what they created and, and where they want, um, what their vision is and, you know, from their vision where we\'re, um, going and or how we\'re evolving as a community. I mean, to have 40% plus of a reach on the.

\n\n\n\n

There\'s so many people around the world that are impacted by this project, you know? So, um, yeah, I love

\n\n\n\n

Cory Miller: that. Let\'s, let\'s add the fifth Freedom. I love that win. Coined by Win, and I love that leadership vision for our community. We need it. Thank you. Thank you, ma\'am. You have a good rest of your year and we\'ll see you in the next year.

\n\n\n\n

For everybody listening, thanks for listening. Tune in, go to support inclusion in [00:47:00] tech.com, and also Winstina Hughes is in our post Slack community. So you can go at Wednesday and you can ping her and, um, get the conversation started there. So thank you, Eena.

\n\n\n\n

Winstina Hughes: Thank you. Thank you, Brent.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 19:17:12 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"Do The Woo Community: Do the Woo is Headed to WordCamp Asia\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74283\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"https://dothewoo.io/do-the-woo-is-headed-to-wordcamp-asia/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:373:\"

We are looking forward to attending WordCamp Asia and also are proud to be a media partner this year.

\n

>> The post Do the Woo is Headed to WordCamp Asia appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 10:19:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"Gutenberg Times: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://gutenbergtimes.com/?post_type=podcast&p=23141\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:114:\"https://gutenbergtimes.com/podcast/gutenberg-changelog-78-state-of-the-word-wordpress-6-2-gutenberg-14-8-and-14-9/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:41325:\"

Birgit Pauli-Haack and Hector Prieto talked State of the Word, Gutenberg releases 14.8 and 14.9, WordPress 6.2 and beyond. 

\n\n\n\n

Show Notes / Transcript

\n\n\n\n\n\n\n\n

Show Notes

\n\n\n\n

State of the Word

\n\n\n\n\n\n\n\n

Gutenberg Times Live Q & A

\n\n\n\n

Gutenberg Times Live Q & A: January 11th at 5 pm ET / 22:00 UTC Layout, Layout, Layout.

\n\n\n\n

Isabel Brison’s talk at WordCamp Asia

\n\n\n\n

WordPress and Gutenberg Releases

\n\n\n\n\n\n\n\n

Stay in Touch

\n\n\n\n
\n\n
\n\n\n\n

Transcript

\n\n\n\n

\n\n\n\n

Birgit Pauli-Haack: Hello, and welcome to the 78th episode of the Gutenberg Changelog podcast. In this first episode of 2023, I wish all our listeners a wonderful, happy, prosperous and healthy new year. In today’s episode, we will talk about Gutenberg releases 14.8, 14.9, WordPress 6.2 and beyond. I’m your host, Birgit Pauli-Haack, curator at the Gutenberg Times and WordPress developer advocate, a full-time contributor to WordPress Open Source project. My guest today is Hector Prieto, full-time contributor on the WordPress Core team, coordinating multiple WordPress and Gutenberg releases. And it’s a great pleasure to finally have you on the show, Hector. Having a conversation about Gutenberg and WordPress with you is a wonderful way for me to start this new year. Happy New Year, feliz año nuevo, Hector. How are you today?

\n\n\n\n

Hector Prieto: Happy New Year. Hi, Birgit. I’m excited to join you on the podcast. It’s my pleasure.

\n\n\n\n

Birgit Pauli-Haack: Oh, the pleasure is really all mine. Where are you right now? Did you have a great holiday break?

\n\n\n\n

Hector Prieto: I’m currently in Alicante in Spain, very close to the Mediterranean Sea. And today we have a lovely sunny winter day with nearly 20 degrees Celsius. I had a few days to recharge and spend time with the family. What about you, did you enjoy your holidays?

\n\n\n\n

Birgit Pauli-Haack: Yeah. Well, that’s some warm weather there in Alicante. I would love to have that. But here in Florida it’s balmy, too. It’s about 27 degrees, so we are in the air conditioning right now. Yes, my husband and I, we spent the week in Mexico City between Christmas and New Year’s. We saw some great art, powerful murals from the ’50s and ’70s and ’60s. And we had fantastic food and a fabulous New Year’s event. It was great, at a restaurant over the roofs of Mexico City, so we really liked it.

\n\n\n\n

Hector Prieto: Wow, sounds really nice.

\n\n\n\n

Birgit Pauli-Haack: Yeah. Well, Hector, as you are the first time on the show, maybe you can share briefly with our listeners your WordPress origin story. When did you come across WordPress the first time and what do you work on now?

\n\n\n\n

Hector Prieto: Well, my first time working with WordPress was around 2015 when I worked at the startup agency building sites. However, it wasn’t until 2020 that I first moved into the contributor space, and here we are. I am currently sponsored by Automattic to work full-time in Core in project management-related duties and supporting the development of WordPress.

\n\n\n\n

Birgit Pauli-Haack: That’s wonderful. Well, thank you. So 2015, that’s just about two years before Gutenberg was introduced into the community. Did you, at your agency, have Gutenberg on the radar already, or did you heed the call to learn JavaScript deeply?

\n\n\n\n

Hector Prieto: It wasn’t until 2018 that we started using Gutenberg for the first time, when it was first released in 5.0.

\n\n\n\n

Birgit Pauli-Haack: Yeah. Then the time between learning about WordPress and then starting contributing, that’s about five years. That’s pretty much the time that it took me to really embrace the contributing on WordPress, but I started at the Community Project in 2014. 

\n\n\n\n

Announcements

\n\n\n\n

All right, so there are a few announcements that were happening since the last podcast episode. If you haven’t watched it yet, the recording of Matt Mullenweg’s State of the Word is available on WordPress TV. The transcript and answers to the questions that didn’t make it into the recording can be read on the follow-up post, State of the Word Reflections. Josepha Haden Chomphosy kicked off the State of the Word with a reminder on the four freedoms of WordPress, that you are free to run the program, you’re free to study and change the code, you’re free to distribute your code and also redistribute WordPress.

\n\n\n\n

She also recorded a separate WP briefing with her reflections in episode 45, State of the Word Reflections in which she highlights, among other things, learn WordPress, that 12,000 students actually went through the courses and the workshops already since the inception. And she also highlighted the WordPress Playground, which is a tool to run WordPress in the browser. You don’t need a server, you don’t need a database. You can run it in the browser and test plugins and themes. I think that changes how we approach some of the discovery for WordPress. We talked about it on the show here as well, but it’s definitely something that will have so many ramifications in the WordPress space later on when it’s still very raw and very not production ready. It’s just an idea that has already a proof of concept. And then the recap posts from the community are linked in the Gutenberg Weekend Edition 239 from December 17th, and you can check it out from there.

\n\n\n\n

I also have a side note that the Pew Research Center received a shoutout for the politology quiz that they built with blocks and had one million people already taking it. Seth Rubenstein is the lead developer and was a guest on a Gutenberg Times Live Q&A last year. And he gave a great demonstration about their team’s work with the block editor, so as they went for the Gutenberg first approach building the website. The recording is available on the Gutenberg Times YouTube channel, and also we have a post here on the Gutenberg Times website as well. So as always, all these links are in the show notes of the 78th episode. So Hector, do you have any comments on this? What is your most exciting topic from the State of the Word? You had a few takeaways?

\n\n\n\n

Hector Prieto: Yeah, there were a handful of them. I would actually highlight everything, starting with WordPress Playground. It’s such amazing technology and it’s going to open so many doors. But if I had to pick something, maybe for me because it is the thing I’m the closest to, it was a great recap about the progress WordPress made in the site editing front during the last year, to the point nowadays we can create themes directly in the editor just with blocks and patterns. This brings us very close to wrapping phase two and starting exploration around phase three in 2023. So it’s great to see that all these progress.

\n\n\n\n

Birgit Pauli-Haack: Yeah, you’re right, you’re right. And it’s been such a long journey as well. I look back at some of the history on the Gutenberg Times and the Gutenberg podcasts, and we first started talking about full-site editing in January of 2020. That was even pre-pandemic, and we had quite a few developers on our live Q&A talking about the first concepts about that. So now, three years later, it’s almost finished and it’s really cool. There are still some things to be done, but I am really excited about the start of phase three of collaboration and I have been constantly trying to unify all the various tools and methods and interfaces to streamline my workflow to produce content for the web. And if I don’t have to use multiple tools to collaborate with people, I will have arrived on internet nirvana. Yeah, it’s a high calling of course, but yeah, we are all in a space where we could maybe make it happen. So I’m really excited about that.

\n\n\n\n

Hector Prieto: Yeah. Also, it’s worth noting that even when we move to phase three and we can call a wrap on phase two, phase two will not be fully finished because there’s always going to be things to do related to site editing improvements, new tools. So I can see contributors working in new features for phase three and also iterating on phase two items. Another big takeaway for me during State of the Word was seeing how much Gutenberg itself has matured. And it’s now been used in more projects such as Tumblr, bbPress, and even in some mobile apps like Day One. Also, let’s not forget how WordCamps have made a comeback after COVID hit and stopped all the in-person events. And we went from one single WordCamp in 2021 to up to 22 in the last year, in 2022. That’s amazing.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s a nice iteration of the numbers. 22 WordCamps in ’22.

\n\n\n\n

Hector Prieto: Exactly. Especially since the community is what makes WordPress what it is, it’s the most important part of WordPress. So that’s really good to see.

\n\n\n\n

Birgit Pauli-Haack: Absolutely. Having the first WordPress in-person event in WordCamp Europe, I realized how much I missed interacting with everybody else in the community and seeing new faces and interacting with old friends. I looked up the number of WordCamps that were done in 2019, in-person WordCamps, and there were 148, or 145, something like that. So there is quite a bit of time to go between 22 to 142 or something like that.

\n\n\n\n

But it’s coming back especially because all those WordPress meetups, the local meetups, are all coming back as well. I think there was a note in the State of the Word that out of the 500, 260 have already come back to in-person events. And we know that WordPress meetups are actually the prerequisite to actually have local WordCamp organizers together to organize a WordCamp. So yeah, it’s all coming back and I’m glad that it’s coming back because of the connection that you have in the community. Yeah.

\n\n\n\n

Hector Prieto: As I mentioned earlier, I came to the contributing space in 2020. It was during the pandemic, so actually my first WordCamp was the only WordCamp in 2021. And my second WordCamp was for computer ware in last year. So it was really nice and refreshing for me to meet all the other contributors. It is something special, for sure.

\n\n\n\n

Birgit Pauli-Haack: Absolutely, yeah. It was great to meet you, Hector, although we had so many meetings with people on Zoom. Yeah.

\n\n\n\n

Hector Prieto: Fun times.

\n\n\n\n

Birgit Pauli-Haack: Yeah.

\n\n\n\n

Hector Prieto: Well, circling back to State of the Word, I would also like to point out that, last but not least, it’s really cool to see how Openverse has grown since joined WordPress about a year and a half ago. And I’m super excited to see that coming, Openverse integration in WordPress that will allow users to directly search and add images from Openverse into their WordPress site without leaving the editor at all. That’s super cool.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s super cool. And I think it would also be really cool to have that also go back to if somebody uploads an image to WordPress and checks the check mark, also put it into Openverse. I think that part would really make it to a 360 kind of integration. I also love that there’s not only for images, but there is a lot of audio already uploaded to the Openverse that you can use on podcasts or on videos, and add free without having to think about royalties and buying for it and all that. Yeah, so free to the community.

\n\n\n\n

Hector Prieto: There’s so many possibilities there. The future is exciting.

\n\n\n\n

Birgit Pauli-Haack: Yeah, it’s really exciting. And I’m glad that it’s all happening in conjunction with WordPress. The same with the WordPress photos library, where people can just upload their photos and have it be it in the public domain and make it available to the broader community. It’s really cool.

\n\n\n\n

Hector Prieto: Yeah.

\n\n\n\n

WordPress 6.2

\n\n\n\n

Birgit Pauli-Haack: All right. So between Christmas and New Year’s, Hector, you published the release schedule proposal for 6.2. I think it was something we were all waiting for. Kind of, okay, how do we plan first quarter when we don’t know when the release is coming? So you provided. So if the release team concurs, what’s the plan? When will we see the first Beta?

\n\n\n\n

Hector Prieto: If the proposed plan is approved, the first Beta release will be on February 7th, which is 10 days before the first of our WordCamp Asia takes place.

\n\n\n\n

Birgit Pauli-Haack: Excellent. In the planning schedule, you also have a call for contributors to volunteer for the release squad. So if you, dear listeners, are inclined to take part in it and you already have a little experience in contributing, throw your hat in the ring by commenting on the release post on the scheduled proposal post. And also throw your hat in the ring also means for those who English is their second language, also means raise your hand, you want to volunteer to be part of it, and then the release team is coming together. When do you expect that you will have a final plan?

\n\n\n\n

Hector Prieto: The call for volunteers is open as we speak. Considering the end of the year vacation people are taking, contributors taking, I think we won’t have anything until end of next week or the following one. We’re leaving some extra time for people to come back from the holidays and chime in.

\n\n\n\n

Birgit Pauli-Haack: All right. Okay. Yeah, so there are only two more Gutenberg releases before the feature freeze, if I calculate that correctly. We better get started in reviewing all the great new features that are coming in, in a more consolidated way.

\n\n\n\n

Hector Prieto: Definitely. I encourage all of our listeners to start testing and giving feedback. It’s always super helpful. Also, compared to the past releases, the proposed 6.2 schedule both include a fourth Beta release compared to the previous three ones to leave some extra buffer time between WordCamp Asia and release candidate one, which will be on March 7th for a final release on March 28th.

\n\n\n\n

Birgit Pauli-Haack: Oh, okay. Yeah, so contributor day at WordCamp Asia is definitely going to be part of it and that is really cool to have. Maybe we need to organize some tables that do some testing there. I don’t know how far the work of Asia contributor day team is about that, but having that plan definitely gives us all focus on that contributor day. All right, cool. So to repeat that, final release could be March 28th, so that’s about three months from today. And we will have a 6.2 release, provided everything works out as we anticipate now.

\n\n\n\n

And I have a reminder for our listeners now for next week. The Gutenberg Times Live Q&A, Layout, Layout Layout will be happening on January 11th at 5:00 PM Eastern. That’s 22:00 UTC. And in this show, Isabel Brison, Andrew Serong, Justin Tadlock and I will discuss the opportunities and challenges for all the layout features for site builders. And we will be available for questions and answer them.

\n\n\n\n

And Isabel Brison will also give us a demo of the various layout scenarios to use. She has, with Andrew, been instrumental in building all the features into the site editor and the blocks, and it’s going to be a very interesting show. It’s also going to be a little preview on Isabel Brison’s talk at WordCamp Asia in February 2023. So join us, link us in the show notes, and don’t forget you need to register there and to be… We will have a recording, of course, with the show notes and as well as a transcript, but it’s always good to have your questions answered live by the experts on the panel, and we have some great experts there. 

\n\n\n\n

What’s Released

\n\n\n\n

So, that brings us to the latest Gutenberg releases. First, there’s Gutenberg 14.8. That was released in December 12th. Ryan Welcher was release lead and it had 167 PRs merged by 42 contributors, five of which were first contributors. So welcome to the project, first contributors. So Hector, what’s the most significant enhancement in this release?

\n\n\n\n

Hector Prieto: Well, Gutenberg 14.8, so several changes to the site editor user interface, and introduce something I’m super excited about, which is browse mode. Thanks to this first iteration of browse mode, users can switch between editing and browsing modes in the site editor, making it much easier to navigate through templates and template parts or even add new ones through the sidebar. It’s a feature that has been long awaited and it’s finally here and I’m super excited.

\n\n\n\n

Birgit Pauli-Haack: Yeah. And it helps you with where you land when you click on the site editor. You are now not landing into editing your homepage and so now you have a better entrance into the site editor. And I really like that because it gets you better settled into what you’re going to do.

\n\n\n\n

Hector Prieto: It makes for a nicer onboarding and it’s less dangerous, let’s say, because it’s much more difficult to break your design just as soon as you land on the site editor.

\n\n\n\n

Birgit Pauli-Haack: Yeah, totally. So the navigation block also had some enhancements, especially with the migration from the old menu. So if you have a location primary, it will now fall back to the navigation menu from the classic menu. That is really helpful on the transition. There are other fallback updates made that it also uses the most recently created menu from the classic theme when you start migrating to a block theme.

\n\n\n\n

Enhancements

\n\n\n\n

So that is definitely a good help for transitioning from a classic theme to a block theme. But also it kind of decreases the mental load that you don’t have to recreate all your menus when you switch out the theme, which is something that was sometimes really critical in the classic menu, in the classic theme space, where everybody had different menu locations. And so I don’t think that it’s completely solved yet, but this is definitely a first step.

\n\n\n\n

Hector Prieto: Yes, it’s a step on the right direction. We all know building menus is one of the most challenging aspects of building your site. And contributors are making a huge step for making the menu building process much easier.

\n\n\n\n

Birgit Pauli-Haack: Yeah, absolutely. Yeah. There’s also one that came with 14.8 that is for the query block. The parent block is they removed the color block support just because it was always clashing up against the other blocks that are in the query block for the post template for the title and the excerpt. You could kind of get lost in which color did we do, and where do we do that? So removing it from the wrapper query block is definitely a good choice because it removes some of that confusion of where colors are actually set.

\n\n\n\n

Hector Prieto: Exactly. Contributors have seen a few inconsistencies when adding the color to the wrapper query block, between the title, between the navigation links. So now the colors block supports are all in the inner blocks and there’s no space for confusion.

\n\n\n\n

Birgit Pauli-Haack: So what else do we see there? Yeah, I think that was it on the 14.8 release, on the highlights. There are certainly the sidebar tabs for the navigation blocks. There is great work on the experimentation that happened. So right now we have five areas of experimentation in the Gutenberg plugin and there is only two more freeze, two more releases to get them out of experimentation into the production of the Gutenberg plugin. One of them is the sidebar for the navigation block. The other one is the separated settings tab in the sidebar that separates the styles from the features. And then the others, I don’t recall right now. Hang on, I’m going to check them out. I just had it there and then I closed my browser because, I don’t know, sometimes I just randomly close browser tabs, which is a really good way to confuse myself.

\n\n\n\n

Hector Prieto: The type interface is making good progress and it’s something we would likely see out of experimental very soon.

\n\n\n\n

Birgit Pauli-Haack: Oh, yeah. And then is the global styles for custom CSS is actually in… We all wait for that, but it’s now in the experimental stage and need to be switched on through experiments menu item on the Gutenberg plugin. And then the other one is the color randomizer utility that lets you mix the current color palette randomly and change it out. That’s kind of a funky way of handling your website to do a randomized color palette, but it certainly is a proof of concept of something bigger. Was there anything else in the 14.8 you want to mention, Hector?

\n\n\n\n

Hector Prieto: Well, there are a few other main highlights that you might have seen, our listeners might have seen in the release post. One of them is super interesting, which is the custom CSS rules for your site. There’s now a tiny CSS text field where you can add your custom CSS directly in the editor. As we all know, with great power comes responsibility. So it’s nice that you can add a custom CSS directly in the editor, but let’s not overuse the important.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s definitely a way to… But that was before, so site editors or site users or site owners who used the custom CSS piece found that that was the missing piece to actually sign on to the full site editing, because they couldn’t do those very fast changes like changing a font size somewhere or changing a space somewhere or change the color of a border very easily by just using the developer tools, identifying the marker, the selector, and then just change the color in a custom CSS. Yeah, it opens up the capabilities for that.

\n\n\n\n

You need definitely have file editing capabilities on the server and that sometimes was not available to anybody. But those who used it, they really missed it in the file site editing, in the site editing features, so that is really a good thing. And there is also a… It’s not yet released and it’s not merged yet in, but I know that Carolina Nymark is actually working on custom CSS for single blocks. And I think that’s also a good way to, in the paradigm of getting atomic design going, that that’s probably a better approach than having custom CSS being pulled in for every site page or page with the custom CSS. Rather do it per block.

\n\n\n\n

Either way, it’s kind of a interesting feature that people want to have some control or at least go back to that what they are used to do and figure out how they can change it. Well, that definitely was a changelog of 14.8. I don’t think we’ve forgotten anything. I think we, in the release post by Ryan, it was a reorganized…. Oh, the style book is definitely something that was in 4.8. We haven’t talked about it. So do you want to talk about it, what that does?

\n\n\n\n

Hector Prieto: Oh yeah, definitely. The style book is a super cool new feature, which is extension of the style site editor. The style book in a nutshell gives you an overview of all the available blocks you have in a single place so that you can easily browse all the blocks you have available and play with their design.

\n\n\n\n

Birgit Pauli-Haack: When Gutenberg first came out, there were quite a few initiatives where you could have a unit test for blocks, where I think Rich Tabor actually had a plugin and I also worked with some of our clients back then when that we had a list of all the blocks in a page and then looked at it, how the theme works with it. And that was kind of a block unit testing in design. And with the additional features that come with site editing, it was a hard time to figure out what is a change in color on the paragraph block will have additional ramification throughout the site, or when you change style variations.

\n\n\n\n

So I’m really glad that the style book, that’s a menu item in the site editor. You can go there and then see all the blocks that you have. And you get an access to the style variations of your theme so you can select them and then see how the blocks change. And that is so powerful that you don’t have this save and surprise effect anymore. You really look at it and say, “Oh yeah, I like it.” And you also see where the style variations may not be entirely working for your site because there are some things that are left out. So this is so powerful for the experience with the block editor.

\n\n\n\n

Hector Prieto: For our listeners to picture it in their mind, it’s like having a page with demo content with all the blocks. You have it registered either core blocks or third party blocks. So as soon as you install it, applying that provides blocks, all these third party blocks will appear in the style book. And you will be able to see all of them together, play with the global styles, play with the accelerations, and see how they affect all these first party and third party blocks in a single place as if you have demo content page but automatically generated for you.

\n\n\n\n

Birgit Pauli-Haack: Yeah. So it really offsets the need for these block unit testings and it’s very, very powerful. Yeah, I so agree. I think we’ve got it all now. Let’s move on to the next release, which was 14.9. And it has at the time of this recording not been released, but it will come out any hour now. For those who use the Gutenberg plugin on their sites, it’s the first Gutenberg release for 2023. 132 PRs by 46 contributors. Again, five new contributors in there. Congrats for your merge of your PR, and welcome to the project. Thank you so much for your contributions, for all of them.

\n\n\n\n

Hector Prieto: It’s refreshing to see all these new contributors, even in these more maintenance oriented releases that happen during holidays. So congratulations to you all, and welcome.

\n\n\n\n

Birgit Pauli-Haack: What are the highlights? What did you see, or what’s in the release?

\n\n\n\n

Hector Prieto: There are a few changes. They’re mostly iterative, building on top of past features and enhancements. One of them, one very cool, is a new push to global styles button that appears in the cyber blocks, which allows users to, once they edit the blocks’ style and they like it and they say, “Hey, I like this how this is looking or how this image is looking. I would like all my image blocks to look like this.” It allows them to push those styles to global styles so that they automatically affect all the blocks of that type.

\n\n\n\n

Birgit Pauli-Haack: Right. And that’s also why it’s good to have this style book handy so you can actually see if you made a mistake or something like that and said, “Oh no, I didn’t consider this, so let’s do one more time.” Yeah. So that’s a great feature. Yeah, absolutely.

\n\n\n\n

Hector Prieto: Also, for those who like building patterns, now when registering patterns, there’s a new property that allows you to specify in which template a pattern makes sense. Let’s suppose, for example, we are building a 404 pattern. Previously it would be released everywhere, so it would appear everywhere in all kinds of templates. Now you can limit it to only appear on a 404 template, so it doesn’t bring noise to other templates where it doesn’t make sense. So this is going to improve pattern discoverability in general as patterns.

\n\n\n\n

Birgit Pauli-Haack: And it also improves separation of concerns. As you said, it will not show up on every page where even if it’s not suitable for the pattern. But it also themes can then, or plugin can now create custom post types and that all, and just make those patterns available for certain custom post types. I think that is definitely a missing piece that has now been added to it. Excellent. Yeah, I’m really excited about that.

\n\n\n\n

Hector Prieto: Yes, there’s a minor update following up on Gutenberg 14.5. So we are thinking, we’re looking at two months ago, three months ago, when the list view and the document outline were merged in a single panel. We have seen there are a few improvements that can be made in the design. So now, for example, the word count has been moved to the top of the outline for more clarity.

\n\n\n\n

Birgit Pauli-Haack: Yeah, there was some confusion. Where is it now? Yeah. And then you didn’t see it at first because when you hit on update, you post and then the little notification ball totally covered that piece. So it took a while till that goes away so you see the word count and the time to read and also the block count. There were a few pieces missing. I don’t know why they’re missing, but they probably don’t seem to be very important for content creators to see. And the outline, having the other one on the list here in one it definitely makes sense to have that.

\n\n\n\n

So if you haven’t seen that yet, it was in Gutenberg 14.5. It will come to 6.2, so checking it out through the Gutenberg plugin is definitely worth trying, worth a look so your site owners or the clients are prepared to find it in a different space. What I’m also very excited about is that there is now an option to import widgets from the sidebars into template parts. And that is in the whole idea of transitioning from a classic theme to block themes or make a site be better prepared to move to a site, to full site editing block theme. This is definitely a step forward. Any additional thoughts on that?

\n\n\n\n

Hector Prieto: Oh yeah, absolutely. This is a very important milestone towards block adoption because it allows users to migrate from classic widgets to native blocks. It’s worth noting that it doesn’t work on template focus mode yet, it’s only available for the block inspector. But this is definitely a step on the right direction to increase block adoption.

\n\n\n\n

Birgit Pauli-Haack: Excellent, yeah. And George Mamadashvili, who heads… That’s his PR. He also has a nice video on how he demonstrates how it’s going to work. So I hear quite a few people celebrating this piece to make the transition over. Another one is, this is minor thing, but the configurable settings for the fluid typography in the theme JSONs now has a minimum font size, so it can be anchored on the smallest font size. So the fluidity then can increase the font size on a bigger screen. There was a hard coded value of 14 pixels before, with no way to change. And now you can have the minimum font size, like 16 pixels or 18 pixels depending on your site needs. That’s a minor thing, but I think it is something that quite a few designers were missing.

\n\n\n\n

Hector Prieto: Yes, absolutely. It’s a minor improvement, but we’ve seen lots of these minor improvements in the last, I don’t know, four or five Gutenberg releases building on top of free typography. And when you look at them altogether combined, you can see huge improvements on how the feature is becoming more and more powerful by the day.

\n\n\n\n

Birgit Pauli-Haack: Yeah, absolutely, absolutely. I think that was at 14 point… No, one thing is still really important from the release and that is the adding shadow presets support for the theme JSON. So you can do box shadows on your blocks or wrapper blocks, and that is now available for designers of themes. There is no user interface for that yet. But as we said repeatedly here on the podcast, things will be…

\n\n\n\n

Hector Prieto: It will come.

\n\n\n\n

Birgit Pauli-Haack: Hmm?

\n\n\n\n

Hector Prieto: It will come.

\n\n\n\n

Birgit Pauli-Haack: But it’s important to make it work for the theme developers first. Before you have all the added implementation for site owners that want to change it, you first need to know how it’s actually working so you can see where the pieces are that need to be surfaced in a user interface. So there is a new setting object called shadow, and then you can add different palettes to it for natural and crisp and sharp and soft shadows. And the PR has quite a few information about how that’s implemented. It gives you quite a few use cases on how you can do the shadow boxes for the buttons, for cover block, for menu block. If you have a sticky menu, then you can put a little shadow underneath it to see the difference between the page and the menu. So there are quite a few design use cases to try that out.

\n\n\n\n

Hector Prieto: I’m curious to see what designers come up with thanks to this new setting. I can see lots of 3D buttons and shadow buttons and all these cool things.

\n\n\n\n

Birgit Pauli-Haack: You could even do the outlines of the shadows, kind of, if you have an outline… Yeah, there are some great designs out there right now. So, from the changelog we are on, anything else that you wanted to talk about here that we missed? I know that Tonya Mark has updated the tracking issue for the web fonts API. And what’s merged in this release is the change of architecture to use the Core’s dependencies API with the web fonts API. And there’s a call for testing out there, or it will be out there, and making sure that how you use it. She has an update where she asked how you can help. And that is if you have an idea about naming the API, should it be webfonts, or web fonts, two words, or just the fonts API, which I tend to be the fonts API, but there is a renaming before everything gets into the Core that we’ll be name things right.

\n\n\n\n

And then the other one is a call to test the new architecture and share feedback on your testing reports and using the web fonts API. I’m not quite sure how the planning is because it seems to be still blocked furthermore through additional architectural work. Hector, do you think that that will come with 6.2, or is it now a little late for 6.2?

\n\n\n\n

Hector Prieto: Well, Tony and the other contributors are making their best to have this feature land in 6.2, so I’m pretty positive it can make it in 6.2. And the best way to ensure it can land in that version is to help with testing and with feedback. That will help unlock the architecture redesign and the renaming and everything that’s currently being discussed right now.

\n\n\n\n

Birgit Pauli-Haack: All right. Okay. Yeah, if you all are contributing to things, dear listeners, help getting that over the finish line. It definitely needs some testing. So I think that’s the end of talking about Gutenberg 14.9. We’re coming also up on the hour, so I think we can go to closing things. Are there anything that you want to point out that are on the roadmap for 6.2 that you want to have our listeners know? And if not, how can the listeners get in contact with you, where to best meet you online?

\n\n\n\n

Hector Prieto: Well, you can reach out to me in WordPress Slack. Handle is Prieto. I guess it will be written in the show notes. So please feel free to ping me there or in GitHub or in Track. I’m using the handle everywhere, so that’s easy. I would just like to circle back to the 6.2 planning and reminding everyone the call for volunteers is open. So if you’re interested in participating in the squad, you are more than welcome. We will assist you if it’s your first time. If you’re an assistant contributor, you are also welcome and we can learn from you. So everybody’s welcome, that’s the long story short.

\n\n\n\n

Birgit Pauli-Haack: Yeah. And the only thing that I want to remind you is about the next week’s Gutenberg Live Q&A with Isabel Brison, Andrew Serong and Justin Tadlock on Layout, Layout, Layout. January 11th at 5:00 PM Eastern and 20:22 UTC. That’s 10:00 PM on UTC. And as always, the show notes will be published on gutenbergtimes.com/podcast. This is episode 78. And if you have questions and suggestions or news you want us to include, send them to changelog@gutenbergtimes.com. That’s changelog@gutenbergtimes.com. So thank you so much, Hector, for joining me here for the first Changelog podcast in 2023 to spend the time on preparation as well as in the show. Thank you all for listening and goodbye and again, Happy New Year.

\n\n\n\n

Hector Prieto: Thank you for having me and see you soon. Happy New Year, everybody.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 07 Jan 2023 19:14:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:19:\"Gutenberg Changelog\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:121:\"Gutenberg Times: 209 Block Themes, Query Block Variations, Forms with Blocks, Block Art and more – Weekend Edition #240\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://gutenbergtimes.com/?p=23042\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"https://gutenbergtimes.com/209-block-themes-query-block-variations-forms-with-blocks-block-art-and-more-weekend-edition-240/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:27679:\"

Howdy,

\n\n\n\n

Tomorrow is the 5-year anniversary of Gutenberg Times. It feels like I just started yesterday to be fascinated by the possibilities of the block editor. For many people, it actually was just yesterday that they dipped their toes into the world of the new thing. Not you of course. You have been a wonderful subscriber and reader for a while now, and I am infinitely grateful for your support. Thank you!

\n\n\n\n

Welcome to all new subscribers this year. So glad you are here.

\n\n\n\n

Let’s dive into the sixth year together, and learn what will be next for the block editor and what other people make with it and for it. The ecosystem seems to keep expanding quite a bit with the block editor.

\n\n\n\n

Wishing you and yours a fabulous 2023. May you be prosperous, happy, and healthy!

\n\n\n\n

Yours, 💕
Birgit

\n\n\n\n

PS: Reminder: Hope to see you next week at the Gutenberg Times Live Q & A. Get your seats now for January 11, 2023, at 5pm / ET 22:00 UTC

\n\n\n\n\n\n\n\n\n\n

Developing Gutenberg and WordPress

\n\n\n\n

Hector Prieto published the WordPress 6.2 Planning Schedule Proposal, and it’s also a call for volunteers for the release squad. The 6.2 release squad will then decide on the final release schedule. For now, Feature Freeze and Beta 1 would be on February 7th, 2023. Tthere are four Beta releases planned before release candidate 1 will be available on March 7th, and a final release on March 28th, 2023.

\n\n\n\n
\n\n\n\n

Reminder: January 10, 2023, at 9:30 ET / 14:30 UTC: Hallway Hangout: Performance Considerations for Block Themes Anne McCarthy wrote: “At a high level, we’ll go through general intros (what each person does/focuses on), current work underway to address performance, what work is being done specifically for block themes, and general open Q&A. Hallway hangouts are meant to be casual and collaborative so come prepared with a kind, curious mind along with any questions or items you want to demo/discuss.”

\n\n\n\n

From the WordPress Developer Blog

\n\n\n\n

Justin Tadlock published a tutorial for building a book review grid with a Query Loop block variation. WordPress 6.1 introduced an extension to the Query Loop block, which allows plugin developers to filter existing functionality in core WordPress rather than building custom blocks to query posts. This tutorial shows how to build a WordPress plugin that display a list of book review posts including post_meta` data, using a block variation for the Query Loop and set up rendering it on the front end.

\n\n\n\n

Nick Diego tweeted: I always knew the Query Loop block was incredibly powerful, but I had never explored integrating post metadata into custom block variations! Learn how in this fantastic article by @justintadlock on the new WordPress Developer Blog.

\n\n\n\n
\n\n\n\n

Micheal Burridge composed a Roundup post to review 2022 from a block developer’s perspective in is post. You’ll find a select list of resources, to get started or to catch up on the development from the last 12 months, via the Make Blog, WordPress TV and the Learn WordPress site.

\n\n\n\n

Gutenberg plugin releases

\n\n\n\n

Gutenberg 14.8 was released on December 22, 2022, and release lead Ryan Welcher highlighted in his post What’s new in Gutenberg 14.8? (21 December)

\n\n\n\n\n\n\n\n

Sarah Gooding reported on the release as well via the WPTavern: Gutenberg 14.8 Overhauls Site Editor Interface, Adds Style Book

\n\n\n\n
\n\n\n\n

Gutenberg 14.9 is the first release of 2023, and release lead Justin Tadlock pointed out a few new features in his post What’s new in Gutenberg 14.9? (4 January):

\n\n\n\n\n\n\n\n

On the WPTavern, Sarah Gooding took the version for spin and reported on the new magic: Gutenberg 14.9’s New Magic: Push Block Changes to Global Styles

\n\n\n\n
\n\n\n\n

In the upcoming Gutenberg Changelog episode 78, Hector Prieto was my guest. He is a full-time core contributor and coordinator of multi-release WordPress and Gutenberg releases. We discussed Gutenberg 14.8 and 14.9 as well as 6.2 release schedule proposal and other topics. The episode will hit your favorite podcast app over the weekend.

\n\n\n\n\n\n\n\n
\n

🎙️ New episode: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9 with Birgit Pauli-Haack and special guest Hector Prieto

\n
\n\n\n\n

Plugins, Themes, and Tools for #nocode site builders and owners

\n\n\n\n
\n\n\n\n

Sarah Gooding wrote about Block Protocol Announces New WordPress Plugin Coming in 2023 It will allow users to embed interactive blocks that are compatible with Gutenberg, and will include blocks for drawing, GitHub pull request overview, timer, calculation, and more. The plugin will also include new blocks powered by OpenAI DALL-E and GPT .

\n\n\n\n

The Block Protocol project is open source and designed to be an open protocol, and WordPress hopes to integrate more with it in the future.

\n\n\n\n
\n\n\n\n

In the latest WPTavern Jukebox podcast episode, Damon Cook, developer advocate at WPEngine, discussed with Nathan Wrigley the future of website styling in WordPress. Wrigley wrote in the introduction: ” Block-based themes are revolutionizing website styling. You’re going to be able to change any aspect of your website from the UI that you’re familiar with. The hope is that it’ll make styling more accessible to a wider audience.

\n\n\n\n

Damon talks about the fact that we’re in a period of flux right now. The documentation and tooling needed to work with website styles is maturing, but is by no means complete.”

\n\n\n\n
\n\n\n\n

Torsten Landsiedel scratched his personal itch and built the plugin Ignore block name in search, after finding that the WordPress built-in search included in the findings posts where the search keywords are in the HTML comments of blocks, and with that skews, the search result less relevant. It’s particular helpful when your blog is about working with the block editor or about content creation with WordPress. Landsiedel feels that the block editor makes the shortcomings of the built-in search feature worse because blocks contain full words, and not just HTML tags. It’s been a long-standing issue, that this plugin now solves.

\n\n\n\n
\n\n\n\n

209 Block Themes are now available in the WordPress repository with new submissions by Themeisle, sparklewpthemes, olivethemes, deothemes, sonalsinha21, Blockify, hamidxazad, WPZOOM.

\n\n\n\n\n\n\n\n

Ana Segota of Anariel Design also announced Yuna, a block theme for Nonprofits that comes with 100+ Design Patterns, you can add to your page with a simple drag and drop. Use built-in options to arrange and style them any way you want. It also includes built-in styles for the popular GiveWP donations plugin and is also ready to house your ecommerce store.

\n\n\n\n

Making Block Art

\n\n\n\n

Curious about some art behind Matt Mullenweg during State of the Word? Below are those pieces designed for the Museum of Block Art which represent the creativity that Gutenberg blocks inspire. Be sure to stop by and experience the museum’s digital interactive exhibit.

\n\n\n\n

You can see

\n\n\n\n\n\n\n\n\n\n\n\n

Anne McCarthy, instigator and curator of the Museum of Block Art, wrote an insightful blog post about how she approached making art with the Block Editor. Take a look Behind the scenes of creating art with WordPress.

\n\n\n\n

Chuck Grimmett has more examples of WP Block Art on his blog.

\n\n\n\n
\n\n\n\n

Rich Tabor and Courtney Portnoy discussed The creative side of blocks on WordPressTV. Rich Tabor walks the viewers through one of his block art creations. It’s quite inspiring to watch Tabor’s exploratory creative process using the block editor. I learned quite a few things about the power of the various color features: gradient, nested group blocks, and how to replace the theme’s primary and secondary colors for the whole site. You’ll also get an introduction to the Museum of Block Art, where Rich and other block artists showcase their creations. (also mentioned in GT 239)

\n\n\n\n

Form Plugins working with Blocks

\n\n\n\n

Two plugins emerged that take advantage of the block editor and its components and scripts so site owners and builders can use them to create forms.

\n\n\n\n

Munir Kamal, created a block integration for the popular CF7 Forms. It’s aptly names CF Blocks and available in the WordPress repository. He wrote in the description: “With CF7 Blocks, you can easily create and customize contact forms within the familiar block editor interface. No more fiddling with short codes or HTML – just drag and drop blocks to build your forms exactly how you want them.” Sounds spectacular, doesn’t it?

\n\n\n\n

In here article New CF7 Blocks Plugin Brings Blocks to Contact Form 7, Sarah Gooding, took a more in-depth look and shares her findings.

\n\n\n\n
\n\n\n\n

On Twitter, JR Tashjian developer at GoDaddy, introduced OmniForm, the next-generation Form Builder for your website. Sign up for early access now and be among the first to try it. The plan is to make the plugin available in the WordPress plugin directory at the end of January, with early access provided to users the week prior. Tashjian continues: “OmniForm embraces the block editor to the fullest extent and unlike any solution right now. The block editor is the future of editing in WordPress and building any kind of form will be no different from creating a post or page.” Tall order. Looking forward to doing some testing, too.

\n\n\n\n

Theme Development for Full Site Editing and Blocks

\n\n\n\n

Anne McCarthy has a new video up on YouTube: Building a site with WordPress 5.9 vs. WordPress 6.2 (in progress features) – To better show what’s changed with the Site Editor from when it was first introduced in WordPress 5.9, this video goes through both a demo of the original state and a brief look at what’s in place today and what’s to come, especially as 6.2 looks to wrap up much of the work around site editing/phase 2 of Gutenberg. Keep in mind that WordPress 6.2 is not out yet and much of what’s being shown is very much a work in progress with big opportunities to provide feedback along the way. Either way, I hope you enjoy taking a peak back and a look forward.

\n\n\n\n
\n\n\n\n\n

 “Keeping up with Gutenberg – Index 2022” 
A chronological list of the WordPress Make Blog posts from various teams involved in Gutenberg development: Design, Theme Review Team, Core Editor, Core JS, Core CSS, Test, and Meta team from Jan. 2021 on. Updated by yours truly. The index 2020 is here

\n\n\n\n\n

In this video tutorial, Jonathan Bossenger gives you an Introduction to theme.json. You will learn how the theme.js file works, and how you can control these settings and styles.

\n\n\n\n
\n\n\n\n

Daisy Olsen started a new Live Stream schedule and will show off Block Themes in WordPress every Friday at 10:30 am ET / 15:30 UTC on Twitch.

\n\n\n\n

The inaugural show took place Friday, January 6th, 2023 with the topic: Building a Block Theme. It’s a great opportunity to follow along with Daisy and ask questions along the way.

\n\n\n\n\n\n\n\n

Building Blocks and Tools for the Block editor.

\n\n\n\n

Munir Kamal takes you on a journey of From WordPress to the World: Intro to the Standalone Gutenberg Block Editor. In his new plugin, Kamal made the journey and found a few challenges along the way, overcame them and new put it all together for others to follow. Using the app ‘Isolated block editor, from the public repo, maintained by Automattic. Matt Mullenweg in the State of the Word emphasized that the block editor is also used outside of WordPress, with Tumblr, Day One app and with bbPress instance.

\n\n\n\n
\n\n\n\n

The team working on GiveWP went on a similar route on the revamp of the highly popular donation plugin. Post Status recently posted an article about that: The Future of GiveWP and the Block Editor

\n\n\n\n

GiveWP will hold a Town hall event about the new version on January 25th, 2023 at 10am PT / 18:00 UTC – in case someone is interested. Learn more Town Hall: GiveWP Design Mode and What’s Next for 3.0

\n\n\n\n


Kyle Johnson, JavaScript developer at GiveWP will present his talk: Using Gutenberg as a Development Foundation, Not Just a Block Builder at WordCamp Birmingham on February 4th, 2023. As far as we know, the talks will be recorded, but not livestreamed. So, they will show up on WordPress TV in the weeks after the WordCamp.

\n\n\n\n
\n\n\n\n

Mohammed Noufal of Hubspot wrote about How to Create Custom Blocks in WordPress, providing answers to the questions: why use a custom block, how to make Custom Block Templates and how to use custom blocks on your site.

\n\n\n\n
\n\n\n\n

Jonathan Bossenger‘s last section of his series: Let’s code: developing blocks without React!  is now also available on WordPress TV. Let’s code: developing blocks without React! – Review. If you followed along over the past few weeks, you would have learned to build a small WordPress block using plain (vanilla) JavaScript. In this session, we will review everything we’ve learned so far, by rebuilding the entire block from scratch.

\n\n\n\n

The other editions for the series are in order of broadcast/

\n\n\n\n\n\n\n\n\n

Need a plugin .zip from Gutenberg’s master branch?
Gutenberg Times provides daily build for testing and review.
Have you been using it? Hit reply and let me know.

\n\n\n\n

\"GitHub

\n\n\n\n\n

Upcoming WordPress events

\n\n\n\n

January 10, 2023 – 9:30 ET / 14:30 UTC
Hallway Hangout: Performance Considerations for Block Themes with Anne McCarthy

\n\n\n\n

January 11, 2023 – 5 pm ET / 22:00 UTC
Gutenberg Times Live Q & A: Layout, layout, layout
Panel discussion with Isabel Brison, Andrew Serong, Justin Tadlock and Birgit Pauli-Haack

\n\n\n\n

February 4 + 5, 2023
WordCamp Birmingham, AL

\n\n\n\n

February 17 – 19, 2023
WordCamp Asia 2023 

\n\n\n\n

Learn WordPress Online Meetups

\n\n\n\n

January 17, 2023 – 3pm / 20:00 UTC
Patterns, reusable blocks and block locking

\n\n\n\n

January 19, 2023 – 7 pm ET / 24:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 31, 2023 – 3pm ET / 20:00 UTC
Creating a photography website with the block editor

\n\n\n\n
\n\n\n\n\n

Featured Image:

\n\n\n\n
\n\n\n\n

Don’t want to miss the next Weekend Edition?

\n\n\n\n

We hate spam, too and won’t give your email address to anyone except Mailchimp to send out our Weekend Edition

Thanks for subscribing.
\n\n\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 07 Jan 2023 14:25:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Birgit Pauli-Haack\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Wed, 25 Jan 2023 13:14:29 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:13:\"last-modified\";s:29:\"Wed, 25 Jan 2023 13:00:35 GMT\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:16:\"content-encoding\";s:4:\"gzip\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20211220193300\";}','no'),(143,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1674695670','no'),(144,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1674652470','no'),(145,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1674695670','no'),(146,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(147,'recently_activated','a:0:{}','yes'),(148,'can_compress_scripts','1','no'),(149,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1674652484;s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":10:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"5.0.2\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.5.0.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:60:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=2818463\";s:2:\"1x\";s:60:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=2818463\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}s:8:\"requires\";s:3:\"5.0\";}s:9:\"hello.php\";O:8:\"stdClass\":10:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:2:{s:2:\"2x\";s:67:\"https://ps.w.org/hello-dolly/assets/banner-1544x500.jpg?rev=2645582\";s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}s:8:\"requires\";s:3:\"4.6\";}}s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"5.0.2\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}}','no'),(152,'theme_mods_twentytwentythree','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(157,'finished_updating_comment_type','1','yes'),(171,'_transient_timeout_global_styles_twentytwentythree','1674661327','no'),(172,'_transient_global_styles_twentytwentythree','body{--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #ffffff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--color--base: #ffffff;--wp--preset--color--contrast: #000000;--wp--preset--color--primary: #9DFF20;--wp--preset--color--secondary: #345C00;--wp--preset--color--tertiary: #F6F6F6;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--duotone--dark-grayscale: url(\'#wp-duotone-dark-grayscale\');--wp--preset--duotone--grayscale: url(\'#wp-duotone-grayscale\');--wp--preset--duotone--purple-yellow: url(\'#wp-duotone-purple-yellow\');--wp--preset--duotone--blue-red: url(\'#wp-duotone-blue-red\');--wp--preset--duotone--midnight: url(\'#wp-duotone-midnight\');--wp--preset--duotone--magenta-yellow: url(\'#wp-duotone-magenta-yellow\');--wp--preset--duotone--purple-green: url(\'#wp-duotone-purple-green\');--wp--preset--duotone--blue-orange: url(\'#wp-duotone-blue-orange\');--wp--preset--font-size--small: clamp(0.875rem, 0.875rem + ((1vw - 0.48rem) * 0.24), 1rem);--wp--preset--font-size--medium: clamp(1rem, 1rem + ((1vw - 0.48rem) * 0.24), 1.125rem);--wp--preset--font-size--large: clamp(1.75rem, 1.75rem + ((1vw - 0.48rem) * 0.24), 1.875rem);--wp--preset--font-size--x-large: 2.25rem;--wp--preset--font-size--xx-large: clamp(4rem, 4rem + ((1vw - 0.48rem) * 11.538), 10rem);--wp--preset--font-family--dm-sans: \"DM Sans\", sans-serif;--wp--preset--font-family--ibm-plex-mono: \'IBM Plex Mono\', monospace;--wp--preset--font-family--inter: \"Inter\", sans-serif;--wp--preset--font-family--system-font: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;--wp--preset--font-family--source-serif-pro: \"Source Serif Pro\", serif;--wp--preset--spacing--30: clamp(1.5rem, 5vw, 2rem);--wp--preset--spacing--40: clamp(1.8rem, 1.8rem + ((1vw - 0.48rem) * 2.885), 3rem);--wp--preset--spacing--50: clamp(2.5rem, 8vw, 4.5rem);--wp--preset--spacing--60: clamp(3.75rem, 10vw, 7rem);--wp--preset--spacing--70: clamp(5rem, 5.25rem + ((1vw - 0.48rem) * 9.096), 8rem);--wp--preset--spacing--80: clamp(7rem, 14vw, 11rem);}body { margin: 0;--wp--style--global--content-size: 650px;--wp--style--global--wide-size: 1200px; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*=\"wp-block-\"]:not(.alignfull):not([class*=\"__\"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*=\"wp-block-\"]:not(.alignfull):not([class*=\"__\"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: 1.5rem; }body { --wp--style--block-gap: 1.5rem; }body .is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-flow > * + *{margin-block-start: 1.5rem;margin-block-end: 0;}body .is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-constrained > * + *{margin-block-start: 1.5rem;margin-block-end: 0;}body .is-layout-flex{gap: 1.5rem;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body{background-color: var(--wp--preset--color--base);color: var(--wp--preset--color--contrast);font-family: var(--wp--preset--font-family--system-font);font-size: var(--wp--preset--font-size--medium);line-height: 1.6;--wp--style--root--padding-top: var(--wp--preset--spacing--40);--wp--style--root--padding-right: var(--wp--preset--spacing--30);--wp--style--root--padding-bottom: var(--wp--preset--spacing--40);--wp--style--root--padding-left: var(--wp--preset--spacing--30);}a:where(:not(.wp-element-button)){color: var(--wp--preset--color--contrast);text-decoration: underline;}a:where(:not(.wp-element-button)):hover{text-decoration: none;}a:where(:not(.wp-element-button)):focus{text-decoration: underline dashed;}a:where(:not(.wp-element-button)):active{color: var(--wp--preset--color--secondary);text-decoration: none;}h1, h2, h3, h4, h5, h6{font-weight: 400;line-height: 1.4;}h1{font-size: clamp(2.719rem, 2.719rem + ((1vw - 0.48rem) * 1.742), 3.625rem);line-height: 1.2;}h2{font-size: clamp(2.625rem, calc(2.625rem + ((1vw - 0.48rem) * 8.4135)), 3.25rem);line-height: 1.2;}h3{font-size: var(--wp--preset--font-size--x-large);}h4{font-size: var(--wp--preset--font-size--large);}h5{font-size: var(--wp--preset--font-size--medium);font-weight: 700;text-transform: uppercase;}h6{font-size: var(--wp--preset--font-size--medium);text-transform: uppercase;}.wp-element-button, .wp-block-button__link{background-color: var(--wp--preset--color--primary);border-radius: 0;border-width: 0;color: var(--wp--preset--color--contrast);font-family: inherit;font-size: inherit;line-height: inherit;padding: calc(0.667em + 2px) calc(1.333em + 2px);text-decoration: none;}.wp-element-button:visited, .wp-block-button__link:visited{color: var(--wp--preset--color--contrast);}.wp-element-button:hover, .wp-block-button__link:hover{background-color: var(--wp--preset--color--contrast);color: var(--wp--preset--color--base);}.wp-element-button:focus, .wp-block-button__link:focus{background-color: var(--wp--preset--color--contrast);color: var(--wp--preset--color--base);}.wp-element-button:active, .wp-block-button__link:active{background-color: var(--wp--preset--color--secondary);color: var(--wp--preset--color--base);}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-base-color{color: var(--wp--preset--color--base) !important;}.has-contrast-color{color: var(--wp--preset--color--contrast) !important;}.has-primary-color{color: var(--wp--preset--color--primary) !important;}.has-secondary-color{color: var(--wp--preset--color--secondary) !important;}.has-tertiary-color{color: var(--wp--preset--color--tertiary) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-base-background-color{background-color: var(--wp--preset--color--base) !important;}.has-contrast-background-color{background-color: var(--wp--preset--color--contrast) !important;}.has-primary-background-color{background-color: var(--wp--preset--color--primary) !important;}.has-secondary-background-color{background-color: var(--wp--preset--color--secondary) !important;}.has-tertiary-background-color{background-color: var(--wp--preset--color--tertiary) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-base-border-color{border-color: var(--wp--preset--color--base) !important;}.has-contrast-border-color{border-color: var(--wp--preset--color--contrast) !important;}.has-primary-border-color{border-color: var(--wp--preset--color--primary) !important;}.has-secondary-border-color{border-color: var(--wp--preset--color--secondary) !important;}.has-tertiary-border-color{border-color: var(--wp--preset--color--tertiary) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;}.has-xx-large-font-size{font-size: var(--wp--preset--font-size--xx-large) !important;}.has-dm-sans-font-family{font-family: var(--wp--preset--font-family--dm-sans) !important;}.has-ibm-plex-mono-font-family{font-family: var(--wp--preset--font-family--ibm-plex-mono) !important;}.has-inter-font-family{font-family: var(--wp--preset--font-family--inter) !important;}.has-system-font-font-family{font-family: var(--wp--preset--font-family--system-font) !important;}.has-source-serif-pro-font-family{font-family: var(--wp--preset--font-family--source-serif-pro) !important;}','no'),(173,'_transient_timeout_global_styles_svg_filters_twentytwentythree','1674661327','no'),(174,'_transient_global_styles_svg_filters_twentytwentythree','','no'); +INSERT INTO `wp_options` VALUES (1,'siteurl','http://localhost','yes'),(2,'home','http://localhost','yes'),(3,'blogname','Datadog Test WP DB','yes'),(4,'blogdescription','','yes'),(5,'users_can_register','0','yes'),(6,'admin_email','test@gmail.com','yes'),(7,'start_of_week','1','yes'),(8,'use_balanceTags','0','yes'),(9,'use_smilies','1','yes'),(10,'require_name_email','1','yes'),(11,'comments_notify','1','yes'),(12,'posts_per_rss','10','yes'),(13,'rss_use_excerpt','0','yes'),(14,'mailserver_url','mail.example.com','yes'),(15,'mailserver_login','login@example.com','yes'),(16,'mailserver_pass','password','yes'),(17,'mailserver_port','110','yes'),(18,'default_category','1','yes'),(19,'default_comment_status','open','yes'),(20,'default_ping_status','open','yes'),(21,'default_pingback_flag','0','yes'),(22,'posts_per_page','10','yes'),(23,'date_format','F j, Y','yes'),(24,'time_format','g:i a','yes'),(25,'links_updated_date_format','F j, Y g:i a','yes'),(26,'comment_moderation','0','yes'),(27,'moderation_notify','1','yes'),(28,'permalink_structure','/%postname%','yes'),(29,'rewrite_rules','a:93:{s:11:\"^wp-json/?$\";s:22:\"index.php?rest_route=/\";s:14:\"^wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:21:\"^index.php/wp-json/?$\";s:22:\"index.php?rest_route=/\";s:24:\"^index.php/wp-json/(.*)?\";s:33:\"index.php?rest_route=/$matches[1]\";s:17:\"^wp-sitemap\\\\.xml$\";s:23:\"index.php?sitemap=index\";s:17:\"^wp-sitemap\\\\.xsl$\";s:36:\"index.php?sitemap-stylesheet=sitemap\";s:23:\"^wp-sitemap-index\\\\.xsl$\";s:34:\"index.php?sitemap-stylesheet=index\";s:48:\"^wp-sitemap-([a-z]+?)-([a-z\\\\d_-]+?)-(\\\\d+?)\\\\.xml$\";s:75:\"index.php?sitemap=$matches[1]&sitemap-subtype=$matches[2]&paged=$matches[3]\";s:34:\"^wp-sitemap-([a-z]+?)-(\\\\d+?)\\\\.xml$\";s:47:\"index.php?sitemap=$matches[1]&paged=$matches[2]\";s:47:\"category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:42:\"category/(.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:52:\"index.php?category_name=$matches[1]&feed=$matches[2]\";s:23:\"category/(.+?)/embed/?$\";s:46:\"index.php?category_name=$matches[1]&embed=true\";s:35:\"category/(.+?)/page/?([0-9]{1,})/?$\";s:53:\"index.php?category_name=$matches[1]&paged=$matches[2]\";s:17:\"category/(.+?)/?$\";s:35:\"index.php?category_name=$matches[1]\";s:44:\"tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:39:\"tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?tag=$matches[1]&feed=$matches[2]\";s:20:\"tag/([^/]+)/embed/?$\";s:36:\"index.php?tag=$matches[1]&embed=true\";s:32:\"tag/([^/]+)/page/?([0-9]{1,})/?$\";s:43:\"index.php?tag=$matches[1]&paged=$matches[2]\";s:14:\"tag/([^/]+)/?$\";s:25:\"index.php?tag=$matches[1]\";s:45:\"type/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:40:\"type/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?post_format=$matches[1]&feed=$matches[2]\";s:21:\"type/([^/]+)/embed/?$\";s:44:\"index.php?post_format=$matches[1]&embed=true\";s:33:\"type/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?post_format=$matches[1]&paged=$matches[2]\";s:15:\"type/([^/]+)/?$\";s:33:\"index.php?post_format=$matches[1]\";s:12:\"robots\\\\.txt$\";s:18:\"index.php?robots=1\";s:13:\"favicon\\\\.ico$\";s:19:\"index.php?favicon=1\";s:48:\".*wp-(atom|rdf|rss|rss2|feed|commentsrss2)\\\\.php$\";s:18:\"index.php?feed=old\";s:20:\".*wp-app\\\\.php(/.*)?$\";s:19:\"index.php?error=403\";s:18:\".*wp-register.php$\";s:23:\"index.php?register=true\";s:32:\"feed/(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:27:\"(feed|rdf|rss|rss2|atom)/?$\";s:27:\"index.php?&feed=$matches[1]\";s:8:\"embed/?$\";s:21:\"index.php?&embed=true\";s:20:\"page/?([0-9]{1,})/?$\";s:28:\"index.php?&paged=$matches[1]\";s:41:\"comments/feed/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:36:\"comments/(feed|rdf|rss|rss2|atom)/?$\";s:42:\"index.php?&feed=$matches[1]&withcomments=1\";s:17:\"comments/embed/?$\";s:21:\"index.php?&embed=true\";s:44:\"search/(.+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:39:\"search/(.+)/(feed|rdf|rss|rss2|atom)/?$\";s:40:\"index.php?s=$matches[1]&feed=$matches[2]\";s:20:\"search/(.+)/embed/?$\";s:34:\"index.php?s=$matches[1]&embed=true\";s:32:\"search/(.+)/page/?([0-9]{1,})/?$\";s:41:\"index.php?s=$matches[1]&paged=$matches[2]\";s:14:\"search/(.+)/?$\";s:23:\"index.php?s=$matches[1]\";s:47:\"author/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:42:\"author/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:50:\"index.php?author_name=$matches[1]&feed=$matches[2]\";s:23:\"author/([^/]+)/embed/?$\";s:44:\"index.php?author_name=$matches[1]&embed=true\";s:35:\"author/([^/]+)/page/?([0-9]{1,})/?$\";s:51:\"index.php?author_name=$matches[1]&paged=$matches[2]\";s:17:\"author/([^/]+)/?$\";s:33:\"index.php?author_name=$matches[1]\";s:69:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:64:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:80:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&feed=$matches[4]\";s:45:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/embed/?$\";s:74:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&embed=true\";s:57:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:81:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]&paged=$matches[4]\";s:39:\"([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$\";s:63:\"index.php?year=$matches[1]&monthnum=$matches[2]&day=$matches[3]\";s:56:\"([0-9]{4})/([0-9]{1,2})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:51:\"([0-9]{4})/([0-9]{1,2})/(feed|rdf|rss|rss2|atom)/?$\";s:64:\"index.php?year=$matches[1]&monthnum=$matches[2]&feed=$matches[3]\";s:32:\"([0-9]{4})/([0-9]{1,2})/embed/?$\";s:58:\"index.php?year=$matches[1]&monthnum=$matches[2]&embed=true\";s:44:\"([0-9]{4})/([0-9]{1,2})/page/?([0-9]{1,})/?$\";s:65:\"index.php?year=$matches[1]&monthnum=$matches[2]&paged=$matches[3]\";s:26:\"([0-9]{4})/([0-9]{1,2})/?$\";s:47:\"index.php?year=$matches[1]&monthnum=$matches[2]\";s:43:\"([0-9]{4})/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:38:\\([0-9]{4})/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?year=$matches[1]&feed=$matches[2]\";s:19:\"([0-9]{4})/embed/?$\";s:37:\"index.php?year=$matches[1]&embed=true\";s:31:\"([0-9]{4})/page/?([0-9]{1,})/?$\";s:44:\"index.php?year=$matches[1]&paged=$matches[2]\";s:13:\"([0-9]{4})/?$\";s:26:\"index.php?year=$matches[1]\";s:27:\".?.+?/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\".?.+?/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\\.?.+?/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\".?.+?/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\".?.+?/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"(.?.+?)/embed/?$\";s:41:\"index.php?pagename=$matches[1]&embed=true\";s:20:\"(.?.+?)/trackback/?$\";s:35:\"index.php?pagename=$matches[1]&tb=1\";s:40:\"(.?.+?)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:35:\"(.?.+?)/(feed|rdf|rss|rss2|atom)/?$\";s:47:\"index.php?pagename=$matches[1]&feed=$matches[2]\";s:28:\"(.?.+?)/page/?([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&paged=$matches[2]\";s:35:\"(.?.+?)/comment-page-([0-9]{1,})/?$\";s:48:\"index.php?pagename=$matches[1]&cpage=$matches[2]\";s:24:\"(.?.+?)(?:/([0-9]+))?/?$\";s:47:\"index.php?pagename=$matches[1]&page=$matches[2]\";s:27:\"[^/]+/attachment/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:37:\"[^/]+/attachment/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:57:\"[^/]+/attachment/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:52:\"[^/]+/attachment/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:33:\"[^/]+/attachment/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";s:16:\"([^/]+)/embed/?$\";s:37:\"index.php?name=$matches[1]&embed=true\";s:20:\"([^/]+)/trackback/?$\";s:31:\"index.php?name=$matches[1]&tb=1\";s:40:\"([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:35:\"([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:43:\"index.php?name=$matches[1]&feed=$matches[2]\";s:28:\"([^/]+)/page/?([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&paged=$matches[2]\";s:35:\"([^/]+)/comment-page-([0-9]{1,})/?$\";s:44:\"index.php?name=$matches[1]&cpage=$matches[2]\";s:24:\"([^/]+)(?:/([0-9]+))?/?$\";s:43:\"index.php?name=$matches[1]&page=$matches[2]\";s:16:\"[^/]+/([^/]+)/?$\";s:32:\"index.php?attachment=$matches[1]\";s:26:\"[^/]+/([^/]+)/trackback/?$\";s:37:\"index.php?attachment=$matches[1]&tb=1\";s:46:\"[^/]+/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/(feed|rdf|rss|rss2|atom)/?$\";s:49:\"index.php?attachment=$matches[1]&feed=$matches[2]\";s:41:\"[^/]+/([^/]+)/comment-page-([0-9]{1,})/?$\";s:50:\"index.php?attachment=$matches[1]&cpage=$matches[2]\";s:22:\"[^/]+/([^/]+)/embed/?$\";s:43:\"index.php?attachment=$matches[1]&embed=true\";}','yes'),(30,'hack_file','0','yes'),(31,'blog_charset','UTF-8','yes'),(32,'moderation_keys','','no'),(33,'active_plugins','a:1:{i:0;s:19:\"datadog/datadog.php\";}','yes'),(34,'category_base','','yes'),(35,'ping_sites','http://rpc.pingomatic.com/','yes'),(36,'comment_max_links','2','yes'),(37,'gmt_offset','0','yes'),(38,'default_email_category','1','yes'),(39,'recently_edited','','no'),(40,'template','twentytwentythree','yes'),(41,'stylesheet','twentytwentythree','yes'),(42,'comment_registration','0','yes'),(43,'html_type','text/html','yes'),(44,'use_trackback','0','yes'),(45,'default_role','subscriber','yes'),(46,'db_version','53496','yes'),(47,'uploads_use_yearmonth_folders','1','yes'),(48,'upload_path','','yes'),(49,'blog_public','0','yes'),(50,'default_link_category','2','yes'),(51,'show_on_front','posts','yes'),(52,'tag_base','','yes'),(53,'show_avatars','1','yes'),(54,'avatar_rating','G','yes'),(55,'upload_url_path','','yes'),(56,'thumbnail_size_w','150','yes'),(57,'thumbnail_size_h','150','yes'),(58,'thumbnail_crop','1','yes'),(59,'medium_size_w','300','yes'),(60,'medium_size_h','300','yes'),(61,'avatar_default','mystery','yes'),(62,'large_size_w','1024','yes'),(63,'large_size_h','1024','yes'),(64,'image_default_link_type','none','yes'),(65,'image_default_size','','yes'),(66,'image_default_align','','yes'),(67,'close_comments_for_old_posts','0','yes'),(68,'close_comments_days_old','14','yes'),(69,'thread_comments','1','yes'),(70,'thread_comments_depth','5','yes'),(71,'page_comments','0','yes'),(72,'comments_per_page','50','yes'),(73,'default_comments_page','newest','yes'),(74,'comment_order','asc','yes'),(75,'sticky_posts','a:0:{}','yes'),(76,'widget_categories','a:0:{}','yes'),(77,'widget_text','a:0:{}','yes'),(78,'widget_rss','a:0:{}','yes'),(79,'uninstall_plugins','a:0:{}','no'),(80,'timezone_string','','yes'),(81,'page_for_posts','0','yes'),(82,'page_on_front','0','yes'),(83,'default_post_format','0','yes'),(84,'link_manager_enabled','0','yes'),(85,'finished_splitting_shared_terms','1','yes'),(86,'site_icon','0','yes'),(87,'medium_large_size_w','768','yes'),(88,'medium_large_size_h','0','yes'),(89,'wp_page_for_privacy_policy','3','yes'),(90,'show_comments_cookies_opt_in','1','yes'),(91,'admin_email_lifespan','1690204371','yes'),(92,'disallowed_keys','','no'),(93,'comment_previously_approved','1','yes'),(94,'auto_plugin_theme_update_emails','a:0:{}','no'),(95,'auto_update_core_dev','enabled','yes'),(96,'auto_update_core_minor','enabled','yes'),(97,'auto_update_core_major','enabled','yes'),(98,'wp_force_deactivated_plugins','a:0:{}','yes'),(99,'initial_db_version','53496','yes'),(100,'wp_user_roles','a:5:{s:13:\"administrator\";a:2:{s:4:\"name\";s:13:\"Administrator\";s:12:\"capabilities\";a:61:{s:13:\"switch_themes\";b:1;s:11:\"edit_themes\";b:1;s:16:\"activate_plugins\";b:1;s:12:\"edit_plugins\";b:1;s:10:\"edit_users\";b:1;s:10:\"edit_files\";b:1;s:14:\"manage_options\";b:1;s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:6:\"import\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:8:\"level_10\";b:1;s:7:\"level_9\";b:1;s:7:\"level_8\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;s:12:\"delete_users\";b:1;s:12:\"create_users\";b:1;s:17:\"unfiltered_upload\";b:1;s:14:\"edit_dashboard\";b:1;s:14:\"update_plugins\";b:1;s:14:\"delete_plugins\";b:1;s:15:\"install_plugins\";b:1;s:13:\"update_themes\";b:1;s:14:\"install_themes\";b:1;s:11:\"update_core\";b:1;s:10:\"list_users\";b:1;s:12:\"remove_users\";b:1;s:13:\"promote_users\";b:1;s:18:\"edit_theme_options\";b:1;s:13:\"delete_themes\";b:1;s:6:\"export\";b:1;}}s:6:\"editor\";a:2:{s:4:\"name\";s:6:\"Editor\";s:12:\"capabilities\";a:34:{s:17:\"moderate_comments\";b:1;s:17:\"manage_categories\";b:1;s:12:\"manage_links\";b:1;s:12:\"upload_files\";b:1;s:15:\"unfiltered_html\";b:1;s:10:\"edit_posts\";b:1;s:17:\"edit_others_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:10:\"edit_pages\";b:1;s:4:\"read\";b:1;s:7:\"level_7\";b:1;s:7:\"level_6\";b:1;s:7:\"level_5\";b:1;s:7:\"level_4\";b:1;s:7:\"level_3\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:17:\"edit_others_pages\";b:1;s:20:\"edit_published_pages\";b:1;s:13:\"publish_pages\";b:1;s:12:\"delete_pages\";b:1;s:19:\"delete_others_pages\";b:1;s:22:\"delete_published_pages\";b:1;s:12:\"delete_posts\";b:1;s:19:\"delete_others_posts\";b:1;s:22:\"delete_published_posts\";b:1;s:20:\"delete_private_posts\";b:1;s:18:\"edit_private_posts\";b:1;s:18:\"read_private_posts\";b:1;s:20:\"delete_private_pages\";b:1;s:18:\"edit_private_pages\";b:1;s:18:\"read_private_pages\";b:1;}}s:6:\"author\";a:2:{s:4:\"name\";s:6:\"Author\";s:12:\"capabilities\";a:10:{s:12:\"upload_files\";b:1;s:10:\"edit_posts\";b:1;s:20:\"edit_published_posts\";b:1;s:13:\"publish_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_2\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;s:22:\"delete_published_posts\";b:1;}}s:11:\"contributor\";a:2:{s:4:\"name\";s:11:\"Contributor\";s:12:\"capabilities\";a:5:{s:10:\"edit_posts\";b:1;s:4:\"read\";b:1;s:7:\"level_1\";b:1;s:7:\"level_0\";b:1;s:12:\"delete_posts\";b:1;}}s:10:\"subscriber\";a:2:{s:4:\"name\";s:10:\"Subscriber\";s:12:\"capabilities\";a:2:{s:4:\"read\";b:1;s:7:\"level_0\";b:1;}}}','yes'),(101,'fresh_site','1','yes'),(102,'user_count','1','no'),(103,'widget_block','a:6:{i:2;a:1:{s:7:\"content\";s:19:\"\";}i:3;a:1:{s:7:\"content\";s:154:\"

Recent Posts

\";}i:4;a:1:{s:7:\"content\";s:227:\"

Recent Comments

\";}i:5;a:1:{s:7:\"content\";s:146:\"

Archives

\";}i:6;a:1:{s:7:\"content\";s:150:\"

Categories

\";}s:12:\"_multiwidget\";i:1;}','yes'),(104,'sidebars_widgets','a:4:{s:19:\"wp_inactive_widgets\";a:0:{}s:9:\"sidebar-1\";a:3:{i:0;s:7:\"block-2\";i:1;s:7:\"block-3\";i:2;s:7:\"block-4\";}s:9:\"sidebar-2\";a:2:{i:0;s:7:\"block-5\";i:1;s:7:\"block-6\";}s:13:\"array_version\";i:3;}','yes'),(105,'cron','a:7:{i:1674663182;a:1:{s:34:\"wp_privacy_delete_old_export_files\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"hourly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:3600;}}}i:1674695582;a:4:{s:18:\"wp_https_detection\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_version_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:17:\"wp_update_plugins\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}s:16:\"wp_update_themes\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674695658;a:1:{s:21:\"wp_update_user_counts\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:10:\"twicedaily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:43200;}}}i:1674738782;a:2:{s:30:\"wp_site_health_scheduled_check\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:6:\"weekly\";s:4:\"args\";a:0:{}s:8:\"interval\";i:604800;}}s:32:\"recovery_mode_clean_expired_keys\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738858;a:2:{s:19:\"wp_scheduled_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}s:25:\"delete_expired_transients\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}i:1674738859;a:1:{s:30:\"wp_scheduled_auto_draft_delete\";a:1:{s:32:\"40cd750bba9870f18aada2478b24840a\";a:3:{s:8:\"schedule\";s:5:\"daily\";s:4:\"args\";a:0:{}s:8:\"interval\";i:86400;}}}s:7:\"version\";i:2;}','yes'),(106,'widget_pages','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(107,'widget_calendar','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(108,'widget_archives','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(109,'widget_media_audio','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(110,'widget_media_image','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(111,'widget_media_gallery','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(112,'widget_media_video','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(113,'widget_meta','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(114,'widget_search','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(115,'widget_recent-posts','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(116,'widget_recent-comments','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(117,'widget_tag_cloud','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(118,'widget_nav_menu','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(119,'widget_custom_html','a:1:{s:12:\"_multiwidget\";i:1;}','yes'),(121,'recovery_keys','a:0:{}','yes'),(122,'https_detection_errors','a:1:{s:20:\"https_request_failed\";a:1:{i:0;s:21:\"HTTPS request failed.\";}}','yes'),(123,'_site_transient_update_core','O:8:\"stdClass\":4:{s:7:\"updates\";a:1:{i:0;O:8:\"stdClass\":10:{s:8:\"response\";s:6:\"latest\";s:8:\"download\";s:59:\"https://downloads.wordpress.org/release/wordpress-6.1.1.zip\";s:6:\"locale\";s:5:\"en_US\";s:8:\"packages\";O:8:\"stdClass\":5:{s:4:\"full\";s:59:\"https://downloads.wordpress.org/release/wordpress-6.1.1.zip\";s:10:\"no_content\";s:70:\"https://downloads.wordpress.org/release/wordpress-6.1.1-no-content.zip\";s:11:\"new_bundled\";s:71:\"https://downloads.wordpress.org/release/wordpress-6.1.1-new-bundled.zip\";s:7:\"partial\";s:0:\"\";s:8:\"rollback\";s:0:\"\";}s:7:\"current\";s:5:\"6.1.1\";s:7:\"version\";s:5:\"6.1.1\";s:11:\"php_version\";s:6:\"5.6.20\";s:13:\"mysql_version\";s:3:\"5.0\";s:11:\"new_bundled\";s:3:\"6.1\";s:15:\"partial_version\";s:0:\"\";}}s:12:\"last_checked\";i:1674652480;s:15:\"version_checked\";s:5:\"6.1.1\";s:12:\"translations\";a:0:{}}','no'),(126,'_site_transient_timeout_theme_roots','1674654196','no'),(127,'_site_transient_theme_roots','a:3:{s:15:\"twentytwentyone\";s:7:\"/themes\";s:17:\"twentytwentythree\";s:7:\"/themes\";s:15:\"twentytwentytwo\";s:7:\"/themes\";}','no'),(128,'_site_transient_update_themes','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1674652481;s:7:\"checked\";a:3:{s:15:\"twentytwentyone\";s:3:\"1.7\";s:17:\"twentytwentythree\";s:3:\"1.0\";s:15:\"twentytwentytwo\";s:3:\"1.3\";}s:8:\"response\";a:0:{}s:9:\"no_update\";a:3:{s:15:\"twentytwentyone\";a:6:{s:5:\"theme\";s:15:\"twentytwentyone\";s:11:\"new_version\";s:3:\"1.7\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentytwentyone/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentytwentyone.1.7.zip\";s:8:\"requires\";s:3:\"5.3\";s:12:\"requires_php\";s:3:\"5.6\";}s:17:\"twentytwentythree\";a:6:{s:5:\"theme\";s:17:\"twentytwentythree\";s:11:\"new_version\";s:3:\"1.0\";s:3:\"url\";s:47:\"https://wordpress.org/themes/twentytwentythree/\";s:7:\"package\";s:63:\"https://downloads.wordpress.org/theme/twentytwentythree.1.0.zip\";s:8:\"requires\";s:3:\"6.1\";s:12:\"requires_php\";s:3:\"5.6\";}s:15:\"twentytwentytwo\";a:6:{s:5:\"theme\";s:15:\"twentytwentytwo\";s:11:\"new_version\";s:3:\"1.3\";s:3:\"url\";s:45:\"https://wordpress.org/themes/twentytwentytwo/\";s:7:\"package\";s:61:\"https://downloads.wordpress.org/theme/twentytwentytwo.1.3.zip\";s:8:\"requires\";s:3:\"5.9\";s:12:\"requires_php\";s:3:\"5.6\";}}s:12:\"translations\";a:0:{}}','no'),(130,'_site_transient_timeout_browser_894dc60a4e148f4652615ed246d3e298','1675257258','no'),(131,'_site_transient_browser_894dc60a4e148f4652615ed246d3e298','a:10:{s:4:\"name\";s:6:\"Chrome\";s:7:\"version\";s:9:\"109.0.0.0\";s:8:\"platform\";s:9:\"Macintosh\";s:10:\"update_url\";s:29:\"https://www.google.com/chrome\";s:7:\"img_src\";s:43:\"http://s.w.org/images/browsers/chrome.png?1\";s:11:\"img_src_ssl\";s:44:\"https://s.w.org/images/browsers/chrome.png?1\";s:15:\"current_version\";s:2:\"18\";s:7:\"upgrade\";b:0;s:8:\"insecure\";b:0;s:6:\"mobile\";b:0;}','no'),(132,'_site_transient_timeout_php_check_ce267f3653936506950ae9448202043a','1675257259','no'),(133,'_site_transient_php_check_ce267f3653936506950ae9448202043a','a:5:{s:19:\"recommended_version\";s:3:\"7.4\";s:15:\"minimum_version\";s:6:\"5.6.20\";s:12:\"is_supported\";b:1;s:9:\"is_secure\";b:1;s:13:\"is_acceptable\";b:1;}','no'),(135,'_site_transient_timeout_community-events-1de8873aa0984c1dbee47981d08b0def','1674695662','no'),(136,'_site_transient_community-events-1de8873aa0984c1dbee47981d08b0def','a:4:{s:9:\"sandboxed\";b:0;s:5:\"error\";N;s:8:\"location\";a:1:{s:2:\"ip\";s:10:\"172.21.0.0\";}s:6:\"events\";a:1:{i:0;a:10:{s:4:\"type\";s:8:\"wordcamp\";s:5:\"title\";s:15:\"WordCamp Torino\";s:3:\"url\";s:33:\"https://torino.wordcamp.org/2023/\";s:6:\"meetup\";N;s:10:\"meetup_url\";N;s:4:\"date\";s:19:\"2023-04-14 00:00:00\";s:8:\"end_date\";s:19:\"2023-04-15 00:00:00\";s:20:\"start_unix_timestamp\";i:1681423200;s:18:\"end_unix_timestamp\";i:1681509600;s:8:\"location\";a:4:{s:8:\"location\";s:12:\"Turin, Italy\";s:7:\"country\";s:2:\"IT\";s:8:\"latitude\";d:45.050238;s:9:\"longitude\";d:7.669286;}}}}','no'),(137,'_transient_timeout_feed_9bbd59226dc36b9b26cd43f15694c5c3','1674695664','no'),(138,'_transient_feed_9bbd59226dc36b9b26cd43f15694c5c3','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:52:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:8:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"The latest news about WordPress and the WordPress community\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:13:\"lastBuildDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 11:56:32 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"en-US\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"generator\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"https://wordpress.org/?v=6.2-alpha-55136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"image\";a:1:{i:0;a:6:{s:4:\"data\";s:11:\"\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:3:\"url\";a:1:{i:0;a:5:{s:4:\"data\";s:29:\"https://s.w.org/favicon.ico?2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"WordPress News\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:26:\"https://wordpress.org/news\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:5:\"width\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"32\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:6:\"height\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"32\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}s:4:\"item\";a:10:{i:0;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"The Month in WordPress – December 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2023/01/the-month-in-wordpress-december-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:18:\"month in wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14191\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:339:\"Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg: “We are now, as […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:12820:\"\n

Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg:

\n\n\n\n
\n

“We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.”

\nJosepha Haden Chomphosy
\n\n\n\n

December brought with it a time for reflection—a time to look back, celebrate, and start planning new projects. Read on to find out what 2023 holds for WordPress so far.

\n\n\n\n
\n\n\n\n

WordPress is turning 20!

\n\n\n\n

2023 marks the 20th anniversary of WordPress’ launch. The project has come a long way since the first release as it continues to advance its mission to democratize publishing. From its beginnings as a blogging platform to a world-leading open source CMS powering over 40% of websites.

\n\n\n\n

Join the WordPress community in celebrating this important milestone. As the anniversary date approaches, there will be events, commemorative swag, and more.

\n\n\n\n
\n

Stay tuned for updates.

\n
\n\n\n\n
\n\n\n\n

WordPress 6.2 is scheduled for March 28, 2023

\n\n\n\n

Work on WordPress 6.2, the first major release of 2023, is already underway. It is expected to launch on March 28, 2023, and will include up to Gutenberg 15.1 for a total of 10 Gutenberg releases.

\n\n\n\n

The proposed schedule includes four Beta releases to accommodate the first WordCamp Asia and avoid having major release milestones very close to this event.

\n\n\n\n
\n

Read more about the 6.2 schedule and release team.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.8 was released on December 21, 2022. This version features a reorganized Site Editor interface with a Browse Mode that facilitates navigation through templates and template parts. In addition, it includes the ability to add custom CSS via the Style panel and a Style Book that provides an overview of all block styles in a centralized location.
  • \n\n\n\n
  • Gutenberg 14.9 became available for download on January 4, 2023. It introduces a new “Push changes to Global Styles” button in the Site Editor, which allows users to apply individual block style changes to all blocks of that type across their site. Other features include typography support for the Page List block, and the ability to import sidebar widgets into a template part when transitioning from a classic theme.
  • \n
\n\n\n\n
\n

Learn how Gutenberg’s latest releases are advancing the Site Editor experience to be more intuitive and scalable.

\n
\n\n\n\n
\n\n\n\n

Team updates: WordPress big picture goals, new Incident Response Team, and more

\n\n\n\n\n\n\n\n
\n

Check out the 2022 State of the Word Q&A post, which answers submitted questions that Matt could not address at the live event.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

Have thoughts for improving the Five for the Future contributor experience? This post calls for ideas on how this initiative can better support the project and the people behind it.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n\n\n\n\n
\n

Would you like to be a speaker at WordCamp Europe 2023? Submit your application by the first week of February.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @laurlittle, @rmartinezduque.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"WP Briefing: Episode 47: Letter from the Executive Director\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/episode-47-letter-from-the-executive-director/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14175\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:114:\"Hear from WordPress Executive Director Josepha Haden Chomphosy on her vision for the open source project in 2023. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2023/01/WP-Briefing-047.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:8912:\"\n

On episode forty-seven of the WordPress Briefing podcast, Executive Director Josepha Haden Chomphosy shares her vision and current thinking for the WordPress open source project in 2023. Rather read it? The full letter is also available.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Show Notes

\n\n\n\n

make.WordPress.org/core
Join the 6.2 Release!
Submit Topics for the Community Summit!

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:40] 

\n\n\n\n

Last month at State of the Word, I shared some opening thoughts about why WordPress. For me, this is an easy question, and the hardest part is always knowing which lens to answer through. Though I always focus on the philosophical parts of the answer, I know that I often speak as an advocate for many types of WordPressers.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

So as we prepare ourselves for the start of a new year, I have a few additional thoughts that I’d like to share with you, my WordPress community, to take into the year with you. 

\n\n\n\n

Firstly, the Four Freedoms. If you have already listened to State of the Word, you have heard my take on the philosophical side of open source and the freedoms it provides.

\n\n\n\n

But if you didn’t, then the TL;DR on that is that open source provides protections and freedoms to creators on the web that I really think should just be a given. But there are a couple of other things about the Four Freedoms, and especially the way that WordPress does this kind of open source-y thing that I think are worth noting as well.

\n\n\n\n

One of those things is that WordPress entrepreneurs, those who are providing services or designing sites, building applications, they have proven that open source provides an ethical framework for conducting business. No one ever said that you aren’t allowed to build a business using free and open source software, and I am regularly heartened by the way that successful companies and freelancers make the effort to pay forward what they can.

\n\n\n\n

[Josepha Haden Chomphosy 00:02:02]

\n\n\n\n

Not always for the sole benefit of WordPress, of course, but often for the general benefit of folks who are also learning how to be entrepreneurs or how to kind of navigate our ecosystem. And the other thing that I love about the Four Freedoms and the way that WordPress does it is that leaders in the WordPress community, no matter where they are leading from, have shown that open source ideals can be applied to the way we work with one another and show up for one another.

\n\n\n\n

As a community, we tend to approach solution gathering as an us-versus-the-problem exercise, which not only makes our solutions better, it also makes our community stronger. 

\n\n\n\n

As I have witnessed all of these things work together over the years, one thing that is clear to me is this: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, but open source methodologies represent a process that can change the way we approach our work and our businesses.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:01] 

\n\n\n\n

The second big thing that I want to make sure you all take into the year with you is that we are preparing for the third phase of the Gutenberg project. We are putting our backend developer hats on and working on the APIs that power our workflows. That workflows phase will be complex. A little bit because APIs are dark magic that binds us together, but also because we’re going to get deep into the core of WordPress with that phase.

\n\n\n\n

If you want to have impactful work for future users of WordPress, though, this is the phase to get invested in. This phase will focus on the main elements of collaborative user workflows. If that doesn’t really make sense to you, I totally get it. Think of it this way, this phase will work on built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and things like programmable editorial, pre-launch checklists.

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00] 

\n\n\n\n

So phases one and two of the Gutenberg project had a very ‘blocks everywhere’ sort of vision. And phase three and, arguably, phase four will have more of a ‘works with the way you work’ vision.

\n\n\n\n

And my final thought for you all as we head into the year is this, there are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was State of the Word 2013, where Matt dreamed on stage of a true WYSIWYG editor for WordPress. Some say it was State of the Word 2016, where we were all encouraged to learn JavaScript deeply. For a lot of us though, it was at WordCamp Europe in 2018 when the Gutenberg feature plugin first made its way to the repo.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like it’s been a long time because it has been a long time. But I can also confirm that it takes many pushes to knock over a refrigerator. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

For early adopters, both to the creation of Gutenberg as well as its use, hyperfocus on daily tasks makes it really hard to get a concept of scale.

\n\n\n\n

And so I encourage everyone this year to look out toward the horizon a bit more and up toward our guiding stars a bit more as well. Because we are now, as we ever were, securing opportunity for those who come after us because of the opportunity that was secured for us by those who came before us. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:33] 

\n\n\n\n

That brings us now to our small list of big things. It’s a very small list, but two pretty big things. The first thing on the list is that the WordPress 6.2 release is on its way. If you would like to get started contributing there, you can wander over to make.WordPress.org/core. You can volunteer to be part of the release squad. You can volunteer your time just as a regular contributor, someone who can test things — any of that. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

We’ll put a link in the show notes. And the second thing that I wanted to remind you of is that today is the deadline to submit topics for the Community Summit that’s coming up in August. That comes up in the middle of August, like the 22nd and 23rd or something like that. 

\n\n\n\n

We’ll put a link to that in the show notes as well. If you already have chatted with a team rep about some things that you really want to make sure get discussed at the community summit, I think that we can all assume that your team rep has put that in. But if not, it never hurts to give it a second vote by putting a new submission into the form.

\n\n\n\n

And that, my friends, is your small list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in a couple of weeks.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:57:\"\n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:49:\"Letter from WordPress’ Executive Director, 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/letter-from-wordpress-executive-director-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14180\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:127:\"If Phases 1 and 2 had a \"blocks everywhere\" vision, think of Phase 3 with more of a “works with the way you work” vision. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:5903:\"\n

Last month at State of the Word, I shared some opening thoughts about “Why WordPress.” For me, this is an easy question, and the hardest part is knowing which lens to answer through. The reasons that a solopreneur will choose WordPress are different than the reasons a corporation would. And while artists and activists may have a similar vision for the world, their motivations change their reasons, too. That’s why I always focus on the philosophical parts of the answer because I know that I am speaking as an advocate for many types of WordPressers. I have a few other reasons, too, which you may not be aware of as you use our software every day.

\n\n\n\n

Why WordPress?

\n\n\n\n

Most importantly, the Four Freedoms of Open Source. If you have already listened to State of the Word, you have heard my thoughts on the philosophical side of open source and the freedoms it provides. If you didn’t, then the tl;dr on that is that open source provides protections and freedoms to creators on the web that should be a given. There’s an extent to which the idea of owning your content and data online is a radical idea. So radical, even, that it is hard for folks to grasp what we mean when we say “free as in speech, not free as in beer.” Securing an open web for the future is, I believe, a net win for the world especially when contrasted to the walled gardens and proprietary systems that pit us all against one another with the purpose of gaining more data to sell.

\n\n\n\n

A second reason is that WordPress entrepreneurs (those providing services, designing sites, and building applications) have proven that open source offers an ethical framework for conducting business. No one ever said that you cannot build a business using free and open source software. And I am regularly heartened by the way successful companies and freelancers make an effort to pay forward what they can. Not always for the sole benefit of WordPress, but often for the general benefit of folks learning how to be an entrepreneur in our ecosystem. Because despite our competitive streaks, at the end of the day, we know that ultimately we are the temporary caretakers of an ecosystem that has unlocked wealth and opportunity for people we may never meet but whose lives are made infinitely better because of us.

\n\n\n\n

And the final reason is that leaders in the WordPress community (team reps, component maintainers, and community builders) have shown that open source ideals can be applied to how we work with one another. As a community, we tend to approach solution gathering as an “us vs. the problem” exercise, which not only makes our solutions better and our community stronger. And our leaders—working as they are in a cross-cultural, globally-distributed project that guides or supports tens of thousands of people a year—have unparalleled generosity of spirit. Whether they are welcoming newcomers or putting out calls for last-minute volunteers, seeing the way that they collaborate every day gives me hope for our future.

\n\n\n\n

As I have witnessed these three things work together over the years, one thing is clear to me: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, open source methodologies represent a process that can change the way we approach our work and our businesses. 

\n\n\n\n

WordPress in 2023

\n\n\n\n

As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows. Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.

\n\n\n\n

If Phases 1 and 2 had a “blocks everywhere” vision, think of Phase 3 with more of a “works with the way you work” vision. 

\n\n\n\n

In addition to this halfway milestone of starting work on Phase 3, WordPress also hits the milestone of turning 20 years old. I keep thinking back to various milestones we’ve had (which you can read about in the second version of the Milestones book) and realized that almost my entire experience of full-time contributions to WordPress has been in the Gutenberg era.

\n\n\n\n

I hear some of you already thinking incredulous thoughts so, come with me briefly.

\n\n\n\n

There are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was at State of the Word 2013 when Matt dreamed of “a true WYSIWYG” editor for WordPress. Some say it was at State of the Word 2016 where we were encouraged to “learn Javascript deeply.” For many of us, it was at WordCamp Europe in 2017 when the Gutenberg demo first made its way on stage.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like a long time because it has been a long time. I can also confirm that it takes many pushes to knock over a refrigerator. For early adopters (both to the creation of Gutenberg and its use), hyper-focus on daily tasks makes it hard to get a concept of scale.

\n\n\n\n

So I encourage you this year to look out toward the horizon and up toward our guiding stars. We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.

\n\n\n\n

Rather listen? The abbreviated spoken letter is also available.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:63:\"\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:43:\"WordPress is Turning 20: Let’s Celebrate!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wordpress.org/news/2023/01/wordpress-is-turning-20-lets-celebrate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 21:38:49 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:3:{i:0;a:5:{s:4:\"data\";s:6:\"Events\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:4:\"WP20\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14155\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"2023 marks the 20th year of WordPress. Read on to learn about how WordPress is celebrating this milestone.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Dan Soschin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:1476:\"\n

2023 marks the 20th year of WordPress. Where would we all be without WordPress? Just think of that! While many technologies, software stacks, and fashion trends have come and gone throughout the past two decades, WordPress has thrived. This is due to the fantastic work and contributions of the WordPress community, comprised of thousands of contributors; and millions of users who have embraced the four freedoms of WordPress and the mission to democratize publishing.

\n\n\n\n

Let’s celebrate!

\n\n\n\n

Throughout the beginning of 2023, leading up to the official anniversary date of WordPress’s launch (May 27, 2003), a number of different events will celebrate this important milestone, reflect on the journey, and look toward the future.

\n\n\n\n

Please join in!

\n\n\n\n

Over the next few months, be sure to check WordPress’s official social media accounts along with the official anniversary website for updates on how you can be involved in this exciting celebration by contributing content, collecting cool anniversary swag, and much more. 

\n\n\n\n

Use the hashtag #WP20 on social media so the community can follow along.

\n\n\n\n

If you have something planned to celebrate that you would like to be considered for inclusion on the official website, please use this form to share the details.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14155\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"WP Briefing: Episode 46: The WP Bloopers Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wordpress.org/news/2022/12/episode-46-the-wp-bloopers-podcast/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 31 Dec 2022 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14123\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:115:\"This episode of the WP Briefing features all the Josepha bloopers our little elves have stored away over the year. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2022/12/WP-Briefing-046.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:9636:\"\n

This episode of the WP Briefing features all the Josepha bloopers our little elves have stored away over the year.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can normally catch quick explanations of the ideas behind the WordPress open source project with the hope that deeper understanding creates deeper appreciation.

\n\n\n\n

But on today’s bonus episode, instead of catching quick explanations, you’ll catch some quick bloopers. 

\n\n\n\n

The end of the year is a time when many people and many cultures gather together, and whether you observe traditions of light or faith, compassion, or celebration from everyone here at the WordPress Briefing Podcast, we’re wishing you a happy, festive season and a very happy New Year.

\n\n\n\n

Sit back, relax, and enjoy some of the laughs and outtakes from recording the WP Briefing over the year.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress. This is the thing I’ve done 25 times, and I know how to do it for reals.

\n\n\n\n

Welcome to WordPress Briefing, episode 20. Oh no, 7? 27? 26? Episode 27. I know how many things I’ve done.

\n\n\n\n

Ooh, neat. This is Josepha recording episode 46 of the WP Bonus Briefings. Not because we’ve had 46 bonus Briefings, but because this is the 46th one and it is a bonus, it will also have a fancy name. But right now, I’m just calling it the bonus. It’s gonna be quick. Here I go. 

\n\n\n\n

Group them into two big buckets, themes, uh, themes and tools. Mmm, I’m gonna have to redo the whole thing! No! I thought I could save it, and I didn’t save it. I had a typo in my script, and then I messed it up. I, it said into you big buckets instead of into two big buckets. 

\n\n\n\n

[Josepha Haden Chomphosy 00:02:00] 

\n\n\n\n

I’m gonna start over from the target release date because I kind of smeared it all together, um, despite what I intended to do.

\n\n\n\n

And gives everyone, no. What is this ringing of phones? Oh, I was doing so well. Where was I? Let’s see if I can just pick it up.

\n\n\n\n

All righty, live from my closet. It’s episode 20, the WordPress Briefing, WP Briefing. So I have a title for this, and when I started writing it, I really had every intention of writing it to the title. And then what I wrote doesn’t fit the title at all, but does really hang together well. And so we’re gonna have to come up with a new title, but at the moment, it’s called So Many Ways to WordPress.

\n\n\n\n

Here in a minute, you will see why it doesn’t fit. Also, at the end, I feel like I get very, like, angry nerd leader.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:00]  

\n\n\n\n

And so I may, I may at the end, give that a second go and see if there’s a way that I can soften it a little bit, but, I, I don’t know that I can soften it. I feel very strongly about it. So, maybe I am just an angry nerd leader.

\n\n\n\n

Oh, okay. I’ll get us started now that I apparently have filled the room with apologies, not the room, the closet. 

\n\n\n\n

We’ll figure out something very catchy as a title or as an alternative. Very descriptive, and people will click on it because they must know, but we’ll figure out the title later.

\n\n\n\n

@wordpress.org. However, I don’t know why I decided to do an invitation to email me in the middle of that. I’m gonna start from the top of that paragraph. I just got too excited by the opportunity to get mail.

\n\n\n\n

I gotta slow it down. I’m like the fastest talker, had too much coffee. Okay, slowing it down now. 

\n\n\n\n

Huh? What am I saying? No, no, that’s what I’m saying. It’s fine. I, I can do this. 

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00]

\n\n\n\n

Hold on. Oww. Sorry. I was adjusting my microphone, and then it fell down. I happened to be holding it at the time, so it didn’t, like, slam down, I think, and hurt your ears and so I apologize. Good thing I stopped so it didn’t just, like, slam down in the middle of a recording.

\n\n\n\n

That’s all right. I’m gonna give myself that win, even though it’s a hollow one. All right. Trying again. Starting right there, at now since.

\n\n\n\n

This year, it starts on October 18th, 2001. That’s the year? No, 2021. That’s the year. Oh man. I’m doing such a great job of this.

\n\n\n\n

Um, I’m recording this slightly before, um, you’re hearing it? What, how am I gonna start this? Hold on. I don’t know how to start this. All right. I’m, I can do it.

\n\n\n\n

Oh, I’m so glad I remembered. We had guests that could have been so embarrassing.

\n\n\n\n

Now for me, the trade-offs work well. How many times can I say now?

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

Do I just start every sentence with now now? Is this just how I do things? Uh, now, now, now, now. I’m gonna start all over again because I’m in my head about the words in my mouth now. So.

\n\n\n\n

In some near timeframe, some near timeframe. This is not a thing that people say, Dustin, I’m sorry. That’s not a thing people say. I’m just gonna retry that one sentence to sound like I speak with other human beings sometimes.

\n\n\n\n

Today is the start of… I can do these things.

\n\n\n\n

This was a terrible ending. I need to just finish that last part. I’m gonna redo the part where I started with my name and not the name of the podcast. Um, and we’ll do that.

\n\n\n\n

And if you’re supporting or building anything to hand off to clients, you know that timely, easy to ship changes on a site are considered a vital part of any overarching brand and marketing strategy. Wow. It’s like, I don’t know what words are right there. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

I tripped over my own tongue a lot. I’m gonna sit, I’m gonna do that paragraph again because I didn’t do a very good job of it.

\n\n\n\n

I’ll do a better job.

\n\n\n\n

I literally digress, and now I don’t know. I am in my thing. What was I saying? Oh, there we go. 

\n\n\n\n

Topher DeRosia, who founded Word not WordPress. Holy moly. That was a, I knew I was gonna say that, and I was like, don’t say that when you actually get around to saying this, but here I am, and I did it. Even though I knew I was gonna do it and I told myself not to. Doing it again. Right from there.

\n\n\n\n

Not which audiench segment. Oh man. Audiench is not a word, folks. I was on a roll. I’m gonna start right from the primary thing.

\n\n\n\n

I don’t even remember how I started this podcast. What is the last thing I said? I said, here we go. All right. 

\n\n\n\n

Kind of covered some interesting ground, and so, oh no, this is not where I’m gonna start it. I know exactly where I’m gonna start it. Okay. I’m really ready now. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:07:00] 

\n\n\n\n

I suddenly, I’m gonna pause right here because I suddenly got really worried that I didn’t actually hit record. Oh my gosh. I did. Woo. I’m all over the place. Okay. We’ll now continue. Wait, did I? Oh my goodness. I did, super sorry.

\n\n\n\n

Of the WordPress Briefing. I’m gonna do some singing in the middle of some talking, but I keep trying to talk myself out of the singing, so I’m gonna go ahead and do the singing, and then I’ll do the talking before I talk myself out of the singing. Here I go, probably.

\n\n\n\n

I added a word. That was so good. I’m gonna start again. I’m gonna get some water, and then I’m gonna start again. Not again. Again. Just from the ‘and finally.’

\n\n\n\n

I don’t know how I finish my show. Y’all, I do this literally every week. I never know how to finish my show. Here we go.

\n\n\n\n

I don’t know why I shouted at you from the other side of the tiny closet. I apologize. I’m gonna start again from ‘and finally.’

\n\n\n\n

Tada we did it.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:00] 

\n\n\n\n

Ha. I hate it. I hate the whole podcast. It’s gonna be fine. 

\n\n\n\n

Done. Nailed it.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

With that, I’m your host, Josepha Haden Chomphosy. Merry Christmas from me. Happy holidays to you, and we’ll see you again in the new year.

\n\n\n\n

Done.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14123\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:7:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:54:\"WP Briefing: Episode 45: State of the Word Reflections\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:76:\"https://wordpress.org/news/2022/12/episode-45-state-of-the-word-reflections/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 22 Dec 2022 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"Podcast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:11:\"wp-briefing\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14070\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:91:\"Josepha reflects on this year\'s State of the Word address here on the WP Briefing podcast. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:9:\"enclosure\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:3:\"url\";s:60:\"https://wordpress.org/news/files/2022/12/WP-Briefing-045.mp3\";s:6:\"length\";s:1:\"0\";s:4:\"type\";s:0:\"\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:14564:\"\n

In the forty-fifth episode of the WordPress Briefing, WordPress Executive Director Josepha Haden Chomphosy discusses highlights from this year’s State of the Word address.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

References

\n\n\n\n

LearnWP
WordPress Playground
ICYMI: State of the Word Recap
Take the 2022 WordPress Survey!
Exploring WordPress Certifications
Community Summit WordCamp Site
Submit Topics for the 2023 Community Summit
20th Anniversary– Stay Tuned for Updates
Check Out Style Variations and the 2023 Theme

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello, everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:39]

\n\n\n\n

Last week, WordPress hosted its annual State of the Word. As usual, this was delivered by our project co-founder Matt Mullenweg and represented a year-long labor of love from the WordPress community as a whole. There are many things I love about State of the Word, but consistently the thing I love the most is being able to shine spotlights on the great work of our global network of contributors.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:02] 

\n\n\n\n

Since that presentation goes by at the speed of light, I wanted to highlight a few things as well. First things first, I wanted to highlight that we had nearly 1,400 contributors, and by nearly, I mean just one too few. We had 1,399 contributors. So that is a big deal in general, but it’s an especially big deal to me because that’s before we start looking at any contributions that aren’t specifically tied to a release. 

\n\n\n\n

You may be wondering what those non-release contributions are. An incomplete list of those contributions would include organizing WordPress events, training others how to use WordPress, the myriad podcasts, articles, and newsletters that make up the WordPress media community, and any participant in a call for testing. Not to mention the unglamorous ways to contribute, like reviewing themes or reviewing plugins.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:58] 

\n\n\n\n

Things like patching security vulnerabilities and the bazillion things that Meta does to make sure that our community has all the tools that it needs to function. So I want to echo, once again, the huge, huge thanks that Matt already shared in State of the Word, and thank all of you for showing up for our project and for each other this way.

\n\n\n\n

The next thing I wanted to be sure to highlight was LearnWP. It was briefly noted that 12,000 learners had found their way to courses on learn.wordpress.org, and then during the Q&A, there was a related question about certifications in WordPress. 

\n\n\n\n

The need for certifications has been a regular topic in our project, and I mentioned that there are two different ongoing discussions at the moment. One of those discussions is happening directly on the make.wordpress.org/training site, so I’ll share a link in the show notes for that.

\n\n\n\n

But I’ve also been personally chatting on and off with Training team reps and other members of the community about what makes that so hard. In case you have not heard my whole spiel about what makes it difficult, it’s the logistics and our speed of iteration, and public perception. 

\n\n\n\n

[Josepha Haden Chomphosy 00:03:05]

\n\n\n\n

So not exactly a small set of hurdles. I’ll be doing a more complete post on this in the New Year so that we can get some solid documentation of the state of things and not let it be lost forever in this podcast. But I do know that it is something that we are very interested in as a community and something that, historically, I have really been resistant to.

\n\n\n\n

Not because I think it’s a bad idea, but because as someone who’s looking out for our operations side of things and our logistics side of things, it is not clear how we’re gonna get that done. Like I said, in the New Year, keep an eye out for a big, big post that takes a look at the benefits versus the costs and everything that we can do to help make those match each other a bit better.

\n\n\n\n

And then the last thing I wanted to highlight was the WordPress Playground. Okay, so this was the last thing that Matt mentioned, and I want to be sure that it’s clear what’s going on with this project because when I first heard about it, I very nearly lept from my chair! 

\n\n\n\n

[Josepha Haden Chomphosy 00:04:03] 

\n\n\n\n

It was such a remarkably big deal. Okay, so the WordPress Playground uses technological magic called ‘web assembly.’ I don’t know what it is, but it’s magic. And when I say magic, I mean that this tool makes it possible to run WordPress, an instance of WordPress, including a theme and a handful of plug-ins entirely inside your browser as a logged-in admin.

\n\n\n\n

You don’t need a server. You don’t need to select a host. You don’t need to download anything at all. You don’t need to know what your domain’s going to be. You simply select the theme you want to test. Add some dummy content and see how all of the posts and pages function as though we’re a real live WordPress site running on your favorite top-tier host.

\n\n\n\n

Then when you close the tab, it’s gone forever. Poof. Just like that. Now, this is a brand new project. It’s brand new to us and has a long way to go. So if working on that sounds cool, stop by the Meta Playground channel in the Making WordPress Slack. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:09] 

\n\n\n\n

But this, in my mind, changes the way that we stage sites.

\n\n\n\n

It could change the way we determine whether a theme or plugin is right for us. And arguably, it can become a stress-free way to introduce new or undecided users to WordPress’s admin area so that they can tell what they’re getting into. So when I say that this is a mind-blowing thing, and when I say that it is powered by magic, like it is astounding, it is astounding.

\n\n\n\n

And the applications for our users as a whole, I think, are untapped yet, and potentially even the applications for our learners and future learners of WordPress– equally untapped. I’m very excited to see what we can do with this project in the future. So stop by the Meta channel. Stop by Meta Playground.

\n\n\n\n

See what’s going on over there. We would love to have you. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

So those are my highlights of the day for State of the Word. Like I said, there are a few things I want to do more of a deep dive on in the text, so keep an eye out on make.wordpress.org/projects for most of those. But right now, let’s make some time for the small list of big things.

\n\n\n\n

[Josepha Haden Chomphosy 00:06:17] 

\n\n\n\n

Today I actually have kind of like a big list of big things. But I pretended it was small, so you didn’t turn off the podcast. So the first thing that I have is that in case you missed State of the Word, if you didn’t have a Watch Party to go to, or you didn’t know it was happening and so you didn’t really tune in at the time, I’m going to drop in a link of the recording.

\n\n\n\n

It’s gonna probably start right when everything gets going. And so you shouldn’t have to scrub through anything. If you end up on one of the recordings that includes like the whole live stream, there is jazz for the first 30 minutes, and just, you know, skip through that.

\n\n\n\n

[Josepha Haden Chomphosy 00:07:00]

\n\n\n\n

The second thing on my big list of big things is our annual community survey. So Matt mentioned this in State of the Word, and he pointed out that one of the things that makes WordPress and open source in general so effective is that we have a way to communicate with people who are using our software and we make every effort to be responsive to it.

\n\n\n\n

So the annual survey that we send out, it used to be quite big, and we’ve cut it down to 20 questions. If you want, you can think of it as like a census, so have your type of work and how long you’ve been working in WordPress, and what you wish to do with WordPress– have all those things be counted so we have a good idea of the type of person who’s currently using WordPress, and we can account for your needs and wants.

\n\n\n\n

But also, if you want to think of it more as an opportunity to share the things that were especially useful for you in the project this year or especially valuable for you as a contributor, this is also an excellent place to do that.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:01] 

\n\n\n\n

There’s a QR code running around on the internet somewhere, but I’ll also put a link in the show notes. If you do not know where the show notes are, by the way, they are at wordpress.org/news/podcast, and you’ll be able to get to the survey.

\n\n\n\n

The third thing on my big list of big things is that next year we’re hosting a community summit. So if you’ve never been to a community summit, Matt mentioned that it is an opportunity for the best and most prolific contributors that we have to show up and discuss the things that are the biggest problems for the WordPress project right now.

\n\n\n\n

But we also want to make sure that we are making space for the voices that we know that we are missing from the community as well as contributors who look like they are probably excellent future stewards of this open source project that we are taking care of together. And so there is a whole website for that.

\n\n\n\n

[Josepha Haden Chomphosy 00:08:55] 

\n\n\n\n

I believe it’s communitysummit.wordcamp.org. Right now, there is a form up asking for topics that you want to be able to discuss while we are there, but it’s taking place, if I recall correctly, on August 22nd and 23rd of 2023.

\n\n\n\n

Number four on my big list of big things is that next year is WordPress’s 20th anniversary. So on May 27th of next year, WordPress will officially be 20 years old. So on our 10th birthday, anniversary rather, and our 15th anniversary, we pulled together some parties all across the world. 

\n\n\n\n

We had some images, some logos, and things that were specific to the celebration that we printed into stickers and that folks put on, like, mugs and backpacks and cakes and stuff. So if you want to learn more about that, keep an eye out in the community channel in making WordPress Slack. They will keep you posted on how to one, find any of those logos and designs so that your local community can join in the celebrations.

\n\n\n\n

[Josepha Haden Chomphosy 00:10:03] 

\n\n\n\n

But they will also help you learn how to have any sort of WordPress celebration party that we’re doing there in May of 2023. 

\n\n\n\n

And then the final thing on my big list of big things, it was mentioned that on the 2023 theme that was shipped with a bunch of style variations and there was this really, I think, excellent illustrative video that Rich Tabor put together for us that shows that you can switch through style variations on a single theme and have a site that looks totally different.

\n\n\n\n

Now, that feels like that’s just a thing that should always have been in WordPress, but it is new this year. And so, if you have not yet had a chance to look at the 2023 theme, it is the default theme that shipped with 6.1. And so, if you have it on your website and just haven’t had a look at it yet, I encourage you to do that.

\n\n\n\n

[Josepha Haden Chomphosy 00:11:00]

\n\n\n\n

It’s a really interesting implementation that makes a single theme potentially look like an infinite number of other themes, and those style variations can be specific to the theme or can just kind of be around and about in the patterns that are also available in Core. 

\n\n\n\n

Give that a look. I think it’s super worthwhile.

\n\n\n\n

And that, my friends, is your big list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in the New Year.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14070\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:40:\"The Month in WordPress – November 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2022/12/the-month-in-wordpress-november-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 20 Dec 2022 12:05:04 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:18:\"Month in WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:18:\"month in wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14124\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:317:\"WordPress enthusiasts tuned in last week for the State of the Word address to celebrate the project\'s yearly accomplishments and explore what 2023 holds. But that’s not the only exciting update from the past month. New proposals and ideas are already emerging with an eye on the year ahead—let’s dive into them!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:13931:\"\n

WordPress enthusiasts tuned in last week for the State of the Word address to celebrate the project’s yearly accomplishments and explore what 2023 holds. But that’s not the only exciting update from the past month. New proposals and ideas are already emerging with an eye on the year ahead—let’s dive into them!

\n\n\n\n
\n\n\n\n

Highlights from State of the Word 2022

\n\n\n\n

WordPress co-founder Matt Mullenweg delivered the annual State of the Word address on December 15, 2022, before a live audience in New York City. Most attendees joined the event via livestream or one of the 33 watch parties held across 11 countries.

\n\n\n\n

Josepha Haden Chomphosy, Executive Director of WordPress, kicked off this year’s event with an introduction to the Four Freedoms of open source and the importance of WordPress in ensuring “a free, open and interconnected web for the future.”

\n\n\n\n

Similar to past State of the Word events, Matt reflected on the project’s achievements over the past year, including Gutenberg’s adoption beyond WordPress, the steady progress in advancing the site editing experience, and the return to in-person events. In addition, he took the opportunity to remind everyone of the 2023 Community Summit and the 20th anniversary of WordPress coming up next year.

\n\n\n\n

Ahead of 2023, Matt announced new taxonomies in the WordPress.org theme and plugin directories to help users identify the extensions that best fit their needs and plans for Phase 3 of Gutenberg—Collaboration—among other notable updates.

\n\n\n\n

People who watched the State of the Word enjoyed a demo of WordPress Playground, an experimental project to explore, experiment, and build apps with a WordPress instance that runs entirely in the browser.

\n\n\n\n
\n

Missed the event? Read the recap or watch the State of the Word recording and Q&A session on WordPress.tv.

\n
\n\n\n\n
\n\n\n\n

The 2022 WordPress Survey is open

\n\n\n\n

The annual WordPress survey helps project leadership and those who build WordPress understand more about the contributor experience, how the software is used, and by whom.

\n\n\n\n

This year’s survey will remain open through the end of 2022 and is available in English, French, German, Italian, Japanese, Russian, and Spanish.

\n\n\n\n
\n

Take the 2022 WordPress Survey to help make an impact on the project.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.6, released on November 23, 2022, came with many refinements to core blocks. Notable highlights include a variation picker that allows users to choose a desired layout when a Group block is inserted on a page, a new list view for editing the Navigation block, and a keyboard shortcut to transform paragraph blocks into headings.
  • \n\n\n\n
  • Gutenberg 14.7, released on December 7, 2022, introduced an experimental tabbed sidebar, colors to help identify some block types in list view, and improvements to the Page List block to make it easier to manage page links in the content.
  • \n
\n\n\n\n
\n

Follow the “What’s new in Gutenberg” posts to stay on top of the latest enhancements.

\n
\n\n\n\n
\n\n\n\n

Team updates: Introducing the block editor in the support forums, a revamped Showcase page, and more

\n\n\n\n\n\n\n\n
\n

Curious about why WordPress has so many releases? Tune in to Episode 44 of WP Briefing to learn about the role of major and minor releases in the project.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

The Community Team is calling on WordPress contributor teams to suggest topics for the 2023 Community Summit by January 16, 2023.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n
    \n
  • The #WPDiversity working group organized several workshops during the past few months. Among other highlights, attendees of the Speaker Workshop for Women Voices in Latin America reported a 52% increase in self-confidence to speak in public. Stay tuned for the next events.
  • \n\n\n\n
  • The WordCamp Europe 2023 organizing team shared their content vision for next year’s flagship event in Athens, Greece.
  • \n\n\n\n
  • WordCamp Asia 2023 is just a few months away, scheduled for February 17-19, 2023, in Bangkok, Thailand. Organizers have announced the first recipient of the WordCamp Asia Diversity Scholarship, Awais Arfan.
  • \n\n\n\n
  • Three more WordCamps are happening in the next few months:\n\n
  • \n
\n\n\n\n
\n

WordCamp Europe 2023 is calling for sponsors and speakers.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @webcommsat, @sereedmedia, and @rmartinezduque.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14124\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"State of the Word 2022: A Celebration of the Four Freedoms of Open Source\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"https://wordpress.org/news/2022/12/state-of-the-word-2022-recap/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 16 Dec 2022 22:11:15 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:6:\"Events\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:17:\"state of the word\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14110\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:355:\"WordPress belongs to all of us, but really we’re taking care of it for the next generation.” Matt Mullenweg A small audience of WordPress contributors, developers, and extenders gathered on December 15 for the annual State of the Word keynote from WordPress co-founder Matt Mullenweg. Those who could not join in person joined via livestream […]\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Chloe Bringmann\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:5677:\"\n
\n

WordPress belongs to all of us, but really we’re taking care of it for the next generation.”

\nMatt Mullenweg
\n\n\n\n
\n\n
\n\n\n\n

A small audience of WordPress contributors, developers, and extenders gathered on December 15 for the annual State of the Word keynote from WordPress co-founder Matt Mullenweg. Those who could not join in person joined via livestream or one of 33 watch parties held across 11 countries, with more than 500 RSVPs.

\n\n\n\n
\"The
\n\n\n\n

Executive Director, Josepha Haden Chomphosy, introduced the event with a reminder of why so many of those gathered choose WordPress—the Four Freedoms of open source. As Haden Chomphosy noted, open source is an idea that can change our generation, and WordPress is one of the most consistent and impactful stewards of those freedoms.

\n\n\n\n

As with past State of the Word events, Matt reflected on the year’s accomplishments, learnings, and aspirations as the project moves into 2023. From Gutenberg concluding its second phase of site editing in preparation for phase three—Collaborative Workflows, to the reactivation of meetups and global WordCamps, to the introduction of a new theme and plugin taxonomy, to musings on the potential of machine learning, WordPress enters its 20th year continuing to define bleeding edge technology in thanks to the ecosystem’s vibrant community. 

\n\n\n\n

The one-hour multimedia presentation was followed by an interactive question and answer session where Matt fielded questions from the livestream and studio audience. All questions will be responded to in a follow-up post on Make.WordPress.org/project

\n\n\n\n

Discover everything that was covered by watching the official event recording and join the ongoing #StateOfTheWord conversation on Tumblr, Instagram, Facebook, Linkedin, and Twitter. For another way to get involved, consider sharing your experience with WordPress in the 2022 WordPress Community Survey.

\n\n\n\n\n\n\n\n

Referenced Resources 

\n\n\n\n\n\n\n\n

Special thanks to @laurlittle and @eidolonnight for review and collaboration.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14110\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:60:\"\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"Share Your Experience: The 2022 WordPress Survey is Open\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:57:\"https://wordpress.org/news/2022/12/2022-wordpress-survey/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 01 Dec 2022 16:00:19 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:2:{i:0;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:6:\"survey\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14062\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:131:\"The 2022 WordPress survey is open for your input and available in English, French, German, Italian, Japanese, Russian, and Spanish.\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:15:\"Chloe Bringmann\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:4584:\"\n

Each year, members of the WordPress community (users, site builders, extenders, and contributors) provide valuable feedback through an annual survey. Key takeaways and trends that emerge from this survey often find their way into the annual State of the Word address, are shared in the public project blogs, and can influence the direction and strategy for the WordPress project.

\n\n\n\n

Simply put: this survey helps those who build WordPress understand more about how the software is used, and by whom. The survey also helps leaders in the WordPress open source project learn more about our contributors’ experiences.  

\n\n\n\n

To ensure that your WordPress experience is represented in the 2022 survey results, take the 2022 annual survey now.

\n\n\n\n\n\n\n\n

You may also take the survey in French, German, Italian, Japanese, Russian, or Spanish, thanks to the efforts of WordPress polyglot contributors. These are the most frequently installed languages based on the number of WordPress downloads. 

\n\n\n\n

The survey will be open through the end of 2022, and then WordPress plans to publish the results sometime in 2023. This year, the survey questions have been refreshed for more effortless survey flow, completion, and analysis. Some questions have been removed, while a few new ones are now present, reflecting the present and future of WordPress. If you’re looking for the analysis of the 2021 survey results, those will also be shared in early 2023.

\n\n\n\n

Spread the word

\n\n\n\n

Help spread the word about the survey by sharing it with your network, through Slack, or within your social media accounts. The more people who complete the survey and share their experience with WordPress, the more the project as a whole will benefit in the future.

\n\n\n\n

Security and privacy

\n\n\n\n

Data security and privacy are paramount to the WordPress project and community. With this in mind, all data will be anonymized: no email addresses nor IP addresses will be associated with published results. To learn more about WordPress.org’s privacy practices, view the privacy policy.

\n\n\n\n

Thank you

\n\n\n\n

Thank you to the following WordPress contributors for assisting with the annual survey project, including question creation, strategy, survey build-out, and translation:

\n\n\n\n

dansoschin, _dorsvenabili, angelasjin, arkangel, audrasjb, atachibana, bjmcsherry, chanthaboune, eidolonnight, fernandot, fierevere, fxbenard, jdy68, jpantani, laurlittle, nao, nielslange, peiraisotta, piermario, rmartinezduque, santanainniss.

\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"14062\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:72:\"\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:4:{s:0:\"\";a:6:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"People of WordPress: Huanyi Chuang\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"https://wordpress.org/news/2022/11/people-of-wordpress-huanyi-chuang/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 30 Nov 2022 20:09:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"category\";a:6:{i:0;a:5:{s:4:\"data\";s:9:\"Community\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:1;a:5:{s:4:\"data\";s:8:\"Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:2;a:5:{s:4:\"data\";s:7:\"General\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:3;a:5:{s:4:\"data\";s:10:\"Interviews\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:4;a:5:{s:4:\"data\";s:9:\"HeroPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}i:5;a:5:{s:4:\"data\";s:19:\"People of WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=13562\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:11:\"isPermaLink\";s:5:\"false\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:144:\"The latest People of WordPress story features Huanyi Chuang, from #Taiwan, on his journey to become a digital marketer and front end developer. \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Abha Thakor\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:40:\"http://purl.org/rss/1.0/modules/content/\";a:1:{s:7:\"encoded\";a:1:{i:0;a:5:{s:4:\"data\";s:11689:\"\n

This month we feature Huanyi (Eric) Chuang, a front end developer from Taiwan, who helps connect local groups to WordPress and the worldwide open source community. He is part of the team helping to make the first WordCamp Asia a success in 2023.

\n\n\n\n

The People of WordPress series shares some of the inspiring stories of how people’s lives can change for the better through WordPress and its global network of contributors.

\n\n\n\n
\"Huanyi
\n\n\n\n

Discovering WordPress and the benefit of child themes

\n\n\n\n

Huanyi’s first footsteps in WordPress began in 2017 when he worked for a firm that built blogs and developed ad content for clients.

\n\n\n\n

After building a few sites using the platform, he discovered child themes and through them opened up a world of possibilities for his clients. To this day, he uses child themes to deliver truly custom designs and functionality for clients.

\n\n\n\n

Later in his career, Huanyi moved into digital marketing, integrating sites with massive ad platforms like Google and Facebook. This led him to learn to work with tracking code and JavaScript. He also began his learning journey in HTML, CSS, and PHP, to be able to improve his development skills and customize child themes.

\n\n\n\n

Meetups bring together software users to learn together

\n\n\n\n
\"Huanyi
Huanyi pictured in Australia during one of his travels meeting a koala bear.
\n\n\n\n

When Huanyi had a problem with a client’s site, he looked to WordPress meetups near where he lived in Taipei to help find the solutions.

\n\n\n\n
\n

“When I encountered an issue with the custom archive pages, a local meetup announcement showed up on my WordPress dashboard.”

\nHuanyi Chuang
\n\n\n\n

At the meetup, he met more experienced WordPress users and developers there, who answered his questions and helped him learn.

\n\n\n\n

“When I encountered an issue with the custom archive pages, a local meetup announcement showed up on my WordPress dashboard. That was my original connection with the local community,” Huanyi said.

\n\n\n\n

The WordPress community gave Huanyi a chance to connect with people, feed his curiosity about the software, and join a circle of people he could share this interest.

\n\n\n\n

At first, he thought meetups were an opportunity to source new clients, and he took his business cards to every event. However, he soon found that these events offered him the opportunity to make friends and share knowledge.

\n\n\n\n

From then on, Huanyi started focusing more on what he could give to these events and networks, making new friends, and listening to people. This led him to share as a meetup speaker his own commercial website management experience.

\n\n\n\n

The road to WordCamp

\n\n\n\n

It was going to his first meetup and then getting involved with WordCamps that changed Huanyi’s whole relationship with WordPress.

\n\n\n\n
\"Huanyi
\n\n\n\n

In 2018, he took the step to help as an organizer, having joined the Taoyuan Meetup in Taiwan. He played several parts across the organizing team, and the welcoming feeling he got in every situation encouraged him to get more involved.

\n\n\n\n

He recalls meeting new friends from different fields and other countries, which gave him a great sense of achievement and strengthened his passion for participating in the community.

\n\n\n\n

When the team started this meetup, numbers were much lower than in the group in the city of Taipei, but they were not disheartened and gradually grew the local WordPress community.

\n\n\n\n

They created a pattern of ‘multiple organizers,’ which spread the workload and grew friendships. 

\n\n\n\n
\n

“Being connected to and from meetups is the most valuable part of the community. Having these friends makes me gather more information. We share information and benefit from others’ information, and thus we gain more trust in each other. With such credibility, we share more deeply and build deeper relations.”

\nHuanyi Chuang
\n\n\n\n

Before the pandemic, the meetup met every month and grew to become the second largest meetup in Taiwan. Huanyi also contributed to the WordPress community as an organizer of WordCamp Taipei 2018 in the speaker team and lead organizer of WordCamp Taiwan 2021.

\n\n\n\n

So why should you join the community?

\n\n\n\n

According to Huanyi, you will always have something to take home with you. It might be new information or experiences. It might be plugins or theme ideas. But most of all, it is the chance to meet fascinating people and make new friends.

\n\n\n\n
\n

Huanyi’s message to other contributors:
“Keep participating, and you will find more you can achieve than you expect.”

\n
\n\n\n\n

He added that long-term participation will ‘let you feel the humanity behind the project’.

\n\n\n\n

Localize: the road ahead for WordPress

\n\n\n\n
\"Huanyi
\n\n\n\n

Huanyi believes WordPress has the power to break down the barriers between designers, project managers, developers, marketers, writers, and publishers. In Taiwan, he said WordPress is ‘a common protocol’ that lets people from all of these disciplines work and communicate together more easily than they ever have before.

\n\n\n\n

That is why he works on and encourages others to localize plugins today. He believes localization of the software is the foundation for the extension of the WordPress community as it enables people to ‘Flex their Freedom’ in a language they speak!

\n\n\n\n

He has helped to organize online events around previous WordPress Translation Day events.

\n\n\n\n

Huanyi said: “I think it’s important to localize WordPress because its very concept of ‘open source’ means that people can access it freely. In another way, free from the monopoly of knowledge and speech. To achieve it, it’s important that people can access it with their own language.

\n\n\n\n

“Localization is the foundation of the extension of WordPress community because it helps people using different languages to access the project and lowers the hurdle to understand how things work.”

\n\n\n\n

Share the stories

\n\n\n\n

Help share these stories of open source contributors and continue to grow the community. Meet more WordPressers in the People of WordPress series.

\n\n\n\n

Contributors

\n\n\n\n

Thank you to @no249a002 for sharing his adventures in WordPress.

\n\n\n\n

Thank you to Abha Thakor (@webcommsat), Mary Baum (@marybaum), Meher Bala (@meher), Chloe Bringmann (@cbringmann), Surendra Thakor (@sthakor), Adeeb Malik (@adeebmalik) for research, interviews, and contributing to this feature article.

\n\n\n\n

The People of WordPress series thanks Josepha Haden (@chanthaboune) and Topher DeRosia (@topher1kenobe) for their support.

\n\n\n\n
\"HeroPress
\n

This People of WordPress feature is inspired by an essay originally published on HeroPress.com, a community initiative created by Topher DeRosia. It highlights people in the WordPress community who have overcome barriers and whose stories might otherwise go unheard. #HeroPress

\n
\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:7:\"post-id\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"13562\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}s:27:\"http://www.w3.org/2005/Atom\";a:1:{s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:0:\"\";s:7:\"attribs\";a:1:{s:0:\"\";a:3:{s:4:\"href\";s:32:\"https://wordpress.org/news/feed/\";s:3:\"rel\";s:4:\"self\";s:4:\"type\";s:19:\"application/rss+xml\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:44:\"http://purl.org/rss/1.0/modules/syndication/\";a:2:{s:12:\"updatePeriod\";a:1:{i:0;a:5:{s:4:\"data\";s:9:\"\n hourly \";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:15:\"updateFrequency\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"\n 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:30:\"com-wordpress:feed-additions:1\";a:1:{s:4:\"site\";a:1:{i:0;a:5:{s:4:\"data\";s:8:\"14607090\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:11:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Wed, 25 Jan 2023 13:14:24 GMT\";s:12:\"content-type\";s:34:\"application/rss+xml; charset=UTF-8\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:25:\"strict-transport-security\";s:11:\"max-age=360\";s:6:\"x-olaf\";s:3:\"⛄\";s:13:\"last-modified\";s:29:\"Thu, 19 Jan 2023 12:00:00 GMT\";s:4:\"link\";s:63:\"; rel=\"https://api.w.org/\"\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:16:\"content-encoding\";s:4:\"gzip\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20211220193300\";}','no'),(139,'_transient_timeout_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1674695664','no'),(140,'_transient_feed_mod_9bbd59226dc36b9b26cd43f15694c5c3','1674652464','no'),(141,'_transient_timeout_feed_d117b5738fbd35bd8c0391cda1f2b5d9','1674695670','no'),(142,'_transient_feed_d117b5738fbd35bd8c0391cda1f2b5d9','a:4:{s:5:\"child\";a:1:{s:0:\"\";a:1:{s:3:\"rss\";a:1:{i:0;a:6:{s:4:\"data\";s:3:\"\n\n\n\";s:7:\"attribs\";a:1:{s:0:\"\";a:1:{s:7:\"version\";s:3:\"2.0\";}}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:1:{s:7:\"channel\";a:1:{i:0;a:6:{s:4:\"data\";s:61:\"\n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:1:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:16:\"WordPress Planet\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:8:\"language\";a:1:{i:0;a:5:{s:4:\"data\";s:2:\"en\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WordPress Planet - http://planet.wordpress.org/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"item\";a:50:{i:0;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:169:\"HeroPress: Becoming A Better Me with Core Contribution – কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায়\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5055\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:160:\"https://heropress.com/essays/becoming-a-better-me-with-core-contribution/#utm_source=rss&utm_medium=rss&utm_campaign=becoming-a-better-me-with-core-contribution\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:25361:\"\"Pull\n

এই নিবন্ধটি বাংলায় পাওয়া যায়

\n\n\n\nHere is Robin reading his own story aloud.\n\n\n\n
\n\n\n\n

Few years back, my daily life started with 10am waking up and going to the office without having breakfast (lazy me). Then doing a 9 hours job with a pretty simple routine and without any major engagement with others.

\n\n\n\n

At present, I wake up with tons of Slack messages and end my day with various in person short/long meetings with my fellow colleagues / mates around the world.

\n\n\n\n

I used to scroll Facebook, you know. But now WordPress Slack has become Facebook to me. How things got changed and became more enjoyable. 

\n\n\n\n

Lucky Me \"😊\"

\n\n\n\n

Hello World : How it all started

\n\n\n\n

I wasn’t supposed to be an engineer in the first place. I was brought up in Cumilla, Bangladesh. Finished my School and College in my hometown. Everyone wanted me to be a Doctor. It is very common here in our country that parents want their child to be a doctor. I completed my 3 months preparation for the Medical exam but later I ended up in Engineering.

\n\n\n\n
\n\n\n\n

I have spent 5 years in Sylhet, a heavenly place to live in. Oh! How I miss Sylhet these days. It has been a few years since I had breakfast (khichuri) in Pach Bhai restaurant (a very popular restaurant in Sylhet) and had tea in chachar tong (a famous tea stall in Modina Market, Sylhet). These days I don’t go out at night but during my Sylhet life, midnight tea was a much desired thing for us and of course that tea from a tong (small tea stall in the roads).

\n\n\n\n\n\n\n\n

My five years at SUST (Shahjalal University of Science and Technology) was a blessing to me. It helped me to become a better person and better me. Sust was full of energy. Seniors and Juniors. Lal Tong (tea stall in our campus). There were almost 300 plus students in our department and we knew personally almost 90 percent of our seniors and juniors. That bond is still alive in Dhaka (most of us living here with our job). Everyone helps each other to get a job or with the recommendation for the best jobs. Almost in every software farm I see SUST CSE seniors or juniors.

\n\n\n\n

Thanks God I got a chance to live those fine memorable years in SUST and Sylhet.

\n\n\n\n

Hello Dolly : Meeting WordPress

\n\n\n\n

My first meeting with WordPress was in my 2nd job. I was facing difficulties with my earlier professional career but as soon as I met WordPress, I just fell for her (WordPress). I found it really easy to adopt and it has a pretty huge community I must say. There were tons of documentation in Codex (but frankly I couldn’t understand at first). Now the documentation (https://developer.wordpress.org/) is much better and much more user friendly. I was amazed with the term Code is Poetry. It felt like I was writing poems instead of doing jobs.

\n\n\n\n

I really enjoyed my early career with WordPress. I wanted to do all by myself (that’s what we call Full Stack these days, LOL).

\n\n\n\n

I used to write markups from design (PSD to Html, that’s write). Then converting that into WordPress. And the training phase which was given to me was really a learning experience. I still keep in my mind that, “You can take unlimited divs. It won’t cost you money”, LOL. I was struggling with CSS opacity. But as soon as I started using it It became Pani(water, means easy) later.

\n\n\n\n

In my earlier life with WordPress I wasn’t aware of the active community and contribution to the project. I did many theme and site customization. Fixed bugs for clients. Built features as per their needs. But I was missing something.

\n\n\n\n

I was missing the large community of WordPress and the inner beauty of the Open Source project.

\n\n\n\n

Code is Poetry : WordPress Core Contribution

\n\n\n\n

My life at WPDeveloper was a blessing to me. It is where I started meeting the large community and the exciting activities of this wonderful community of WordPress. It feels like I truly belong to this community. Everyone is so close and so helpful to each other. 

\n\n\n\n

I have started joining meetups. Taking meetups, yes that’s correct. Started networking with similar minded people. It felt great to see so many people who love the same thing that you love. Such a blessing community.

\n\n\n\n

After joining WordPress slack and attending a few meetings, I found it is actually helping me to improve my skills. I saw how they manage their projects, how they think, how they fix. So many things to learn. I got addicted \"😀\" I started browsing channels often. 

\n\n\n\n

I started attending all the meetings of almost all the Make WordPress teams (that’s funny but I did). I was enjoying my life. 

\n\n\n\n

Slowly I started contributing to the Core WordPress. I do complex tasks in my regular job life but at core a simple task accomplishment gives so much pleasure. 

\n\n\n\n

Everytime I see my name in the commit description it feels good.

I didn’t stop after doing my first contribution to the core. I continued and I checked almost all tickets and figured out what I can fix or help to fix. I got PR reviews from WordPress experts. Their every single suggestion helped me to know the WordPress and Coding standards better. Now I do practice those coding standards in my regular job tasks.

\n\n\n\n

In WordPress 6.1 I contributed to 20 plus core tickets and that was a pretty good number in Bangladesh. These days I take online workshops in the Make Learn team, in person workshops in our Dhaka community. Also taking in house (within company) workshops to show how to join Release Parties and attend meetings and write team meeting notes. 

\n\n\n\n

By the way, I am Marketing Team Representative for the year of 2023. I am excited and looking forward to it. Also a Training Team Faculty member. 

\n\n\n\n

I don’t think all of these would be possible without being an active contributor to the project. Thank you everyone who helped me in this wonderful journey \"😊\"

\n\n\n\n

Life Is Beautiful : Living Success

\n\n\n\n

When I was writing this essay, I became one of the Release Leads of WordPress 6.2 (Test Co-Lead).

\n\n\n\n

It is unbelievable for me even after the declaration. I keep checking that P2 blog post just to make sure I am truly there, funny I know. 

\n\n\n\n

Recently I took contributor days in our office and it felt like there was only one topic in the town and that is “Let’s Do Core Contribution”. It became trending here, loving it \"😊\"

\n\n\n\n

Thanks to WordPress and the community. Due to my outstanding contribution in Core, I recently got selected for the prestigious #YoastCareFund and here I am sharing my stories with our HeroPress friends.

\n\n\n\n

Oh! I am living my dream life. Just one thing is missing. Ronaldo isn’t in UCL and is getting older. I know \"😀\"

\n\n\n\n

WordPress Core Contribution helped me to become a better developer, a better me. It removes your fear of losing your job and instead you will fall in love with your job and definitely you will enjoy every minute of your coding life.

\n\n\n\n

Thank You WordPress.
Code is Poetry and you are the book full of Poems.
I can’t stop reading you \"😊\"

\n\n\n\n

কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায়

\n\n\n\n

এইতো কয়েক বছর আগেও, আমার ডেইলি রুটিন ছিল সকাল ১০ টায় ঘুম থেকে ওঠা এবং নাস্তা না করে অফিসে যাওয়া (আলসেমির কারণে দেরি হয়ে যেত এবং বাসায় নাস্তা করা হত না)। তারপর ৯ ঘণ্টার অফিস শেষ হত গতানুগতিক কাজ দিয়ে।

\n\n\n\n

বর্তমানে, আমার ঘুম থেকে উঠেই দেখি স্ল্যাক ভর্তি মেসেজ এবং দিন শেষ হয় ছোট বড় বেশ কিছু টিম কোলাবোরেশান এবং মিটিং এর মাধ্যমে। 

\n\n\n\n

আমি ছিলাম ফেসবুক পাগল, ইংরেজিতে এডিক্টেড \"😀\"। কিন্তু এখন WordPress Slack হয়ে গেছে ফেসবুক আমার কাছে। কীভাবে ইন্টারেস্ট পরিবর্তিত হয় এবং পরিবর্তনটা উপভোগও করছি।

\n\n\n\n

Lucky Me \"😊\"

\n\n\n\n

Hello World : যেভাবে পথচলা শুরু

\n\n\n\n

প্রথমত আমার ইঞ্জিনিয়ার হবার কথাই ছিল না। আমার শৈশব কাটে কুমিল্লায়। স্কুল এবং কলেজ এলাকাতেই ছিল। সবার চাইছিল আমি যেন ডাক্তার হই।আমাদের দেশে এটা খুব কমন যে বাবা মা চায় তাদের ছেলেমেয়েরা যেন ডাক্তার হয়। আমি মেডিকেলের জন্য তিন মাস প্রিপারেশান নেয়ার পরেও ভাগ্যক্রমে চান্স পেয়ে যাই ইঞ্জিনিয়ারিং এর জন্য।

\n\n\n\n

সিলেটে ছিলাম পাঁচ বছর। আহা সিলেট, Where Heaven touches the Earth <3  

\n\n\n\n
\n\n\n\n

সিলেট নাম শুনলেই থমকে যাই।সে কবে গেলাম।কতদিন পাঁচ ভাইয়ের খিচুরি খাই না, কতদিন মদিনা মার্কেটের চাচার টং দেখি না। কতদিন মাঝ রাতে বের হয়ে টং এর চা খাই না। 

\n\n\n\n

আহা সিলেট!  

\n\n\n\n\n\n\n\n

SUST (Shahjalal University of Science and Technology) এর ৫ বছর ছিল আমার জন্য ব্লেসিং। আমাকে পরিণত করেছিল সাস্ট। সাস্ট ছিল এনার্জিতে ভরপুর।সিনিয়র জুনিয়রদের সম্পর্ক। লাল টং। ৩০০ এর বেশি স্টুডেন্ট ছিল আমাদের ডিপার্টমেন্টে। যাদের মধ্যে ৯০ ভাগই ছিল আমাদের ভাই ব্রাদার। অলমোস্ট সবাইকেই চিনতাম আমরা। বর্তমানে আমরা সবাই ঢাকায় কোন না কোন জবে আছি। দেখা কম হলেও সম্পর্ক এখনও আগের মতই। সবাই সবাইকে জবে হেল্প করছে। জবের বাইরে হেল্প করছে।ঢাকার মোটামোটি সব ফার্মে গেলেই দেখা যায় SUST CSE থেকে কেউ না কেউ আছে।

\n\n\n\n

আল্লাহর কাছে শুকরিয়া সিলেট এবং সাস্টে পরার সুযোগ হয়েছিল।

\n\n\n\n

Hello Dolly : WordPress এর সাথে পরিচয়

\n\n\n\n

WordPress এর সাথে আমার প্রথম পরিচয় যখন আমি আমার দ্বিতীয় জবে জয়েন করি। ক্যারিয়ারের শুরুতে আমার খাপ খাওয়াতে একটু সমস্যা হচ্ছিল। যখনই WordPress এর সাথে পরিচয় তখন থেকেই ফিদা হয়ে গেলাম।এটার ব্যবহার বিগিনার হিসাবে তখন আমার কাছে অনেক সহজ এবং উপকারী ছিল।অনেক বড় একটা কমিউনিটি। রিসোর্স অনেক। যদি Codex ছিল বেশ কঠিন বুঝার জন্য। কিন্তু বর্তমানে ডকুমেন্টেশান (https://developer.wordpress.org/) অনেক ভাল এবং সহজ হয়েছে। প্রথম যখন Code is Poetry শুনেছি এবং দেখেছি আমার অনেক পছন্দ হয়েছিল। মনে হচ্ছিল কোড না যেন কবিতা লিখতেসি।

\n\n\n\n

ক্যারিয়ারের শুরুতে আমি WordPress বেশ উপভোগ করেছি। চাইতাম সব নিজে নিজে করব (যাকে আমরা বলি এখন Full Stack, লোল)। 

\n\n\n\n

শুরু হয়েছিল PSD to Html দিয়ে যা আসলে আমাদের অনেকের ক্ষেত্রেই মিলে যাবে। তারপর তা WordPress এ কনভার্ট করতাম। শুরুতে আমাকে একটা ট্রেনিং দেয়া হয়েছিল যা ছিল খুবী কার্যকর।

\n\n\n\n

আমার এখনও একটা কথা মনে আছে “যত বেশি div নিবা। div নিতে টাকা লাগে না”, লোল।  

\n\n\n\n

আমার CSS opacity নিয়ে সমস্যা হচ্ছিল। কিন্তু যখনই কাজ শুরু করে দিয়েছি আস্তে আস্তে সব পানি (ইংরেজিতে Water, মানে সহজ) হয়ে গেসে। 

\n\n\n\n

প্রথমদিকে আমি WordPress কমিউনিটি নিয়ে ততটা অবগত ছিলাম না। অনেক থিম কাস্টমাইজেশান এবং সাইট কাস্টমাইজেশান করেছি। Bug ফিক্স করেছি অনেক ক্লায়েন্টদের জন্য। ফিচার তৈরি করেছি তাদের চাহিদা অনুযায়ী। কিন্তু কি যেন একটা মিসিং ছিল। 

\n\n\n\n

WordPress Open Source Project এবং WordPress এর বড় একটা কমিউনিটির সাথে যে তখনও আমার পরিচয় হয়ে উঠেনি। 

\n\n\n\n

Code is Poetry : WordPress Core Contribution

\n\n\n\n

WPDeveloper ছিল আমার জন্য ব্লেসিং। এখানে আসার পর থেকেই আমি WordPress এর বড় কমিউনিটির সাথে পরিচিত হই এবং দেখতে থাকি তাদের একের পর এক চমৎকার উদ্যোগ।

\n\n\n\n

মনে হচ্ছিল যেন এটাই এতদিন মিসিং ছিল। সবাই এত আন্তরিক এবং সাহায্য করার জন্য কতটা উদগ্রীব। 

\n\n\n\n

আমি meetup জয়েন করা শুরু করলাম। meetup নেয়াও শুরু করলাম, হা ঠিক শুনেছেন।লোল। 

\n\n\n\n

সবার সাথে নেটওয়ার্কিং হল।দেখে খুব ভাল লাগল যে একই চিন্তা ধারার সবাই একসাথে।

\n\n\n\n

Such a blessing community.

\n\n\n\n

WordPress স্ল্যাক জয়েন করি এবং মিটিং এটেন্ড করা শুরু করি। দেখি যে এটা আসলেই আমাকে সাহায্য করছে আমার স্কিল বাড়াতে।দেখতে পেলাম কিভাবে তারা প্রজেক্ট মেনেজ করে, কিভাবে চিন্তা করে, কিভাবে বাগ ফিক্স করে। কত কিছু শিখার। এডিক্টেড হয়ে গেলাম \"😀\"। চ্যানেলগুলো প্রায়ই ব্রাউজ করতে থাকতাম।

\n\n\n\n

সব টিমের মিটিং জয়েন করতে শুরু করলাম (ফানি বাট সত্য)। সবকিছু ভালই লাগছিল। 

\n\n\n\n

আস্তে আস্তে কোর কন্ট্রিবিউশান শুরু করলাম। যদিও অফিসে কমপ্লেক্স কাজগুলাই আমরা করতাম। কিন্তু যখন একটা ছোট খাটো কোর কন্ট্রিবিউশান করি তখন মনে অনেক আনন্দ কাজ করে। যতবার কমিটে আমার নাম দেখি ততবারই ভাল লাগে। আহা।

\n\n\n\n

প্রথম কন্ট্রিবিউশানের পর আমি থেকে থাকি নাই। কন্টিনিউ করেছি। প্রতিদিন টিকেট গুলো ব্রাউজ করতাম। খুঁজে দেখতাম কোনটা করতে পারব। WordPress expert দের কাছ থেকে রিভিউ পেতে থাকলাম যখনই PR দিতাম।তাদের প্রতিটা সাজেশান আমার পরবর্তিতে বেশ কাজে দিয়েছে। নিজের অফিসের কাজেও তখন সেগুলো ব্যবহার করতে থাকলাম।

\n\n\n\n

WordPress 6.1 এ আমি ২০ এর অধিক টিকেট ফিক্স করতে সাহায্য করেছি। যা বাংলাদেশের জন্য বেশ ভাল একটা নাম্বার। এখন আমি Make Learn টিমের জন্য অনলাইন ওয়ার্কশপ বানাই। ইন পারসন ওয়ার্কশপ নেই আমাদের ঢাকা কমিউনিটির জন্য। ইন হাউজ ওয়ার্কশপ নেই কলিগদের জন্য। দেখাতে সাহায্য করি কিভাবে রিলিজ পার্টিতে জয়েন করতে হয়, কিভাবে টেস্ট রিপোর্ট লিখতে হয়, কিভাবে মিটিং নোট নিতে হয়।

\n\n\n\n

ভালো কথা, আমি এখন Marketing Team Representative ২০২৩ সালের জন্য। এটা আমি বেশ উপভোগ করছি। এবং সাথে আমি Training Team Faculty মেম্বারও। 

\n\n\n\n

আমার মনে হয় না কোর কন্ট্রিবিউশান ছাড়া আমার এই দায়িত্বগুলো পাওয়া পসিবল হত । সবাইকে অনেক ধন্যবাদ আমাকে সাহায্য করার জন্য \"😊\"। 

\n\n\n\n

Life Is Beautiful : সফলতা

\n\n\n\n

যখন আমি এটি লিখছি ততদিনে আরেকটি সুখবর পেয়ে গেছি। আমি এখন WordPress 6.2 এর একজন Release Lead (Test Co-Lead).

\n\n\n\n

একদম অবিশ্বাস্য। প্রায়ই P2 blog post গিয়ে চেক করে দেখি আমার নামটা আছে কিনা, হাস্যকর শুনাবে জানি। 

\n\n\n\n

কিছুদিন আগে কন্ট্রিবিউটর ডে আয়োজন করেছি। মনে হচ্ছিল যেন শহরজুড়ে একটাই ডায়লগ,

\n\n\n\n

“Let’s Do Core Contribution”। ট্রেন্ডিং হতে দেখে বেশ ভালই লাগে \"😊\"

\n\n\n\n

WordPress এবং কমিউনিটিকে অনেক ধন্যবাদ। কিছুদিন আগে #YoastCareFund পাই করে আউটস্ট্যান্ডিং কন্ট্রিবিউশানের জন্য। এবং আজ HeroPress বন্ধুদের সাথে সব শেয়ার করছি।

\n\n\n\n

একেই বুঝে বলে লিভিং ড্রিম লাইফ। একটা জিনিসই শুধু মিসিং। রোনাদোকে আর হয়ত ইউসিএলে দেখা যাবে না \"😀\"

\n\n\n\n

WordPress Core Contribution আমাকে ভাল ডেভেলপার হতে সাহায্য করেছে।জব হারানোর ভয় বাদ দিয়ে জবকে এঞ্জয় করা এবং কোডিং এর প্রতিটা মুহুর্ত উপভোগ করতে সাহায্য করে কোর কন্ট্রিবিউশান। 

\n\n\n\n

Thank You WordPress.
Code is Poetry and you are the book full of Poems.
I can’t stop reading you \"😊\"

\n

The post Becoming A Better Me with Core Contribution – কোর কন্ট্রিবিউশন এবং জীবনের নতুন অধ্যায় appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 25 Jan 2023 02:00:06 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:25:\"A H M Nazmul Hasan Monshi\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:1;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:55:\"WPTavern: Yoast SEO 20.0 Introduces New Admin Interface\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141380\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:66:\"https://wptavern.com/yoast-seo-20-0-introduces-new-admin-interface\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2764:\"

Yoast SEO version 20.0 was released today with a new admin settings interface that also reorganizes the menu to into four main sections: General, Content types, Categories and Tags, and Advanced.

\n\n\n\n\n\n\n\n

In this update, the plugin did not add new features and settings but rather moved them to better match user workflows. The new sidebar menu should result in fewer clicks in accessing the most used settings.

\n\n\n\n

The individual settings pages are also sporting the new design, which is lighter and brighter than the previous screens. With such a large number of settings to re-learn, Yoast SEO has also added a quick search to assist users in finding settings pages faster.

\n\n\n\n\n\n\n\n

“We felt that the default WordPress admin design no longer suited us,” Yoast founder Joost de Valk said. “Our product team was itching to take our experience to the next level. WordPress’ interface was holding us back a bit, as the admin interface outside Gutenberg hasn’t progressed for years.”

\n\n\n\n

The new settings UI was built with Yoast SEO’s React component library, which the company has open sourced and made available on its website.

\n\n\n\n

Reaction to the new design was mostly positive, although some users are not keen on plugins building their own UI in the admin. If all plugins did this, the WordPress admin would become a wild buffet of disparate interfaces that add cognitive load to site management.

\n\n\n\n

“It was… surprising so I’ll reserve real judgement until I use it a while,” WordPress developer Jon Brown said. “First impression though was ‘this needs an advanced mode that hides all the useless banner images and text and just goes back to a list with toggles.’ It’s pretty, but feels overwhelming.”

\n\n\n\n

 The Yoast SEO plugin and the new settings UI work with WordPress version 6.0 or higher. Users who are struggling to adapt to the new settings pages can reference Yoast SEO’s documentation, which has a video and guide to navigating the new interface.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 21:43:57 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:2;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"Do The Woo Community: The WP Community Collective with Sé Reed and Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74360\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://dothewoo.io/the-wp-community-collective/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:422:\"

Sé and Courtney share all things to do with the new WP Community Collective, a source for supporting contributions and initiatives.

\n

>> The post The WP Community Collective with Sé Reed and Courtney Robertson appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 10:36:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:3;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:47:\"WPTavern: Awesome Motive Acquires Thrive Themes\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141347\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"https://wptavern.com/awesome-motive-acquires-thrive-themes\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1769:\"

Awesome Motive has acquired Thrive Themes, its second acquisition of 2023 following the Duplicator plugin deal that was announced earlier this month.

\n\n\n\n

Thrive’s premium plugin suite reports more than 200,000 users. This includes Thrive Architect, a visual drag and drop page builder, an LMS course builder, and other marketing-focused plugins for generating leads, creating quizzes and testimonials, and doing A/B testing.

\n\n\n\n

In 2013, Thrive Themes co-founders Shane Melaugh and Paul McCarthy began their company with early products Hybrid Connect, Viral Quiz Builder, and WP Sharely. Ten years later the product suite has grown to nearly a dozen conversion-focused tools that Thrive Themes sells for $299/year.

\n\n\n\n

Although the co-founders will not be joining Awesome Motive, the team that is currently maintaining and supporting the plugin is being acquired. In the Thrive Themes announcement, Melaugh said the company’s products will not be rebranded or replaced. No price hikes are planned for existing customers and Awesome Motive plans to honor legacy memberships.

\n\n\n\n

“It has always been our policy to reward loyal customers and that will not change,” Melaugh said.

\n\n\n\n

“I’ve been watching Thrive Themes from the sidelines for a long time anyway. So my stepping away changes nothing on that front.

\n\n\n\n

“It will still be the same people building the products, and the roadmap we laid out for 2023 and beyond won’t change because of this acquisition.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 24 Jan 2023 02:57:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:4;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"WPTavern: WP Migrate 2.6 Introduces Full-Site Exports and Import to Local\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141320\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:84:\"https://wptavern.com/wp-migrate-2-6-introduces-full-site-exports-and-import-to-local\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3672:\"

WP Migrate, formerly known as WP Migrate DB and recently acquired by WP Engine, has long since expanded beyond its initial release as a database migration tool. Users may be familiar with the push/pull workflow of installing the plugin on two sites and migrating database, media, themes, and plugin changes back and forth. The most recent 2.6 release expands the plugin’s capabilities to include full-site exports for integration with Local, a popular free WordPress development tool, also owned by WP Engine.

\n\n\n\n

This new remote-to-local workflow is included in both the free WP Migrate plugin and the pro version. The full-site exports bundle the database, media, themes, plugins, and other files into a ZIP archive, which can be seamlessly imported into Local.

\n\n\n\n\n\n\n\n

After clicking Export inside WP Migrate, users are taken to the next screen where they can configure what is included in the export file. This ZIP archive can be dragged and dropped into the Import screen in Local.

\n\n\n\n\n\n\n\n

The WP Migrate team collaborated with the Local team to match environments as closely as possible when exporting for Local import.

\n\n\n\n

“Each site exported with WP Migrate includes a wpmigrate-export.json file which contains metadata such as the PHP and MySQL versions that were last used on the site,” WP Migrate Product Manager Kevin Hoffman said. “During the import, Local reads this file and attempts to match the environment to that of the exported site, so the local site works (and breaks!) just like its remote counterpart.”

\n\n\n\n

In this migration scenario, the WP Migrate plugin can be included in the list of plugins so it is activated on the Local site, speeding up the workflow for setting up a local development site. Previously this required configuring plugins, add-ons, and license keys across both environments.

\n\n\n\n

“In the last year, we really embraced our new identity as a full-site migration solution,” Hoffman said. “One of the goals we set for ourselves was to handle the migration of an entire site from within WP Admin without ever having to touch cPanel, phpMyAdmin, or FTP. This new workflow is the culmination of those efforts delivered as a free end-to-end solution for the WordPress community.”

\n\n\n\n

Customers who have purchased the pro version may still opt for pushing and pulling directly between sites, but this new workflow makes it easier for users (both free and paid) to set up a local development environment for the first time.

\n\n\n\n

“When we realized how much simpler we could make the remote-to-local workflow by embracing full-site exports, we reached out to the Local team who helped make it happen,” Hoffman said.

\n\n\n\n

The WP Migrate team is looking at expanding the integration beyond matching the WordPress, PHP, and MySQL versions to give users the ability to predefine migration profiles for pushing local sites back to the remote host.

\n\n\n\n

“When configuring an export, we could also let users set up one-click admin access in Local,” he said. “Imagine dropping a ZIP into Local and landing in WP Admin without ever having to log in. There are lots of possibilities, and I’m sure more will pop up as the community starts to use it.”

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 23 Jan 2023 22:39:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:5;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:84:\"WPTavern: WordPress Community Team Proposes Adopting GitHub to Improve Collaboration\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141302\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"https://wptavern.com/wordpress-community-team-proposes-adopting-github-for-improved-collaboration\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4432:\"

Although GitHub is primarily used for code collaboration, WordPress’ Community team is considering adopting the platform to standardize their project management tools.

\n\n\n\n

Contributing to open source can already be challenging but when it requires signing up for multiple services in order to access the team’s many spreadsheets, trello boards, Slack groups, and other modes of communication, onboarding new contributors becomes needlessly difficult.

\n\n\n\n

A new proposal, authored by Community team rep Leo Gopal, outlines the benefits of using GitHub as a central communication tool. These benefits include improved collaboration and communication using the platform’s commenting system and the ability to track progress and assign tasks.

\n\n\n\n

Gopal contends that standardizing on GitHub would increase transparency and accountability while supporting better organization with tools like issues, labels, milestones, and project boards.

\n\n\n\n

“By adopting GitHub for project management and issue tracking, the Community Team will standardize our way of working, making it easier for new team members to get up to speed and enabling more effective cross-team collaboration,” Gopal said. “This standardization also makes it easier for Community Team members to track progress, identify issues and make data-driven decisions.”

\n\n\n\n

Other Make teams, such as Learn, Hosting, Meta, Marketing and more, are already successfully using GitHub to manage communication and prioritize projects. Gopal proposes the Community team learn from their efforts and adopt these tooling methods for a quarter as an experiment.

\n\n\n\n

“If after the first Quarter the consensus is that this does not suit our team, we will revert back to initial project and tracking practices and explore more,” Gopal said.

\n\n\n\n

A few participants in the resulting discussion have concerns about transparency and losing track of conversations, as they would not be linked to WordPress.org profiles.

\n\n\n\n

“The truth is that I am unsure about it,” Weglot-sponsored Community team contributor Juan Hernando said. “I think the community team is not particularly technical, and using GitHub may pose certain barriers we didn’t have so far. Maybe for many people opening an issue, requesting a pull request, or similar is their everyday life, but for others, it can be a bit blocking.

\n\n\n\n

“I’m also afraid that discussions will move from this Make site to GitHub, and we shouldn’t lose the spirit of owning our content (linked to our .org profile) and lose the use of this space for decision-making and public discussions like this one.”

\n\n\n\n

Gopal addressed this concern stating that there would be no code and that users who can work with Trello boards will have no problem adopting GitHub’s tools for planning.

\n\n\n\n

“Trello was used for planning and often forgotten until time for reviews or recaps,” Gopal said. “There was no way other teams would know what we are working on or add to the conversation unless they dug up our trello boards AND if we took their suggestion and weighed it in.”

\n\n\n\n

Gopal said using GitHub would allow the team to incorporate advantages like automations, assignments, and inter-team collaboration with advanced reporting capabilities. Overall, GitHub has the potential to increase the visibility of their work for those collaborating across teams.

\n\n\n\n

Milana Cap, who uses GitHub to help organize the Documentation team for reporting issues and automating tasks, recommended adopting the platform and shared how the Docs team is using it.

\n\n\n\n

“All the other benefits: version control, precise contribution tracking, all sorts of project management tools etc., can not be found all in one tool other than GitHub, and I can not recommend it enough – for everything,” Cap said.

\n\n\n\n

Discussion is still open on the proposal and Gopal has published a Proposal Poll for Community Team members to give their feedback on standardizing communications on GitHub.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 21 Jan 2023 04:32:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:6;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:103:\"WPTavern: Gutenberg 15.0 Introduces “Sticky” Position Block Support, Adds “Paste Styles” Option\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141268\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:101:\"https://wptavern.com/gutenberg-15-0-introduces-sticky-position-block-support-adds-paste-styles-option\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3623:\"

Gutenberg 15.0 was released this week with some exciting new features for working with blocks and an improved UI for managing controls in the inspector panel. This release marks the end of the block inspector tabs experiment, which is now stabilized in the plugin.

\n\n\n\n

Users will notice that some blocks will now have separate tabs in the inspector for displaying settings and design controls, and optionally a list view tab that is included in the “off canvas navigation editor” experiment. Taking the block inspector tabs out of experimentation paves the way for the Navigation block’s off-canvas editor to become the default experience.

\n\n\n\nimage credit: Gutenberg 15.0 release post\n\n\n\n

Version 15.0 introduces a new “paste styles” feature that works in a similar way to the “paste” or “paint” formatting function in Microsoft Word or Google Docs. Users can click on any block, select “Copy block” from the menu in the block settings panel and then paste those styles onto another block using the “Paste Styles” menu item.

\n\n\n\n\n\n\n\n

When using this feature, users may have to give the browser additional permissions in order to read from the clipboard. If permissions are denied, Gutenberg will display a warning snackbar to notify the user.

\n\n\n\n

Another major feature in this release is the ability for users to give blocks “sticky” positioning on the page. This will keep the block in the viewport even when scrolling down the page. The sticky/fixed positioning sticks the block to the top of the direct parent block. It can be previewed on the frontend and equally as well inside the editor.

\n\n\n\nvideo credit: Follow-up tasks for Sticky positioning\n\n\n\n

Gutenberg contributors concluded that although sticky positioning will be valuable for headers, footers, and creative instances, it is not likely to be used frequently. For this reason, it is de-emphasized in the UI. This is the first iteration of the sticky positioning feature, and contributors are tracking a list of follow-up tasks to improve it.

\n\n\n\n

A few other important changes in this release include the following:

\n\n\n\n
    \n
  • Edit block style variations from global styles (46343)
  • \n\n\n\n
  • Constrain image sizing to the width of the container (45775)
  • \n\n\n\n
  • Allow resizing the Site Editor’s sidebar and frame (46903)
  • \n\n\n\n
  • Activate copy/cut shortcut in the site editor (45752)
  • \n
\n\n\n\n

If you want to take advantage of these new features before they land in WordPress core, you will need to have the Gutenberg plugin installed. Check out the 15.0 release post to visually explore the highlights with more videos and links to all the pull requests for the release.

\n\n\n\n


\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 21 Jan 2023 00:37:23 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:7;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"Post Status: Launching a WordPress Product in Public: Session 1\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146618\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:73:\"https://poststatus.com/launching-a-wordpress-product-in-public-session-1/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:77120:\"

Corey Maass and Cory Miller go live to discuss the creation and launch of a WordPress product they have partnered to build. Crop.Express originated as a solution to a common problem Maass experienced. Miller loved the idea and wondered how to build this into a plugin to solve problems within the WordPress workflow. This is a candid conversation about the evolution of partnering to develop a WordPress product.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

In this episode, Corey Maass and Cory Miller discuss the origin of the WordPress product they are creating. Together they explore the benefits of partnership, the challenges of being a creator, and what it takes to build viable solutions. This is only the beginning of their process and partnership, but it’s loaded with experience and insight from the journeys they have had within WordPress that brought them to this moment, as well as takeaways they’ve discovered with their new undertaking.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • The Power of Partnering: Many entrepreneurs aren’t interested in partnership. But they create an opportunity to own and contribute the things you do well alongside someone who has other skill sets, strengths, and experiences. Partnerships offer space to practice open dialogue while showing respect and gaining perspective. They are a great solution for all the things you can’t do, don’t want to do, or shouldn’t do. 
  • \n\n\n\n
  • Build for a Need: Sometimes we create things believing we have brilliant ideas that will attract an audience. But where problems exist, so do the needs for solutions. You can trust if you have a problem, other people likely have the same problem and need a solution.
  • \n\n\n\n
  • Look to Make Things Easier: When you have to go out of your workflow to do a task, things feel frustrating and clunky. Finding ways to integrate tools within our natural workflow adds tremendous value to the user experience.
  • \n\n\n\n
  • Products Require Passion and Capacity: Yes, you may have the ability to create really cool, helpful things. But if you lack a sincere passion for the products you build or truly don’t have the time they require, they tend to fall flat somewhere along the way. You tap out at the end of your skillset or energy, and even though there may be real potential, the passion and time to carry things forward are missing.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Sponsor: GoDaddy Pro

\n\n\n\n

Manage your clients, websites, and tasks from a single dashboard with GoDaddy Pro. Perform security scans, backups, and remote updates to many sites on any host. Check up on site performance, monitor uptime and analytics, and then send reports to your clients. GoDaddy Pro is free — and designed to make your life better.

\n
\n\n\n\n
\n\"GoDaddy\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Cory and Corey Episode 1
Cory Miller: [00:00:00] Hey everybody. Welcome to a cool series. Uh, my friend Corey and I have been talking about it for a couple months, a project, and we said, Hey, why don\'t we just broadcast this out, do it in public. And so this series is kind of called Launching a WordPress product in Public. This is session one we\'re gonna talk about.
First. I\'m gonna let Corey introduce himself in just a second, but we\'re gonna talk about the agenda is, um, kind of where we\'ve been, just to catch everybody up. And then second part, we\'re gonna talk about next steps for what we\'re doing. And we\'ll of course describe the project, uh, as we go. So, Corey, I think people know you, but let\'s, let\'s, uh, go ahead and share it.
Tell us more about, uh, who you are, what you\'ve done with WordPress.
Corey Maass: Of course. Uh, so I\'m Corey Moss, currently [00:01:00] residing in the northeast of the United States. Um, I\'ve been a developer and an entrepreneur for 25 years or so, and largely locked into the WordPress space for 10 years or more. It was the day job for a very long time, and I was pushing SaaS apps or BU building and pushing SaaS apps, uh, in evenings and weekends.
And then, I don\'t know, years ago at this point, I went to, uh, WordCamp in Atlanta, Georgia, and met a few WordPress entrepreneurs, including the, um, specifically the Ninja Forms guys down there. And suddenly a light bulb went off of like, oh, there\'s, you know, there\'s a lot more to WordPress products and the WordPress ecosystem than I realized.
And. It can be used to build SaaS apps, which I also do. Um, but [00:02:00] also these plugins that can be grown and built into pot, you know, sometimes, or potentially into, into businesses under themselves. So that really kind of got me started. And so, uh, around that time, I, I learned about the Post Status community.
Uh, I\'m, I am wearing the Post Status t-shirt underneath. It\'s just too cold. Um, being up here in the northeast. But, um, yeah, so it\'s been, you know, fun to be part of the community and fun to grow. Uh, I\'ve now grown and sold a couple of businesses or a couple of WordPress plugins. Um, and here we are about to launch.
Cory Miller: Yeah, I, I\'m trying to remember back when we actually met Corey, but I knew you were like this developer who loved to like launch stuff and you had the kbo, uh, plugin at that time. Mm-hmm. , and I remember talking through that and how passionate you were, you were about it. Um, so, and then we chatted the last year or so comparing notes and I\'m like, man,[00:03:00]
Corey and Cor, sorry, the broadcast system went off on my ears. Excuse me. Just one second. Okay. Whew. That was weird. I\'ve got hearing aids and my phone comes through and I was like, emergency broadcast system. Mm-hmm. Um, but anyway, um, so it was fun. We\'ve gotten to kind of get to know each other over the last year or so and member huddles and you shared this thing you were doing and I\'ve followed up and I was like, I need this, I want this.
Um, and it\'s funny too in parallel is how much stuff that we\'ve got in common or things were stages of life we\'re, we\'re going through. And so I think it was a couple months ago you mentioned on the huddle or, and then we started talking about it in Post Status dms, the project that we\'re launching in public today called Crop Express.
But um, you wanna share a little bit about that, how you came to it? And I can add a little, my perspective on it. Yeah, of course. This was your idea. Um, and I was like, oh my God, this has [00:04:00] to exist in the WordPress. Um, I need it because I need it. And that\'s a typically if I try to keep at the user level and I\'m like, if I like something and use something, I\'m like, maybe there\'s more people out there that would need it too.
But talk about the start of Crop Express.
Corey Maass: Well, before that, I want to fill in a couple of blanks. One, yeah. Uh, you and I met when you were the keynote speaker at, uh, what was it? Word? WordCamp, y\'all. The, the WordCamp in Bur Birmingham, Alabama. I have lots of friends in. Birmingham, England spelled the same, but pronounced very different.
So I have a hard time pronouncing Birmingham . Um, but anyway, um, I was living in Nashville at the time and drove down and uh, that\'s you And I went to lunch with a couple of other people and I, I, I must have had too much of the free coffee, cuz I remember talking your ear off while we were waiting for like barbecue or something [00:05:00] and then, You turned to me at one point you were being a very good listener, I have to say.
And then at one point you turned to me and are like, aren\'t you speaking in like four minutes ? And I looked down and realized that yes, indeed, my session was starting in minutes and I still hadn\'t gotten my food. Um, and so you and the folks we were with were nice enough to bring me my food halfway through the session.
Oh, chicken and waffles. I got chicken and waffles, the weird things you remember. Anyway, . Um, but yeah, I, you and I have, uh, kept in touch over the years and then, um, I think mostly caught up over on the huddles. Um, but I, I mean, I tell that cuz it\'s sort of a fun story and a little background, but I also, I think it\'s.
It\'s a great ex, uh, example of the longevity of a lot of the relationships that I\'ve had in WordPress, in the WordPress ecosystem, the [00:06:00] WordPress community. Um, you know, once in a while I, I get approached, I know you do too, of people who are like, you know, let\'s partner, or, I see you\'re doing a thing, let\'s do a thing together with no background, no context.
Um, and I, I\'m definitely not saying that people shouldn\'t reach out, always reach out. You know, you never know what good is gonna come from, from reaching out. Um, I love that people messaged me directly on Twitter and um, and in Post Status and stuff like that, but also, you know, the long-term. Being part of any, uh, any, uh, being part of the WordPress community and culminating these relationships and staying in touch with people over years.
Cuz at this point, I lived in Nashville like eight years ago, so you and I met eight years ago and I don\'t think talked really for five years Anyway, so that was one of the things that jumped out at me. So getting onto Crop Express. So yeah, I. I built a, [00:07:00] a conbon plug in a few years ago, sold that, um, have launched and been running a couple of others.
One I\'m about to sell. Um, and, and that might actually be something to talk about at another time because I, I built it because I could, um, very typical developer. I built it because I could, but I was never really passionate about it. And so at this point, I\'m, I\'m talking to some folks about, um, selling it because I\'ve just never been able to, man, I\'ve never been able to market it, meaning I\'ve never been able to make myself market it.
Um, and plugins and these businesses, to me are still side hustles. I\'ve never been able to grow them large enough to be the, you know, my primary source of income. And so I have clients and. Right now, I\'ve, I\'ve got clients who run, uh, a couple of pretty big sort of magazine style, pretty traditional blogs, but they\'re, you know, magazine style, full, beautiful, well-written, professionally written articles and [00:08:00] stuff like that.
And they are not technical at all. So they\'re, they\'re entrepreneurs, they\'re writers, they\'re content people. Um, but they. It\'s not that they don\'t understand, they\'re very smart people, but they\'re not experienced with, or they don\'t think in terms of like, oh, all images need to be squares, or all images need to be 16, nine, so that the site looks uniform and consistently good.
Um, and no matter what I did, I, I couldn\'t make it easy enough for them to crop their images consistently. I didn\'t want to get them into Photoshop, you know, other, and that cost of Fortune. Other free editors cost money, da, da da. So anyway, um, almost on a whim, over a weekend, I bought crop.express, the domain.
Um, Here\'s a industry secret. One of, one of my best kept secrets is the.express, um, what is it? Top level domain, [00:09:00] TLD. Um, there\'s so many words that have not been bought yet, so I actually own poll.express, crop.express reply.express. Um, screenshot.express is another project I\'m building out. Um, so if you, anybody listening, if you\'re looking for a good domain, I, I highly recommend it.
I keep wondering what I\'m doing wrong or like, are there companies that can\'t access this or something, you know? Yeah. But
Cory Miller: anyway, um, I think it\'s a hallmark of any, uh, tech entrepreneur in particular is to have like a too big of a. Portfolio that you have. That\'s very continuing. Well, that\'s too, yes. Um, I, I\'ve got way too many, um, my wife is always like, you should put some parking pages on this.
And I go, yeah, but it\'s a cool domain. What happens? I think there\'s two things. Uh, we definitely should, and we\'ll be talking about partnership along this whole way. Um, I\'ve had a good amount of experience with partners and like having [00:10:00] partners. Um, it\'s an anomaly in, in, I in a lot of the entrepreneurs I\'ve talked to is a lot of successful entrepreneurs go, no way.
I\'m not gonna partner with anybody. And I go, well, I kind of need to and want to. Um, but then, so I know we\'re gonna be. Some thoughts about the partnership and that\'s another thing is partnering in public is probably the subtext to this too on. Um, but as we\'ve talked, just real quick before we get back to the product, is, um, I\'m not a developer.
I should get the shirt. I\'m not a developer. Um, but I love products and I\'ve had a product business. Um, tried a bunch of products. I told you, I think yesterday I was like, my, my win rate is probably like in the one hundreds, uh, percentile. Um, we talked about baseball and I was like, you know, I\'m probably a strikeout king because I feel like I failed quite a bit.
But coming to someone, like it\'s an ideal match for me because I can, [00:11:00] you know, business and marketing, but it\'s not one you have to own in this partnership. I can own that and you contribute and obviously I can\'t even try to write code. Um, but I can contribute with product and, and experience and thoughts like that.
So now to the crop express. . Um, so when you shared this, I was like, yes. Because my experience in just talking about the user profile, I\'m so keen to the user profile cuz sometimes I think we come at it artificially and go, I have an idea. Let\'s go find a person for it. And I think some of the best ones come out of just, there\'s a need, and we talked about this, it\'s like, um, you hear the story is build it for your own itch or build it for yourself and all that kind of stuff.
We talked about Pi, PIP and Williamson yesterday, like he\'s a, he\'s the one I think of it\'s like, build it, build something for a need. Mm-hmm. for himself and grew into this great, uh, business called [00:12:00] EDD. Um, what struck me about this is I go, I have a. Like trying to find software that will crop, you know, I used to use, I was an early user of Photoshop, but I don\'t have Photoshop on my computer.
And I\'m like, well, I go to Mac preview and crop and export it out and then try to upload it to WordPress. So instantly I go, I need this. And then I thought, and we started having these discussions. I think other people do too. You know, the classic example I have just like your clients is my mom built a her own site about 10 years ago or so.
And we had a theme, don\'t cringe too much, but a theme that had rotating images in it at the top. Sure. And I tried to load the site . It was like, oh my God. She had 15 images all at like hop resolution. And this is something real quick. Uh, we both were like, this isn\'t something easy. It may be in WordPress, but it\'s not easy in WordPress.
And [00:13:00] my natural question was, If I have this problem, I bet you a lot of people have this problem. We talked, talked about images, we talked about agencies that turn sites over to clients and end up, why is this so slow? Or why isn\'t, you know, why doesn\'t this work? Right? And it\'s like, well, you loaded it native from your phone, , uh, the pick.
And so that was the thesis for me, for the, for the product is you already had the SaaS solution. I was like, yes. My question was, can I get it into a plugin where it\'s inside WordPress in my workflow?
Corey Maass: Yeah. And, and you helped, helped me turn that corner, honestly, cuz I, in a weekend I built. crop.express, which right now the website is the website.
It\'s exactly the first version that I built. Um, it\'s, it\'s not complicated. It\'s not well thought out, too well thought out. Like I have a, I\'ve been also working in product for years, and so I, I do [00:14:00] okay with going, oh, well, this, this will be intuitive enough that somebody could muddle through it. Um, but I really wanted to just solve the problem initially for my clients and yeah, threw it online.
I love doing this anyway. Start showing it to people, showed it to you, um, and you kept, you, you nudged me a couple of times in Post Status, like, how can we make this easier? And originally I was not thinking WordPress plugin, surprisingly. Um, I was thinking more. This is just a, a great little tool that people will use and it will hopefully, you know, maybe I could throw some ads on it or I, it will refer them to my other products.
Um, and so I was building a little Chrome extension and, and you\'re like, okay, that\'s a start. But you know what, if we really start to explore this and yeah, the conversations kind of flowed from there.
Cory Miller: And my premise with products, [00:15:00] particularly with WordPress or any tool is this, there\'s a workflow we all kind of have and you get in this system and when you have to veer out of that workflow, cropping an image, finding, cropping an image.
Yeah. So clunky within WordPress, and you have to go outside of that experience. You just added unnecessary time and energy for something frustration. When most times when I\'m creating content, I go, I want to get this out and edit it and press publish and put it out in the world. And anything that slows me down is a problem.
Um, So, you know, there\'s , our featured image on Post Status. I\'m not happy with it. We\'re still working on, on some of our design on the Post Status website. Uh, my personal side, I don\'t typically use images because of this. And so I think that was some of the, my, my perspective is like, there\'s enough use case here to say let\'s try it.
And I think what you and I go is like, we want to have, we wanna do something that is practical and useful [00:16:00] and then see where it goes. Um, we\'re not looking to get like mega rich on this or anything, but like, it\'s something we both have an interest in. Let\'s see where it, I\'m counting on it, man. . Hey, it would be nice to get me wrong.
Corey Maass: We, uh, we bought the Mega Millions ticket last night. You know, it\'s over a billion, but, uh, it hasn\'t been announced that we won this morning. So, you know, this is, this is the, the next best
Cory Miller: thing. Right. Yet, you haven\'t won yet. When we get some of that, carve off a little bit of that lottery money and we\'ll throw some, we\'ll do some cool, cool products.
Um, yeah. I, I\'m really addicted to products. I\'ve loved it for the longest time. Um, you said something earlier, you said I could build this and you did build things. Mm-hmm. , but the second part I wrote down was so interesting because it\'s, my experience too is I wasn\'t passionate about it. And I know when I\'ve gotten, um, those, that equation wrong is where I\'ve really failed miserably.
Um, the project I think about at Ithe was [00:17:00] called Exchange. It was e-commerce. I was passionate about a user experience that anybody could use, but I wasn\'t as passionate about the field. We just saw a big. I saw a big market potential there. WooCommerce was out there. It was the big, still is, the big behemoth.
And I go, man, it\'s really tough to like just create a new product in WordPress or, or in WooCommerce. Let\'s create an easier path to do that. Um, that didn\'t work. We didn\'t do it. And I think part of it was, I wasn\'t supremely passionate about the, the domain we\'re in. When we talk about this, I go, I have a, I have a lot of experience with images and cropping and content that\'s bulk of my career before I, themes and Post Status was, and communications work and newspapers, journalism.
And I\'m like, you know, it\'s a factor. Everybody wants an image on the site. And so what we decided was to start with the featured image [00:18:00] cropping, that making that experience, um, really smooth and easy.
Corey Maass: So that\'s the, yeah, I think the other thing to talk about here is as a developer, as a human being, I\'ve learned this lesson.
It\'s, it\'s just cuz you can doesn\'t mean you should. Um, and for I think people like you and I, I\'m speaking for you, but I, I hope I\'m right. We, we get excited about a lot of things. It\'s easy to, to dip a toe into a lot of things. Um, but then we end up taking on too much and we get overwhelmed and everything is, you know, what is it?
Do two things and you\'re doing two things half-assed instead of one thing, whole ass. Or, you know, and we\'re never gonna limit ourselves to one thing, let\'s be honest. But having, definitely having too many things. Um, and like I\'ve. Epic trips, um, you know, which is, I, [00:19:00] I was lucky enough to do, but I came home and people were like, was this amazing?
And I was like, I don\'t know why, but it wasn\'t. And I realized that it was like, just because I had the opportunity to take the trip, like I didn\'t, I, I wasn\'t in the right mindset. All I wanted to do was be home, you know? And so just cuz I could, um, doesn\'t mean I should have. And I, I keep trying, I try to think about that when I\'m taking classes or, you know, reading books or things like that.
Um, because time is precious, right? And, um, and we can only experience so much. So anyway, all that to say, um, yeah, with other products, I\'ve definitely built them, um, just because I could. And as a developer it\'s really dangerous because like, I look at that and I\'m like, oh, that\'d be really interesting to solve those problems.
Um, and then, uh, even as soon as you mentioned a WordPress plugin, uh, I was like, okay, well we need. X, y, z we need, you know, big da da da, and, [00:20:00] and that\'s great. Like a year from now, let\'s have all those bells and whistles and let\'s have all those features and, and, you know, and expand. Um, but of course, I\'m, again, I, I, I work, I have client work and w client work and family and obligations and stuff just as you do.
Um, and so you did a really good thing where we were chatting, we scratched our heads, and you were like, well, what if we, you know, what is the MVP here? And, and even that, I couldn\'t, I was like, well, da, da, da. And you were like, okay, featured image, one thing. Let\'s just start with that. Can we, and I, as soon as you said it, it clicked for me.
I was like, that\'s, that\'s the place to start. It\'s the one simple feature that, but it will solve the problem for a lot of people, and it will exemplify the problem we are trying to solve. . And so, and, and again, for me, it, it is tough at times as a developer, all [00:21:00] things are possible. Mm-hmm. , I mean, not literally, but, um, and that\'s, it\'s powerful but dangerous and I\'m, I\'m trying, you\'re, you are being, uh, non a not a developer and having a history of using this kind of thing is immensely valuable.
Um, keeping my feet grounded. And I\'m trying to do the same with thinking from the perspective of my clients, because again, they were the ones that inspired this, so what\'s gonna solve the problem for them? And that\'s where we, that\'s kind of where we\'ve landed and what we\'re getting pretty close to being able to launch.
Cory Miller: I, I think, um, the experience you talked about is like, everything is when , another shirt we should do, when everything is possible, everything sucks. Because when you have, when you\'re in the experience, I know this and I\'ve been. Uh, led teams of developers. I get it. Like, and I have the, I guess I\'ll say a gift in this sense of going, I don\'t know what all [00:22:00] is possible and it helps focus, but I think that\'s where, again, a partner comes in.
I struggle with this in different areas, um, where I\'m like, well, everything is possible. Everything sucks. And I, I lose focus in that. Um, and that\'s something I really enjoy being able to do is like, you worry about everything is possible and I can help just to ask questions. Um, and when we\'re, we talked about the MVP, I think about that iconic, um, like cartoon of this, the stages of an v mvp, how, how you start with an MVP and grow it.
And the one I like best, it feels a lot of theory and cool, like to try to plan this out like this, but it\'s like, what\'s the skateboard version of the. Bike or whatever, you know, the product becomes and it\'s not, uh, a skateboard. And then you add a seat and then you add handle bars to the skateboard and you try to build out.
And I\'m like, that\'s cool in theory. But [00:23:00] I think what this does is, the way we thought about this was what is a, a toe in the market that does solve that problem that can grow? Um, and, you know, marketing and technical and business questions come out of this. And I just saw one yesterday, uh, I can\'t remember his name on Twitter, but I replied to him.
He was trying to think like, where does this thing go? You know, like you start with the skateboard, but well, what if we want to do this with Crop Express and that with crop, you know? And, um, a lot of times, I think some of the best products have been part of grew organically instead of trying to say this is the end product, it was responding to customer needs and opportunities and grow out.
And sometimes maybe it grew into a little bit of a mess out here that we kind of had to make some hard decisions, um, with our ITM security product there for sure. And then backup Buddy over time. Um, we saw that, but it, I think it stays close to the customer [00:24:00] when somebody goes, I will pay money for this.
You go, oh, there\'s magic there because we, we might have something here. Um, and I, we decided, and we should talk about this decision too, we decided to release Crop Express as a free plug in first on the.org repo. We\'ll be talking about that experience as we go. We\'re not there yet, but we\'re really close to releasing the v mvp V1 in the repo.
Uh, and then, but what I like Corey, is we\'ve done this in a way to give us options or paths to go. We\'re not, we didn\'t try to build the bicycle and launch that as a premium product. We said, what time resources do we have? And that mvp all that went into this conversation you and I had of like this.
Okay, let\'s come down to if we can get this point, and that\'s in the stream of people\'s workflow. You know, you\'re firing and proposed headline, okay, I need my future. You\'re gonna go over here, click feature damage. And that\'s where [00:25:00] Crop Crop Express is gonna help you. And I don\'t, you know, you\'ve been great to navigate us technically, where we\'re not gonna hit a dead end on something.
Um, but that\'s the part of this adventure. You never know where you\'re gonna go with it. Right.
Corey Maass: And I\'ve, uh, you know, we\'ve already touched on a, a bunch of things that I see questions about all the time, like part of the MVP. Uh, I\'m, I\'m a, I\'m a good developer, but I have very limited experience with Gutenberg, um, excuse me, the block editor.
Um, and even, and so we, we are looking at doing a custom block down the road, version 1.2 or whatever. Um, but even to get, uh, just the, to, to work with just the featured image. Like I didn\'t have experience with the panels, uh, inside the block editor. And so I looked at it, I hacked at it for a [00:26:00] little while, and then I said, okay, you know what, I\'ve got a buddy who can help me out with this.
So, hired him for a couple of hours to get me over the hump. Um, you know, and so. There\'s that, there\'s again, the partnering, uh, you and I working together, um, which we haven\'t really flushed out, but we\'re kind of excited to do, um, launching something, putting something in, in the plugin directory is, is its own experience.
Um, and so yeah, I think there\'s, there\'s a lot of different things here that if nothing else, just getting that, you know, the tip of the iceberg. Um, or I\'m mixing metaphors here. But anyway, you know, just getting this thing out the door and, and starting, um, is, is where a lot of, uh, a lot of questions arise and there\'s, there\'s a lot of hurdles, you know, unto itself.
But, um, you know, I think the, one of the things that I really like about WordPress is that. It does require, or [00:27:00] WordPress plugins, WordPress products, it does require development, no question. Um, I don\'t think there\'s a big overlap yet enough of an overlap yet with like, no code products, um, services out there that, you know, people are building products against to then somehow get that into WordPress.
Um, but it doesn\'t have to be a huge lift. It doesn\'t have to be like, some of the best, um, plugins out. There are one single feature or, you know, single file, um, the, the plugin that we have so far that, that gets the featured image. Cropped and, and injected into a post is, is still basically just two files.
You know, it\'s not complicated. It\'s not this big convoluted thing. Um, I\'ve got, uh, from, you know, from a nerd perspective, like there\'s a couple of developer patterns that I\'m using, but there, there [00:28:00] aren\'t frameworks. We\'re using a library that, you know, does the cropping for us, cuz there\'s no way I\'m stepping into that quagmire.
Um, you know, but we\'ll grow from there. I mean, and I think that that\'s, that\'s the big difference. It\'s like, yes, we wanna launch something that is useful, um, and complete unto itself, but it can be, it can start as a feature and grow.
Cory Miller: How, how has this experience differed from your past product experiences?
Um, you know, you, you released, let\'s say the CommonBond different plugins on your own. I think, um, were, were similar problems and questions. That we\'ve talked about just in this, I don\'t know, month or so we\'ve actually gotten real serious about it. No, it\'s probably what, three, four weeks I think. . Yeah. Um, but like did you have similar things like that as a developer when you were doing like the combine?
Or did you just go, okay, this is what I want to build and you knew like the N V P V one V two kind of sorted [00:29:00] it out. How did those experience go in comparison to this one?
Corey Maass: Yeah, the con bond, I really, I wanted the name space. That\'s the thing that sticks in my mind. This was, you know, eight years ago now.
Um, so I don\'t, I don\'t remember everything, but we, same sort of experience. I was working at a startup and we needed a conbon solution. Um, Trello has. Rubbed me the wrong way. I don\'t know why. Um, and, and it was then that I was first starting to look at, so another, I\'ll give away another one of my secrets here is honestly, I often look for a, um, blue o, well, red Ocean SaaS solution or SaaS app that I can put into WordPress.
Um, and so with something like Trello, I was like, you know, we are, we are working in [00:30:00] WordPress, um, but we have to go over to Trello and, and do stuff. And for whatever reason, I didn\'t like Trello anyway. Um, and so that\'s part of what made me go, oh yes, if we had a CONBON board built into WordPress, so like posts were your cards or whatever, like, this makes sense anyway.
And so I cranked out a first version, very clunky and. Mostly just because I, I wanted to, I\'m trying to think if I had actually put a plugin in the repo before that. I don\'t, no. I had, I had, but years before. And so it was, it was really a new experience for me. Um, and I made all sorts of mistakes and I was listening to, like, one of the biggest ones was, um, I kept going back and forth.
Coming from, coming from a tra a, um, a, an a developer perspective outside of WordPress, [00:31:00] I wanted to do custom tables. And I was like, no. The word pressy way is you have to use the post tables. And I swear, the week after I released it, I heard an episode of, um, back when Pippin and Brad Ard had their podcast pippin\'s, like one of the greatest regrets of my life was using the post post table for e d D.
And that was like the beginning of when they were trying to release version three, which took them years to, to untangle, basically. I was like, crap. So right away I had to untangle my own thing, which thankfully only had 50 users or something, but I had to, you know, build a migration there and stuff like that.
Um, and then I think there\'s Go ahead. Go ahead, go ahead. Well just, you know, and, but there were, I I think maybe part of your question is like, There was, there were, I was solving bigger problems, you know? Um, whereas this, I think is like, I, I like, I mean, part of why [00:32:00] the, the light bulb went off when you were like, no, just featured image to start with.
So it just, it kept it focused, you know? And that\'s so much easier. Again, like I, I hacked away for a month or two months, you know, to get a working Now conbon board is a more complicated problem than, than what we\'re talking about. But, um, you know, but it, it, it was a much bigger lift to get it out the door, which I don\'t, I don\'t think is the right thing to do.
You know, you, you need, you need, especially talking about customers and clients and users, you need something. You need to get people using it as fast as possible.
Cory Miller: I, I think they\'re, I\'m seeing two paths that when you\'re launching a product, there\'s the technical path and the business path. Um, particularly if you want to monetize from it.
Um, but technical, I saw my teams for years. It was like, I, I always describe development as a, uh, an adventure and territory. You don\'t always know like, what\'s, what\'s gonna [00:33:00] come over the next hill. You could hit a swamp and end up drudging through a swamp or get sidetracked totally off on a minor bug. And so some of the things I started watching over the years is like, it, it\'s, it\'s a tough gig with the technical cuz you got a roadmap for potential.
You don\'t know where all the terrain\'s going cause you don\'t know where the business case is gonna come from, the use case. Um, and I just think it\'s like a blind expedition oftentimes. Like, so what we would do is, and we\'re doing this now too, is just kind of check in and see how we\'re going. And I valued having someone else external watching to at least kind of keep track.
And then I\'ll say this on the business side. Same thing. There\'s potential here. I see potential here from a business, business case. I don\'t know what it is. I\'m not even gonna be foolish enough to try to predict, but there\'s something here, I think. And um, because I don\'t predict anymore, by the way, Corey, because I\'m wrong most of the times when I try to predict, [00:34:00] oh, this is gonna be $20,000 a month, you know, MRR kind of product.
Yeah. I go, there\'s maybe a hope for those things, but I never predict or promise because if I get too mired in that, I start to get too f a little bit off of focus. Because some of the questions we\'ve talked about is, okay, free plugin, what do we do there? We felt it was, at least for our collaboration here, partnership, we want to do this.
We want this in the world, you know? Um, we think though putting it in the world has the potential for something that could grow into. Something We don\'t even, but I, I say this cuz we, we said, I love every time you say something like, Hey, I think we should do this. I\'m like, right on. We should be honest. We should be authentic and share the experience.
I think too, oftentimes in business and stuff, it\'s like, this is the way I felt when I left eye themes is like the pressure real or unreal. Hey, [00:35:00] Corey did this, oh, what\'s his next thing gonna be? And I was like, she, uh, let\'s see here. Um, I don\'t know. I followed the trail, um, and kept following that trail and trying to keep going on that trail for as long as we could.
Um, th this, I just like the fact that. One of the questions I try to ask myself before I begin any new venture or partnership is, what if it fails? What\'s the worst that can happen? You know? And what\'s great is we\'ve been talking about those things along where we manage it. I know when you hired the, the friend to help with some of that stuff, I was like, well, how much is that?
And, you know, do you need me to share it? And you\'re like, Hey, for now, let\'s just, I\'m gonna keep track of it. But, uh, to see where it goes and, um, I think that\'s healthy. That open dialogue and conversation where you respect each other, what each other knows. And know just because you\'re a developer doesn\'t mean you, you have a ton of insight and feedback [00:36:00] and perspectives to share on both business and marketing.
And, but it, it, it, I don\'t know. I see those two pasts. This is the one I\'ll tell you ahead of time, Corey is I\'ll struggle with, is when we get to the point we\'re like, okay, how much should we charge for? , it\'s oftentimes feels like this meandering thing of like, okay, and I\'ll need the same for you to go.
Sure. Hey, what if we do this? Um, because if everything\'s an option, everything sucks. .
Corey Maass: Yeah. I, so a couple of things that you touched on, like, it, this needs to exist in the world. I haven\'t found a better solution. So hiring somebody to get us over the hill immediately was worth it. And just like you said, if it, if it fails, if it never makes, uh, A dollar if you and I af after this call are like, yeah, I don\'t like you in the end it turns out, let\'s just call it, it\'s like, no, it was still money well spent.
You know, and I, I understand that I, I am in [00:37:00] the very fortunate position to have a, a little money that I can throw towards a project like this, but it\'s, it\'s very limited. And I, I think of this type of stuff as a hobby. Um, and there\'s been a lot of life choices that have gone into inclu, especially with, with my, my wife talking about like, okay, what is, if, if this is a hobby, what is an appropriate amount of money to spend on it?
Cuz there were times 20 years ago when I first started building SaaS apps that I was like, every spare dollar that I have is gonna go back into this without thinking about it. Um, because everything I ever think of is brilliant and every product I launch is undoubtedly gonna make me millions. Um, Spoiler alert.
None of it has yet, yet. Um, but uh, you know, yeah, we, we, we gotta start somewhere. Um, and, uh, I\'m with you. So I, I\'m also looking [00:38:00] forward to, like, I\'ve been, I met, it was, it was at a, it wasn\'t a WordCamp, it was like, um, what are they called? Free camp, or there\'s, there\'s conferences where it\'s like anybody can sign up to talk about anything.
Um, and it\'s sort of tech specific. But anyway, I met a young woman, uh, who was a developer and she had lucked onto a client who became a partner, um, who was an older guy who ran, I don\'t remember, an advertising agency, but he had access to an, a pool of customers, basically. And so he would tell her what to build.
and then he would sell it to his audience and they just kept cranking out products. And I was like, okay. Despite being an only child, and despite my first instinct being to do everything by myself, you know, there are things that I can\'t do. There [00:39:00] are things that I don\'t wanna do. and, and things that I shouldn\'t do.
So I\'m happy to weigh in on, you know, as, as your owning, marketing and your owning business, I, I want to weigh in, I want to have opinions, I want to make suggestions. And, you know, I think you and I have established that we, the expectation is that, you know, we, there\'s, there\'s going to be quite a bit of overlap in our concentric circles.
Um, but we, we each are gonna own a lane, which I think makes a huge difference. Um, and we\'re also able to sort of look over the cubicle wall to the other person and say, Hey, you know, like I, I touched on earlier, just cuz I can, doesn\'t mean I shouldn\'t, I\'m. Not going to want. There\'s going to be times where I, I\'m going, I\'m not going to want to build what I need to build.
Like there\'s a feature that every client is clamoring for. You are finally confident. You\'re like, they will all pay X number of dollars if you [00:40:00] just add this. And I\'m gonna be like, yeah, but we need a dark mode or some ridiculous thing that\'s just gonna be more fun to build. Um, and I think there\'s definitely going to be points where, you know, I, we\'re essentially going to need to be each other\'s bosses.
Um, and that\'s going to be interesting and going to be difficult at times. But I, but I think good, you know, you, you, you need other people. There are people out there that are, there are exceptions to this of course, but you know, I, I think we\'ve pretty well established that both you and I do better if nothing else.
Having a sounding board, having somebody else who\'s as invested, um, you know, and helps keeps us, keep us on the line we\'re supposed to be on.
Cory Miller: Yeah. On that note too, um, the partnership side of things where I, I\'ve been in circumstances where, okay, this is Mon Lane, that\'s your lane. [00:41:00] And sometimes, like you were really good to ask me what part of the development do you want to contribute to?
And I said, my strengths through trial and error. By the way, I think my contribution strengths are u UI experience, like how things flow. Um, I obsess over there cuz I want them to be as fast as possible. Mm-hmm. intuitive as possible. Knowing some of my, probably I\'m gonna have to freshen up on some things.
And the other is I said, you gotta be careful with me because I will share all of these things that I would love to see, but we\'ve like, But we gotta put \'em on a, a feature roadmap, A backlog somewhere. Because I said, and I told you this, I said, be careful cuz I\'ll come in and go, what about this, what about that?
And what I had to tell my team too, and I told you is like, please don\'t unless I go, can we get this in the next release? Please don\'t think that. Let\'s do this right now. And that\'s the [00:42:00] idea Fairy in me is mm-hmm. . Uh, but, and so an example of that was we have a square coop cropper. And I was like, okay, I\'m introducing the new customer story here, which is my own, every, the Posts newsletter has those little circles in them for all the, and I\'m like, that is a pain in the butt to do.
Now I flag that because I go, if I\'m the, uh, kind of a typical user, I don. Know how, how to crop that, you know, there\'s tools out there, right? But like I go, there\'s an experience if, if someone has that and I go, Hey, what about a circle cropper? And then I knew you were going to like chase it , and I was like, Hey, hey, hey.
Not for this one unless it\'s an easy thing. This was that back and forth I did with Right. All the developers I\'ve worked with too is just like, please don\'t say, please don\'t interpret that as, can we do this right now? Um, sometimes I\'ll be like, can we do this right now? Because I\'ll, I\'ll feel [00:43:00] like we got something here.
Um, but then you\'re like, okay. I was like, well,
Corey Maass: it\'s just cuz you can doesn\'t mean you should. Yeah. But there\'s also, you know, you and I, I, I also get the sense, we haven\'t talked about this, but I get the sense that we both trust our instincts pretty well, um, when it comes to product. You know, and I\'ve, I\'ve been, I.
Studying product, looking at product. Um, for years and years and years, I\'ve got, you know, books on architecture. And, uh, the, one of my favorite books about, about the Bowhouse School is sitting next to me. I mean, things like this and like, I nerd out about this stuff. And so, um, I\'m not saying I\'m an expert, I\'m not trained in any way, but like, I think I like a lot of people we know, you know, I, I, I love putting, I love loading an app and putting it in front of my mom.
You know, who\'s, who\'s not trained in any way. She has [00:44:00] a little bit of an artistic background. Um, but she is a power user. I mean, she, at this point, she doesn\'t even have a computer. She does everything on her iPad. Bless her heart, honestly, because. Trying to book tickets or, you know, I mean, things that she does on her iPad, I, I didn\'t think possible, um, even, which really is just in a browser and, and her fingertip, you know, but gets an unbelievable amount of stuff done.
But I love putting things in front of her and saying, you know, show me how you would muddle through this. Um, and, and anyway, so all of this to say that I, I trust my instinct a lot of the time, um, when, when somebody mentions a feature to me of like, oh, this is worth doing right now. Even if it, yes, it\'s not mission critical, you know, we haven\'t released yet, so technically any feature other than one feature is, is enough.
But I was like, not only [00:45:00] do, is there not a image cropper for WordPress the way that we want. Out there, but I really don\'t think any of \'em do circles. And again, my clients for most of their stories featured images are 16, nine or square. But for whatever reason, there\'s that, that now that browser pattern where avatars people are circles.
And so, you know, let me see if I can, I can crank this out and it\'s, and it\'s fun. Um, and sure enough, like, like you said, it, it wasn\'t a big lift, but yeah, I think, I think you and I will, we\'re just gonna have to figure that stuff out. Like everything, everything goes on a backlog. Everything gets discussed at least a little bit.
Um, but I also, you know, I don\'t, I don\'t think that there\'s harm in, you know, there\'s low hanging fruit, there\'s return on investment. There\'s lots of different ways to put it. [00:46:00] It\'s like, oh, well if we, you know, if we make all the buttons green, you know, is it, does the user benefit? No. You know, so just cuz it takes a minute isn\'t worth it.
But, you know, we\'re, we\'re just gonna have to, and, and I liked what you said too, of like, we, we are gonna have to, I guess this is the other, the other benefit of trying to get this thing out the door is like, get people using it, talk to people using it. Um, you know, being part of a, a community like Post Status, um, there\'s the great, um, advanced WordPress Facebook group.
Like there\'s, there\'s places that. You and I have been involved for a long time, kind of regardless of, of our actual position within those communities. But, you know, trying to add value or trying to Twitter to trying to just, you reply to tweets for months and then you hope that when you, you do something and you need somebody else to reply that, they will.
So it\'s like, let\'s get this thing out there. Let\'s see what people think. [00:47:00] Give it a try. Um, you know, and, and follow, follow our.
Cory Miller: This is where I struggle back and forth with product. But my typical mo, what I feel instinct is you, uh, there\'s product people that are just genius and gifted. They\'re like, here, you know?
And you\'re like, God, okay, cool. Uh, but for mere mortals, um, for me it\'s been put something enough out there, check some boxes. Okay, is this something you think we need? Like, does anybody even need it? Because I put those things out there, I\'m like, put \'em out there. Not necessarily products, but other things.
I\'m like, nobody\'s even asking for this. And a lot of the entrepreneurial books and stuff, it\'s like, okay, how you scientifically go down it? And I go, it\'s art and science. Yeah, it\'s a blend. It\'s this alchemy and magic of like, but I know the power of like putting something out there and that creates enough a ripple where you get a feedback loop and, um, [00:48:00] That was so helpful along the way when you get feedback like, I, I, we feel this is a good, this is a good V one, solve somebody\'s problem, that laser beam, you know, thing of what we\'re doing for it.
Um, but what I\'m most looking forward to the product is how people react when you hear those. Like, um, backup buddy was in development, uh, and then, I can\'t remember, 2009, 2010, and I, we were at, we had a little group thing where, and this, these two twin brothers ran an agency and I just, this wasn\'t something somebody told me.
I was just like, Hey. We\'re doing this thing and this plugin, and it helps you do, um, basically, uh, backup, restore, and migrate websites. By the way, those were not things that came from me. They came from Dustin Bolton and Christine and I themes, they\'re like, no, a backup needs to do these three things. Okay, okay, let\'s do it.
Sounds good to me. But I mentioned to them [00:49:00] the migrate, or what was it? The migrate side and just in passing, and they, their eyes lit up and they go, we pay somebody $300 to do, to do that now. Wow. Consider the time and everything. This is back in the day. And I was like, okay, I think we got something.
Because, you know, and then we just try to, okay, I think we\'re gonna keep going, keep doing, we obviously launch it, we\'re gonna launch it no matter what. But um, that\'s where I was like those moments where someone lights up and they\'re. Can I pay you now? The shut up pay, shut up. Let me pay you thing. Right? I was like, shut up.
You can take my money. Shut up and take my money. That\'s a magical moment. Um, I think times I\'ve tried to force it, um, and it\'s just, it\'s not, or create a category you hear that\'s not, and I\'m like, cool. Yeah. For those a hundred people out there that have that insane genius to create a category, most of us stumble into it.
Right. You know, um, the garage stories for startup [00:50:00] stories are always make me laugh. Cause I\'m like, what was the background? What was the context? I\'m like, that\'s a sexy headline. We started in a garage and here we are, apple. I\'m like, that\'s a sexy headline. Don\'t, and I like it. Don\'t get me wrong, but I\'m like, what Were all the actual moments, the places you got phenomenally lucky.
I know there\'s a big part of mine luck and every time I\'ve tried to time it and like, okay, I\'m gonna ride this thing, it just hasn\'t worked. And that\'s why I really like her direction with this. Um, Because we kind of had a fleeting thought of like, I think as I recall, like this could be a paid product.
Um, you know, I don\'t even know if we entertained much of starting with a paid, we\'re like, let\'s just do the free plugin. And I will say, remember actually, um, give you credit for this too, is I think I said, what about a Gutenberg block? Put it in editor. So upload image crop, boom, I\'m there. My workflow\'s fast, efficient.
And, [00:51:00] um, you, you looked into that, you chased a little bit of it and I said, Hey, there\'s some roadblocks here. And that\'s that collaboration of how we go, okay, featured image, what if we started right here? We want to grow potentially into that. You know, I think the idea in this, and we\'re, I think we\'re both verbal processors, but is the thesis is start here and it\'ll grow into.
Block, like the inline process where you\'re in the thing and you\'re having the same problem, I need to crop it, figure out right. Dimensions and all that. Um, so I don\'t know where I was going with that other than to say that was some of the background too of decisions and knowing like you could hit a dead end.
And I\'m waiting for that. I think we\'re putting ourselves out there with this to see if there\'s magic in this. Yeah. Journey.
Corey Maass: Yeah. A couple of things you said, um, stuck out to me. One is [00:52:00] like a lot, everybody builds products differently. Everybody b builds UI differently. WordPress has very soft wall, has a lot of walls, but they\'re very soft and there\'s a lot of discussion, often negative, often complaints about, um, The, the experience that a plugin provides.
And I think what\'s different about WordPress, right, is like often you\'ll, you\'ll go to Trello and you interact with Trello, and you go to Slack and you interact with Slack in WordPress, you\'re essentially interacting with numerous apps, really numerous UIs, side by side. Um, and the tolerance for terrible ui.
I mean, let\'s be honest, even WordPress is not great anymore. Um, the tolerance is high for what you can [00:53:00] get done. Uh, and so I think that that\'s, that\'s an, that\'s something that I hadn\'t really thought about, but it\'s like things you can get away with in WordPress as long as you can solve the problem. And so there\'s, there\'s a lot to be said for, bless you.
There\'s a lot to be said for. Solving the problem, um, and not getting caught up in the genius of a product. You know, cuz like you said, people, people wanna get it done and get out, you know, get on with their lives. Um, the other thing that I\'ve had a lot of luck with, so I think we should do this here, is talking about that feedback loop.
Um, with Conbon, I put myself on the homepage and had a, and, and had a nice. Response. Um, with, uh, there\'s an online game that I built during the pandemic that, that I\'ve told you about, um, called Mexican Train [00:54:00] in the web websites, Mexican train.online. So if anybody out there wants to play Mexican train, which is a Domino\'s game, but I built an online version, um, I put myself on the homepage and it\'s a game that is played by a lot of seniors and especially during the pandemic when everybody was really locked down.
And then even now a lot of seniors are still trying to stay inside, stay safe, stay more isolated than they were before. Um, and isolated being the word. They use the game to keep interacting with their friends, um, which is just amazing. Um, but they. Not only does every email that come in start with, Hey Corey, because I am on the homepage.
Um, but apparently when, like, there, there are groups of people that play every week and even every day and uh, they curse me when they get bad dominoes. They praise my name when they get good dominoes. Um, the picture is of me [00:55:00] eating cheezits cuz it\'s sort of as a joke, like, Cheezits are a guilty pleasure for me.
So a number of them actually like, go and buy Cheezits and eat Cheezits while they\'re playing because it\'s become a, you know, uh, a thing. Um, inside joke I guess is the, you know, uh, or whatever. Um, but there\'s the, that feedback loop is definitely there. Like, they talk to Corey, you know, and then even with.
Subsequent products that I\'ve built, me being on the homepage with a blurb about like why I started the Solve the Problem and stuff like that, has made a huge difference. And so I think as, at least early on, that\'s something that you and I should definitely replicate is, you know, as we\'re se I mean, we\'ll we\'ll send this to our friends and family.
Okay, that\'s easy, that\'s obvious. But, um, you know, maybe even building in a mechanism that\'s like, you know, Hey, it\'s your favorite. Corey and Corey, like, tell us what you think. What do you, you know, um, does this work for [00:56:00] you? Does this not work for you? That kind of thing. I usually don\'t think about explicitly collecting feedback until further down the road.
Um, usually wanting to focus on like paid customers and that kind of thing, but, you know, maybe it\'s something we start with sooner than later.
Cory Miller: I definitely think so, because, you know, so many times I\'ve put products out there and not really made that splash. Like, you know, they\'re like, okay, there\'s practical, they\'re doing this thing, um, that we set out to do, but I think you wanna have push, push it to have an opinion.
Mm-hmm. , you know, like the user to have a reaction to it, enough to say it sucks or it\'s awesome. Um, some, some way of that to see where you\'re at. I think both if you get it sucks and it\'s awesome. You\'ve got some validation there, you\'ve got something. Um, but putting things out there, that\'s [00:57:00] how I, my mo with products.
So 2006 or seven I think I, I launched, I did launch, I guess, uh, this is way back in Word Press was different, but I launched a theme and put my zip file. Uploaded it to.org. People downloaded it and I was like, this is crazy. I got a response from them, which I had a contact form up , you know, my website linked in the theme and stuff, and they\'re like, will you build blog for me?
And I was like, whoa. I\'m learning. I did this too because I wanted to do it and I\'m learning. But that\'s the magic that when you put something out there. Yeah. But I think there\'s this case for put something out there that kind of pushes a reaction. You know,
Corey Maass: and I think this will be an interesting point of conflict potentially, is uh, there\'s going to be a point where.
We\'re, we\'re going to see different paths and we\'re gonna want different features too. And so I think this is, that\'ll be an [00:58:00] interesting, you know, let\'s try to have that conversation on camera because it\'s there. There\'s points where I\'m dogmatic, like I\'ve got my, one of my other plugins is like, like I said, I, I often look at products that are out, out on, out in the wild and I repurposed them inside WordPress.
And so I\'ve, I\'ve got a plugin that\'s kind of like a link tree or a card or an About me where it builds very simple social focused landing pages. Like the link bio pages is kind of the, the phrase most people think of. And uh, and even like when I submitted it, the, the people reviewing the plugin were like, um, you\'ve kind of built WordPress inside WordPress.
And so I still get a lot of requests for features that are beyond. The point of the product, because it is within, like WordPress using the right theme or page builder, you can do literally anything. [00:59:00] So this is supposed to be very focused and people come in, come, come in and are like, well make it do this.
And I\'m like, that makes no sense. Like, go use WordPress. Um, and so I have found myself being more and more dogmatic about like, my own vision or, you know, certain vision for a product. Um, you know, and right now, like you and I have it easy, like we know it, it it\'s a one trick pony or one and a half if we do circles.
Um, you know, so what\'s, what\'s the next thing that I think that\'ll, and, and, you know, in a year down the road, I think that\'ll be interesting. Um, again, that, that backlog, you\'re probably gonna end up hearing more feedback than I am. Um, you know, uh, Product ownership might ha end up being a thing that we, we actually have to sort out.
So, and it\'ll be an interesting ride.
Cory Miller: Well, that\'s been a lot of the background, um, that we wanted to share and kind of catch you all up since we were, were launching [01:00:00] this live or in public. Um, but catching you up on some of the background, some of those key conversations. I hope people can use some of this to, uh, inform their own product journey.
Um, where we are today, where are we today, Corey, with the actual product? Sure. Um,
Corey Maass: yeah, and I just to add to what you just said, like as people watch this, there are a few people watching live. Um, my expectation, like most things recorded is, you know, more people are going to watch it on the playback. Um, but we are going to.
Looking at comments, and I think both of us are pretty easy to find. Um, you know, so, so as, as the, as the conversation gets started, you know, I encourage anybody listening, please ask us questions, you know, give including hard questions. You know, what do you want us to talk about? What do you want? What questions do you want our answers to?[01:01:00]
Um, not that we have the answers to all these problems, but you know, this is, we\'re doing this out loud, recorded on the internet, you know, so we\'re happy to talk about it. Um, and we\'re both pretty candid out, outspoken kind of people. So we\'re, we\'re happy to talk about prayer, pretty much anything. Um, but anyway, where are we at now?
Um, so I, with, again, with the, the help of a freelancer built, uh, a first version, I did the p h P. Um, he helped get the. JavaScript and React part of the, um, panel inside of the block editor integrated. Um, and then I took the, the cropping library that we\'re using, stuck that in. Um, and we\'ve, we\'ve gotten pretty far with that.
The, what, what we had been limited to for the last couple of weeks [01:02:00] is the selecting of an image. So, you know, nobody\'s, nobody\'s seen this yet. So talking through the flow real quick, you\'re opening up a, a new post in WordPress. There\'s, you know, the built-in featured image panel on the right. Um, we\'re essentially replacing.
It looks very similar to the built-in one intentionally, but when you click on it, instead of it opening the media library where you upload an image or select an image, it uploads a, uh, or excuse me, it opens a modal where it says What shape do you want a crop? Um, it does say, do you want a circle? Um, you select an image from your hard drive, it then opens the crop.
And one of the nice things about this kind of tech is that that image is not uploaded yet. And so it\'s all just in the browser until you say, okay, set this, you know, I\'ve moved the crop. I want it this part. Set that as the featured image and that\'s what gets uploaded. [01:03:00] Um, as of today, I got a poll request again from my freelancer who helped me get started with the media library, cuz this is the one thing.
I\'m, I\'m undermining you here, but you said, I really want circles. To me, I was like, that\'s a differentiator. We need circles. Um, to, from my perspective, I\'m saying also we need very basic media library integration. I think you originally suggested this as a nice to have, and I was like, no, you\'re right like this.
To launch with, you need to be able to select an image that has already been uploaded or select an image from your hard drive, crop it and set it. Um, and so we\'re, we\'re pretty much there. The media library is opening and you can select an image. Um, so I need to do a, a couple more hours of development, I think, to get it so that it\'ll save that essentially re cropped version of what is in your media library.
Um, [01:04:00] and then from a d a product standpoint, we\'re pretty much ready to go, um, on, on your list. Um, I know we have the readme.
Cory Miller: That\'s, it was like, Hey, Corey, you have 15 minutes of work to do. .
Corey Maass: That\'s not true. I mean, it, it is to get it in the repo because it\'s one of those, you know, no, nobody does it if a tree falls in the wo if a plugin gets committed to the repo and there\'s nobody there to hear it. Yeah. Um, you know, or, or security by obfuscation kind of thing.
But, you know, there\'s, it\'s the beginning of the marketing. How do we describe this thing? What do we even really, what do we call it? You know, is it, is it crop express? Is it crop express image cropper? Is it image, crop express, da da da da da. Like, just, we have the domain, but that\'s it. So there\'s,
Cory Miller: uh, it presents a lot of questions.
[01:05:00] Um, and I know we\'ve run outta time, um, but it presents a lot of questions because you go, there\'s wordpress.org plugin search that is, Pretty big, right? Um, the, these are some of the things coming outta my mind with the readme because it does turn into that plug-in repo section. Um, I\'ve seen a bunch throughout the years how people like, enough there to go.
Here it is. And then my balancing act is, let\'s get enough to show this is the value proposition, this is what it can do for you. Uh, and then just like everything iterate over time. Um, but I can\'t help but tell and admit to you. I think, oh, it\'s gotta be like side bki put a plugin on the repo. Like he knows he\'s a marketer, he\'s got all these talents, but he, he understands how to put a plugin, um, and showcase it, right?
And so I\'m battling that a little bit, but I go, okay, get enough to, so here\'s the value prop and that this is an active development and we want that [01:06:00] feedback loop back about what\'s next. But I think the read me is showing. Telling enough of what we\'re trying to do where someone goes, that is a problem. I have this plugin, will will solve it.
Now getting to that is gonna be, is gonna be fun, but I started on the Readme file from the Generate WP site you gave me. And um, that\'s where I\'ll honestly spin some wheels a little bit, cuz I\'ll try to be perfect. But I think the two outcomes there really are, you know, clearly understanding what this does.
So someone, mm-hmm can go, oh, I\'ve got this problem, or my client\'s got this problem. And then second is, we need a loop. We need to know these things. Even the things you go, we\'re never gonna do. I still want to have \'em up there. I still want to have \'em in our visibility because it just allows us to make better informed decisions as we over time hone in on, you know, A lot of the products we [01:07:00] released at I themes, it was years before we go, oh, that group right there, because you get enough of big sample size and you go, okay, convert Kit had a very similar, uh, fault, Nathan Berry.
He started out with one thought in mine, and then he saw it was this creators, you know, um, economy. And then he just, when he got that bead, he just, you know, doubled down on that. And I, I see, I see that similar here. I think we have pretty good profiles, like anyone that wants to make image cropping easier, um, and faster from a blogger to an agency doing work for clients, um, that\'s a big use case for me.
And I\'m like, there\'s, that\'s why I have some faith that there\'s something here that we can do in an advanced case, but it\'s just discovery to me, you know, so.
Corey Maass: Yeah. Well, and I think that\'s part of, I, I think you should take notes on your experience and then tell me about it. The next time we have a call, like [01:08:00] mm-hmm.
you are a, apparently you launched a pro a theme many years ago, , uh, but have it since. And so when I was like, okay, you go, go and do the read me. You were like, uh, I need some guidance. Like I, yes, I can write words, but tell me more about the Read me and what are the consequences of, you know, the, what I put in the read Me.
Um, and I think that that\'s, you know, you, here\'s a prime example of your experiencing something for the first time. You know, tell us about that experience and, and, and the thinking, some of the thinking that goes into it, like, it is, it is something that gets iterated on often, but there are consequences of, uh, you know, when we submit the plugin, the slug, the u r l is going to be locked.
You can. ask them to change it [01:09:00] once within, I don\'t know, the first couple of days or something. But then that\'s it. So, you know, cuz and you\'ll, and you\'ll see that with plugins on the repo that the U R L is W P S E O, but the product is Yost, you know? Right. Or things like that. Um, things that they\'ve had to change over time, but you can\'t change the slug.
Cory Miller: I know that firsthand too . Right. I sure think security was better WP security and, and it still is. I think. I don\'t think we That\'s right. Get there\'s, yeah. So that\'s right. Yeah. There are some foundational things that can\'t change over time, which is tough when you\'re doing new products as you don\'t.
Always know where it\'s gonna go or what the right, you know, do we need to say image cropping, you know, kind of thing. Whatever the, the kind of keywords are.
Corey Maass: Yep. So, yep. So, but I, I definitely think that\'s, that\'ll be a great experience for you to talk about and, and also a lot of the, the thinking that, that it makes you do will subsequently guide at least some of our early work [01:10:00] when we do put up a marketing site.
Cory Miller: Absolutely. Well, okay, so last question. We\'ll wrap this up since we, since we got over time. Um, but it\'s hard not to stop talking with you. I enjoyed this. Um, so by next Wednesday, um, what do you think is realistic for us to make progress on and we can start talking about that next. Because we\'re gonna be doing this, by the way, for the next five, six weeks, I think.
Um, there\'s a webinar, um, that was in the newsletter, the link to that. And then of course, if you\'re watching on YouTube, you can just come back to Post Status on YouTube. But Corey, what do you think, um, our next steps are, the progress we wanna make in this week interval?
Corey Maass: Yeah. I think the goal should be either we get this across the first finish line or past the first milestone or whatever of it.
Either we submit it to the [01:11:00] plug-in repo or it\'s, or it\'s ready to go and we can talk about that. But, you know, feature, feature complete as far as version one is concerned, um, and, and that, that read me, basically it\'s the whole zip file ready to go and be submitted and then we can either, Maybe we even, we could even submit it while we\'re on the, uh, you know, on the call and kind of talk about like that.
And then I think we\'ll end up talking about like, you know, whenever I\'ve submitted plugins, um, I\'ve, I\'ve never just had one like stamp done. Like there were questions asked or there were, um, code revisions that I needed to make based on, I know that they use a programmatic, um, I can\'t think of what it\'s called, but basically code sniffer, um, to, you [01:12:00] know, it basically some little AI that, that will flag variables that aren\'t escaped or things like that.
And, um, and then I\'ve also usually wound up having a conversation with a human being who\'s like, you know, what are your intents? What, what\'s your intention of this? Or, you know, why do you think we need this? Or whatever. And so if, you know, I think that\'ll be worth talking about too.
Cory Miller: Because the submission to the repo takes some time because it\'s gotta go in the review and all that stuff too.
So, um, I think about timing wise as well as like, once it\'s there, it\'s, we\'re gonna have just by nature of the review process, which is good. I, I, I get it. Um, it\'s gonna push us out some to actual, to actual launch. That\'s something to consider too.
Corey Maass: So, you know, so we can, I think let\'s, you know, let\'s regroup, um, today\'s Wednesday, you know, end of the week, beginning of the week kind of thing.
Um, and we can. basically just hit submit. Um, and [01:13:00] I th the last I heard the review process takes a couple of days and I, that, that fits with my experience. Mm-hmm. , um, you know, so maybe we\'ve heard if we submit Friday or Monday, we might have heard by Wednesday. Um, and then we\'ll have that to talk about, you know, or we can just submit on Wednesday and then the following week we definitely should have something to talk about.
We might not be live in the repo, but um, you know, we should have heard back. I know we\'ll hear back within a week. Yeah.
Cory Miller: Okay. Well, my intention is to carve out some time today. I think I\'ve got some buckets of time to finish, to read me at least get a draft that you can review and we can go back and forth, um, to have that, at least you not be waiting on that or me, so that sounds great.
Corey Maass: Yeah, I\'m.
Cory Miller: All right, Corey. Thanks, man. It\'s always fun talking through this stuff. Yeah, having a partner and a collaborator. And, uh, thanks everybody else for, uh, joining in as you can. Um, we\'re gonna be here Wednesdays 11:00 AM Central Standard time, um, [01:14:00] for the next five, six weeks throughout January and February.
As we talk, just share the progress we\'re making for this WordPress product called Crop Express. Thanks everybody. Thanks Corey. See ya. See ya.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 20:00:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:6:\"Emilee\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:8;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"Do The Woo Community: AI Text, Art, and Code\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74344\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:41:\"https://dothewoo.io/ai-text-art-and-code/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:397:\"

I chat with Mark Westguard from WS Form about how we have both used AI with content, art and even WordPress. With some added thoughts of AI and WooCommerce.

\n

>> The post AI Text, Art, and Code appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 12:12:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:9;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"WPTavern: WooCommerce Seeks to Improve Cart and Checkout Blocks Performance\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141242\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"https://wptavern.com/woocommerce-seeks-to-improve-cart-and-checkout-blocks-performance\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3517:\"

WooCommerce Blocks maintainers are asking the developer community to share feedback on any performance issues they are experiencing with the Cart and Checkout blocks.

\n\n\n\n

“We’re aware there is work to be done in this area and we want to improve,” WooCommerce developer Alex Florisca said.

\n\n\n\n

“We’re specifically interested in any performance related issues that may be stopping merchants or developers from adopting the Cart and Checkout blocks over the shortcode version.”

\n\n\n\n

The plugin’s repository has nine open issues categorized as related to performance. Most of them are not straight forward and require more research and testing. For example, an issue with running multiple blocks of product grids was reported as having increased response times of 4+ seconds. Contributors have proposed a few different ideas to address performance issues, such as experimenting with useSuspenseSelect to improve the perceived loading experience for various blocks and finding a way to track the performance of the Cart and Checkout blocks. Neither of these tickets have seen much movement yet.

\n\n\n\n

Store owners will not be eager to switch over to a checkout experience that is slower, so the WooCommerce team is seeking feedback that will help them make the cart and checkout blocks faster. So far, one user reported that due to a bug in a third-party plugin, he got a glimpse of what the block-based checkout adds to the JS asset payload.

\n\n\n\n

“I think this adds at least ~300 kB (compressed) JS payload (initial numbers, my measurement process is still ongoing),” Leho Kraav said.

\n\n\n\n

“We don’t plan to convert our classic theme to a block theme any time soon, but still, I feel uneasy about this direction.”

\n\n\n\n

Florisca followed up on this feedback with a few cursory benchmarks comparing the legacy shortcode checkout with blocks checkout and Shopify:

\n\n\n\n
Blocks CheckoutShortcode CheckoutShopify
Total Payload2.9MB935kb6.1MB
Total Transferred2.1MB1.3MB*3MB
Number of requests14477146
\n\n\n\n

“The number of requests has almost doubled for Blocks, which isn’t great so this is something that we can look into,” Florisca said. “I suspect the reason is because we rely on a few layers of abstraction on top – WooCommerce and WordPress, each with their packages and set ways of doing certain things. We can investigate if we can simply this.”

\n\n\n\n

The discussion on how to improve cart and checkout block performance is still open for more developers to give feedback, and investigations are ongoing. The good news is that WooCommerce maintainers are aware of how much weight the block-based checkout adds and are actively looking for ways to improve it for users.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 20 Jan 2023 03:53:58 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:10;a:6:{s:4:\"data\";s:13:\"\n\n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"WPTavern: WordCamp Europe 2023 Tickets Now on Sale\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141212\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:61:\"https://wptavern.com/wordcamp-europe-2023-tickets-now-on-sale\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2405:\"

WordCamp Europe announced the first batch of tickets on sale for the 2023 event that will be hosted in Athens, Greece, June 8-10. General tickets are € 50.00, a fraction of their true cost, which is heavily subsidized by sponsors. It includes admission to the two-day event, lunches, coffee, snacks, Contributor Day, a commemorative t-shirt, and an invitation to the After Party.

\n\n\n\n

WCEU is also offering micro-sponsorship tickets at € 150.00, which organizers say is closer to the real cost of attendance.

\n\n\n\n

Speaker applications are still open but will close soon in the first week of February. Applicants will be notified by the second week of March and organizers will announce the lineup in mid-April.

\n\n\n\n

WCEU is also seeking a host city for 2024. The minimum requirements are considerably less stringent than in previous years. Hosting the event is open to any team that has organized at least one successful in-person WordCamp in a European city in the last four years with a community that has been active during 2022. Organizers have also published an update to the selection process:

\n\n\n\n
\n

For this year, we have tweaked the selection process to concentrate more on the local community and the city instead of deep knowledge about how to organise a successful WordCamp Europe.

\n\n\n\n

The selection of the WordCamp Europe 2024 host city will be based on the overall evaluation of the application, instead of ranking different parts of it. We don’t ask your team to prepare a budget for the whole event, but estimated costs for the proposed venue(s) should be available.

\n
\n\n\n\n

Contributor Day registration for this year’s event is not yet open but will be free with the purchase of a conference ticket.

\n\n\n\n

At the time of publishing, only 257 tickets remain in this first round, but more batches will be released in the future. Register now to lock in your spot or sign up for email updates on the registration page to be notified of future ticket releases.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 19:37:51 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:11;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"Post Status: Interview With Product Lead Tiffany Bridge Of Nexcess — Post Status Draft 137\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146391\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"https://poststatus.com/interview-with-product-lead-tiffany-bridge-of-nexcess-post-status-draft-137/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:57251:\"

In this episode, Tiffany Bridge joins Cory Miller to talk about the latest innovations she and her team at Nexcess have created for beginner online store owners, simplifying WordPress for users, and the ongoing battles between centralization and decentralization.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

Tiffany Bridge has been working in WordPress almost since the beginning of WordPress. She is the Product Manager for WordPress eCommerce at Nexcess and talks with Cory Miller about their hosting services and products, specifically highlighting the benefits and capabilities of Store Builder. They dive into optimizing UX in WordPress, the benefits of open source, and more.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • WooCommerce Simplified with Store Builder. As you know, WordPress and WooCommerce love to hide settings in layers of menus. Nexcess saw the struggles people had in trying to set up eCommerce sites and created StoreBuilder as an easy tool to go from zero to having an online store. This removes the initial learning curve required to get started in Woo and sets up a DIY tool for merchants.
  • \n\n\n\n
  • A Platform to Grow with You: One of the great things about setting people up on WordPress and Woo as they start businesses is the flexibility available for future growth. If their model totally shifts, they can just uninstall a plugin and add another to obtain the functionality they need to sustain their business growth without the hassle of migration or the increased fees of a platform.
  • \n\n\n\n
  • Solving for What Users Shouldn’t Have to Know. Kadence and so many WordPress and WooCommerce plugins are designed for WordPress professionals. We are working to leverage the power of Kadence by creating a top-notch user experience for people who don’t know what things like a border radius or gutter are. These tools enhance and expand the power of WordPress, so creating solutions that lower the knowledge barrier to entry is the kind of work that moves WordPress forward.
  • \n\n\n\n
  • You Can Own Your Own Platform. Often people aren’t aware that this is an option. From Etsy to Twitter, controversies tend to increase demand for alternatives. Bringing more awareness to individual ownership on the web-for blogs, stores, or anything else-empowers people to show up online and conduct business on their terms.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Sponsor: GoDaddy Pro

\n\n\n\n

Manage your clients, websites, and tasks from a single dashboard with GoDaddy Pro. Perform security scans, backups, and remote updates to many sites on any host. Check up on site performance, monitor uptime and analytics, and then send reports to your clients. GoDaddy Pro is free — and designed to make your life better.

\n
\n\n\n\n
\n\"GoDaddy\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Cory Miller: [00:00:00] Hey everybody. Welcome to back to Post Status Draft. This is an interview in the series of product people that we\'re doing with some of the great product companies in WordPress. And today I have my new friend Tiffany. Um, we get to talk a couple weeks back and I love her energy, her experience, her approach to WordPress overall. She\'s very distinguished, uh, experienced person in WordPress having done some cool stuff that I\'m gonna let her talk about. But we\'re gonna be talking about Nexus and Store builder today I think So, um, Tiffany, welcome to Draft podcast. Thanks Corey. You tell us what you do, what, what you do in WordPress now, and where, where you got to this.

\n\n\n\n

Tiffany Bridge: Okay. Well, so right now I am the product manager for WordPress e-commerce at Nexus, which is, uh, basically I kind of, uh, I have my hands in the entire experience [00:01:00] of using WordPress on our platform as a, as an e-commerce focused host. Um, that\'s a pretty wide swim lane, so I do a lot, a lot of different things.

\n\n\n\n

Um, the thing that I\'ve been focusing on is our store builder. Um, before Nexus I was, uh, I was at Automatic for a while doing, uh, I was on their special projects team, um, which works with, um, you know, interesting people and organizations to try and make sure they have a great experience on WordPress. So I did a lot of, sort of very bespoke projects there.

\n\n\n\n

Um, before that I freelanced. You know, was kind of doing what a lot of, uh, my colleagues are doing is just trying to, you know, help my clients have, um, you know, with by setting up like WordPress sites for them and things like that. And before that I was doing a lot of WordPress just kind of in personal projects.

\n\n\n\n

I started teaching myself WordPress in 2004. So, um, I\'ve been with WordPress almost as long as WordPress has been WordPress, which is, um, which is fun, like to see how far we\'ve. As a, as a community and as a, and as a piece of software. Right?

\n\n\n\n

Cory Miller: We\'re gonna have to [00:02:00] talk about that later. I\'m gonna come back to that cuz you, you predate me. I was just a blogger in 2006 on, on this cool thing called WordPress . Um, but you said this, uh, as part of you, I know you\'re so, you\'re so humble, but I want to act accentuate a part of this, like that special projects team you did at Automatic is known for doing. Big, glamorous, cool sites with potential big problems attached to them.

\n\n\n\n

And I can\'t remember what the code name for the team has called, but I knew about it for years. And then when we met a couple weeks ago, months ago, um, and you told me your background, I was like, you were on that team. Cuz it\'s very, um, I, I would say like, You know, a celebrity status in my sense, because I know I\'d go, I\'d go to this blog site of this cool site and realize it was on WordPress, or somebody would say, now this is on WordPress, and you kind of dig into the details and you go, it\'s that team at Automatic that was doing it, that you were a part of for such a long time.

\n\n\n\n

Tiffany Bridge: Yeah, I was there for, uh, well, it was just like, [00:03:00] it was a couple of years and, um, yeah, I mean I worked on some very, very cool projects and it\'s kind of like WordPress bootcamp, right? Like if you don\'t, whatever you think you know about WordPress, you will know more after, after like six months on that team.

\n\n\n\n

Um, because we solved like, Like every WordPress problem there is, right? Like you\'re, sometimes you\'re rescuing a site from a developer that maybe didn\'t do a great job. Sometimes you\'re converting a site that isn\'t on WordPress to WordPress. Um, like a, a project that I worked on that is very close to my heart that I can talk about is, um, I worked on the conversion of a list part from Expression Engine to WordPress, which was just an incredible experience.

\n\n\n\n

Um, I learned so much, and the a list part team was super great. So, um, yeah, like that was a, that was an intense couple of years. Like there\'s a lot, there\'s a lot that goes into those projects and our job was to kind of make it, it was like, you know, like the metaphor of the duck, right? Like you\'re, you\'re swimming seren except underneath, you\'re like furiously paddling

\n\n\n\n

And like that\'s, uh, [00:04:00] that\'s the special projects team.

\n\n\n\n

Cory Miller: Can you say this special code name for it? I wanna say stiff.

\n\n\n\n

Tiffany Bridge: Um, the, so, I mean, every team at Automatic has like an internal nickname, right? Like the, the, the name. Because the names of teams at Automatic have historically not been, um, they have, there, there isn\'t just like, oh, that\'s accounts payable.

\n\n\n\n

Like there\'s, that\'s not what any of the teams are called, right? They all have like clever names, , um, special projects team is, uh, the overarching team is called Team 51. There are a lot of, there are a lot of rumors about why that was chosen. Um, none of them are, all of them are more glamorous and interesting than the real reason it was chosen

\n\n\n\n

Um, but now team 51 is actually, like, when I was there it was like 13 people, but it\'s now like 40 some people and so there\'s lots of subteams and those subteams all have names and things like that as well. So, but the overarching team internally is called Team 51.

\n\n\n\n

Cory Miller: This is why I wanted to do these set of interviews cuz there\'s people behind, oftentimes behind the scenes with these vast experie.[00:05:00]

\n\n\n\n

Building the cool products that so many people use and why? I wanted to highlight your background. When we got to talk, I was like, oh, I\'ve gotta share this, because I think it\'s so compelling to see one, you\'ve been doing WordPress for a very long time. Two, you did it for with this like, very, uh, interesting team doing some cool projects that really put a great face on WordPress.

\n\n\n\n

Um, like a list apart. You know, so many people in our community know that like the back of their hands. Um, I wanna share that. Cause I think that that all formulates these compelling stories into today in your role at Nexus and what you\'re doing and formulates all this background. Like I remember at I themes, there\'s so many times we\'re building cool stuff, but people don\'t see inside the workshop, they don\'t see all this stuff.

\n\n\n\n

They don\'t know all the history and background, the care and passion that goes into this. And so that\'s one of the reasons I was doing this and why I wanted to like point it out, you know, , um, So, um, okay, so that brings us to [00:06:00] today, and now you\'re at Nexus doing store builder of many things. But I really wanna talk about store builder because I think it\'s really interesting.

\n\n\n\n

I know you\'ve been focusing on it, um, at Nexus and it, there\'s a big problem that I think it solves for my own work. , I shouldn\'t even say work, trying to use w this thing called WooCommerce, which is incredible. one I, I think I, I\'ve said at least, and you correct me, kept, but I\'m like WooCommerce is the default e-commerce software on the planet because it\'s used so broadly.

\n\n\n\n

I think it\'s growing faster still than WordPress and for good reason, but you can do anything and everything with it. And that presents a lot of complexity. Absolutely. Absolutely. What is the problem you\'re trying to solve with store builder?

\n\n\n\n

Tiffany Bridge: Sure. Oh, well. So as you say, like the more flexible and powerful something is, the more complicated it is.

\n\n\n\n

And you know, something that I learned, and this I think, especially I learned at, um, on special projects is that, [00:07:00] you know, setting up WordPress and WooCommerce, that\'s a different set of skills than just using them day-to-day. And the problem is that people who, like once you, once the, the site is set up right, people can learn to use it.

\n\n\n\n

It\'s not, it\'s not that hard to use, but getting to that point where you can just use it and run your business on it requires a ton of knowledge. And you know how WordPress. Is like, it likes to hide all of the settings, like in all of these different menus. And you have to, you have to kind of know what you\'re looking for in order to find it.

\n\n\n\n

Um, and that\'s a real, that\'s a real challenge for people. So the problem that we\'re trying to solve with store builder is this idea of like, okay, there\'s like five or six things you have to do in order to go from zero to a store. And we wanna like gather those all up in one place and just walk you through them in a very logical way.

\n\n\n\n

So, okay, first we\'re doing like what we call first time. Consider. You\'re setting like the name and address of the store and the name of the site. And then we wanna do look and feel. Um, so let\'s just get some pages into your site. Let\'s get some content into your site that you can edit and make your own.[00:08:00]

\n\n\n\n

Then we wanna, like, let\'s add a domain. We\'ve got this very cool, like we call it the Go Live wizard, where you just, um, where it like walks you through the process of, of connecting a domain right there from inside WP admin. And then we\'ve got, okay, great. Now it\'s time to add your products. Products we don\'t have a wizard for.

\n\n\n\n

We\'re just sort of surfacing a lot of help content to just help people make good choices as they\'re configuring their product, their products. And then it\'s like, great. Now let\'s connect your payment. Now let\'s set up your shipping. Hey, congratulations, you have a store. Is there more work to do on the site?

\n\n\n\n

Of course there is. There\'s always more work to do. But now we have gotten to a point where you have products and you can take payment and you can ship them, and your site has a domain name and therefore an SSL certificate. So here you are, now you\'re in business on the. And that\'s the problem that we\'re really trying to solve is just like, let\'s just get p get all of these, like things that you have to configure in front of people so they don\'t have to go hunting for.

\n\n\n\n

Cory Miller: And that\'s a huge problem I see that firsthand, um, is, you know, WordPress enabled me [00:09:00] to start a business, start a blog first, and then it evolved into a business. And that\'s the beauty of it. And I see that with, with commerce. Nearly any, uh, nuance thing you want to do, you can probably do it with WooCommerce.

\n\n\n\n

There\'s so many extensions, plug ons and addons and stuff. It from my experience, it seems like, you know, you get in and, and e-commerce just set aside from e-commerce is just complex because, okay, well you\'re selling in Europe and you need that and you need invoices or something like that. You\'re selling, you know, a digital good with a physical product and you want a free trial.

\n\n\n\n

I was just talking to somebody about that yesterday. The whole thing on e-commerce. And then you get to WooCommerce, great tool, awesome ecosystem and stuff. And I see this problem that you\'re trying to tackle over and over, uh, and I think it provides a huge need for those trying to build stores on the web.

\n\n\n\n

Um, tell me about who the product is really for. [00:10:00]

\n\n\n\n

Tiffany Bridge: So you know, this product is really for that sort of like merchant who is either setting up the site themselves or maybe they\'re working with somebody to set up, but they\'re not like hiring an agency to build them a site, right? Like they might have, they might have a buddy who\'s good with computers, or they might even have paid a freelancer, but it\'s really meant to be kind of, Right at that like level of the person who is actually gonna be running the business should be able to set up the store.

\n\n\n\n

That\'s always the goal that we\'re after, right? Is if you decide, if you\'re like knitting hats and selling them on Etsy and you decide you wanna get off of Etsy, like you should be able to do this. So it\'s, it\'s meant for people whose skill is whatever it is that their business is. Not building websites, and that\'s who we\'re really targeting with this.

\n\n\n\n

Now, that is a very complicated problem and there\'s a lot of layers to it. And so we are always in the process of trying to solve for that use case. I think, um, I don\'t know if you can ever be, you can never say. We\'ve solved it, right? Like there\'s always gonna be more to do. [00:11:00] Um, and that\'s what we\'re doing with Store Builder right now, but that\'s who, that\'s for.

\n\n\n\n

Like a lot of our other products, like we host, we have Manageable commerce hosting, manage WordPress hosting. What we like to say about those products is that we\'re the hosts that you graduate to, right? If you\'re coming to us, you\'ve probably already been somewhere else. Um, but with Store Builder, we\'re really focusing on people who probably don\'t already have a website, and that\'s, uh, that\'s who the product\'s for.

\n\n\n\n

Cory Miller: That\'s unique with Nexus, but I know Nexus is a brand company, has extensive experience with e-commerce too. And this offering is really interesting because one, you\'re tackling a big problem. Um, but two, you\'ve got a lot of experience on your team and the company that has really dealt with this, um, the e-commerce question for a long time.

\n\n\n\n

So.

\n\n\n\n

Tiffany Bridge: Yeah well, and it\'s such a privilege to be able to work with people who like really think about e-commerce, right? Like Nexus got its start doing Magento. And so like we have a lot of like all of our, you know, engineering and our operations, like, they understand like what an e-commerce site [00:12:00] needs. And so it\'s, it\'s been great to watch them kind of apply that knowledge to WordPress and w as well.

\n\n\n\n

Cory Miller: Excuse me. And I think this is. It\'s one thing to have a blog, you don\'t wanna have blog. Mm-hmm. , I didn\'t worry too much about downtime. Sure. When you have downtime or something happens and you can\'t get things done with your story, you\'re probably likely losing money. So Absolutely. I think that experience is, is key to highlight Mato Gun back to the, the days, you know, this big, big behemoth of an e-commerce platform that switched hands and

\n\n\n\n

.

\n\n\n\n

hear that background. Next is, So you, you said this, uh, just a second ago, but you talked about some of the things, like what you\'re trying to do, and you mentioned some, some key things in the last year or so, as you\'ve b led this project. Um, what are some of the things that, that stand out that you\'re, um, excited about, proud about that uh, you can share.

\n\n\n\n

Tiffany Bridge: You know, I think in terms of like actual product features, you know, I\'m so proud of that Go Live Wizard. Um, because like, [00:13:00] you know, what\'s this saying? Like it\'s always d n s, right? D n s is hard and that\'s. and that\'s such, and there\'s no way to talk about it in a way that isn\'t like technical, right? How do you connect a, a domain name to your site?

\n\n\n\n

Well, you\'ve gotta go change your name servers. Well, what\'s a name server? What\'s a cname? What\'s an a record? Um, people shouldn\'t have to know that, right? Like people shouldn\'t have to know that in order to get online, I think. Um, so it\'s been really fun to kind of build this cool tool that just walks people kind of through a decision tree.

\n\n\n\n

The first thing it asks you is, , do you have a domain name or do you need one? If you need one, it\'ll send you out to the Nexus checkout, or we\'re working on this feature where it\'ll send you out to the, the Nexus checkout. We\'re working on the feature where it brings you back, back into your store. Like right now, we can, we can send you out to our domain registration, but we, we have to rely on you to come back.

\n\n\n\n

We\'re working on a feature where we can move you out and then just bring you right back to where you left off. But you know, so that\'s the first question. And then like once you have it, it like it will actually validate whether your domain is ready to connect, right? It\'ll do all the queries to see like, [00:14:00] are your name servers set or do you have the C name set up?

\n\n\n\n

And it\'ll tell you. If not, it\'ll tell you what it is that you need to do. Um, And then, you know, you, as you proceed with it, it\'ll like set up the DNS zone in your portal and it will like do the, um, the find and replace on your database to make sure that like WordPress knows what domain it\'s supposed to be using and that all of your internal links are now referring to the correct domain.

\n\n\n\n

So like it does all of those like little things that, like on special projects, we have a whole checklist for, to make sure that a human does them well. Now we\'ve got like a. Um, so that, that does that, and that\'s, I actually tease my former coworkers sometimes and I\'m like, Hey, I\'m over here trying to replace special projects with a series of onboarding wizards.

\n\n\n\n

And they\'re like, yeah, good luck with that . I\'m like, Hey, look, I never said I like small problems. Right? . So, um, but so that, like, that feature is something that I\'m really, really proud of and, um, and excited about. And I\'m always telling people it\'s like the best single piece of store builder

\n\n\n\n

Cory Miller: is, is this different [00:15:00] from the wizard?

\n\n\n\n

You mentioned a bit ago.

\n\n\n\n

Tiffany Bridge: It\'s the same one. Okay. I mean, it\'s like the, like that\'s the, that\'s the one that I\'m most excited about. And, and I think it\'s the reason that I may, that we\'re able to do that one so beautifully is because you don\'t have to, like, there isn\'t like a third party that we\'re having to connect with.

\n\n\n\n

Um, you know, when you start getting into like payments and shipping, like suddenly you\'re dealing with other people\'s APIs and so there\'s a limit to what you can do. Um, but like where we\'re able to kind of control the experience, we\'re able to make it like really beautiful and functional.

\n\n\n\n

Cory Miller: I know I\'ve, I\'ve helped people.

\n\n\n\n

You know how it is, I\'m sure you get this too. It\'s like if they know you do WordPress or websites, you know, everybody has some kind of idea. And, um, there\'s platforms out there, but again, the power of WooCommerce and, and WordPress particularly to, to grow your business. But there\'s complexity that happens that, that I know you\'re wiring in as you think about and build, continue to build the.

\n\n\n\n

For that experience. Um, it\'s kind of [00:16:00] going back for a second. I know Nexus does. Okay. You graduate to us. Uh, store builder specifically, I think is for a different kind of, um, problem. And you might have said this, but I want to come back to it cause I, I think I might have missed sharing this part of it. So, store builder, if you, you know, want to start a store and here are, you know, 15 options.

\n\n\n\n

This is the option if you want to, um, start a store and grow it.

\n\n\n\n

Tiffany Bridge: Is that right? Yeah, I think so. I mean, I think there\'s a no better platform than WordPress and Woo for something that\'s gonna grow with your business and be flexible to your business. Like maybe you get farther down the road and you decide, you know what?

\n\n\n\n

I don\'t actually want to sell merchandise anymore. What I would rather do is do courses or events. I mean, all right, well just install another plugin. You can uninstall WooCommerce. , off you go. Um, and so, you know, having that option always available to people as well is really important. Like you can, [00:17:00] because as you know, it\'s so flexible and you can just swap in the pieces you need and take out the pieces you don\'t.

\n\n\n\n

Um, I think it\'s, it\'s really great to just get people, like, just, just get on the platform that\'s going to grow with you at the beginning instead of having. Migrate later, right? Like, nobody likes migrations, nobody likes, you know, having to convert their data and carry their, carry their orders from like their Shopify store and their commerce.

\n\n\n\n

Just start with WooCommerce. It\'s fine.

\n\n\n\n

Cory Miller: I know. Um, so we talked about in that experience, like really making that initial experience where you\'re like, I\'ve got something I want to sell. Um, you mentioned when we were talking before this too, like particularly you\'re on another platform, like an Etsy or some other platform.

\n\n\n\n

This is when, um, you\'re ready to go and there\'s this, there\'s this learning curve with WordPress WooCommerce that you\'re trying to sort out. Um, I think you said it when we were, um, prepping for this like idea to selling [00:18:00] is, is kind of that key, which I think is so awesome because I know from experience.

\n\n\n\n

People, you know, non-word, pressure related. Go, I\'m ready to do this. Lindsay and I, my wife have a, a partner, great founder who does physical products. And, and that was the question I was like, okay, well you have a couple of options. , they all have pros and cons, they have some things. Um, but having an experience like this, I think is so key because of that initial learning curve going live online.

\n\n\n\n

But there, I know there\'s other things too. Nexus happens to be in the family of LiquidWeb, which is Own, has a number of WordPress specific company outside of the Nexus brand of families that you all, um, leverage within the platform too.

\n\n\n\n

Tiffany Bridge: Yes, absolutely. Um, the biggest, uh, so you know, the liquid web family of brands is large and growing, right?

\n\n\n\n

And, and, and as our post status friends know, there are quite a lot of like WordPress plug-in businesses that are now part of the family of brands. And the one that we are leveraging most right now in store builder is [00:19:00] cadence. And cadence. For those who don\'t know, is this really great? I don\'t wanna call.

\n\n\n\n

I mean, it\'s a theme, but it\'s like so much more than a theme, right? Um, it, it is a theme. It is blocks, it is starter templates. It\'s this whole package and it\'s really geared around people who are like web designers, but just need a great, um, like way to build and customize a site that doesn\'t necessarily rely on like a third party page builder.

\n\n\n\n

Right? Something I appreciate about Cadence is the way it sort of embraces. Extends the WordPress Block editor rather than trying to replace it. Um, cadence is there, there\'s so much great stuff, right? Like right now, store Builder really leverages this Cadence starter template. So you pick one of the starter templates around, uh, around e-commerce, and we import a site for you, basically.

\n\n\n\n

Um, and then you just have to edit it and make it your own. Replace the images, replace the text. But, you know, the, the feedback that we\'re getting from our customers is that that\'s still a lot of work and it. Their feedback is that because it is, they are correct. [00:20:00] That is still a lot of work to do. And so something that we\'re kind of, the next problem we\'re trying to tackle in store builder is this idea of editing all the not store parts of your site, making sure that you have a homepage and an about page and you know, all of your policy pages and things like that.

\n\n\n\n

And making it as easy as possible for people. Because you know, cadence was kind of designed around people who are already web designers and that isn\'t who our audience is. So we\'ve been working very closely with the ca cadence team on, you know, what\'s a, how can we leverage cadence and the power and the, the, the experience that they have, but create like a really great experience for, um, people who aren\'t.

\n\n\n\n

Who aren\'t already savvy with web design, right? Who don\'t know, like, what is a gutter, what\'s a border radius like, you know, no one should have to know that. Um, so we\'re, that\'s the next problem that we\'re trying to solve and um, and it\'s been a real privilege to work with my colleagues over on that side of the house on that.

\n\n\n\n

Cory Miller: I, That\'s you just kind of like [00:21:00] highlighted one of, one of the benefits why we, our partner and, and the founder of that physical products company. Like why not just to use, let\'s say a Shopify site or something is like mm-hmm. , the stuff you said that the non-store stuff is so awesome and attractive.

\n\n\n\n

Mm-hmm. and helpful for store owners where you can blog and. NCO and different things like that. And I happen to have some inside knowledge as far as . Um, having been at Lake Web a couple years ago, sold, sold our themes to, uh, lake Web, that there\'s a suite of tools That\'s awesome. And to see, you know, post status by the way, also runs cadence and such a powerful framework, whatever we call it, you know, word critical.

\n\n\n\n

Tiffany Bridge: Yeah. It\'s a, it\'s a sweet a package. I don\'t know, it\'s like, it\'s a theme. It\'s a lot. It\'s a lot of stuff. Um, and it\'s, it\'s just great. And, um, I\'ve been really, it\'s been really nice to be able to, to work with, um, something that both kind of embraces kind of the WordPress way of doing things, but also really [00:22:00] enhances and expands it.

\n\n\n\n

Cory Miller: Okay. So help me complete this sentence. As for product lead for this, this particular. Um, there\'s probably all these things that your, your team knows in sudden and out cuz you built them and you built them based on these customer, this journey of these problems with obstacles people ran into. I wish people knew or did about what?

\n\n\n\n

As part of store builder. Is there things from like, you know, your team just goes, gosh, they\'re not taking advantage of the school teacher. They\'re not doing this one thing that would make their life easier, the business would grow better. What are, what are some of those things, part of the platform that\'s come to mind?

\n\n\n\n

Tiffany Bridge: Oh, that\'s a hard one. I mean, I think the thing that I find is that the thing that I always want customers to know is usually it\'s bec, usually they don\'t know it cuz I haven\'t adequately conveyed it to them. So it seems a little bit almost self-serving. Right. To be like, oh, I wish [00:23:00] they knew. Like, one thing that I always find myself wishing that people knew is that e-commerce is really complicated.

\n\n\n\n

Right. Um, cuz I think sometimes we get people who come to. To store builder and expect us to solve all of the complexity of the e-commerce when what we\'re really able to solve is like the complexity of the website part. Like I read our, um, One of the things I do as a product manager is I read all of our cancellation reasons.

\n\n\n\n

Um, so like anytime somebody has left the product and they wanna tell me why it\'s hard reading, sometimes , it\'s very bad for the ego, but it\'s very good for the product. And somebody once said, well, I, I can\'t believe how many things I have to log into to use this. Like, okay. Well if you\'re talking about like our Nexus portal, like I agree with you.

\n\n\n\n

I would love to reduce the need for people to have to log into a web hosting portal. Right? But if you\'re talking about payments shipping, like was there ever a future where you weren\'t gonna need a Stripe account? I know some people are [00:24:00] tackling that by like building their own payments, but then I feel like that\'s another form of lock-in that I don\'t love.

\n\n\n\n

Right. Um, so, you know, so a thing that I, I want people to know is that, um, the system ha the, this, we\'re trying to, we\'re trying to balance like that like. Opinionated versus like freedom thing, right? Like, can we be very opinionated? Like, look, you\'re just gonna use, this is the payment system you\'re gonna use.

\n\n\n\n

Just, just, you know, while also still giving people that freedom of w of, of WooCommerce, um, I think that\'s always like when I\'m reading stuff, that\'s always what I\'m wishing people knew. And so now it\'s just a question of like, well, how do I then, like how do I teach \'em that it\'s not their fault? They don\'t know that I know that they don\'t know that.

\n\n\n\n

I think about e-commerce all day. You don\'t, you, all you wanna do is just get online and like sell this thing you made,

\n\n\n\n

Cory Miller: sell your stuff. Absolutely. Well, and, and there\'s platforms out there like Shopify for instance, and it, it\'s super fast gets [00:25:00] something going, but the complexity exists of some of these things.

\n\n\n\n

Like, you gotta think through, are you selling to Europe? What do you, you know, that\'s just one that comes to mind for me. Exactly. Um, but I totally get it. Um, the space that you all are in, what the product you\'re trying to provide, um, that, that is kind of like a pro and con of the beauty of the. , you can with store builder, with WordPress, with WooCommerce, get a store up and going mm-hmm.

\n\n\n\n

Um, so you can do it. And that\'s a great freedom that we have and enjoy for sure. But that, uh, I know from having done had, obviously businesses that run e-commerce rely on e-commerce or website was our front door to our store, but it was down. We didn\'t make money. Um, and then trying to help navigate some of those complexities is, is a pretty tough job.

\n\n\n\n

Anything else that kind of comes out to. About what I wish people knew. Yeah.

\n\n\n\n

Tiffany Bridge: Oh gosh. So many things. All the [00:26:00] things. Um, , they need anything, I guess they wouldn\'t need store builder

\n\n\n\n

Cory Miller: anything about the product that we haven\'t. Mentioned that, that you want to share too? I

\n\n\n\n

Tiffany Bridge: mean, I think, like, I think we\'ve covered all the things that I\'m like most passionate about.

\n\n\n\n

Like I just, yeah. You know, well, we were, you remember that controversy several months ago about Etsy and like Etsy\'s increase in fees and people were sh closing down their Etsy stores. And, um, like I just, like, I want people to know that it doesn\'t have to be that. . Like, it doesn\'t have to be that way.

\n\n\n\n

Like you can own the plat, like you can own your platform. We\'re seeing this now with Twitter, right? The implosion of Twitter. People are like, what are we gonna do? Where are we gonna go? And I\'m like, you should have a blog is what you should do. Um, you know, I think I, it just, I want people to know that it doesn\'t have to be that way.

\n\n\n\n

We don\'t have. Like our presences on the web, which is an increasingly important way of way, way that we conduct business, the way we conduct our relationships, the way we meet new people. Like we don\'t have to, it doesn\'t have to be that way, right? You [00:27:00] can own your home on the web, whether that home is a store or just a blog.

\n\n\n\n

Or just a blog or, um, or anything else. Like it. Just like, it doesn\'t have to be this way. It can be. There are many of us who would love to help you with it. And like, I\'m not saying that just as a person who wants to sell store builders, I wanna sell store builders, but I want to sell, like the reason that I care about store builder is because what it allows people to do.

\n\n\n\n

Cory Miller: Mm-hmm. Absolutely. You backed into my question I was gonna ask you next was to, you\'ve been a workforce a long time and you know when we prop. Uh, examples, like, I don\'t want to just poo poo Shopify, but use Shopify software is a service. There\'s benefits to having a SaaS Absolutely. Solution for what you\'re doing, but there\'s also,

\n\n\n\n

Tiffany Bridge: there\'s a reason they\'re successful.

\n\n\n\n

Cory Miller: Absolutely. There\'s also downside, and you mentioned earlier it\'s like WooCommerce, WordPress, and even store builder and Nexus grows with you. Um, but I want you to share a little bit more about that. You know, Shopify, what I was telling our partner, I said, you know, [00:28:00] Shopify\'s the glamorous thing people look at.

\n\n\n\n

And I see, I see why. But I said, you\'re gonna trade some problems for a new set of problems. And one of those you\'ve mentioned a couple times is lock in. And the beauty of, I want you to share a little bit about the, what your thoughts are around WordPress, WooCommerce, and open.

\n\n\n\n

Tiffany Bridge: Yeah, I mean, I think, I mean, the number one, biggest one is that you can own it and you can go, you know, wherever you want, and you can decide the experience that you wanna have.

\n\n\n\n

Um, I think that\'s something that a lot of us are spending a lot of time thinking about right now as like various social media platforms or like the, the downsides of like, for example, kind of lock in, uh, in social media pro. Platforms is becoming apparent, right? So that\'s like one thing that I think is really important.

\n\n\n\n

Um, another thing that\'s important is that, you know, the thing about, like, there are lots of companies in WordPress and Yes, here we all are trying to sell you our solution, right? We\'re all trying to make money. We\'re all trying to, you know, everybody, we, we live in capitalism. We\'re all trying to make money here.

\n\n\n\n

[00:29:00] But at the same time, like there is no reason. That you have to have any of that, right? Like the only thing that, that you have to pay for to use WordPress is someplace to. Right. You can download it, you can use it, it\'s all free, and that you can decide what you need and then you know what\'s worth paying for versus what\'s worth not paying.

\n\n\n\n

Like you can, it\'s such a like a choose your own adventure kind of platform. And I feel like, you know, we\'ve had so much centralization and so much, um, You know, like it\'s just so much centralization, so, so much like merging and like this company buys this company that we kind of forget that like we don\'t have to be that way.

\n\n\n\n

And I think it\'s, it\'s really important. Uh, I think open source is really important to like individual autonomy in that way. Like we\'re starting to get a little of like philosophical here, but I think, you know, just knowing that. If nothing else, you can just go download WordPress and learn to use it. Like I started downloading WordPress and learning to use it because, um, [00:30:00] movable type was going to a pay a for pay model and it was more money than I could pay at that time to indulge my like personal blog habit.

\n\n\n\n

And everybody was talking about this new system, WordPress that was open source and free. And I was like, free is good cuz I am broke. And I downloaded it and I started teaching myself to use it and it completely changed my. And I know I\'m not the only one. Right. I have talked to other people who are like, great.

\n\n\n\n

WordPress was free for me to learn to use, so I learned to use it. Word camp was $20 for me to go, so I slept on somebody\'s couch and went to a Word camp. Something that I think is, is so important is, is that kind of low financial barrier to entry. I would love to see us have a lower like knowledge barrier to.

\n\n\n\n

and I think we\'re all working on that every day. Um, but um, that, that\'s just like, that barrier to entry I think is always really close to my heart because I really believe that, you know, these are things that can change people\'s lives if they just have what they need in order to take advantage of them.

\n\n\n\n

Um, and I think that the community really [00:31:00] does care about that. And that\'s something that\'s like, makes me very proud to be involved in WordPress.

\n\n\n\n

Cory Miller: Well, you, you just, there\'s a practical side to this too, and I love the philosophical because it has practical implications as well. It\'s like Absolutely. You get locked into a platform, like you\'re talking about, whether it\'s an Etsy or a Twitter or a Shopify.

\n\n\n\n

Mm-hmm. , you\'re at kind of the whims of. What they\'re doing. That\'s a little bit different in word control,

\n\n\n\n

Tiffany Bridge: like company gets bought by somebody who then does all kinds of questionable things with it, and then here you are, like, I\'ve been on Twitter for 15 years, right? Like I\'ve been on Twitter since, yeah, 2007.

\n\n\n\n

So I\'ve been on Twitter like 15 years and here I am. Like with my like 15 year old, like at Tiffany Twitter handle, because that\'s how long I\'ve been on it. I got my first name and now somebody\'s over here like running it into the ground, making all kinds of questionable decisions, messing up the experience I have.

\n\n\n\n

And then I\'m like, well, now what? Like half the people I know I met here, like now what do I do? And like here I am like. I got locked in. I said I wasn\'t gonna get [00:32:00] locked in, but here I am, locked in. Um, so yeah, I mean that has like very practical considerations. There\'s people that I\'m struggling to stay in touch with because I only knew them on Twitter and like, how do I find them now?

\n\n\n\n

Cory Miller: Well, and you know, just a real direct one-to-one is, um, Shopify and Etsy platform versus this. And you, you look at a lot of entrepreneurs, e-commerce merchants start something, it blows. It. It starts to really grow and that lock in down the stream really comes into play For sure. Like you start getting taxed on your success in a sense where you, like you said, to that own and locked in feature where you go now.

\n\n\n\n

Exactly. With WordPress, we built a tool to, I themes that stellar brand that you can move websites very easily with. Exactly. Including at Nexus Brands.

\n\n\n\n

Tiffany Bridge: Exactly. And you know, like you, you build something, you go viral, you\'re like, suddenly your Etsy store\'s [00:33:00] going crazy. Now you have like, you know, transaction fees at Etsy.

\n\n\n\n

So the bigger you are, like the more your fees grow at ets, you know, at Etsy. And um, so you have that problem, but also like maybe you never bought a domain name. So now everybody only knows where to find you on Etsy instead of getting a domain name. So now you\'ve gotta like figure out how to teach people to go somewhere else.

\n\n\n\n

Like if you wanna move, like it\'s, yeah, it\'s a real. . I see this a lot of times too with like content creators and like Instagram. They\'re like, oh my gosh. If, I mean, Instagram\'s how I reach my audience, how are people gonna find me? If inst, if Instagram goes down, y\'all, that is a problem. Like you need a website and, and it just like, it makes me nuts, like a thing that is, it just makes me like pound the table cuz I get so annoyed about it.

\n\n\n\n

Is so you don\'t have like, People, you can only have like one link on Instagram, right? It\'s in your bio link in bio. And so people will like pay money for a link in bio service and then like link to their website and a link in bio. And I\'m like, what if I told you that you could just put a page on your website with the list of all your [00:34:00] links and then put that link in your bio.

\n\n\n\n

Um, and then you wouldn\'t be locked into yet another service, right? You don\'t have to get locked into the, like, there\'s the lock into Instagram and then there\'s the lock into the, the thing that you did to like work around the limitations of Instagram. Just have websites. Y\'all just have websites.

\n\n\n\n

Cory Miller: It\'s well in this, this partner of our same thing, built a great, huge audience on Instagram.

\n\n\n\n

Mm-hmm. that you gotta have an gotta have a website, gotta have an email list that you\'re trying, you know, things have, things have evolved. There\'s other marketing opportunities. But I go for me, website, email list that you can contact them that you quote own. So if something shifts, but you know, Tiffany, I\'m interested too.

\n\n\n\n

You see all this, you know, looking, looking around Instagram for instance. Some of the people that have got huge audiences, and I click those links and I think, okay, well maybe they\'re what, you know, at some point, how do they monetize that? And I go and I wanna get your thoughts on this and this whole creator [00:35:00] economy and what, I think probably 10 years ago we thought it\'s like bloggers and , you know, we have a new name for it now, but the creator economy, where they used the platform to get some initial buzz, but then, Okay.

\n\n\n\n

What\'s the path to Monet monetization. I mean, we\'re all passionate about what we do, but at some point you also need to, you know, keep the lights on and pay the pay the bills kind of thing. Absolutely. But I\'m curious too, like seeing that you\'ve been at WordPress a long time, seen in the web, a long time, been a technologist, but like, you know, what\'s your thoughts on that creator economy?

\n\n\n\n

Just like you said, okay, hey, here\'s a good point. Build your audience here. Hey, maybe not just a link tree or whatever it\'s called, but like, here\'s your website and all that. But what kind of trends and, and themes are you seeing in, in the foreseeable future, uh, that you know, you have thoughts on and ideas for as the creator economy builds?

\n\n\n\n

Tiffany Bridge: I mean, I\'m seeing, I\'m seeing a lot of people kind of fall back to newsletters, which is very cool in like retro, right? Like this idea of [00:36:00] like email, like we\'ve all got email. We neglected our email boxes for a while, but now it\'s back email\'s back, baby. Um, I think that\'s really interesting. And, and you know, and we\'re still seeing like some consolidation there, right?

\n\n\n\n

Because then now it\'s like, oh, let\'s, let\'s have a CK and like, okay, but now you\'re like locked into ck, right? Yeah. Um, which, which is a little bit of a concern, but you can at least like export. Subscribers out from ck, like if nothing else, like you can take your list with you, which I think is really great.

\n\n\n\n

CK has put together like a really easy to use stack of things that you need to run a four page newsletter. And, um, and so they\'re, they\'re popular for a reason, even if I still think people should have websites mm-hmm. , um, you know, but, but we are seeing that and even within sub, I\'m starting to see people like branch out into.

\n\n\n\n

Having websites like ghosts, which is another open source project. I\'m seeing people do that instead. Um, I think it\'s, it\'s really interesting right now because we ha we\'re in this moment where like the, the platform, the [00:37:00] social media platforms are really starting to show the seams and, and it\'s starting to feel like maybe we\'re on the edge of something.

\n\n\n\n

And I was just talking about this with a friend of mine the other day, and cuz he was saying like, Man, like Google Reader died and it kind of killed R Ss, right? Like, and nobody\'s figured that problem out since then. I\'m like, well, no, because everybody just started aggregating through Twitter. Twitter\'s the new, your new Google reader, except now like Twitter is twittering.

\n\n\n\n

And, um, because then we all, you know, we, and, and that, and again, that\'s like that problem of consolidation. Like even Google Reader, which was aggregating sources, it was like the dominant r s s reader. And I don\'t know, I don\'t know how to solve that problem. decent, uh, of centralization. Right. But I think it\'s very interesting that we\'re seeing people kind of move to newsletters because then they at least know that they can contact you.

\n\n\n\n

Mm-hmm. , and, and you can, um, and you, and you can have more control of your audience that way. Well, and then I\'m watching people like try out, like mastered on and that\'s interesting. [00:38:00] I don\'t, I don\'t know how that\'s gonna go cuz I feel like Mastodon is still. It\'s too difficult from like an administrative perspective.

\n\n\n\n

Like it\'s too difficult to start an instance right. Still. Um, I was talking about this actually in post status Slack the other day. I feel like a big reason that I ever got as far as I did with WordPress is cuz they had that five minute install so early on. Yeah. Like even in 2004, it was easy enough to install that I could figure it out myself and that like, I tried to set up ma on like ju like just like on a Nexus test account and like, , we don\'t have a way to run that particular form of like, of SQL that it uses of S SQL L and so like, like I would immediately stop and like, well, I.

\n\n\n\n

Like this, this thing doesn\'t even, like, it has dependencies that aren\'t necessarily available everywhere. And um, and then you have to, like, there\'s all this stuff that you have to do to set it up. And I\'m like, and you all have to, and it all has to be done from the command line. Um, so I feel like, you [00:39:00] know, these kind of like federated platforms where you run under an instance are gonna have to put a lot of attention into installation and onboarding if they want to, if they really wanna take off.

\n\n\n\n

I think that\'s gonna be a big thing.

\n\n\n\n

Cory Miller: What I take from this too is really going back to if you\'re thinking about building a business, even if you\'re dancing for passion, all of a sudden you\'re back in. You go, oh my gosh, I\'m a business owner. The thought process here to me is make sure you understand. What you own and what you\'re renting or borrowing for a time.

\n\n\n\n

Yeah, and just like you said, like I think so much from the we, I think we so much, by the way, benefit from de decentralization, AK WordPress, . You can, yes, you can copy it, you can for it and do whatever you want with WordPress. And there\'s power in that. And that shift of power where another platform has the rules.

\n\n\n\n

and regulations and policies that they change like Instagram, changing from more focus on [00:40:00] video to compete what\'s, let\'s say a TikTok and you go mm-hmm. Well, and, and I\'m not looking at my analytics all the time, but I look at likes, right? And I go, well, my likes went down quite a bit. Well, because I don\'t do video, I don\'t want to do video.

\n\n\n\n

Right. And right. Then you go, there\'s a way to build, it seems like build some initial audience, but make sure you have these off-ramps into something, even like an email list, you said, much less complex to export your subscriber list and go to another platform than e-commerce, but be really choosy and picky about what you\'re doing because.

\n\n\n\n

When your business does continue to grow, you want to be able to grow with it in the right platform to do that.

\n\n\n\n

Tiffany Bridge: Absolutely. Absolutely. And also, you know, as like the thing about decentralization is that there are a lot of problems that we are accustomed to having platforms solved for us. That now we have to solve on our own a decentralized situation.

\n\n\n\n

And so those of us who\'ve been working in open source a long time and and who work in tech, kind of like we already understand that like moderation is a problem and you have to think [00:41:00] about it. But you\'ve got all these, like for example, new MA on instance, admins who\'ve never really thought about moderation is like a problem.

\n\n\n\n

They have to solve , , and, and, and you\'d better. Right? And so, and that\'s like a, I think that\'s gonna be a real adjustment for people to make as we kind of like, if we\'re, if we\'re really gonna see like the beginning of a decentralization here, like there\'s gonna be a lot of like lessons that have to get relearned.

\n\n\n\n

Cory Miller: Yes. And when you said that about the five minute install, raise my hand because I go, that\'s why I loved WordPress. I didn\'t have to, what\'s a command line? What\'s the, you know, how do I. Upload, install, extract, set up my databases. Like that kind of simple. I\'ve seen so many tools over the years that promise some decentralization.

\n\n\n\n

But it\'s great for the developers that know all those things. But for the everyday person, once that gets figured out, that five minute or click, click install, I, I think we\'re gonna see some shifts in power.

\n\n\n\n

Tiffany Bridge: Yeah, I think so too. I think, um, I think if they pay a lot of, at pay more attention to that, I think you\'ll start to see a lot more.

\n\n\n\n

Cory Miller: [00:42:00] Tiffany, thanks so much for being on, um, post draft today and sharing some of your background and obviously your vision values, and then, um, what you\'re doing over at Nexus with store Builder and the other products. Um, tell, tell people where they can find you.

\n\n\n\n

Tiffany Bridge: Well, um, my slightly less neglected these days.

\n\n\n\n

Personal blog is tiff.is so, https://tiff.is/, you can find me there as long as there\'s still a Twitter. You can find me on Twitter at Tiffany. And, uh, you can find me on Mastodon at, uh, Tiffany@theinternet.social social.

\n\n\n\n

Cory Miller: Awesome. Thanks so much, Tiffany.

\n\n\n\n

Tiffany Bridge: All right. Thank you.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 18:45:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:12;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:60:\"WordPress.org blog: The Month in WordPress – December 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14191\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"https://wordpress.org/news/2023/01/the-month-in-wordpress-december-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:12476:\"

Last month at State of the Word, WordPress Executive Director Josepha Haden Chomphosy shared some opening thoughts on “Why WordPress” and the Four Freedoms of open source. In this recent letter, she expands on her vision for the WordPress open source project as it prepares for the third phase of Gutenberg:

\n\n\n\n
\n

“We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.”

\nJosepha Haden Chomphosy
\n\n\n\n

December brought with it a time for reflection—a time to look back, celebrate, and start planning new projects. Read on to find out what 2023 holds for WordPress so far.

\n\n\n\n
\n\n\n\n

WordPress is turning 20!

\n\n\n\n

2023 marks the 20th anniversary of WordPress’ launch. The project has come a long way since the first release as it continues to advance its mission to democratize publishing. From its beginnings as a blogging platform to a world-leading open source CMS powering over 40% of websites.

\n\n\n\n

Join the WordPress community in celebrating this important milestone. As the anniversary date approaches, there will be events, commemorative swag, and more.

\n\n\n\n
\n

Stay tuned for updates.

\n
\n\n\n\n
\n\n\n\n

WordPress 6.2 is scheduled for March 28, 2023

\n\n\n\n

Work on WordPress 6.2, the first major release of 2023, is already underway. It is expected to launch on March 28, 2023, and will include up to Gutenberg 15.1 for a total of 10 Gutenberg releases.

\n\n\n\n

The proposed schedule includes four Beta releases to accommodate the first WordCamp Asia and avoid having major release milestones very close to this event.

\n\n\n\n
\n

Read more about the 6.2 schedule and release team.

\n
\n\n\n\n
\n\n\n\n

What’s new in Gutenberg

\n\n\n\n

Two new versions of Gutenberg have shipped in the last month:

\n\n\n\n
    \n
  • Gutenberg 14.8 was released on December 21, 2022. This version features a reorganized Site Editor interface with a Browse Mode that facilitates navigation through templates and template parts. In addition, it includes the ability to add custom CSS via the Style panel and a Style Book that provides an overview of all block styles in a centralized location.
  • \n\n\n\n
  • Gutenberg 14.9 became available for download on January 4, 2023. It introduces a new “Push changes to Global Styles” button in the Site Editor, which allows users to apply individual block style changes to all blocks of that type across their site. Other features include typography support for the Page List block, and the ability to import sidebar widgets into a template part when transitioning from a classic theme.
  • \n
\n\n\n\n
\n

Learn how Gutenberg’s latest releases are advancing the Site Editor experience to be more intuitive and scalable.

\n
\n\n\n\n
\n\n\n\n

Team updates: WordPress big picture goals, new Incident Response Team, and more

\n\n\n\n\n\n\n\n
\n

Check out the 2022 State of the Word Q&A post, which answers submitted questions that Matt could not address at the live event.

\n
\n\n\n\n
\n\n\n\n

Feedback & testing requests

\n\n\n\n\n\n\n\n
\n

Have thoughts for improving the Five for the Future contributor experience? This post calls for ideas on how this initiative can better support the project and the people behind it.

\n
\n\n\n\n
\n\n\n\n

WordPress events updates

\n\n\n\n\n\n\n\n
\n

Would you like to be a speaker at WordCamp Europe 2023? Submit your application by the first week of February.

\n
\n\n\n\n
\n\n\n\n
\n\n\n\n

Have a story we should include in the next issue of The Month in WordPress? Fill out this quick form to let us know.

\n\n\n\n

The following folks contributed to this edition of The Month in WordPress: @cbringmann, @laurlittle, @rmartinezduque.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"rmartinezduque\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:13;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:112:\"Do The Woo Community: Bringing WordPress Certification to the Community with Talisha Lewallen and Sophia DeRosia\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74322\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:44:\"https://dothewoo.io/wordpress-certification/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:427:\"

Talisha Lewallen & Sophia DeRosia from CertifyWP chat with us about the importance of WordPress certification.

\n

>> The post Bringing WordPress Certification to the Community with Talisha Lewallen and Sophia DeRosia appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 10:57:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:14;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: WooCommerce Blocks 9.4.0 Adds Support for Local Pickup\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141197\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"https://wptavern.com/woocommerce-blocks-9-4-0-adds-support-for-local-pickup\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1740:\"

WooCommerce Blocks version 9.4.0 was released with support for a new block-powered Local Pickup option under shipping settings. The feature plugin offers users early access to new blocks and improvements to existing blocks before they become available in WooCommerce core.

\n\n\n\n

Local Pickups introduces two new blocks: a shipping method toggle block that allows shoppers to select between regular shipping or pickup from a specified location, and a pickup location block that displays local pickup rates.

\n\n\n\nimage source: WooCommerce Blocks 9.4.0 release post\n\n\n\n

These blocks can both be enabled and configured via a new local pickup settings page. Store owners can even rename Local pickup to something else, and optionally add a price for this option.

\n\n\n\n\n\n\n\n

It’s important to note that the new Local pickup blocks can only be used with the Checkout block. WooCommerce Blocks also introduces a change with this new Local Pickup experience that will support location-based taxes based on the pickup address, improving tax reporting. Previously, WooCommerce based local pickup taxes on the store address.

\n\n\n\n

WooCommerce Blocks 9.4.0 includes a handful of other small enhancements and bug fixes. Check out the release post for a more detailed look at everything that’s new in the latest version of the plugin.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 02:59:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:15;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:21:\"Matt: Polls on Tumblr\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75803\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:38:\"https://ma.tt/2023/01/polls-on-tumblr/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:301:\"

We just launched polls on Tumblr, and it’s been pretty fun. Cool to bring together the Crowdsignal (née Polldaddy) technology into a new world.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 19 Jan 2023 01:38:22 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:16;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"WPTavern: WordPress Project Aims to Complete Customization Phase and Begin Exploring Collaboration in 2023\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141181\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:117:\"https://wptavern.com/wordpress-project-aims-to-complete-customization-phase-and-begin-exploring-collaboration-in-2023\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3124:\"

WordPress Executive Director Josepha Haden Chomphosy published a summary of the project’s “big picture” goals for 2023. The goals fall into three major categories: CMS, Community, and Ecosystem.

\n\n\n\n

WordPress development will focus on completing the remaining tasks for Phase 2 (Customization), and will move on to begin exploring Collaboration in Phase 3.

\n\n\n\n

“As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows,” Haden Chomphosy said in her recent Letter to WordPress.

\n\n\n\n

“Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.”

\n\n\n\n

The vision for the first two phases was “blocks everywhere” and Haden Chomposy said this will be updated for Phase 3 to be centered on the idea of “works with the way you work.”

\n\n\n\n

In addition to the Phase 3 APIs, Haden Chomphosy identified the following items as part of the CMS goals for 2023:

\n\n\n\n
    \n
  • Openverse search in Core
  • \n\n\n\n
  • Navigation block
  • \n\n\n\n
  • Media management
  • \n\n\n\n
  • Simplify the release process
  • \n\n\n\n
  • PHP 8.2 compatibility (Core and Gutenberg)
  • \n\n\n\n
  • Block theme development tools
  • \n
\n\n\n\n

Under the Community category, WordPress will be focusing on planning the Community Summit, which will be held at WordCamp US in 2023, contributor onboarding, improving Polyglot tools, establishing mentor programs, revamping WordPress.org designs, and keeping pace with learning content. The project is also aiming to develop a canonical plugin program, which should be helpful as some Performance team contributors recently expressed that they don’t fully understand what the process is for canonical plugins.

\n\n\n\n

The Ecosystem category will focus on the WordPress Playground, an experimental project that uses WebAssembly (WASM) to run WordPress in the browser without a PHP server with many useful applications for contributors.

\n\n\n\n

WordPress contributors also prevailed upon Matt Mullenweg to consider having the project devote some time to working through old tickets and fixing bugs. Mullenweg said he is amenable to tackling one long-standing ticket (the kind that are stuck because of missing decisions or multiple possible solutions) each month in 2023.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 22:57:07 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:17;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"Post Status: Big Picture Goals 2023 • WP 6.2 Planning • LearnWP Needs Analysis • Wrong Plugins\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146539\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"https://poststatus.com/big-picture-goals-2023-wp-6-2-planning-learnwp-needs-analysis-wrong-plugins/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:16840:\"

This Week at WordPress.org (January 16, 2023)

\n\n\n

Where is WordPress going in 2023? Read Josepha\'s Big Picture Goals for the year. WordPress certifications are in the planning phases, and the foundation will include LearnWP. The Training Team is conducting a Needs Analysis. Help gather the community\'s input. Plugins Team is seeking intentionally wrong plugins, and Core has the 6.2 Planning Roundup.

\n\n\n
\n\n\n\n\n\n\n\n

News

\n\n\n\n\n\n\n\n

\n\n\n\n
\n
\n

Central

\n\n\n\n\n\n\n\n

CLI

\n\n\n\n\n\n\n\n

Community

\n\n\n\n\n\n\n\n

Core

\n\n\n\n\n\n\n\n

WordPress 6.2

\n\n\n\n\n\n\n\n

Meetings

\n\n\n\n\n\n\n\n

Developer Blog

\n\n\n\n\n\n\n\n

Design

\n\n\n\n\n\n\n\n

Docs

\n\n\n\n\n\n\n\n

Hosting

\n\n\n\n\n\n\n\n

Marketing

\n\n\n\n\n\n\n\n

Meta

\n\n\n\n\n\n\n\n

Openverse

\n\n\n\n\n
\n\n\n\n
\n

Performance

\n\n\n\n\n\n\n\n

Polyglots

\n\n\n\n\n\n\n\n

Plugins

\n\n\n\n\n\n\n\n

Project

\n\n\n\n\n\n\n\n

Test

\n\n\n\n\n\n\n\n

Themes

\n\n\n\n\n\n\n\n

Training

\n\n\n\n\n\n\n\n

Online Workshops

\n\n\n\n\n\n\n\n

Tutorials

\n\n\n\n\n\n\n\n

WPTV

\n\n\n\n\n
\n
\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n

Thanks for reading our WP dot .org roundup! Each week we are highlighting the news and discussions coming from the good folks making WordPress possible. If you or your company create products or services that use WordPress, you need to be engaged with them and their work. Be sure to share this resource with your product and project managers.

Are you interested in giving back and contributing your time and skills to WordPress.org? \"🙏\" Start Here ›

Get our weekly WordPress community news digest — Post Status\' Week in Review — covering the WP/Woo news plus significant writing and podcasts. It\'s also available in our newsletter. \"💌\"

\n\n\n\n
\n\n\n\n
\"Post
\n

You — and your whole team can Join Post Status too!

\n\n\n\n

Build your network. Learn with others. Find your next job — or your next hire. Read the Post Status newsletter. \"✉\" Listen to podcasts. \"🎙\" Follow @Post_Status \"🐦\" and LinkedIn. \"💼\"

\n
\n\n\n\n
\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 20:57:41 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:18;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:80:\"WPTavern: #59 – Corey Maass on How To Use WordPress To Kickstart Your SaaS App\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://wptavern.com/?post_type=podcast&p=141113\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:94:\"https://wptavern.com/podcast/59-corey-maass-on-how-to-use-wordpress-to-kickstart-your-saas-app\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:46303:\"Transcript
\n

[00:00:00] Nathan Wrigley: Welcome to the Jukebox podcast from WP Tavern. My name is Nathan Wrigley.

\n\n\n\n

Jukebox is a podcast which is dedicated to all things WordPress. The people, the events, the plugins, the blocks, the themes, and in this case, how WordPress can be used to get your SaaS app off the ground.

\n\n\n\n

If you’d like to subscribe to the podcast, you can do that by searching for WP Tavern in your podcast player of choice, or by going to WPTavern.com forward slash feed forward slash podcast. And you can copy that URL to most podcast players.

\n\n\n\n

If you have a topic that you’d like us to feature on the podcast, I’m keen to hear from you and hopefully get you or your idea featured on the show. Head to WPTavern.com forward slash contact forward slash jukebox. And use the form there.

\n\n\n\n

So on the podcast today, we have Corey Maass.

\n\n\n\n

Corey is a full stack developer who works with agencies and businesses, large and small. He specializes in advanced WordPress functionality and building products for, and using, WordPress.

\n\n\n\n

Over the last decade or so SaaS, or software as a service, apps have become more and more popular. Not only are we using our computers more, but with the rise of smartphones, we’re connected to our services all the time. There does not appear to be any corner of life where online platforms don’t have some presence. From email to taxis, fitness to food planning and delivery. You can find it all in a SaaS app somewhere.

\n\n\n\n

Now that many people are comfortable using SaaS apps, there’s been a deluge of new players coming into the market, but it won’t surprise you to learn that most of them fail to make an impact and shut up shop.

\n\n\n\n

Corey is on the podcast today to talk about why he thinks that building an MVP, or minimum viable product, app on top of WordPress is a good way to start your product journey.

\n\n\n\n

We talk about how WordPress comes bundled with many of the features that apps require. User login, roles, permissions, and the REST API. This means that you don’t have to reinvent the wheel for the things that WordPress already does.

\n\n\n\n

On top of that, the plugin ecosystem which surrounds WordPress, might enable you to short circuit the need to build all the features that your service needs. It may be that there’s an existing plugin, which does most of what you require, and is ready to go right away.

\n\n\n\n

Corey talks about how using WordPress in this way might enable you to see if there’s really a market for your app. And if there’s not, you’ve used less resources finding that out. And if there is, then you might have some revenue to develop the app in other ways.

\n\n\n\n

If you’ve toyed with the idea of creating a SaaS app in the past, but never quite got there, this episode is for you.

\n\n\n\n

If you’re interested in finding out more, you can find all of the links in the show notes by heading to WPTavern.com forward slash podcast. Where you’ll find all the other episodes as well.

\n\n\n\n

And so without further delay, I bring you Corey Maass.

\n\n\n\n

I am joined on the podcast today by Corey Maass. Hello, Corey.

\n\n\n\n

[00:03:58] Corey Maass: Hey there.

\n\n\n\n

[00:03:58] Nathan Wrigley: Very nice to have you on. Corey, we’re going to talk today all about the capabilities of WordPress as a SaaS platform. But as we typically do on this podcast, it would be very nice if we could orientate the listeners, allow them to figure out what your credentials are, what your WordPress chops are, if you like. So would you spend a few moments just giving us a brief potted history of your relationship with tech and WordPress more specifically?

\n\n\n\n

[00:04:24] Corey Maass: Absolutely. Back in the late nineties in college, a roommate of mine introduced me to this internet thing and the first websites I saw were some of my favorite bands. And I was a aspiring musician at the time, and I said, well, I want to appear as famous as they are. How do I make one of these website things, and the rest is history.

\n\n\n\n

I taught myself basic web design, web development. That led to learning some programming, JavaScript and then ASP classic way back in the day. But around that time there was the new trend of SaaS apps. 37 Signals was popular talking about this. Forums like Joel Spolsky’s, Joel on Software. And I caught the bug because I’ve always had an entrepreneurial streak.

\n\n\n\n

So I said, oh, this internet thing, building software, but not selling a download, but selling access to a website. So, I started going down that path, building websites for clients, but also building SaaS apps to try to sell on the side. And then WordPress took off and for a number of years, WordPress was pretty much my day job. Doing development or website setup or what have you, and then building Sass apps. Not using WordPress for a number of years.

\n\n\n\n

And then suddenly the light bulb went off. One, the WordPress market was getting bigger and bigger, and I realized that there actually was money in it. So that led me to start building plugins, which I think is what had you and I talking last time. But also at some point it occurred to me that WordPress had matured enough and solved enough of the problems that I was encountering over and over building SaaS apps that I said, let me look at WordPress as a SaaS platform, and I’ve been doing it ever since. So now it’s been probably five years or something, and WordPress only continues to mature, and this conversation continues to evolve.

\n\n\n\n

[00:06:27] Nathan Wrigley: So you, in the last few years, you’ve joined together the idea of a SaaS platform, but with WordPress handling some of the basic things in the background, if you like. I say basic, I just mean some of the things that we are more familiar with in WordPress. So user management, obviously if you throw some other things like WooCommerce at it, you may be able to handle billing or subscription or whatever it might be, and getting people to the right page depending on whether they’re logged in or not. Is it basically the promise of that? You can cut out a whole body of work, which you would need to build, well potentially from scratch, each time you create your own new SaaS app?

\n\n\n\n

[00:07:04] Corey Maass: Yeah, I think that’s the way to think about it. So, when you’re solving problems for people online, these days it’s definitely more broad than it was five years ago and 10 or 15 years ago, of course. So if you’re building something that’s B2B, technically speaking. So if you’re trying to build an API or some sort of true service that other systems are going to talk to. WordPress is probably not the answer you want.

\n\n\n\n

The REST API is, has come a long way, but it’s not really what it’s meant for, right? But if you think of most B2C apps, business to consumer, most of these apps are websites that you’re signing into. Well, WordPress accommodates that. You’re clicking through from page to page. WordPress accommodates that. You’re taking billing, you’re handling subscriptions. WordPress with WooCommerce or Easy Digital Downloads, or Restricted Content Pro or any number.

\n\n\n\n

I’ve been paying more attention to the membership plugins lately, which are in some ways are specifically designed to handle exactly this problem. Users signing in and doing something, interacting. Interacting with the website. Interacting with each other, that kind of thing. One of the things that, an example that I pick up on a lot is, years ago when I was building apps regularly for clients, for friends, for myself. Over and over and over again, I had to implement some sort of user password reset. And it’s so mundane. Once you’ve solved it once, it’s boring to solve as a developer. But it’s crucial to every app.

\n\n\n\n

And I got to the point where I was like, I just don’t want to ever think about this stupid problem again. But I had to integrate the code, again every time over and over again. It’s like with WordPress, I never have to think about that. And there’s a plugin called Theme My Login, that’s one of my favorites that you drop in and users can register for your website and immediately get access to a slash dashboard, which you can change. But arguably that’s the first huge leap, you set up a basic website.

\n\n\n\n

You want users to be able to register and have exclusive access to a page that they don’t have if they haven’t signed in or haven’t paid or what have you. So, these kinds of plugins just solve all of these basic problems. The bottom of the pyramid, so to speak. So that you can get onto whatever problem, your unique problem, that your SaaS is going to solve. As opposed to spending days, weeks, months, tackling the not unique problems like user registration.

\n\n\n\n

[00:09:36] Nathan Wrigley: So what you are suggesting here, let’s just lay this out. The audience that you are suggesting this to, is people who want to get something shipped quickly. And really, if you are at the beginning of your SaaS app journey, you’re not quite sure yet whether the market even exists. You’re just trying to float a solution to something that you believe might be viable in the marketplace, but you’re not sure.

\n\n\n\n

So we’re creating a shortcut. We’re offsetting the billing, the user management and so on to WordPress, just as a, as a quick way of getting an MVP or a minimum viable product out there. Is that the idea? Just to sort of test the water? WordPress is a good bet for that, and then presumably at some point you would advise that if it turns out to be an out and out success, then maybe, at that point you might need to look at different tooling.

\n\n\n\n

[00:10:28] Corey Maass: Not necessarily. There was a time when I would’ve said that definitively, but WordPress has come a long way. Hosting has come a long way. Optimization has come a long way. So it’s definitely the scenario that I’m using WordPress the most. I’ve got a new idea, or I’m working with somebody and they’ve got a new idea and this is how I want to get it off the ground.

\n\n\n\n

But there are a number of companies, big companies, in the WordPress space that continue to work, use WordPress as the core of their SaaS app, and they’ve got plenty of customers. I think it really, when you get to that level of, if you see a, a good amount of success, then there’s going to be technical problems to overcome.

\n\n\n\n

And so it’s either ramping up hosting, server power or optimizing queries or rewriting certain aspects of your app. We can talk about that. I had to do that for one of mine, about a year ago. Or again, depending on the amount of user inactivity or user, user interactivity, how much and how often your users are using your app, you may find that it handles it just fine.

\n\n\n\n

[00:11:43] Nathan Wrigley: So right at the beginning you started talking about why you use WordPress. You mentioned a few plugins, which might assist you on this journey. So I think some of the ones that you mentioned were things like Easy Digital Downloads, WooCommerce, and so on. Whilst I don’t want to necessarily promote certain plugins, I’m just wondering if, given the experience that you’ve had, if you could give us some tips as to plugins that you have found to be helpful for particular problems that you’ve faced while you’ve been trying to build it. And then in a few moments we’ll get onto the subject of how you’ve had to amend WordPress to do things, let’s say more efficient.

\n\n\n\n

[00:12:20] Corey Maass: Sure. So these days, I actually use Beaver Builder for building pages out. Beaver Builder’s a page builder. Elementor is another good one. But I find that doubling down and knowing these tools well, helps greatly with being able to solve a variety of problems because they’re not a theme, so they’re not locked into a certain layout or that kind of thing.

\n\n\n\n

But most SaaS apps have a pattern called CRUD, create, retrieve, update, and delete. So if it’s Twitter, then you are creating tweets. You are retrieving tweets, meaning you’re viewing all of them. You can’t really update tweets, but you can update your profile, that kind of thing. And again, you can’t really delete tweets, but you could delete your account, and that kind of thing. Facebook, you can create posts, you can delete posts, your viewing posts, so your retrieving posts, that kind of thing.

\n\n\n\n

So, a lot, a lot, a lot of software comes down to that pattern, and so using something like, Advanced Custom Fields and there’s a great plugin called ACF Front End, I think it’s called, that essentially puts an ACF form on the front end. So that’s how users can create and update. You could also use Gravity Forms. Or there are a couple of other plugins, form plugins, that you can then put on the front end, for again, collecting data from users or letting users post data. Essentially insert data into the database. And then using something like Beaver Builder or Elementor that have post modules.

\n\n\n\n

So it’s like if I was recreating Twitter, I would create a form, and this obviously once I’m logged in, but I would create a form that said, what do you want to tweet? And that would insert it into the database as a post record. And then I would use Beaver Builder, me personally, but you could use Elementor or again, any number of page builders, with a posts module that says, okay, show all posts, meaning tweets, with the author of Corey. So then you’ve just created a way to create tweets and then for somebody else to go look at all of Corey’s tweets, that kind of thing.

\n\n\n\n

So thinking, breaking it down to these kinds of patterns and then looking at these different plugins on how to solve them. A lot of the time I’m able to find ways to quickly implement. And it, again, it doesn’t have to be quick, and this doesn’t have to be forever, but a lot of the time it can be where WordPress and these plugins can solve these problems so that my SaaS offers the, again, the unique problem or solves the unique problem that I’m, the whole reason I’m building it in the first place.

\n\n\n\n

To get back to your question about those other plugins in particular. If you only want users to sign in, I love the plugin called Theme My Login. Again, look at membership plugins. And then, if you want to charge, again, break down the problem. What are you actually, what do you want? Usually you want subscriptions, like that’s a SaaS pattern that most people are used to now. And what are users paying for? Usually they’re paying for access to a page or pages or content or some feature to interact with other users or something like that. And there are plenty of plugins that restrict content. Which is the way to think about that.

\n\n\n\n

And so there’s literally Restricted Content Pro as a plugin. Easy Digital Downloads, which is e-commerce, but they have an add-on for restricting content. WooCommerce is really more e-commerce, but can handle this kind of stuff. And then again, membership plugins that are, as people are setting up communities, as at least some people are trying to get away from social media and get back to more private communities without relying on Facebook groups or Twitter or what have you.

\n\n\n\n

Membership plug-ins have been mature for a while, but are, I’m seeing them become even more and more popular. And are designed exactly for this. So a user pays for access to features, pages, what have you. And that’s again, kind of the core of most SaaS apps.

\n\n\n\n

[00:16:24] Nathan Wrigley: I suppose that if you are thinking of building a SaaS app, you must have some kind of kernel of an idea of whatever it is that you are trying to solve. So, you’ve got this fabulous idea, and the most important thing at that point is to judge whether or not this idea A, can be built, and let’s assume that after sitting down and thinking it through and mapping it out, you’ve decided, yep, yeah, this has got legs. This can be built with the technology that’s currently available on the web.

\n\n\n\n

And then thinking, okay, is there an audience for this? Are there going to be enough people out there who are willing to open their wallet to make it worthwhile? And if you go down the SaaS route, you may very well be an incredibly adept developer, in which case this may be in your purview.

\n\n\n\n

But if you are not and you are just trying to figure out whether the market is there and you want to do that affordably, then WordPress seems like a fairly decent bet, just because of what you said. The fact that with 60,000 plus plugins in the WordPress repository and countless more that you can purchase, in many cases for a very small amount of money.

\n\n\n\n

It may be that you can get 90%, 80%, 70% of the features that you are trying to build, but without having to do much in the way of custom coding. It may be that you can’t get a hundred percent of the way there, and that would require some tweaking, which we’ll get into. But is that essentially it? You know, you might have to cut some corners or, on your roadmap, cut out some of the things that you really thought would be nice to have in and just go for the things which can be enabled quickly and affordably.

\n\n\n\n

[00:17:58] Corey Maass: Yeah, I think it just depends on what you’re trying to accomplish. I have a buddy who is non-technical, knows enough CSS to be dangerous, which he’s learned over times, specifically for this scenario. He wanted to create a mentor program, and so he needed scheduling for matching mentorees to mentors.

\n\n\n\n

So we found a plugin that did that, or did that well enough. And then put I think a membership plug in. I don’t remember how he handled subscriptions. But basically put WordPresses stylized user management in front of it. Limited access to features based on a user being logged in or a user paying. And then a little bit of CSS to make it look a little more integrated or little more branded or what have you.

\n\n\n\n

And that was kind of all he needed. It solved the problem. He was able to charge for it. He got some customers. And then at some point he did end up hiring a developer to add a few bells and whistles or whatever features he found that were missing. But yeah, it got him 70, 80% of the way. Arguably it got him a hundred percent of the way of solving the problem enough that at least users could start using it.

\n\n\n\n

[00:19:10] Nathan Wrigley: Yeah, I suppose that’s it, isn’t it? If he’s got a core body of users, and he’s determined that, in this case he can use a calendar plugin or whatever it may be, and it will get him the user base that he needs. Then he can start to use the revenue that’s generated from the, let’s call it the SaaS app, to invest in having something done bespoke.

\n\n\n\n

That’s really interesting. That’s kind of nice to know. I guess one concern, which I may have, and I’m sure you’ve come across this before. Is just the notion that if you did build this and you fully had the intention of it staying on WordPress for all time. Then you are of course very much dependent upon the plugins that you are using. The spaghetti of plugins being updated regularly.

\n\n\n\n

In many cases that would very much be the case. It’s updated frequently. It’s made secure, and any vulnerabilities and things like that are taken care of. But there is always that chance that the developer of a key part of your SaaS app may just decide to call it quits, and then you might be left hanging a little bit.

\n\n\n\n

[00:20:14] Corey Maass: And the scenario I’ve seen more often is a mature product. Meaning your own SaaS app evolves away from what the plugin that you purchased does. So I saw this with a very big company in the WordPress space, who long ago had built their platform on top of EDD, Easy Digital Downloads. But over time had hacked and slashed at it, so that they couldn’t update it anymore.

\n\n\n\n

And that’s just a decision they had to make at some point of whether they were going to keep going with EDD and just lean into the features that EDD had and forego the other features. Or most good, big WordPress plugins are well documented and have hooks so you can add function extra functionality, or figure out how to sort of hack around them, to a point.

\n\n\n\n

And then, yeah. They had to make the decision to just stop updating it, and there was discussion. Last I heard that they were going to maybe move to something custom altogether. But the idea being, one of my favorite phrases, we made the best decision we could with the information we had at the time, right?

\n\n\n\n

So starting out early. It solves all your problems. Go for it. And then down the road you can migrate away from it. You can code around it. You could build something custom, what have you. But yes, that is certainly a risk. I mean, it’s also a problem that a lot of apps have broadly speaking. So it’s, you know, if you’ve built an app that uses the Twitter or Facebook API, you’re putting yourself in their, their hands.

\n\n\n\n

Or if you are operating system dependent or even, something I’m seeing right now is, microchip dependent, right? If you build software for MacOS and it only works on Intel and, and they move to M1 or M2. So these are just risks that I think you assess over time.

\n\n\n\n

But what I like is, the point you keep emphasizing, that this is a, a way to solve the technical problem. What I think that a lot of SaaS founders, small and large, real and imaginary, don’t take into account and, I struggle with, and most of us struggle with, is that these days the technical lift of building an app often pales in comparison to the marketing.

\n\n\n\n

We hear about these wonderful, amazing stories, like Instagram selling for whatever it was, 8 billion after two months, and yada, yada, yada. Most SaaS apps fail. And so you, you want to build quickly with a low lift and then spend most of your time, like you said, trying to get it in front of customers, validating the idea, getting feedback from customers about what features they actually want, or now that you’ve built the features they want, does it actually solve the problem for them?

\n\n\n\n

All of that is arguably way more important than the actual platform you use. But that’s what brings me back to WordPress as a platform, is in fact often a great way to get something out the door. Even if it’s just a form to collect data and then a page builder or a theme of some kind to then show the data back to the user, if that’s what solves the problem.

\n\n\n\n

[00:23:36] Nathan Wrigley: It’s interesting because if there’s a body of people listening to this who are not building SaaS apps on WordPress, and they’re just building client websites, you’ve probably encountered that scenario where the client comes and they have incredibly grandiose expectations of what they want the website to do.

\n\n\n\n

And because you’ve been building websites for so long, you just know, you have an instinct which says, well, we could build all of that. But how about we just start here? Because I would imagine it’s quite unlikely that your staff are actually going to start using some kind of intranet solution that we build as WordPress. Or some messaging system that we build in the app. It’s much more likely that they’ll continue to use things like Facebook Messenger or WhatsApp or Slack or whatever it may be.

\n\n\n\n

And so over the years you’ve become accustomed to figuring out what is plausible, what is likely to work, and I think I feel it’s the same with SaaS apps. It’s very easy to come to the table. You’ve got your blank canvas and you throw everything at it, every idea, every permutation, every possible thing that the app could do, and then decide that’s what must be built.

\n\n\n\n

That’s it. Until that is all done, we’re not going to launch it. And I think history shows that you have to be much more agile than that. You have to be able to drill it down and say, okay, what’s the 10, 20, 30% of all of that, that we’ve decided upon, which is going to get us off the ground? And so that feels like where this goes. If you try to build everything, it’s probable that you’ll A run out of money, B run out of time, and nothing will be shipped.

\n\n\n\n

Whereas in your scenario, offset the uninteresting jobs that probably don’t need to be tackled because they’ve already been tackled by plugins or WordPress Core. And just concentrate on the things which are going to benefit your users. And frankly, you don’t know what is going to benefit your users.

\n\n\n\n

It’s always amazing to me when I open up a new SaaS app that I’ve never use before. And you think, oh, this will be perfect what I need. And you end up on support saying, does it do this? No, I wish it did that. And those companies that succeed tend to be, well in my experience, the ones who listen to their early adopters and quickly pivot their solution to satisfy them.

\n\n\n\n

[00:25:45] Corey Maass: Exactly. There’s obviously no harm in thinking through what your dream app does, all the features. You make a long, long list. But one of the things that drew me to WordPress plugins, and selling WordPress plugins early on, was a rather cynical observation that I made.

\n\n\n\n

I was building blogs for customers. I was building e-commerce websites for customers. And instead of writing another article, which is hard and work. Or instead of inserting more products, which is hard and feels like work. A lot of my clients would get in the WordPress plugin repo where all the plugins are free and go, oh, I could use a to-do list plugin and they’d install it.

\n\n\n\n

Or, it’s winter. I should install a plugin that adds snowflakes falling over my theme. And they would waste an unbelievable amount of time on what felt productive and felt free. And I was like, well, if people are people, we are all human, we are all valuable and we are all, don’t want to do the things that are hard.

\n\n\n\n

But I see all these people that are spending time just digging through the plugin repo, I’m going to start building and selling plug-ins, because the discoverability is amazing. And so I think you’ve touched on that for SaaS as well, which is, we generally shy away from the things that are hard.

\n\n\n\n

We also tend to skew towards our own genius. What we think is the best idea. Because we thought of it isn’t necessarily the features, or it isn’t ecessarily solving the problem that your actual paying customers have. The real strength, and the real challenge, comes more in that side of things. Marketing, sales, talking to customers, getting over your own ego, optimizing your own time, all that kind of stuff.

\n\n\n\n

[00:27:48] Nathan Wrigley: Yeah. It’s interesting the marketing piece you mentioned. Never ceases to amaze me how much of the overall budget needs not to be sunk into the development of the actual software, but in alerting people to its existence. A significant amount. And it’s not to be underestimated.

\n\n\n\n

And obviously if at the beginning you sink a hundred percent of your finances into the code, that’s great, but I guess you better be a really good word of mouth, somebody that can spread by word of mouth incredibly successfully. Because experience at least tells me that it’s very hard to gather an audience from a standing start.

\n\n\n\n

So we’re a WordPress podcast. We’re obviously very keen on WordPress, we think it’s amazing. But I’m guessing that there must be downsides to this. Let’s just talk about that for a moment. Any drawbacks to this system that you’ve encountered over time? Just some quick examples may be that, well, does it scale very well? Does WordPress tend to be doing a lot of things in the background that a leaner, more specifically custom-built solution may get you out the hole of? Just questions around that. Any drawbacks that you would alert people to if they do decide to go down this approach?

\n\n\n\n

[00:28:59] Corey Maass: A few years ago, I was tasked with building a food subscription website. So think Blue Apron or Freshly kind of website, if you’re familiar with those. And for better or worse was told that I had to use WooCommerce. And so I spun up a WordPress website, installed WooCommerce, got subscriptions going, customized the choose the meals that you want, and then check out. And that all was okay.

\n\n\n\n

But it turned out that, I think some of this has been changed, because this was a number of years ago but, WooCommerce was storing all of the data in a very WordPressy way, which was fine because it was a known pattern. But was not very optimal. And then for the business, because all of those meals were cooked every morning and then shipped out, all of the charges had to go through at the same time, at like two in the morning. And it turned out that WooCommerce subscriptions was built so that if you signed up for a subscription at 10:30 in the morning, it would renew at 10:30 in the morning. While we needed it to renew at two in the morning so that all of the orders went through, so then the chef knew how many dishes to make, and how many chicken dishes to make or whatever.

\n\n\n\n

And that’s the kind of risk that you run into, right? So if you are using a third party piece of software, WordPress, and then with plugins. And you are essentially building it to your, or bending it to your will, so that it’s doing things that it’s not necessarily meant to do. You’re going to run into issues.

\n\n\n\n

We found that our server didn’t have enough power to process all of these orders at the same time, because it’s essentially multiple threads need to be run at the same time. We wound up in that instance sticking with WooCommerce and WordPress for at least a little while longer.

\n\n\n\n

But switching off of a hosting company that really was most popular for blogs and delivering content and not necessarily running process, CPU power. And moving to a custom AWS set up. And we watched the CPU go from 80% all the time, to 3% all the time. So in that instance, we just needed to throw more metal at it.

\n\n\n\n

But again, we were definitely using a tool, at least slightly, in ways that it wasn’t meant to do. I also, during the pandemic, or at the beginning of the pandemic, my wife made the mistake of turning to me and saying, you know, my family plays this game called Mexican Train, in person all the time. Boy, I wish there was an online version. And she should just know better than to put that kind of idea in my head.

\n\n\n\n

So within a couple of months I had spun up the only interactive online version of Mexican Train, which was great for our family, but it’s a very popular game in retirement communities. And naturally during the pandemic a lot of people in retirement communities were isolating a lot more. The game became quite popular, because it spread word of mouth. And the first Christmas, I think I built it early in the year, and, and the first Christmas it peaked at like 2,600 concurrent games or something. Which, for me, I had never built anything that needed quite that much power.

\n\n\n\n

And it did eventually fall over. But initially I’d built it so that every time somebody played, all the other games, so four people are playing, basically all four games are sitting there pinging the server, looking for updates. That’s very inefficient because most of those pings don’t return anything, but the CPU still has to accommodate them. So I wound up switching to a pushing system. So I had to integrate with that. And originally I had built it so that the game itself, so when you’re signing into mexicantrain.online, that’s the website, the login screen you’re seeing is Theme My Login.

\n\n\n\n

All of the delivery of content, so like when you go to the My Games page and you see all of your games, that’s just Beaver Builder. And then the actual game I had to build, so it was quite a lift as far as development goes. But that was what that SaaS needed. But I built an app in a JavaScript framework called React that then talks to the server.

\n\n\n\n

Well, I built the initial version using the WordPress API. So my game talked to WordPress, functionality that was built into WordPress. And the API worked, until it didn’t. So, in that instance, again, too many people hitting the server too much. Aw, shucks, it was too successful.

\n\n\n\n

I had to revisit it after a year or two and build a custom API. Now I’m a developer. I have that luxury, right? But these are things that, I got enough of a version out the door. So, thinking about it from the perspective of a non-developer. I could have set up most of it except for the game itself.

\n\n\n\n

And the game is sponsored by donations. So I installed GiveWP, which is one of the bigger WordPress donation plugins. And I still used the free version. And so I got most of those sort of basic stuff using third party plugins out of the box. And then if I wasn’t a developer, I might have had to hire a developer.

\n\n\n\n

And so yes, I would’ve had to put some money into it. But they wouldn’t have had to build everything. And I also could conceivably hire different developers, or I could by using WordPress. So one of the things we haven’t talked about is because of the popularity of WordPress, you also have a lot more developers to choose from if you’re going to hire somebody.

\n\n\n\n

But anyway, if I wasn’t a developer, I would’ve had to hire somebody to build the game. And then down the road, presumably I would’ve proven that the platform was popular, hopefully in the form of donations, which would’ve been enough money to then hire somebody to rebuild the API, if I couldn’t have done it myself.

\n\n\n\n

You know? So there’s sort of this evolution of, as you’ve said. Try things, see if it’s popular, and then maybe hire somebody if you have to, you know, if you’re going to grow parts of the platform, parts of the app beyond WordPress.

\n\n\n\n

[00:35:40] Nathan Wrigley: It’s really interesting you mentioning about all of the very large number of WordPress developers. The developers I guess, go into different niches, don’t they? They might be experts in one field or another. Do you detect that there’s a lot of people doing this kind of thing? Building SaaS on top of WordPress. Or is it just you shouting into an empty room? What I’m basically saying is, is there a community, a subset of the WorldPress developer community who, like you, are interested in building SaaS apps on top of WordPress.

\n\n\n\n

[00:36:10] Corey Maass: There is a book called Building Web Apps with WordPress that came out from O’Reilly. So it’s popular enough that people are writing books about it. I’ve given talks on it at a few different WordCamps as far back as I think four or five years ago or more. And I’ve come across a number of people who are doing it, or are thinking about it or have done it. But it’s definitely not, and even Mullenweg has talked about it, but it’s not the most common use case.

\n\n\n\n

I think in part because people just don’t necessarily think about SaaS apps separately as much anymore. More and more websites do something. And so if they have functionality, maybe that people are paying for, and users are signing in to use the web app to do something.

\n\n\n\n

It’s a SaaS app. But that’s, again, I think more and more commonly just how people view websites. So it’s not necessarily something that people are thinking about or searching for. Except for, I think, as you’ve mentioned a few times, if you’re looking for no code now means something different. But if you’re looking for a non-developery way to spin something up quickly using third party software, then it still gets some attention. But to answer your question, no, I’ve never found a community. I’ve thought about starting one, but never have. Because I just haven’t gotten a sense that enough people are talking about it.

\n\n\n\n

Which is okay. Maybe at some point they will, or, you know, maybe some other better solution will come along and consistently solve the problems. But, right here, right now, I still find WordPress a great option.

\n\n\n\n

[00:37:57] Nathan Wrigley: It’s really interesting because curiously, there’s a great deal of overlap with something that’s going on in my world at the moment in that I have been working with a developer on a SaaS app. I won’t go into the details, but reached a point where a couple of years ago, the interest in it, from my point of view, I think probably, is best to describe it. It waned a little bit and so it went on the back burner and it’s never been revived.

\n\n\n\n

And as a couple of years have gone by, I’ve decided that, actually wouldn’t it be nice to revive this? And so with a couple of friends decided that, yeah, let’s give this another go. But actually, let’s just begin again, because I’ve noticed there’s significant things in what’s already been built that I would change.

\n\n\n\n

And guess what we’ve decided to do? We’ve decided to do the MVP inside of WordPress. Basically for pretty much all the reasons that you’ve suggested. We’re familiar with it. There are sometimes free, sometimes commercially available plugins, which will do a significant amount of the lifting. Will it be exactly what we would like from our roadmap? No. Will it be close enough to get us to measure whether there’s an audience for this? Yes, I think it will. And so, curious that this is actually playing itself out in my life at this moment.

\n\n\n\n

[00:39:19] Corey Maass: Nice, yeah. Depending on the problems you’re trying to solve, but I think that’s like most things, a bit of planning, sit down, design. I encourage everybody to do this. What is the all the bells and whistles version. We nerds are a big fan of what’s called the 80 20 rule.

\n\n\n\n

So what’s the 20% that needs to be solved now, today to prove the idea? And then see what plugins align with that. How they can get you there. Will it solve the problem? Do you need custom development? Are there features that just don’t have solutions or aren’t solved by any of the plugins you might want to use.

\n\n\n\n

And then go from there. See what you can do. The nice thing too about WordPress is you can start locally, which is free. Locally meaning on your computer, not locally in your town, although you can do that too. Most computers using software like Local WP, I’m a big fan of, and there’s a few others. Also InstaWP, which lets you spin up instances of WordPress online for free, for, you know, seven days or something, and then pay to keep them, or you can download them, I think, I don’t know.

\n\n\n\n

I definitely have been guilty of getting an idea and I needed to illustrate the idea rather than just write the idea down. So I spun up an instance of WordPress real quick. Installed a couple of plugins real quick, and then said, what do I need next? Or what would the next step be? Or, if I was a user, what would I expect to see next? All that cost me was a little bit of time. There’s kind of that advantage too, where it’s, you can use it for wire framing means something specific, but conceptually you can use it for wire framing ideas, which I think is crucial. Without it costing you anything.

\n\n\n\n

[00:41:04] Nathan Wrigley: Corey, if people listening to this, if they’re resonating with it and they’re thinking actually, do you know what, this is something that I’ve been doing for a while, or, I’m curious to get into the community that you said might need to exist. Where would be the best place to get in touch with you?

\n\n\n\n

[00:41:20] Corey Maass: Honestly, the place that I talk about this the most is Twitter. twitter.com/coreymaass, c o r e y m a a s s. Just start a conversation with me. I’d love to hear people who are interested in this. If this resonated with them, if they’ve tried it at all. Because again, I’ve run into people who have done it. I’ve heard about people doing it. A book exists. So there must be people talking about it somewhere.

\n\n\n\n

But I think it would be neat to have a community of people, or even just a network of people, helping each other out, solving some of these problems. Hey, does anybody have a good recommendation for a plugin that solves such and such a functional, or a problem that I have. Where should I start? Suggestions for hosting companies. I mean, there’s, there’s always information to be shared. And honestly, that’s one of my favorite things about the WordPress community is that it’s so open. So many people are talking to each other and willing to help each other. I definitely think there could be more conversation around using WordPress as a SaaS platform.

\n\n\n\n

[00:42:21] Nathan Wrigley: Corey Maass. Thank you for chatting to us on the podcast today.

\n\n\n\n

[00:42:25] Corey Maass: My pleasure.

\n
\n\n\n\n

On the podcast today we have Corey Maass.

\n\n\n\n

Corey is a full-stack web developer who works with agencies and businesses, large and small. He specialises in advanced WordPress functionality and building products for, and using, WordPress.

\n\n\n\n

Over the last decade or so, SaaS, or software as a service, apps have become more and more popular. Not only are we using our computers more, but with the rise of smartphones, we’re connected to our services all the time.

\n\n\n\n

There does not appear to be any corner of life where online platforms don’t have some presence. From email to taxis, fitness to food planning and delivery. You can find it all in a SaaS app somewhere.

\n\n\n\n

Now that many people are comfortable using SaaS apps, there’s been a deluge of new players coming into the market, but it won’t surprise you to learn that most of them fail to make an impact, and shut up shop.

\n\n\n\n

Corey is on the podcast today to talk about why he thinks that building a MVP, or minimum viable product, app on top of WordPress is a good way to start your product journey.

\n\n\n\n

We talk about how WordPress comes bundled with many of the features that apps require, user login, roles, permissions and the REST API. This means that you don’t have to reinvent the wheel for the things that WordPress already does.

\n\n\n\n

On top of that, the plugin ecosystem which surrounds WordPress might enable you to short circuit the need to build all the features that your service needs. It may be that there’s an existing plugin which does most of what you require, and is ready to go right away.

\n\n\n\n

Corey talks about how using WordPress in this way might enable you to see if there’s really a market for your app. If there’s not, you’ve used less resources finding that out. If there is, then you might have some revenue to develop the app in other ways.

\n\n\n\n

If you’ve toyed with the idea of creating a SaaS app in the past, but never quite got there, this episode is for you.

\n\n\n\n

Useful links.

\n\n\n\n

37 Signals

\n\n\n\n

Joel Spolsky’s, Joel on Software

\n\n\n\n

Easy Digital Downloads

\n\n\n\n

WooCommerce

\n\n\n\n

Advanced Custom Fields

\n\n\n\n

ACF Frontend

\n\n\n\n

Gravity Forms

\n\n\n\n

Beaver Builder

\n\n\n\n

Elementor

\n\n\n\n

Theme My Login

\n\n\n\n

Restrict Content Pro

\n\n\n\n

Corey’s Mexican Train website

\n\n\n\n

GiveWP

\n\n\n\n

Building Web Apps with WordPress book

\n\n\n\n

Local WP

\n\n\n\n

InstaWP

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 15:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Nathan Wrigley\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:19;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:46:\"Do The Woo Community: Looking at Code as Words\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74188\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:45:\"https://dothewoo.io/looking-at-code-as-words/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:425:\"

The hurdle is getting past looking at code and saying, \"Oh, this is code. I can\'t understand it.\" You\'re not looking at zeros and ones, you\'re looking at words you can understand.

\n

>> The post Looking at Code as Words appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 11:07:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:20;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:125:\"WPTavern: Jetpack Revamps Mobile App, WordPress.com Users Must Migrate to Keep Using Stats, Reader, and Notification Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141116\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:133:\"https://wptavern.com/jetpack-revamps-mobile-app-wordpress-com-users-must-migrate-to-keep-using-stats-reader-and-notification-features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:6594:\"

When Automattic launched a mobile app for Jetpack in June 2021, it was targeted mainly at users who were on a paid Jetpack plan, as it enables access to features like backups, restores, and security scanning. Most importantly, the app gave Automattic a more direct path for monetizing Jetpack, without adding more commercial interests into the official WordPress apps.

\n\n\n\n

This week Jetpack announced that it has revamped the app and is offering a more compelling reason for those using the free plan to migrate. As part of a longterm effort to refocus the official WordPress apps, features that require Automattic’s products (the Jetpack plugin or a WordPress.com account) in order to use them, will soon be removed. This includes the Stats, Reader, and Notifications features, which have been relocated to the Jetpack app.

\n\n\n\nWordPress.com announcement for the revamped Jetpack app\n\n\n\n

WordPress.com users and Jetpack users on the free plan who previously relied on these features will need to switch to the free Jetpack mobile app. All the features that are moving over from the core WordPress app will still be free in the Jetpack app.

\n\n\n\n

While most self-hosted Jetpack users may easily understand the need for the switch, this transition may be rougher for WordPress.com users who do not understand the history of the mobile apps and see it all as “WordPress.” They may not be aware that Automattic’s integrated products have been controversial features in the official WordPress apps for nearly a decade.

\n\n\n\n

The announcement on WordPress.com is confusing, as it presents Jetpack as just a new optional app and doesn’t convey the urgency of migrating if users still want access to stats, notifications, the reader, and any additional paid features.

\n\n\n\n

The post’s FAQ section describes the Jetpack app as “the premium mobile publishing experience for our super-connected world” and states that “the Jetpack app is free to download.” WordPress.com users who commented on the post found the words “premium” and “free to download” to be suspicious and confusing. They don’t understand the reason for two apps:

\n\n\n\n
\n

“Do we have to change over? I only want to blog, I’m not technical and I don’t understand why you have done this or how to use it?”

\n
\n\n\n\n
\n

“So is WordPress now called Jetpack?”

\n
\n\n\n\n
\n

“If it ain’t broke, don’t fix it. This move is not in your users’ best interests so why is it being done? This smacks of the recent pricing debacle.”

\n
\n\n\n\n
\n

“I’m really disappointed by this decision. Why are you forcing us to use two apps? Your explanation of the differences makes no sense, and sounds like you made a decision for some reason you won’t tell us and you’re just trying to justify it. This is not user-focused at all.”

\n
\n\n\n\n

Users are also concerned about data loss, as those who are migrating to the newly revamped app are advised to delete the WordPress app after installing the Jetpack app. The announcement states that “Managing your site across both apps is currently unsupported and may lead to issues like data conflicts.”

\n\n\n\n

One user asked if there are premium features in the Jetpack app that will carry additional cost, and if there is any advertising included within the app.

\n\n\n\n

“For clarity, the Jetpack app is free to use and doesn’t include in-app advertisements,” Automattic representative Siobhan Bamber said.

\n\n\n\n

“We’re still planning our 2023 roadmap, and it’s possible in-app purchases will be a part of our plans. The driving goal would be to offer features that bring most value to users, and we’re keen to hear any ideas or feedback. Any in-app purchases would be optional, with the currently free features remaining free to use.”

\n\n\n\n

In response to those asking about the differences between the two apps, Bamber said there will be a couple more posts on the WordPress.com news blog in the following weeks.

\n\n\n\n

Users will need to have the latest version of the WordPress app installed in order to automatically migrate their data and settings to the Jetpack app. This includes locally stored content, saved posts, and in-app preferences. The FAQ states that after users download the Jetpack app, they will be “auto-magically” logged in with all their content in place.

\n\n\n\n

“One good way to confirm whether your version of the WordPress app supports ‘auto migration’ is to tap one of the in-app ‘Jetpack powered’ banners,” Bamber advised users in the comments. “You’ll find these banners at the bottom of sections including Stats and Reader. If you tap the banner, you’ll only see the ‘Switch to the new Jetpack app’ prompt in versions that support migration.”

\n\n\n\n

The revamped Jetpack app has been presented to WordPress.com users as a more feature-rich way to publish to their websites, but it also lays the burden of choice on users to try to understand the difference between the two apps and select one for all the sites they manage. Many don’t want the inconvenience of switching to a new app. Based on the users’ responses, it might have been easier for them to understand that the official WordPress apps are removing all features require the Jetpack plugin or a WordPress.com account – instead of selling it as a new, shiny publishing experience.

\n\n\n\n

Migrating to the Jetpack app is the best option if you want to continue using the Stats, Reader, and Notifications features. In order to make it easy for users to choose the best path forward, future posts on WordPress.com should make it crystal clear what features users can expect in each app and when they will need to take action.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 18 Jan 2023 04:57:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:21;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"HeroPress: Reflecting on My 3 Foundational Pillars\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5037\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:152:\"https://heropress.com/essays/reflecting-on-my-3-foundational-pillars/#utm_source=rss&utm_medium=rss&utm_campaign=reflecting-on-my-3-foundational-pillars\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:9650:\"\"Pull\n

I strongly believe that every experience we have up to our most current place in time shapes our identity. With that being said, as we go about living our lives it is not always obvious to see just how those compounding experiences shape us into who we are today. This is what makes all our journeys unique and worth reflecting on, because in our past often lies the tools and budding potential that influences the possibilities in our future.

\n\n\n\n

With that said, I’d like to share three pillars of my journey that have shaped me as a person and become the foundations of my current work.

\n\n\n\n

Technology

\n\n\n\n

I’ve found in one way or another that I have always lived technology-adjacent. When I was a kid my family had a Super Nintendo in the house which I always loved playing Super Mario World on– this device was essentially my first step into computers before we got our first home computer in the house when I was around 6 years old. By age 10, our computer was connected to AOL dial-up, which then allowed me to explore the early internet more deeply– MIRC, Livejournal, AOL Games, Neopets, MySpace… you name it. For the first time my world expanded beyond my immediate home of Rancho Cucamonga, CA, and El Paso, TX and into the interconnected world of the web.

\n\n\n\n
\n

Due to this opportunity of early access to computers, I became proficient in typing and navigating the internet at a very young age.

\n
\n\n\n\n

From what I’ve described so far, one would think that I was on track for a technology-related degree; however, there weren’t any people in my family or immediate network of friends who held a position in tech– so the idea that the computer could become a tool to propel my career didn’t really click until after graduating college.

\n\n\n\n

A slight detour– I’m a Social-Anthropologist by trade, having graduated from Lewis and Clark College with a Bachelor’s in East Asian Studies and a minor in Japanese. Following my passion for Japan and inter-cultural studies, I moved back to Tokyo after college and it was about one year later when I landed my first job as a Product Manager for a mobile gaming company called Cocone. This was my reintroduction to the idea that I could have a technology-driven career.

\n\n\n\n

In between working at Cocone and my return to the United States in 2016 I held a couple jobs that were not necessarily reliant on strong technical knowledge such as English Teacher, Executive Assistant, and even working at a karaoke bar. What my time as a Product Manager taught me, however, is that I do have a large thirst for working in the technology space so when I moved back to the States I applied to a Digital Agency called ASA Digital to get me back into that space.

\n\n\n\n

After a year at ASA Digital being a sort of Jack of all trades on projects that included mobile apps, web apps, websites, MR/ER/VR/AR, I knew that this was the right choice for me. Sometimes when you know you know, and when I moved on from ASA Digital to Automattic I was fully embracing my love for and need to have technology in my life.

\n\n\n\n

Diversity, Equity, Inclusive, and Belonging

\n\n\n\n

I haven’t always been aware of what the world now collectively calls DEIB, but since I was little I disliked the idea of injustice and lies. I have also faced adversity in the past due to who I am and what I look like, and it never sits right with me when others are in this kind of predicament as well. Due to this, DEIB practices deeply impacted my values and how I show up to work and with other people.

\n\n\n\n

It wasn’t until around 2019 that I became more involved in the world of DEIB in an official capacity at Automattic or at the incluu, LLC (a woman-owned and operated consultancy specializing in DEIB-thoughtful product strategy and advisement), and this is when I further developed this lens by participating in webinars on various DEIB topics, taking on leadership roles in the space, and keeping my eyes open to not only injustices that are happening but how they are being responded to.

\n\n\n\n
\n

The principles behind DEIB affect everyone and every aspect of our daily lives in some capacity, and embracing this space more fully not only allowed me to better understand the many systemic practices at play that keep us all from moving forward positively, but it also opened my mind to the real needs of people all over the world. 

\n
\n\n\n\n

Everyone deserves to live in a world or operate in a space with dignity and mutual respect.

\n\n\n\n

Community Building

\n\n\n\n

While I can understand the intent around the phrase “don’t mix friend groups”, I was never the type to follow this social role wholeheartedly. There are many times in our lives when we are put in situations where we interact with people we wouldn’t necessarily have engaged with such as school projects, clubs, sports, work, etc., and while it’s not all the time, sometimes a positive reaction can occur and we can meet someone new and interesting through these random groupings.

\n\n\n\n

I’m not quick to make friends, but when I do create a strong friendship it is because we share values and experiences which serve as the foundation for our relationship despite any other differences. Maybe it’s because of my (still ongoing) gaming days, but I tend to visualize people in the world as a character with a rich background story and something only they can bring to the table.

\n\n\n\n
\n

It has always brought me joy to bring people together and see how these chain reactions occur.

\n
\n\n\n\n

It could be that by some happenstance one of the friends is recruiting, they share a similar hobby, or come from a similar background. Facilitating safe spaces where folks can develop a sense of community has always been a passion of mine.

\n\n\n\n

I have had the pleasure of building community in the WordPress community through various outlets like BlackPress, with the Training Team, and even in Automattic’s Black employee resource group Cocoamattic.

\n\n\n\n

The Outcome

\n\n\n\n

Early last year I applied for the Community Education Manager with a basic idea based off of the job description of what I would be doing– fast forwarding to today I have found that the three pillars shared above gave me the tools I need to perform in this role successfully.

\n\n\n\n

As a Community Education Manager I work to break down perceived barriers for folks who want to contribute to the Make WordPress Training Team’s goals, and work as a close partner with the Training Team Representatives and members to empower them to excel in their leadership, goals, and strategy. I also help shepherd the Faculty Program, and therefore work to enable these folks to fully own and participate in their roles.

\n\n\n\n
\n

When working with our contributors, I focus on building relationships, encouraging engagement, and enabling contributions.

\n
\n\n\n\n

We have contributors from all over the world, so I also take care to be mindful of any language or cultural differences that may be at play and lean in with curiosity to better understand each community’s unique needs.

\n\n\n\n

When working with our Team Reps, I similarly focus on building relationships, and work with them (not for them) to create an environment where the goals of the team can be realized. 

\n\n\n\n

Lastly, I work with our Faculty Program Members by building relationships and connecting them with work related to their role, and with contributors who can benefit from their expertise and mentorship.

\n\n\n\n

Can you see how my pillars are directly impacting and influencing the work I currently do?

\n\n\n\n

Exploring Your Own Foundational Pillars

\n\n\n\n

There are probably many articles with thought-provoking exercises that can lead you in your own self-reflection, so I’ll leave you all with just a some questions from me that have worked to get me started:

\n\n\n\n
    \n
  • What have you been given positive feedback on lately?
  • \n\n\n\n
  • What actions/things bring you the most joy in life?
  • \n\n\n\n
  • What actions/things make you feel motivated?
  • \n\n\n\n
  • When was the last time you found yourself “in the zone”?
  • \n
\n\n\n\n

As you go through the questions for yourself don’t discredit or try to change your initial thoughts.

\n\n\n\n

Using these as a starting point, even if what comes up doesn’t immediately surface something that could be a pillar, you’ll surely learn or get to acknowledge something about yourself that shapes your character and how you present in the world.

\n\n\n\n

Take your time with it– the way we walk through life is a long-term journey which is constantly being updated by new experiences along the way.

\n

The post Reflecting on My 3 Foundational Pillars appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 17 Jan 2023 23:00:08 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Destiny Kanno\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:22;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:89:\"Do The Woo Community: Accepting Cryptocurrency in a WooCommerce Store with Lauren Dowling\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74258\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://dothewoo.io/cryptocurrency-woocommerce-store/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:521:\"

Lauren Dowling, lead product for Coinbase commerce joins returning guest Dave Lockie from Automattic as the conversation covers accepting cryptocurrency on WooCommerce shops, whether it be for your clients sites or your own.

\n

>> The post Accepting Cryptocurrency in a WooCommerce Store with Lauren Dowling appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 17 Jan 2023 11:06:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:23;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:79:\"WordPress.org blog: WP Briefing: Episode 47: Letter from the Executive Director\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://wordpress.org/news/?post_type=podcast&p=14175\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/episode-47-letter-from-the-executive-director/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:8660:\"

On episode forty-seven of the WordPress Briefing podcast, Executive Director Josepha Haden Chomphosy shares her vision and current thinking for the WordPress open source project in 2023. Rather read it? The full letter is also available.

\n\n\n\n

Have a question you’d like answered? You can submit them to wpbriefing@wordpress.org, either written or as a voice recording.

\n\n\n\n

Credits

\n\n\n\n

Editor: Dustin Hartzler
Logo: Javier Arce
Production: Santana Inniss
Song: Fearless First by Kevin MacLeod

\n\n\n\n

Show Notes

\n\n\n\n

make.WordPress.org/core
Join the 6.2 Release!
Submit Topics for the Community Summit!

\n\n\n\n

Transcript

\n\n\n\n\n\n\n\n

[Josepha Haden Chomphosy 00:00:00] 

\n\n\n\n

Hello everyone, and welcome to the WordPress Briefing, the podcast where you can catch quick explanations of the ideas behind the WordPress open source project, some insight into the community that supports it, and get a small list of big things coming up in the next two weeks. I’m your host, Josepha Haden Chomphosy. Here we go.

\n\n\n\n

[Josepha Haden Chomphosy 00:00:40] 

\n\n\n\n

Last month at State of the Word, I shared some opening thoughts about why WordPress. For me, this is an easy question, and the hardest part is always knowing which lens to answer through. Though I always focus on the philosophical parts of the answer, I know that I often speak as an advocate for many types of WordPressers.

\n\n\n\n

[Josepha Haden Chomphosy 00:01:00] 

\n\n\n\n

So as we prepare ourselves for the start of a new year, I have a few additional thoughts that I’d like to share with you, my WordPress community, to take into the year with you. 

\n\n\n\n

Firstly, the Four Freedoms. If you have already listened to State of the Word, you have heard my take on the philosophical side of open source and the freedoms it provides.

\n\n\n\n

But if you didn’t, then the TL;DR on that is that open source provides protections and freedoms to creators on the web that I really think should just be a given. But there are a couple of other things about the Four Freedoms, and especially the way that WordPress does this kind of open source-y thing that I think are worth noting as well.

\n\n\n\n

One of those things is that WordPress entrepreneurs, those who are providing services or designing sites, building applications, they have proven that open source provides an ethical framework for conducting business. No one ever said that you aren’t allowed to build a business using free and open source software, and I am regularly heartened by the way that successful companies and freelancers make the effort to pay forward what they can.

\n\n\n\n

[Josepha Haden Chomphosy 00:02:02]

\n\n\n\n

Not always for the sole benefit of WordPress, of course, but often for the general benefit of folks who are also learning how to be entrepreneurs or how to kind of navigate our ecosystem. And the other thing that I love about the Four Freedoms and the way that WordPress does it is that leaders in the WordPress community, no matter where they are leading from, have shown that open source ideals can be applied to the way we work with one another and show up for one another.

\n\n\n\n

As a community, we tend to approach solution gathering as an us-versus-the-problem exercise, which not only makes our solutions better, it also makes our community stronger. 

\n\n\n\n

As I have witnessed all of these things work together over the years, one thing that is clear to me is this: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, but open source methodologies represent a process that can change the way we approach our work and our businesses.

\n\n\n\n

[Josepha Haden Chomphosy 00:03:01] 

\n\n\n\n

The second big thing that I want to make sure you all take into the year with you is that we are preparing for the third phase of the Gutenberg project. We are putting our backend developer hats on and working on the APIs that power our workflows. That workflows phase will be complex. A little bit because APIs are dark magic that binds us together, but also because we’re going to get deep into the core of WordPress with that phase.

\n\n\n\n

If you want to have impactful work for future users of WordPress, though, this is the phase to get invested in. This phase will focus on the main elements of collaborative user workflows. If that doesn’t really make sense to you, I totally get it. Think of it this way, this phase will work on built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and things like programmable editorial, pre-launch checklists.

\n\n\n\n

[Josepha Haden Chomphosy 00:04:00] 

\n\n\n\n

So phases one and two of the Gutenberg project had a very ‘blocks everywhere’ sort of vision. And phase three and, arguably, phase four will have more of a ‘works with the way you work’ vision.

\n\n\n\n

And my final thought for you all as we head into the year is this, there are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was State of the Word 2013, where Matt dreamed on stage of a true WYSIWYG editor for WordPress. Some say it was State of the Word 2016, where we were all encouraged to learn JavaScript deeply. For a lot of us though, it was at WordCamp Europe in 2018 when the Gutenberg feature plugin first made its way to the repo.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like it’s been a long time because it has been a long time. But I can also confirm that it takes many pushes to knock over a refrigerator. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:00] 

\n\n\n\n

For early adopters, both to the creation of Gutenberg as well as its use, hyperfocus on daily tasks makes it really hard to get a concept of scale.

\n\n\n\n

And so I encourage everyone this year to look out toward the horizon a bit more and up toward our guiding stars a bit more as well. Because we are now, as we ever were, securing opportunity for those who come after us because of the opportunity that was secured for us by those who came before us. 

\n\n\n\n

[Josepha Haden Chomphosy 00:05:33] 

\n\n\n\n

That brings us now to our small list of big things. It’s a very small list, but two pretty big things. The first thing on the list is that the WordPress 6.2 release is on its way. If you would like to get started contributing there, you can wander over to make.WordPress.org/core. You can volunteer to be part of the release squad. You can volunteer your time just as a regular contributor, someone who can test things — any of that. 

\n\n\n\n

[Josepha Haden Chomphosy 00:06:00] 

\n\n\n\n

We’ll put a link in the show notes. And the second thing that I wanted to remind you of is that today is the deadline to submit topics for the Community Summit that’s coming up in August. That comes up in the middle of August, like the 22nd and 23rd or something like that. 

\n\n\n\n

We’ll put a link to that in the show notes as well. If you already have chatted with a team rep about some things that you really want to make sure get discussed at the community summit, I think that we can all assume that your team rep has put that in. But if not, it never hurts to give it a second vote by putting a new submission into the form.

\n\n\n\n

And that, my friends, is your small list of big things. Thank you for tuning in today for the WordPress Briefing. I’m your host, Josepha Haden Chomphosy, and I’ll see you again in a couple of weeks.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Santana Inniss\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:24;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:69:\"WordPress.org blog: Letter from WordPress’ Executive Director, 2022\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:81:\"https://wordpress.org/news/2023/01/letter-from-wordpress-executive-director-2022/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5901:\"

Last month at State of the Word, I shared some opening thoughts about “Why WordPress.” For me, this is an easy question, and the hardest part is knowing which lens to answer through. The reasons that a solopreneur will choose WordPress are different than the reasons a corporation would. And while artists and activists may have a similar vision for the world, their motivations change their reasons, too. That’s why I always focus on the philosophical parts of the answer because I know that I am speaking as an advocate for many types of WordPressers. I have a few other reasons, too, which you may not be aware of as you use our software every day.

\n\n\n\n

Why WordPress?

\n\n\n\n

Most importantly, the Four Freedoms of Open Source. If you have already listened to State of the Word, you have heard my thoughts on the philosophical side of open source and the freedoms it provides. If you didn’t, then the tl;dr on that is that open source provides protections and freedoms to creators on the web that should be a given. There’s an extent to which the idea of owning your content and data online is a radical idea. So radical, even, that it is hard for folks to grasp what we mean when we say “free as in speech, not free as in beer.” Securing an open web for the future is, I believe, a net win for the world especially when contrasted to the walled gardens and proprietary systems that pit us all against one another with the purpose of gaining more data to sell.

\n\n\n\n

A second reason is that WordPress entrepreneurs (those providing services, designing sites, and building applications) have proven that open source offers an ethical framework for conducting business. No one ever said that you cannot build a business using free and open source software. And I am regularly heartened by the way successful companies and freelancers make an effort to pay forward what they can. Not always for the sole benefit of WordPress, but often for the general benefit of folks learning how to be an entrepreneur in our ecosystem. Because despite our competitive streaks, at the end of the day, we know that ultimately we are the temporary caretakers of an ecosystem that has unlocked wealth and opportunity for people we may never meet but whose lives are made infinitely better because of us.

\n\n\n\n

And the final reason is that leaders in the WordPress community (team reps, component maintainers, and community builders) have shown that open source ideals can be applied to how we work with one another. As a community, we tend to approach solution gathering as an “us vs. the problem” exercise, which not only makes our solutions better and our community stronger. And our leaders—working as they are in a cross-cultural, globally-distributed project that guides or supports tens of thousands of people a year—have unparalleled generosity of spirit. Whether they are welcoming newcomers or putting out calls for last-minute volunteers, seeing the way that they collaborate every day gives me hope for our future.

\n\n\n\n

As I have witnessed these three things work together over the years, one thing is clear to me: not only is open source an idea that can change our generation by being an antidote to proprietary systems and the data economy, open source methodologies represent a process that can change the way we approach our work and our businesses. 

\n\n\n\n

WordPress in 2023

\n\n\n\n

As we prepare for the third phase of the Gutenberg project, we are putting on our backend developer hats and working on the APIs that power our workflows. Releases during Phase 3 will focus on the main elements of collaborative user workflows. If that doesn’t make sense, think of built-in real-time collaboration, commenting options in drafts, easier browsing of post revisions, and programmatic editorial and pre-launch checklists.

\n\n\n\n

If Phases 1 and 2 had a “blocks everywhere” vision, think of Phase 3 with more of a “works with the way you work” vision. 

\n\n\n\n

In addition to this halfway milestone of starting work on Phase 3, WordPress also hits the milestone of turning 20 years old. I keep thinking back to various milestones we’ve had (which you can read about in the second version of the Milestones book) and realized that almost my entire experience of full-time contributions to WordPress has been in the Gutenberg era.

\n\n\n\n

I hear some of you already thinking incredulous thoughts so, come with me briefly.

\n\n\n\n

There are a couple of different moments that folks point to as the beginning of the Gutenberg project. Some say it was at State of the Word 2013 when Matt dreamed of “a true WYSIWYG” editor for WordPress. Some say it was at State of the Word 2016 where we were encouraged to “learn Javascript deeply.” For many of us, it was at WordCamp Europe in 2017 when the Gutenberg demo first made its way on stage.

\n\n\n\n

No matter when you first became aware of Gutenberg, I can confirm that it feels like a long time because it has been a long time. I can also confirm that it takes many pushes to knock over a refrigerator. For early adopters (both to the creation of Gutenberg and its use), hyper-focus on daily tasks makes it hard to get a concept of scale.

\n\n\n\n

So I encourage you this year to look out toward the horizon and up toward our guiding stars. We are now, as we ever were, securing the opportunity for those who come after us, because of the opportunity secured by those who came before us.

\n\n\n\n

Rather listen? The abbreviated spoken letter is also available.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 12:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:7:\"Josepha\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:25;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:72:\"Do The Woo Community: Finding Team Members to Fit Your Companies Culture\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74204\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"https://dothewoo.io/finding-team-members-to-fit-your-companies-culture/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:442:\"

Marius Vetrici has built a process to bring in new employees that are drawn to fit his companies values, and to grow with them as a team member.

\n

>> The post Finding Team Members to Fit Your Companies Culture appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 16 Jan 2023 10:09:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:26;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:95:\"Gutenberg Times: Box Shadow, Newsletter Theme, Testing Call 20 and more – Weekend Edition 241\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://gutenbergtimes.com/?p=23190\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:100:\"https://gutenbergtimes.com/box-shadow-newsletter-theme-testing-call-20-and-more-weekend-edition-241/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:15190:\"

Howdy,

\n\n\n\n\n\n

Last week’s Live Q & A on Layout features went really well, with numerous participants. The post and the show notes are still in the works. The recording is available on YouTube, should you want to revisit parts of it or missed it entirely.

\n\n\n\n

Now that feature freeze for the major WordPress release is only three weeks away, the contributors would appreciate it if you could heed the 20th call for testing from the FSE Outreach program. You can help find quirks, bugs and annoyances, so they can be fixed before February 7th and during the round of beta version of the release.

\n\n\n\n

Have a lovely weekend!

\n\n\n\n

Yours, 💕
Birgit

\n\n\n\n\n\n\n\n\n\n\n\n

Developing Gutenberg and WordPress

\n\n\n\n

Gutenberg 15.0 release candidate is available for testing. Sticky positioning, resizable Site editor, updated to the Page List block, modify block style variations from global styles, and a lot more refinements are coming to the Gutenberg plugin

\n\n\n\n
\n

🎙️ New episode: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9 with Birgit Pauli-Haack and special guest Hector Prieto

\n
\n\n\n\n

Last April, a group of contributors started working on research on how to best implement an API for to make blocks more interactive. This week, JuanMa Garrido shared a progress report: Update on the work to make building interactive blocks easier.

\n\n\n\n

The resources linked in the post are mostly code internals, so they are definitely very technical at this point. With that said, understanding how the new API works, will not be necessary for developers to use this new standard. A standard proposal will be published the next few months. So for now, this is all bit technical and architectural. The work on the underlying framework is shared on this GitHub Repository

\n\n\n\n
\n\n\n\n

Plugins, Themes, and Tools for #nocode site builders and owners

\n\n\n\n

Munir Kamal, GutenbergHub, shows you in his latest post How to Find and Use Block Patterns in WordPress. You learn, how to find patterns in the post and site editor, how to navigate the WordPress Pattern Directory and how to install the patterns via the plugin Extendify Patterns and Templates

\n\n\n\n

If you want to create your own patterns, but don’t know how to code them, you can use the plugin Blockmeister – Block Pattern Builder.

\n\n\n\n
\n\n\n\n

Sarah Gooding reports on the Lettre Newsletter Theme Now Available on WordPress.org, It can be used with the newly release newsletter feature in Jetpack plugin or as a stand-along theme. “The theme puts the focus on the subscription form, which is the most important thing a newsletter landing page can do – make it easy for people to sign up. Beneath the form there is a link to read all the posts, followed by another subscription form. All of these elements in the home page design are blocks, making it easy for them to be removed or rearranged.” Gooding wrote.

\n\n\n\n
\n\n\n\n

Will Morris explained the three ways add a Table of Contents in WordPress in is post for the Torque Magazine. The three ways are:

\n\n\n\n
    \n
  • Install a plugin
  • \n\n\n\n
  • Use on the Custom Table of Contents blocks
  • \n\n\n\n
  • Create you Table manually in the Block Editor.
  • \n
\n\n\n\n

Soon you will be able to use the core Table of Content’s block once it comes out of the experimental stage. It’s already available via the Gutenberg plugin.

\n\n\n\n

Theme Development for Full Site Editing and Blocks

\n\n\n\n

In his post, Justin Tadlock, walks you through the layout classes in WordPress 6.1. With the latest release of WordPress, the software has now centralized its layout definitions, created semantic class names, and reduced code duplication on container blocks. “Originally, this post was intended to be a quick look at the changes to the system for theme authors. However, given the heftiness of the topic, it has evolved into a full overview of the layout framework.” Tadlock wrote.

\n\n\n\n\n\n\n\n

In his second post published on the Developer Blog, Using the box shadow feature for themes, Justin Tadlock took a look at the box shadow support, that what just released in Gutenberg 14.9. As it happens with similar features, the first iteration of box shadow support is only available via code. The interface for the site editor screens are still in the works.

\n\n\n\n\n

 “Keeping up with Gutenberg – Index 2022” 
A chronological list of the WordPress Make Blog posts from various teams involved in Gutenberg development: Design, Theme Review Team, Core Editor, Core JS, Core CSS, Test and Meta team from Jan. 2021 on. Updated by yours truly. The index 2020 is here

\n\n\n\n\n

Daisy Olsen held her inaugural live programming session on Twitch this week. The recording is now available on YouTube. In this stream, she talked about:

\n\n\n\n
    \n
  • using LocalWP for local WordPress development,
  • \n\n\n\n
  • the Create Block Theme Plugin, and
  • \n\n\n\n
  • took a look at the code from a couple of existing block themes.
  • \n
\n\n\n\n

You need a Twitch account and follow DaisyonWP to get notified when she goes live.

\n\n\n\n\n\n\n\n

In his latest post for CSS-Tricks: Styling Buttons in WordPress Block Themes, Fränk Klein, takes a detailed look markup of various buttons and how to style them via the theme.json properties.

\n\n\n\n

Building Blocks and Tools for the Block editor.

\n\n\n\n

Tom McFarlin continued his series A Backend Engineer Learns to Build Block Editor Blocks with Part 5 in which he covers adding color controls to a custom block for the use case, when you want to give the user the option to select the colors for the block themselves.

\n\n\n\n

McFarlin, recommend the previous articles first as they build on top of each other. So far, he published:

\n\n\n\n
    \n
  1. Required Tools, Plugin Structure, Dependencies, Block Metadata
  2. \n\n\n\n
  3. The Backend, The Frontend, Functionality, Styles, a Working Demo
  4. \n\n\n\n
  5. Block Attributes, Editable Content, Components, Editor Styles
  6. \n\n\n\n
  7. Saving Data, Styling the Frontend
  8. \n
\n\n\n\n
\n\n\n\n

Phil Sola create a Custom Color Picker for WordPress. Sola added some improvements to the existing color picker. It’s more an experiment rather than a full-fledged solution. His exploration might also be an inspiration for others to start experimenting with WordPress component library.

\n\n\n\n
\n\n\n\n\n

Need a plugin .zip from Gutenberg’s master branch?
Gutenberg Times provides daily build for testing and review.
Have you been using it? Hit reply and let me know.

\n\n\n\n

\"GitHub

\n\n\n\n\n

Upcoming WordPress events

\n\n\n\n

February 4 + 5, 2023
WordCamp Birmingham, AL

\n\n\n\n

February 17 – 19, 2023
WordCamp Asia 2023 

\n\n\n\n

Check the schedule of WordCamp Central of upcoming WordCamps near you.

\n\n\n\n

Learn WordPress Online Meetups

\n\n\n\n

January 17, 2023 – 3pm / 20:00 UTC
Patterns, reusable blocks and block locking

\n\n\n\n

January 19th, 2023 – 10:30 ET / 15:30 UTC
Live stream: Building an Advanced Query Loop block variation plugin w/ Ryan Welcher @ryanwelchercodes

\n\n\n\n

January 19, 2023 – 7 pm ET / 24:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 20, 2023 – 3 am ET / 8:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 20, 2023 – 10:30 am 15:30 UTC
Block Themes and WordPress: Live Stream w/ Daisy Olsen @daisyonwp

\n\n\n\n

January 23, 2023 – 10 pm ET / 1 am UTC
Patterns, reusable blocks and block locking (APAC time zone)

\n\n\n\n

January 26, 2023 – 10:30 am ET / 15:30 UTC
Live stream: Reviewing developer-focused features in Gutenberg 15.0 w/ Ryan Welcher @ryanwelchercodes

\n\n\n\n

January 31, 2023 – 3pm ET / 20:00 UTC
Creating a photography website with the block editor

\n\n\n\n
\n\n\n\n\n

Featured Image: Amit Patel: Mango Shake Orange Sweet found in WordPress.org/photos

\n\n\n\n
\n\n\n\n

Don’t want to miss the next Weekend Edition?

\n\n\n\n

We hate spam, too and won’t give your email address to anyone except Mailchimp to send out our Weekend Edition

Thanks for subscribing.
\n\n\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 22:30:28 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Birgit Pauli-Haack\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:27;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: WooCommerce 7.3 Introduces New Products Block in Beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141097\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wptavern.com/woocommerce-7-3-introduces-new-products-block-in-beta\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2242:\"

WooCommerce 7.3 was released this week with the new Products block now in beta. In December 2022, the Products block went into testing in WooCommerce Blocks version 9.1.0. It’s based on the Query Loop block and is intended to replace all of WooCommerce’s current product-displaying blocks.

\n\n\n\n

This first beta version of the Products block allows users to list products based on specific criteria and their layout in the list or grid.

\n\n\n\n\n\n\n\n

Version 7.3 also introduces three “commerce-adjacent” patterns for building WooCommerce store pages. These are patterns that do not tap into WooCommerce store data but allow store owners to customize the images and the links. These patterns were also tested in WooCommerce Blocks 9.1.0. They include an alternating image and text block pattern, a product hero with two columns and two rows, and a “Just Arrived” full hero pattern.

\n\n\n\nimage source: WooCommerce 7.3 release post\n\n\n\n

This release also brings store owners a new multichannel marketing experience in beta. Under the Marketing menu in the admin, users can now view a list of recommended marketing extensions without leaving the dashboard. These can be installed directly from the Marketing page.

\n\n\n\n\n\n\n\n

Other notable features in WooCommerce 7.3 include Pinterest and Codisto extensions added to the onboarding wizard, a new warning banner when the tax settings have a conflict, and an improved UI for creating product attributes and uploading product images.

\n\n\n\n

Check out the release post to see the template changes and all the new actions and filters available for developers. The full 7.3 changelog is available on GitHub.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 04:25:34 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:28;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:64:\"WPTavern: Lettre Newsletter Theme Now Available on WordPress.org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141076\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:75:\"https://wptavern.com/lettre-newsletter-theme-now-available-on-wordpress-org\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:2595:\"

Automattic has published its Lettre theme to WordPress.org. The company launched its newsletter product at the end of December 2022 using Lettre as the default theme. The self-hosted version of this block theme is for those who want to publish a newsletter using Jetpack.

\n\n\n\n\n\n\n\n

The theme puts the focus on the subscription form, which is the most important thing a newsletter landing page can do – make it easy for people to sign up. Beneath the form there is a link to read all the posts, followed by another subscription form. All of these elements in the home page design are blocks, making it easy for them to be removed or rearranged.

\n\n\n\n

Lettre comes with 15 block patterns for building different pages and designs, including about the author(s), a bold color signup, a two-column signup, various designs for the newsletter intro with light and dark background images, newsletter signup with media on the left, newsletter signup with logos for the background, a list of posts, an in-post article promo, three columns of text, and more.

\n\n\n\n\n\n\n\n

A live demo of the theme is available on WordPress.com. The menu items on the demo give a few examples of the different signup patterns in action.

\n\n\n\n

Lettre is designed to be used with Jetpack’s Subscription block, which uses WordPress.com’s infrastructure to manage emails and subscribers. If you like the design but are already using another newsletter service, the Jetpack Subscribe block can be replaced with any other block, including the shortcode block for newsletter services that haven’t yet made their subscription forms available via a block. Be advised, you may need to write some custom CSS to ensure that the subscribe form matches the original design.

\n\n\n\n

Lettre is one of the only themes in the WordPress Themes Directory that was made to be a newsletter landing page and certainly the only block theme dedicated to this purpose. Combined with Jetpack’s subscription feature, this is one of the most seamless ways to distribute a newsletter without all the extra steps of copying the content into a newsletter service’s editor. Lettre is available for free download from WordPress.org. I wouldn’t be surprised to see more themes like this pop up now that WordPress.com has launched its newsletter service.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 14 Jan 2023 02:50:40 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:29;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"Do The Woo Community: Taking a Deep Dive Into the Current State of Social Media with David Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74300\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:50:\"https://dothewoo.io/current-state-of-social-media/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:422:\"

David Bisset and I share our current experiences with Twitter, Mastodon, Linked, Tumblr, the Fediverse and open source.

\n

>> The post Taking a Deep Dive Into the Current State of Social Media with David Bisset appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 13 Jan 2023 10:58:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:30;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:77:\"WPTavern: New Video Explores Site Building Progress from WordPress 5.9 to 6.2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=141039\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:88:\"https://wptavern.com/new-video-explores-site-building-progress-from-wordpress-5-9-to-6-2\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3346:\"

WordPress 5.9 “Josephine” was released in January 2022, but that seems like ages ago when you compare the advances made in site building over the past year. Anne McCarthy, an Automattic-sponsored contributor who heads up the Full Site Editing Outreach Program, has published a short video that tours the important changes in WordPress over the past few major releases. The video also doubles as a preview of some of the features coming in 6.2.

\n\n\n\n
\n\n
\n\n\n\n

If you are using the Gutenberg plugin and have been tracking the relentless progress of the Site Editor, you will notice how limited the design options are in 5.9 and how much more consistent and expansive they are today. In 5.9 users users can only add a Front page template, and the site building interface is disjointed and less polished.

\n\n\n\n

McCarthy demonstrates how WordPress 6.2 will introduce smoother interactions with browse mode. It will also greatly expand the template options available for users to add and includes a new colorized list view.

\n\n\n\n

The Navigation block has had a long, rocky journey but seems to be reborn in 6.2. McCarthy showed how much more intuitive it has become with the new experience of editing navigation in the sidebar, and repositioning via drag and drop with live previews.

\n\n\n\n\n\n\n\n

The instant that Style Variations were introduced in WordPress 6.0, it seemed they were always with us. Looking back at 5.9 in the video, the Site Editor appears so bare without them. WordPress 6.2 will extend this even further with improved block style previews, a style book, and a new zoomed out view that makes it easy to see changes at a glance.

\n\n\n\n

Everything coming in 6.2 is converging towards better usability and more design options for site editors. The challenge here is to continue introducing new features without the interface becoming cluttered and chaotic. Many of these features are still being ironed out. For example, McCarthy mentioned that the Edit button is still a work in progress and may soon be relocated to be more prominent in the Site Editor.

\n\n\n\n

This video gives a quick visual summary of what is being done to wrap up the full-site editing phase of the Gutenberg project before contributors move on to Collaboration. It is worth a watch to see the site building progress that contributors have made in just one year.

\n\n\n\n

If you want to get involved in making sure all these features in 6.2 are ready for prime time, check out McCarthy’s latest FSE Testing Call: Find Your Style. It will plunge you into the new features of the Site Editor to perform a few tasks. It’s essentially a guided opportunity to explore the new interface while contributing back to WordPress, and you will earn a fancy testing contributor badge that will display on your WordPress.org profile.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Fri, 13 Jan 2023 03:56:21 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:31;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:96:\"Post Status: On OpenAI And WordPress With Jannis Thuemmig Of WP-Webooks— Post Status Draft 136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146297\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"https://poststatus.com/on-openai-and-wordpress-with-jannis-thuemmig-of-wp-webooks-post-status-draft-136/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:43263:\"

Jannis Thuemmig, founder of WP Webhooks, joins Cory Miller to discuss Open AI and WordPress. Jannis is passionate about utilizing the power of technology to increase efficiency. WP Webhooks is exploring the ways Open AI can be used to revolutionize website processes and management. It seems we are only at the tip of the iceberg for what is possible when working with WordPress and Open AI.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

In this episode, Jannis Thuemmig, serial entrepreneur and founder of WP Webhooks, dives into the world of automation and Open AI with Cory Miller. Together they look at what is currently possible within the world of integration and automation within WordPress. Then they lean into what is unfolding as Open AI finds its way into the mainstream and discuss what this might mean for the WordPress community.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • Integrations & Automations to Save Time: Everyone is in need of some kind of automation. Our main goal is to save time by creating automations wherever there are pain points. Rather than doing things manually, WP Webhooks enables you to automate them within your dashboard.
  • \n\n\n\n
  • Avoiding Automation through Software: Using software as a service partner means hosting your data on their platforms. Using Webhooks for integration and automation allows you to keep things on your server and within your complete control.
  • \n\n\n\n
  • Possibilities with Open AI Integration: Webhooks is focused on using Open AI as an advantage to speed up processes by creating integrations between services and generating original content. They are working on finding cool use cases and understanding the actual power of what it makes possible.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n
\n

\"🙏\" Sponsor: WordPress VIP

\n\n\n\n

Founded in 2006, WordPress VIP is the agile content platform that empowers marketers to build content both faster and smarter so they can drive more growth. We empower content and development teams with the flexibility and ubiquity of WordPress—the agile CMS that powers more than 40% of the web—while ensuring the security and reliability organizations need to operate at scale

\n
\n\n\n\n
\n
\"WordPressWordPress VIP
\n
\n
\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

GMT20230105-161248_Recording

\n\n\n\n

GMT20230105-161248_Recording

\n\n\n\n

Cory Miller: [00:00:00] Hey everybody. Welcome back to Post Status Draft. This is another interview and discussion in our Product People series, and I\'ve got someone I\'ve met, let\'s see, last year or the year before Giannis and doing good work, but we were talking about AI and that led to OpenAI and something they\'re doing with WP Webhooks.

\n\n\n\n

So that\'s what the conversation is gonna be about today. But Giannis, welcome to the draft podcast. Would you tell us a little bit about yourself and your work and WordPress?

\n\n\n\n

Jannis Thuemmig: Sure, totally. Thanks for having me here. Uh, it\'s always an honor. Uh, my name is Giannis. I\'m from Germany originally, but started traveling a long time ago and since then, I basically work as a digital dumper from anywhere.

\n\n\n\n

And I would say with a, with a very, very deep focus on web. And specifically in automation. This is where W P Airport comes from. So we are basically focused on connecting different services and WebPress plugging to let them talk to each other and kind of just automate the [00:01:00] system and get rid of the human error and save everyone a little bit of time and money, which is really interesting nowadays.

\n\n\n\n

Cory Miller: Yeah, I, and I love it. Uh, one part I\'ll just sidebar real quick is I know when you say digital notepad, uh, the several times we\'ve had zooms, I\'m like, where are you in the world today, y\'all? It\'s like, . Um, so I, I love that. I love to see the nude, like landscapes you\'re in every time we talk. Um, okay. So WP Webhooks, um, I know you\'ve been, so automation is key.

\n\n\n\n

It\'s about efficiency, um, like really saving that time. In the processes you\'re doing, um, what, tell me what all does WP Webhooks do?

\n\n\n\n

Jannis Thuemmig: So basically it allows you to use a set of redefine integrations to let other services and WebPress plugin specifically talk to each other. So let\'s say there\'s, um, a woo commerce shop, for example, and you have a, a custom programmed plugin that has no integrations [00:02:00] whatsoever.

\n\n\n\n

You can use our plugin as a middleman to allow sending data in between, and that works with mostly any kind of WebPress plugins as well as external data like, uh, external services, something like Zapier or make or integrated. So the, the basic main goal is to just make them compatible, which they, in a lot of cases, aren\'t from the beginning. Or if they are, they\'re often very limited, which is something we realized as well. So we just want to kind of get that interoperability to WordPress, which is something that was just lacking over the last couple of years.

\n\n\n\n

Cory Miller: Yeah, I, I love that. I know Zapier has used quite a bit uh, obviously we\'ve used it in the past at, at Post Status because of all the external services, and you\'re trying to link these and do some things that certain pieces of software doesn\'t do out of the bat.

\n\n\n\n

So I, I love the premise of web hooks for sure. Uh, WP Web Hooks, what are you seeing or finding? Customers are gravitating to webhooks [00:03:00] for, like is there specific tasks that stand out that people are using these over and over and over and going, this is what I need. I don\'t want to pay for Zapier or some other alternative, I wanna do something here with my WordPress site.

\n\n\n\n

What are you seeing from your customers?

\n\n\n\n

Jannis Thuemmig: So I\'d say that\'s not a specific use case. There\'s, uh, quite a lot. So everyone, literally, everyone who\'s in the, in the, has a web presence or has an online shop or something related and does something with website. Everyone is in in need of doing some kind of automations.

\n\n\n\n

Let it be to automatically book orders into your accounting system or synchronize your properties from a property management website with your WebPress website. Or let\'s say you have a Teachable account and you sell online courses and you want to synchronize your students with a WebPress website to give them extra features.

\n\n\n\n

This is stuff that they are using it for. So basically wherever there\'s a pain point and there\'s some time that just can be avoided by automating it through software. This is something where we are, um, jumping in [00:04:00] and it\'s specifically interesting right now for people that are very critical about privacy because especially in Europe, a lot of people don\'t want to use software as a service partners like Zapier or Integra.

\n\n\n\n

Because they are hosting their data on other platforms, right? So they have no full control over it, which comes very handy with our plugin because you have your own server, so everything runs on your own server. You are in full control where your data is, what your data does. And this is a very, very critical point that is, uh, always, always well seen at the moment.

\n\n\n\n

Yeah.

\n\n\n\n

Cory Miller: Yeah, yeah, for sure. I, you went to a subject I didn\'t even think about, which is if you don\'t want your information out there on another service, having it in in WordPress, something you control. I think that\'s a key facet. Before we start talking about, uh, AI and specifically OpenAI, what are you most excited about with webhooks this year?

\n\n\n\n

Jannis Thuemmig: Ooh, for sure. Bringing that AI space model [00:05:00] web. Because we had so much fun over the last couple of months trying these things out, seeing in which direction it goes. And it\'s just incredibly fun to, to play around with it because the possibilities are really endless. And we are, we are fully about saving time.

\n\n\n\n

Right? And this is something we can even use to leverage more time out of our daily task, which is really, really good. Okay.

\n\n\n\n

Cory Miller: Well let\'s roll into that because I think that\'s one of the most, uh, uh, Interesting themes in our community is ai. I\'ve seen a couple tweets saying AI is gonna revolutionize, um, a lot of stuff with a website by the end of 2023.

\n\n\n\n

I can\'t remember who said that online. And I was like, well, I\'ve been paying attention enough. But talk to me about ai, OpenAI and what you, you see on the horizon for, um, for WordPress particularly, and opportunities and possibilities. . Yeah,

\n\n\n\n

Jannis Thuemmig: so ai uh, specifically in our case with OpenAI, uh, there\'s, uh, a little differentiation.

\n\n\n\n

So [00:06:00] right now it\'s very much hyped, the G P T three. So the, the kind of chat ai as you can, as you can, uh, think of, which is basically you just type in something and it, it gives you like a very human answer back, which is really, really incredible. And, uh, we specifically talk about the, the OpenAI api, which kind of allows you to.

\n\n\n\n

Communicate data on a programmatic level, which means you basically don\'t even need to type something yourself, but you can already use a service to let these things run through the web automatically without ever touching this kind of data. And this is, this is just something that that works very well with, with the automation part.

\n\n\n\n

Right. So we are, we are basically looking into bringing more possibilities that AI through non-static data, and, uh, what I mean by non static data, it\'s probably interesting to, to understand what an AI actually is. So it\'s a pre-trained network, right? It has the data that was feeded to it at some point. And with OpenAI, it\'s made from [00:07:00] mostly 2021.

\n\n\n\n

So it has no actual new data. If you ask it something like what happened yesterday, for example, it could probably not give you an answer to, you could give it the information if you have it yourself. But it can never give you like the, the news and accurate information. And using things like automation, you can basically bring a whole new word to it because you can kind of give the AI the possibility through response and, uh, requests to send data through automation, uh, validate it somewhere else and send it back to the AI and tell the ai, Hey, look, there\'s new information.

\n\n\n\n

We can use that, uh, or, or learn about that and, um, send me some more information or summarize me something. So it\'s, it\'s just a very interesting time in, in regards to giving the AI actual information that you can feed it, uh, that is currently not within its its own, um, possibilities.

\n\n\n\n

Cory Miller: So you said something there.

\n\n\n\n

Um, I, I haven\'t even gotten that in depth with OpenAI, but So in [00:08:00] 2021 they fed it a ton of data you\'re saying, and then trained it to be able to, to answer questions and things.

\n\n\n\n

Jannis Thuemmig: Yeah, exactly. So basically they had a, a huge dataset or couple of datasets for sure about the information that they fitted. And the AI can basically make.

\n\n\n\n

An answer that is, uh, in a human real reform, and that seems like it is made from a human, but the data that was fed is all from 2021, right? So it is a static data if we, if we want to hear it or not. So if you\'re gonna ask the ai, what is the latest model of iPhone, for example, it\'ll probably tell you something like it\'s the iPhone 13, because I don\'t think it has information about iPhone 14.

\n\n\n\n

That would be something cool to try, but I guess it must be, uh, outdated information. And with that gap of, of using that, that automation in, in connection with ai, you can kind of close that gap and you can actually feed it real time data and use that data to, to do certain things within the AI [00:09:00] and, uh,

\n\n\n\n

Cory Miller: I see, thats a new one.

\n\n\n\n

Yeah, it does. Um, totally to me, and I\'m asking as a newbie to all of this, um, because I\'ve used it and I\'m like, this is pretty dang fast. And I\'m like, how the heck are they doing that? That makes total sense. And then from the training side, um, the model itself is, I was like, gosh, if this had access to that, and you could just ask it questions like that.

\n\n\n\n

It\'s the, it\'s the a hundred times better Google. Yeah, because, yeah, it, like, I was, I, I mean I asked what are the strengths and weaknesses of WordPress, for instance, and it came back. Um, but knowing it\'s, it\'s a little bit lag on the data side is interesting to me. Um, but I saw the potential for this to truly.

\n\n\n\n

Revolutionized some things on the web. Um, so it\'s, it, it\'s been really intriguing and I mean, I asked it all kinds of questions that I was just actually curious about and seeing what, not just from the what, [00:10:00] how the model would work, but the answer. And I was like, this is like a perfectly uh, formatted.

\n\n\n\n

Informative, um, short essay that I would\'ve gotten in college, you know, so that\'s

\n\n\n\n

Jannis Thuemmig: intriguing. It\'s actually you can, you can write like on demand stories for your kids based on your own characters. Just type in a sentence, say you run a short story and it spits you out a short story that you can read them from going to bed.

\n\n\n\n

It\'s amazing. It\'s just like incredible.

\n\n\n\n

Cory Miller: I\'m gonna have to try that today. I, I continue. This subject fascinates me and I think it\'s something we need to be thinking about and looking at and talking about in WordPress and Post Status, because this new technology coming and then how WordPress is placed in this.

\n\n\n\n

And for years, I think this is a segway to talk about OpenAI and WordPress specifically. But you know, I\'ve either built sites for people or known a bunch of people that build sites for clients. And you turn on this awesome, it\'s like you turn this car over, you pull this car up to \'em, and [00:11:00] you\'re like, here\'s your car.

\n\n\n\n

But you gotta drive it with content, with things inside the site, and it\'s such a great vehicle for that. But oftentimes people get hung up on that part of. Oh, I don\'t know what to, I don\'t know how to drive my car. Right? Like these, you know, WordPress sites with the right architecture, the right things can really drive and make a dent.

\n\n\n\n

That\'s our kind of thing with WordPress is like it\'s magic like that. But you still have to like, Drive it, you have to put gas in it and drive it, uh, with content. So that\'s a compelling angle for me with OpenAI. And I\'ve heard about all these things. Before we segue specifically to the integration you\'ve done too and some possibilities there, what, where do you see all of this and WordPress going?

\n\n\n\n

Jannis Thuemmig: Like, that\'s a very interesting question. Yeah, yeah. Uh, I think, I think it will be in relay, I mean, it\'s, right now we are specifically in the content age, right? So I, I\'ve seen a lot of people. [00:12:00] Going into the space where they try to create on demand articles using an ai, which is probably a terrible idea just through plagiarism because it\'s very easily detected if you don\'t lose like a rewriter and you use your very own wordings in between.

\n\n\n\n

So this is something that we will see switching, definitely. But what I see as an advantage in the future with WebPress is that people will use to. We, we learn to use AI for the advantage in the sense of speeding up their process. So it\'s also kind of a, a way of automating things, uh, in the sense that they don\'t need to write their content anymore from scratch, or they don\'t need to write a, think about copywriting that much.

\n\n\n\n

They just ask the ai, it\'s bit something out. They put it in, maybe adjust it, tweak it in their own way so that it has their very own style. And they probably just make the, the way of, of riding blocks 10 times a hundred times faster than it\'s right now. So we\'ll definitely see like a, a boost in performance and [00:13:00] probably block block posts over the long term.

\n\n\n\n

Cory Miller: Okay. Well, so that leads into this specific integration you have and the tutorial I, I was looking through before we started. Um, so you saw OpenAI has an api and tell us, tell us about that in WP Web Web Hook.

\n\n\n\n

Jannis Thuemmig: So, yeah, we, we basically started, um, after trying a couple of times how OpenAI works to, um, to integrate it with our plugin.

\n\n\n\n

So we, we usually go for creating integrations for different services and plugins, and, uh, in that case it\'s, it\'s once separately for OpenAI, which makes it compatible with all of the other services and, uh, plugins. We are integrated. And the main goal was just to provide the integration, right? Because it\'s so new, barely anyone understands the actual power of it and what is possible.

\n\n\n\n

So we, we just kind of created it out of the blue with a thought of, Hey, it would be cool to just have it, you know, let\'s see what, what\'s going to happen. And right now we are basically just [00:14:00] working on finding cool use cases. And, uh, there are definitely a couple, uh, like I\'ve, I\'ve, uh, showed you earlier.

\n\n\n\n

We already have a blog post on our. That basically describes how you can auto generate method descriptions using OpenAI and Yost seo. So you basically just feed it in the title and it spits your order, perfectly made method description that you can just use or adjust as you want. And these kind of things, they just now come through trial and error basically.

\n\n\n\n

And, uh, it\'s, it\'s very interesting to see where it goes. And I can see that with these kind of automat. Um, we can also provide what I mentioned earlier, that that possibility of feeding the AI new information that is not available within the AI itself. So because we can make these kind of workflows, um, if that makes sense.

\n\n\n\n

And this is, uh, this is mostly what we are trying to do right now. We basically just working on, on use cases, see what\'s possible, trying out different things and it\'s a super, super exciting. [00:15:00]

\n\n\n\n

Cory Miller: Yeah, absolutely. Because I mean, you talk about this car, you some a a site builder turns the car over and they start to use it.

\n\n\n\n

But that meta, uh, description is one thing. Like I honestly confess, I never do, you know, but it\'s, it\'s helpful, it\'s vital. And so like that one little use case in the bigger picture of what I can do, I think starts to step us into this and it is really interesting. Um,

\n\n\n\n

Jannis Thuemmig: oh, totally. Yeah. This is, this is literally just the, the tip of the iceberg.

\n\n\n\n

If you want, you can basically let the AI create a, a full schema, uh, like a shima for your, for your website. So whenever there\'s a blog post, it can write the how-tos and everything in, in kind of adjacent format and, uh, spits your order perfectly well formatted SEO description and, and everything keyboards, the, the, the whole how to, and this is just, it\'s just such a time saver.

\n\n\n\n

It\'s incredible.

\n\n\n\n

Cory Miller: Well, okay. Do you have a tutorial on that [00:16:00] too? Because that\'s really interesting. Um, , or if you don\'t, we need one. Um, but so you\'re going into OpenAI or chat GPT or whatever, and then you\'re saying you\'re asking a question or something like that, and then it\'s gonna give you back those things.

\n\n\n\n

Jannis Thuemmig: Yeah, exactly. It\'s just you literally ask it just a humanly written question, something like, give me back adjacent with each of these information. And it spits you out adjacent with each of the information. And Jason, you can always use on a technical level, right? So we can just leverage that out and use it through our plugin to use it in different automations and do different things.

\n\n\n\n

Cory Miller: Oh, that\'s super cool. Well, what do you have anything you wanna share about what you\'re doing next with this WP WebHooks?

\n\n\n\n

Jannis Thuemmig: Um. As a, as a use case, it\'s a, I mean, we, we definitely, for, for now we really try to just work on the OpenAI things mm-hmm. and try to find some cool use cases there. Uh, we had a lot of, um, a lot of actually customers reaching out about the possibilities as well and how exactly it works because the models [00:17:00] and the configuration is a bit complicated if you, if you\'re not fully aware of it.

\n\n\n\n

But, uh, yeah, we, we just follow the standards and, uh, things should be fairly easy. But yeah, for us, it, it\'s mostly, mostly OpenAI and creating new integrations. That\'s something we\'re, we are hardly focused on at the moment.

\n\n\n\n

Cory Miller: I, I really think this is, like you said, the tip of the iceberg that, um, I\'m really intrigued by our WordPress community post at Post Status to go, okay, here\'s this cool technology.

\n\n\n\n

How do we translate this into practical? Um, uses for the end client, the end user in WordPress. Um, so that, that\'s, that\'s interesting. We\'ll be excited to hear what, what you find in explorer and launch launch next.

\n\n\n\n

Jannis Thuemmig: Yeah, you should just see the block post, uh, our, our, our block. There will be a couple of more tutorials coming.

\n\n\n\n

They\'re already in the making, so in the next days we should see someone there.

\n\n\n\n

Cory Miller: Okay. Perfect. All right. Um, okay. So. You, you [00:18:00] showed me something as like this. I think this is just showing the power of what it could do when we start to get these types of integrations into WordPress. Do you mind showing me the one you were telling me about?

\n\n\n\n

Jannis Thuemmig: Totally. Yeah. Not a problem. Of course. I\'m just gonna share my screen, probably this one. Yes, so I, I was basically just fooling around the other day on. With our integration, trying to find some new cool ways we can use to, to present the OpenAI integration. And, you know, like, like I mentioned earlier, you can kind of ask the AI to create adjacent format, um, with specific data.

\n\n\n\n

So Jason is basically just a structured way of presenting data within the web that is something that the, the browser or the, the server can. And in our case, we, we, we wanted to have like, like in here, uh, just a simple field that you can write something and based on whatever you write, it updates the block post of [00:19:00] your choice.

\n\n\n\n

So in our case, we just created a quick contact form seven form as we have an integration with contact as well. And we connected that with open. To create a so-called Jason and update a block post based on a specific information. So I can just demonstrate it here. You can see I have three block posts available and let\'s just take this one.

\n\n\n\n

I just need the ID because that\'s the way we wrote it. So we have ID 97, and what I would like to do is, let\'s say I want to update the, the title of this post, right? So I can, I can write something like, Update the post with the id, let\'s say post title

\n\n\n\n

with ID 97 and change it to, um, this is a new title based on OpenAI. So it\'s, it\'s basically what we read as a, as a human tech, [00:20:00] right. But if I submit that and I let our workflow. The AI basically interprets that and, uh, changes it based on our parameters within, uh, Jason. And when I refresh here and, um, the flow ran, it should display it.

\n\n\n\n

See if it doesn\'t, no, it doesn\'t. Uh, so the thing is, because it depends what you feed the ai. So the AI basically needs to understand what you do. And, uh, in some, in some cases, that\'s, that\'s the problem with ai. It fits you out text, right? So you try to, you need to, to format. And kind of use the text in a different way so we can see.

\n\n\n\n

Okay. Just didn\'t follow it. Just what I\'m gonna do is, so to, to just, so the very same example, I just tried to type the similar thing again. Let\'s try it again. So, um, update the post with the ID 87 and change the title to, [00:21:00] um, OpenAI. Something new. Let\'s see now

\n\n\n\n

Cory Miller: I always love live demos, . Yeah, I know. When you were showing me before I was like, wow, that\'s super cool.

\n\n\n\n

Jannis Thuemmig: Yeah, it really depends on the AI, if I, if I do it right or not. Um, but it seems like you see that it\'s not completed. So basically something stopped within the AI and uh, yeah, I would need to, I would need to see what.

\n\n\n\n

Cory Miller: Yeah, so you were showing, you were showing just now the webhooks, uh, pro dashboard. Do you mind taking us for a spin around the Webhooks Pro dashboard?

\n\n\n\n

Jannis Thuemmig: Uh, yeah, totally. So it\'s basically like, you know, standard WebPress plugin and stuff. On the site menu we have, uh, our W2 Web Hooks Pro, um, menu item, and basically it\'s, it\'s separated into two main things, which is the automations, the flows, and the web. So there\'s, there\'s kind of a difference in between, because originally we came from the web website, which means it\'s kind of like a [00:22:00] one-way street for information to present.

\n\n\n\n

Let\'s say you, you update a, a post on your WebPress website and based on that post you can send data into a, a certain direction, like directly and instantly to inform another service about that there\'s a new post. But then we realized that there\'s more of a need to actually automate kind of certain work.

\n\n\n\n

And then we created something called Flows, which basically allows you to connect the, or create a consecutive order of triggers and actions. So web book triggers and actions to do certain things in a, in a specific flow as we call it. So I just head into it, uh, into one, which is the human posture. This was the example I tried to show you, which, uh, was currently not working out because of something that I need to see. Um, but what we have basically, within the floor. You can see we have a trigger, right? The trigger fires on a contact form seven. Within the settings, we basically selected the form that we created earlier, which is [00:23:00] embedded in, in the site.

\n\n\n\n

And we don\'t want to send the email as we just want to send the data to OpenAI. And it was tested. We set up some conditionals, um, stuff that\'s not really important for now, but, uh, this is, this is basically what causes the actual workflow to fire, right? So, This specific trigger comes along with all the data that was sent within this form, and we then reuse the data in the other kind of actions here.

\n\n\n\n

And as you can see, the first action is something, uh, is our OpenAI integration, where we basically sent that information that we had earlier, as you can see here, to OpenAI as a, as a text. And this is, this is what we read. So it basically says, get the posterity and the PostIt from the JSON, uh, in the JSON format.

\n\n\n\n

This is the sentence, and the sentence basically is a dynamic string that comes from the input that we sent within this form. So it makes more sense if, if we go through it logically while, while building it. But, um, [00:24:00] when you click into a field like this, you will see it shows a dropdown, and inside of the dropdown you will see all of the information that was sent within the trigger, including the question like, change the title of the post idea, ???

\n\n\n\n

So this is basically what we selected here. And this is kind of more details about the OpenAI stuff. Sure. And yeah, when you, when you continue safe, you can test the action directly within here. That\'s something I can try, um, just as an example to see what comes back. So basically right now I\'m sending a real request with the data that we got earlier.

\n\n\n\n

And this is basically the response. So we can see, we got some text back from the AI, which looks a bit weird as it\'s text. But within our plugin we have something like a formatter, which allows us to format the data and change into something readable. So I\'m just gonna quickly do that to, to give you a better example of what we get back.

\n\n\n\n

So as you can see, this is what we get back from the AI or from the formatter, which came originally from [00:25:00] the OpenAI. And this specific information we want to then use in another action to actually update the post. And this is, this is literally everything it does. You can just think it of simple steps that, like we have a trigger.

\n\n\n\n

The trigger causes, uh, runs whenever the, the specific contact form was sent, then we sent the data to OpenAI. We format it in a certain way, and then we update the post based on whatever data we got back from the OpenAI. Excellent. So, yeah, exactly. This is, this is basically it for that.

\n\n\n\n

Cory Miller: What, what are some of the automations.

\n\n\n\n

Yeah, I, I saw the create the automation. So what are the, some of the things that webhooks can do from the automation side?

\n\n\n\n

Jannis Thuemmig: Uh, you mean some examples for example? Ah-huh. Yeah. Yeah, yeah. Like I say, you can, you can, for example, connect the different services together. Let\'s, for example, say you have newcomer, right?

\n\n\n\n

So you can go to the, to the integration [00:26:00] screen. You can install any kinds of integrations that you, you are working. So we have around hundred right now. And let\'s, for example, say you have commerce installed, right? So you can then install commerce once it\'s, once it\'s available on your website and within one of those automation workflows, you can then say, whenever commerce fires, then send the data using, uh, a WebBook, for example, to mm-hmm.

\n\n\n\n

your bookkeeping system. Or send, send an email using the WordPress integration. So in here I can show you. Click send email, and then you have the possibility to send an email directly from your WebPress to the customer whenever, whenever, uh, an order was created. So it basically, it basically just allows you to do certain things that you would manually do within your dashboard.

\n\n\n\n

Mm-hmm. ? Yes. In an automated way

\n\n\n\n

Cory Miller: There\'s a bunch of those things for the Post Status setup out the way. I\'m like, oh gosh. Yeah.

\n\n\n\n

Jannis Thuemmig: I can\'t imagine. Same here.

\n\n\n\n

Cory Miller: And, and what are, what are workflows to, uh, or I\'m sorry, it\'s [00:27:00] webhooks. Oh, I thought that\'s a workflow somewhere. I read that wrong. Okay. Yes. So what

\n\n\n\n

Jannis Thuemmig: I can show you, it\'s, it\'s basically separated in two parts.

\n\n\n\n

It\'s sent data and received data. What it basically means is these are kind of the triggers available, right? So whenever a user created, or when a user was deleted or when a form was submitted, you can send data to a specific url. Let\'s say, for example, I want to send a URL to my website, um, I cannot call it demo, and I, I add my api endpoint here and I edit, and then you can see it here. Which basically means when ev, every time a user gets created, you can send a direct webhook request to this url and you can test it, you can customize it with, with more features, more setups, um, based on your needs. Gotcha. And this is, this is what I mean earlier, like a, a direct connection.

\n\n\n\n

And the receive data is basically the exact opposite. So instead of sending data out on a specific event, you send data in and to do something specific. So you can, for example, activate a plugin. As you can see, you can call a PHP [00:28:00] function, you can create a comment, a post a user. So we have basically mostly all of the options of WebPress available through, uh, web as well.

\n\n\n\n

Excellent.

\n\n\n\n

Cory Miller: You don\'t have a Slack integration, do you by chance ?

\n\n\n\n

Jannis Thuemmig: Um, that\'s the, that\'s the thing. Depends what Slack has as an api. Um, if they truly have an API and if they have an api and it can be integrated with something like an API key or a hero token, it can also be used with our plugin. Um, and that\'s an interesting point.

\n\n\n\n

That\'s good that you mentioned that we have something like,

\n\n\n\n

Cory Miller: um, it\'s a, it\'s a. Post Status specific thing, but I think a lot of membership sites, which is a big trend too. People building membership sites, course sites, you know, a lot of people like us obviously use Slack. Being able to, um, one, this is a nuance and I\'m, uh, sorry for sharing, but this is like create a private group or something like that.

\n\n\n\n

I\'ve looked in some of the Slack API and. I\'m using us as a [00:29:00] test for a second to say it is a broader thing. I think a lot of membership sites, they\'re using Circle, for instance, maybe they want to use something else. So I, I stopped you though. Keep going.

\n\n\n\n

Jannis Thuemmig: Oh, no problem. No problem. Um, yeah, but what I, what I mentioned earlier is that, like you say, with, with, uh, slack, we can kind of integrate with any service that allows, like simple API calls or web and uh, we have an integration available that is called Web itself.

\n\n\n\n

So, When you install one like that, for example, and you go to, let\'s say an automation workflow, I can, I can come within here, add a new action, and you see I have a WebBook endpoint available, which basically allows me to send data or send a request to a specific site. So if you have Slack, you would, you would uh, add your Slack U URL here, for example, right?

\n\n\n\n

Slack API or something, and then you. Select the method you want to use to send data, and you can send the data and add it here along. [00:30:00] So if there\'s a, a service out there that just follows the standard rep hook or api, um, standards, you can integrate them as well with our plugin. So there\'s not directly, uh, integration necessary. Basically.

\n\n\n\n

Cory Miller: Excellent. Well, one thing that\'s intriguing about this is for as long as I\'ve been in WordPress, I, it, it has led the way in truly democratizing publishing, but over the years, you see Facebook, Twitter, what name, whatever default. Closed wall type garden come out. And um, I just did an interview with Mattias who does activity plug plugin for the Fed averse.

\n\n\n\n

And I was like, the, you, you think about what you\'re doing here with webhooks and then the Fed averse is kind of bringing that power back to the. To the user and saying, okay, fed averse can help. To me, I just see the potential to go, let\'s, let\'s decentralize some of the social [00:31:00] networks. So when a billionaire buys the next thing, or they change their policy at one of these closed set social networks, you\'re, you know, all these people are affected by it.

\n\n\n\n

And, and taking some of that control. So that\'s where I see FE averse. And then I go, what\'s the power here is. Ground zero for what you\'re doing is your WordPress site, and with things like tools like this, then you can start, I don\'t know, it\'s just helping bridge that gap of power. There\'s so much usability and features that these closed gardens have, but tools like webhooks and potential with the Fed averse is like bringing some of that power back, and I see WordPress truly being in the space to lead and innovate and bring that power back to the users.

\n\n\n\n

Jannis Thuemmig: Totally. Yeah, I fully agree with you. The, the, an interesting point about that is actually that using our plugin, for example, you can use it kind of as a standalone on WebPress. So if you say you want to make automations, you don\'t necessarily need to use WebPress, but [00:32:00] you can just set up a WebPress environment and install our plugin and you can.

\n\n\n\n

Automate external services through WebPress. Right? So you can use it kind of as a middleman for yourself without actually using WebPress.

\n\n\n\n

Cory Miller: And you still maintain control

\n\n\n\n

Jannis Thuemmig: in a lot of ways you have full control. Yes.

\n\n\n\n

Cory Miller: Even if it\'s not a public facing site where you have content on like using that, that\'s the power, that\'s the other side of WordPress.

\n\n\n\n

Do this has been become this huge power powerhouse of a, you know, a software. I talked to a lot of people on the enterprise and they mentioned. the connections. There\'s a, um, my friend Kareem at Crowd favorite talks about WordPress being the open source hub to connect services. So, like your example there.

\n\n\n\n

I, I resonate with it cause I just talked to Kareem a couple weeks ago. I love that example. Yeah. Yep. Well, Gianni, anything else you wanna share, um, that you all have going on or you\'re excited about or anything I forgot to.

\n\n\n\n

Jannis Thuemmig: Uh, yeah, I\'m excited to make this tutorial work, so I think the next blockbuster [00:33:00] will see is probably about this example.

\n\n\n\n

Okay. .

\n\n\n\n

Cory Miller: I love it. Just to have, I love it. That\'s the beauty of being a part of this community as I get to ask cool, smart people that can do these things and see, see how they go. But I, I\'ll be playing around with open api. OpenAI\'s, API\'s, mouth, um, just cuz I was playing with that and like, wow, this is powerful and I love this kinda stuff and I love there\'s people like you experimenting with it, testing it, and giving users, um, that opportunity to do that.

\n\n\n\n

Um, so thanks so much today for being on the Post Status draft podcast. Uh, this is under our product People series. I love our innovators in our community like you, and so thanks for joining me today.

\n\n\n\n

Jannis Thuemmig: My pleasure, really. So it\'s an honor. Thank you very much as well for inviting me.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 14:44:47 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:32;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"WordCamp Central: WordCamp Entebbe: First Wordcamp to happen in Africa in 2023 is on!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:39:\"https://central.wordcamp.org/?p=3158482\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:108:\"https://central.wordcamp.org/news/2023/01/wordcamp-entebbe-first-wordcamp-to-happen-in-africa-in-2023-is-on/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3126:\"

\n\n\n\n\"\"\n\n\n\n

WordCamp Entebbe 2023 is set to be a major community event for WordPress developers, website designers, online publishers, students, and teachers to come together and share knowledge and experiences, network with other WordPress users, and gain inspiration for their work. Taking place on Friday, March 10th and Saturday, March 11th at the Uganda Wildlife Education Centre (UWEC) in Entebbe City, this WordCamp will be the first to happen in Africa and is poised to be a memorable event for all attendees.

\n\n\n\n

The event will feature a range of activities, including beginner’s training, inspirational talks, showcases, best practices, and the latest trends in WordPress development. In addition, there will be a Women in Tech panel discussion, aimed at inspiring and empowering women-led businesses to thrive in the technology industry. A Teacher’s Workshop will explore the integration of WordPress in the local education curriculum, providing teachers with the tools and resources they need to introduce WordPress to their students for web design projects and assessments.

\n\n\n\n

Attendees will also have the opportunity to take a free tour of the Uganda Wildlife Conservation Education Center, where they can learn about the animals of Uganda and the ecosystems in which they live. The center, which was founded in the 1950s to accommodate confiscated and injured wildlife, has grown considerably in recent years and is considered a premier facility for showcasing wildlife on the African continent.

\n\n\n\n

Accommodation options are available for those traveling to Entebbe for the first time. Attendees can find a list of hotels and guest houses through booking.com https://bit.ly/entebbehotels or by contacting the WordCamp team at entebbe@wordcamp.org for more information and guidance. The full schedule of activities will be published soon, and we look forward to welcoming you to WordCamp Entebbe 2023!

\n\n\n\n

Get Involved

\n\n\n\n

There are several ways to get involved! Check out the details below:

\n\n\n\n\n\n\n\n

Join the discussion via #WordCampEbbs hashtag on Twitter

\n\n\n\n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 11:58:51 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Kasirye Arthur\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:33;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:104:\"Do The Woo Community: Reflecting on the Past and Embracing the Future for WooCommerce with Paul Maiorana\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74261\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://dothewoo.io/2022-2023-woocommerce-paul-maiorana/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:442:\"

It\'s that time of year again when Paul Maiorana, CEO of WooCommerce, joins us for a review of the year and a looking into 2023.

\n

>> The post Reflecting on the Past and Embracing the Future for WooCommerce with Paul Maiorana appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 11:29:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:34;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:17:\"Matt: Thirty-Nine\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75200\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:34:\"https://ma.tt/2023/01/thirty-nine/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4687:\"

The last year of my thirties! WordPress turns twenty this year. Automattic is now ~2,000 people across 98 countries. There’s so much that has happened in the past decade yet it feels very much like we’re on the cusp of something even more exciting.

\n\n\n\n

This morning started well; I pulled the hammock out of the garage (it had been hiding from the rain) and read for a bit, trying to get my 5-10 minutes of sun in the first 30 minutes like Huberman suggests.

\n\n\n\n

Candidly, the last year was a really challenging one for me personally. There were some beautiful moments, and I consider myself the most lucky in my family, friends, and colleagues, yet among that same group there was a lot of loss, existential health challenges, and that weighed heavily on me. It’s also my last year to get on 40 under 40 lists! \"😂\"

\n\n\n\n

Usually when people ask me what I want for my birthday I don’t have a good answer, but this year I do! As Heather Knight wrote about in the SF Chronicle, the beloved Bay Lights are coming down in March. This has to happen — the vibrations and corrosive environment of the Bay Bridge is taking lights out strand by strand. Fortunately it’s now been a decade since the lights first went up, and there’s much better technology both for the lights and how they’re mounted and attached to the suspension cables. Finally, the lights were not visible from Treasure Island or the East Bay before, but this new version 3.0 will be, which is why the artist behind the lights, Leo Villereal, is calling it Bay Lights 360.

\n\n\n\n\n\n\n\n

Like the Foundation series, we can’t stop the coming period of darkness from happening, but if we raise $11M we can bring the lights back. If we raise it soon we can shorten the time they’re down to just a few months, so I’m working with the 501c3 non-profit Illuminate to help fundraise. The idea is to find ten people or organizations to put one million each, and raise the final million in a broader crowdfunding campaign, to re-light the Bay Bridge and give an incredible gift to the people from every walk of life that see the bridge, and hopefully have their spirits lifted by the art. I’ve heard 25 million people see the Bay Lights every year.

\n\n\n\n

It’s a lot to raise, but every little bit helps so please donate here, and if you are interested to do a larger gift please get in touch. I’m committing a million dollars to the fundraise, and myself, Illuminate director Ben Davis, and the artist Leo Villereal are happy to personally connect with anyone considering a larger donation.

\n\n\n\n

Because of some family health reasons I’m back in lockdown, so going to try and throw an online party tonight in the “Matterverse.” We’re going to party like it’s late 2020. \"🎉\"

\n\n\n\n

All birthday posts: 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Thu, 12 Jan 2023 04:37:53 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:35;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:82:\"WPTavern: Automattic Launches Blaze Ad Network for Jetpack and WordPress.com Sites\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140985\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:93:\"https://wptavern.com/automattic-launches-blaze-ad-network-for-jetpack-and-wordpress-com-sites\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:5113:\"

Automattic is bringing Tumblr’s Blaze ad tool to WordPress sites with its launch today on WordPress.com and Jetpack. Blaze made its debut in April 2022, to the delight of Tumblr users who will gladly shell out cash to get people to look at their cat or promote a game they made. It’s an affordable way to attract new followers or just send out something funny into the universe, starting at $5/day.

\n\n\n\n
\n

Note To all small Twitch streamers. Tumblr Blaze is the best advertising tool out there. It has an embedding feature allowing you to embed streams. I went from 20 to 100+ live viewers rn! pic.twitter.com/aolZduCTEe

— Ian Miles Cheong\'s Prime Minister (@MToph91) January 3, 2023
\n
\n\n\n\n

WordPress.com users can now to go to wordpress.com/advertising, select a site, and promote content with Blaze. Jetpack users have access to the ad network inside the WordPress.com dashboard.

\n\n\n\n\n\n\n\n

After selecting a post, users are taken to the design wizard where they can add an image, title, a snippet, and a destination URL. The URL can be the post or page or it can direct visitors to the main website.

\n\n\n\n

When Blaze first launched on Tumblr there was no way to target the promoted content – it just displayed to random users. Now there are a few more options. When promoting content from WordPress.com or a Jetpack-enabled site, users can narrow the audience by device: mobile, desktop, or all devices, select from a few main geographic areas (continents) or serve it everywhere. There is also a dropdown with topics of interest, but they are fairly general, e.g. Arts & Entertainment, Automotive, Business, Education.

\n\n\n\n\n\n\n\n

After selecting the audience, users can set the budget for the campaign, starting at $5 with a max daily budget of $50. With a minimum of $5/day for a week users can expect an estimated 5,900 – 8,000 impressions. For $25/day, users can expect 29,700 – 40,200, and up to 59,500 – 80,500 for $50/day. Site owners can monitor the success of their ads in the Campaigns tab.

\n\n\n\n

Content sponsored by Blaze will be promoted across WordPress.com sites and Tumblr pages, an audience that accounts for an estimated 13.5 billion impressions per month.

\n\n\n\n

Blazing has become somewhat of an art in the short time it has been available on Tumblr. It will be interesting to see how ads originating from WordPress.com and Jetpack go over with the Tumblr audience.

\n\n\n\n
\n

There\'s a whole code of ethics around Blaze on Tumblr basically if you use it to do anything other than shitpost people hate you, you can also buy Tumblr merch and buy fake verification (which isn\'t seen as a serious thing, it\'s just a way to support tumblr)

— New Year New Simisear Fan \"🐀\" (@LakeUncalming) January 10, 2023
\n
\n\n\n\n\n\n\n\n

Creating advertising content that works across the disparate audiences between WordPress and Tumblr-powered pages may be a challenge for some site owners. Tumblr users can only target audiences by location for blazed posts. It’s possible that WordPress’ additional targeting options can help funnel the ads to sites where they will be most well-received, but the announcement says ads will be promoted across WordPress.com and Tumblr.

\n\n\n\n

Blaze campaigns require approval to be in compliance with Automattic’s Advertising Policy before being published. They are currently moderated in approximately 30 minutes but this may change in the future as more users try out Blaze.

\n\n\n\n

Automattic is treading new ground in creating its own ad network that any user across Tumblr and WordPress can tap into. It’s a strategic move to extend access to the world of WordPress, given that it’s such a large audience, and it will be interesting to see how the company improves the targeting options to meet the challenges of serving both audiences.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 22:52:36 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:36;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:107:\"Post Status: Improving 5ftF Contributor Journey • Building Interactive Blocks • Layout Classes • WP20\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146399\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:106:\"https://poststatus.com/improving-5ftf-contributor-journey-building-interactive-blocks-layout-classes-wp20/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:16911:\"

This Week at WordPress.org (January 9, 2023)

\n\n\n

Share your feedback about how to improve the Five for the Future contributor journey. Check out work underway on how to make interactive blocks easier to build, and take a walkthrough of layout classes in WordPress 6.1. It\'s time to start planning; how will you celebrate WordPress\' 20th birthday?

\n\n\n
\n\n\n\n\n\n\n\n

News

\n\n\n\n\n\n\n\n

\n\n\n\n
\n
\n

Community

\n\n\n\n\n\n\n\n

Core

\n\n\n\n\n\n\n\n

Meetings

\n\n\n\n\n\n\n\n

Developer Blog

\n\n\n\n\n\n\n\n

Docs

\n\n\n\n\n\n\n\n

Hosting

\n\n\n\n\n\n\n\n

Marketing

\n\n\n\n\n\n\n\n

Meta

\n\n\n\n\n\n\n\n

Openverse

\n\n\n\n\n\n\n\n

Performance

\n\n\n\n\n\n\n\n

Polyglots

\n\n\n\n\n\n\n\n

Plugins

\n\n\n\n\n
\n\n\n\n
\n

Project

\n\n\n\n\n\n\n\n

Support

\n\n\n\n\n\n\n\n

Test

\n\n\n\n\n\n\n\n

Themes

\n\n\n\n\n\n\n\n

Training

\n\n\n\n\n\n\n\n

Online Workshops

\n\n\n\n\n\n\n\n

Tutorials

\n\n\n\n\n\n\n\n

WPTV

\n\n\n\n\n
\n
\n\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n

Thanks for reading our WP dot .org roundup! Each week we are highlighting the news and discussions coming from the good folks making WordPress possible. If you or your company create products or services that use WordPress, you need to be engaged with them and their work. Be sure to share this resource with your product and project managers.

Are you interested in giving back and contributing your time and skills to WordPress.org? \"🙏\" Start Here ›

Get our weekly WordPress community news digest — Post Status\' Week in Review — covering the WP/Woo news plus significant writing and podcasts. It\'s also available in our newsletter. \"💌\"

\n\n\n\n
\n\n\n\n
\"Post
\n

You — and your whole team can Join Post Status too!

\n\n\n\n

Build your network. Learn with others. Find your next job — or your next hire. Read the Post Status newsletter. \"✉\" Listen to podcasts. \"🎙\" Follow @Post_Status \"🐦\" and LinkedIn. \"💼\"

\n
\n\n\n\n
\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 18:08:54 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Courtney Robertson\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:37;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"WPTavern: ClassicPress Community Votes to Re-Fork WordPress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140878\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:70:\"https://wptavern.com/classicpress-community-votes-to-re-fork-wordpress\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:3772:\"

In December 2022, the ClassicPress community voted on whether to re-fork WordPress or continue on with the project as-is. As WordPress continues to evolve, ClassicPress fell behind in pursuit of PHP 8+ compatibility. The fork is based on WordPress 4.9 and users are increasingly limited in what plugins will work with the five-year-old codebase.

\n\n\n\n

In a discussion limited to ClassicPress core contributors, Viktor Nagornyy, one of the project’s directors, announced the results of the vote: “The option to re-fork has 20 votes while continue-as-is has 18.” Nagornyy summarized previous discussions and suggested an approach that would be more realistic for the project’s limited contributors:

\n\n\n\n
\n

ClassicPress can’t be WordPress without Gutenberg, but it also can’t be its own CMS with a small core team at this time. There are simply not enough developers to make progress without backporting code from WP to move away from WP.

\n\n\n\n

An almost even split in the poll suggests the best option might be a hybrid one, find a compromise solution that will satisfy both sides.

\n\n\n\n

With a small core team, we have to find ways to be more efficient, to get more done with less. The only way to do that is to leverage all the work that’s done by WP contributors. As the core team grows, we can always explore the possibility of splitting away from WP but at this point in time, it’s simply not feasible.

\n
\n\n\n\n

Some participants in the previous discussion saw re-forking as postponing the inevitable, kicking the can down the road until the next re-fork, but it is the only option if users want to retain compatibility with the rest of the WordPress ecosystem.

\n\n\n\n

“If you read recent threads, you find out that the community expects plugin compatibility with WordPress… another reason for the re-fork option,” ClassicPress core committer Álvaro Franz said.

\n\n\n\n

Franz, who is also the author of the WP-CMS fork based on WordPress 6.0, previously said he would be unwilling to help with a continuation of the current version based on WordPress 4.9.

\n\n\n\n

“It [ClassicPress] doesn’t have to be a competition (and it never could compete with WordPress anyways), but it can be a leaner version, for people who are already disabling Gutenberg via plugins, for developers who want a different approach to the way they develop their projects (closer to ‘the classic’ experience, but yet… modern!),” Franz said.

\n\n\n\n

“Eventually, it won’t make sense to run a fresh copy of WordPress to then go and install a plugin that ‘disables’ half of it. What’s the point? Why not have a version that covers that specific use case?”

\n\n\n\n

As part of Nagornyy’s proposed hybrid approach, he suggested the project retain some changes that were introduced in ClassicPress in v1.x, such as the privacy-oriented changes (anonymizing data CP sends to APIs), the news widget, and ensure that all API endpoints use ClassicPress APIs as in v1.x.

\n\n\n\n

The discussion continues around how to proceed with the fork. ClassicPress contributors are leaning towards using Franz’s WP-CMS fork based on WordPress 6.0 but have not finalized the details yet.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 15:10:57 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:38;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WPTavern: #58 – Lax Mariappan on How Headless WordPress Works\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:48:\"https://wptavern.com/?post_type=podcast&p=140972\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:77:\"https://wptavern.com/podcast/58-lax-mariappan-on-how-headless-wordpress-works\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:56075:\"Transcript
\n

[00:00:00] Nathan Wrigley: Welcome to the Jukebox podcast from WP Tavern. My name is Nathan Wrigley.

\n\n\n\n

Jukebox has a podcast which is dedicated to all things WordPress. The people, the events, the plugins, the blocks, the themes, and in this case, how Headless WordPress works.

\n\n\n\n

If you’d like to subscribe to the podcast, you can do that by searching for WP Tavern in your podcast player of choice, or by going to WPTavern.com forward slash feed forward slash podcast. And you can copy that URL into most podcast players. If you have a topic that you’d like us to feature on the podcast, well, I’m very keen to hear from you, and hopefully get you or your idea featured on the show. Head to WPTavern.com forward slash contact forward slash jukebox, and use the form there.

\n\n\n\n

So on the podcast today, we have Lax Mariappan. Lax is a web developer based in the Philippines. He’s an open source enthusiast and lover of all things WordPress. Lax has been tinkering with websites since high school. But it all changed when he discovered WordPress in 2010. Lax currently works as a backend engineer at WebDevStudios.

\n\n\n\n

We talked today about Headless WordPress, and it’s a complex topic. Headless is the concept of decoupling the WordPress admin from the front end of the site. WordPress will continue to work as expected, but the presentation layer will be done by a different technology. React Gatsby and Remix being some popular choices.

\n\n\n\n

This implementation of WordPress is complex, requires technical knowledge above and beyond that needed for a more typical WordPress install. But it has its benefits.

\n\n\n\n

Lax talks through all of this in great detail. How keeping on top of all the additional dependencies Headless WordPress requires can be time consuming. How it can create difficulties for content editors who don’t always get to see what their work will actually look like in real time. Why this approach to WordPress can take more time and resources during the build.

\n\n\n\n

Lex explains how these problems typically crop up, and how it’s possible to plan ahead and build in solutions for all the problems that you might encounter.

\n\n\n\n

If you’ve ever thought about going headless with WordPress, then the podcast today is for you.

\n\n\n\n

If you’re interested in finding out more, you can find all of the links in the show notes by heading to WPTavern.com forward slash podcast. Where you’ll find all the other episodes as well.

\n\n\n\n

And so without further delay, I bring you Lax Mariappan.

\n\n\n\n

I am joined on the podcast today by Lax Mariappan. Hello Lax.

\n\n\n\n

[00:03:30] Lax Mariappan: Hello, Nathan.

\n\n\n\n

[00:03:30] Nathan Wrigley: Very nice to have you with us on the show today. I have to commend you for your staying power, because Lax and I have tried to record this episode a couple of times and he’s been incredibly, incredibly thoughtful about getting his, all of his equipment and everything working. So thank you, first of all, I would like to express my gratitude for you staying the course.

\n\n\n\n

But before we get into the podcast, Lax, I wonder if you wouldn’t mind spending a moment just introduce yourself. Tell us who you are, where you are, who you work for, how long you’ve been using WordPress, all of those kind of things.

\n\n\n\n

[00:04:06] Lax Mariappan: Thank you. It’s good to be on WP Tavern, it’s one of my favorite publications, and also the favorite podcast. So I’m Lax, Lax Mariappan. I’m from India, and also I’m from Philippines. So I would say I live in both countries, and I use WordPress since my school days, like 2009. So I was looking for a platform to build a website for an event or something, and then I found out Blogger versus WordPress, and I liked WordPress more even that time.

\n\n\n\n

So since then, I’m using WordPress almost every day. And my first job I got started working as a PHP developer, I would say, and then fully focused on WordPress. And I wrote my first plugin in 2011. It’s a very simple one. It’s now kind of obsolete because Facebook changed it a lot. So I wrote a plugin for something to fetch Facebook feed. So, and then my journey goes on. Right now, I work as a backend engineer at WebDevStudios. So where I get a chance to learn and work more with headless CMS every day almost.

\n\n\n\n

[00:05:09] Nathan Wrigley: Your work at WebDevStudios, I don’t know a great deal about the company, but my impression of the company is that you work with, how should we describe it? Enterprise clients. You’re dealing with fairly large projects. I would imagine sizable budgets. Those kind of things, right?

\n\n\n\n

[00:05:27] Lax Mariappan: Yeah, yes. Enterprise level.

\n\n\n\n

[00:05:28] Nathan Wrigley: So when we decided we were going to have this conversation, Lax introduced the subject to me of headless WordPress. Now this is a word which I imagine some of you have heard before. Maybe some of you have never heard the word before. Perhaps there’s a subset of you which have experimented with it, but I’m expecting that the majority of WordPress users have not.

\n\n\n\n

So, first of all Lax, would you mind giving us a very, in depth I suppose is the right word. Give us an analysis of what headless WordPress is because I’m sure many people think they know what it is, but perhaps they don’t.

\n\n\n\n

[00:06:06] Lax Mariappan: So headless, or decoupled CMS, so first we all know content management system, right? So WordPress, we are using WordPress now as a content management system. It started out as a blogging platform. We used it mainly for blogging. And then WordPress introduced custom post types, taxonomies and all that sort of stuff.

\n\n\n\n

So we are now using WordPress to build simple to complex websites. Forums. Some people use it for their colleges, universities as a social media platform, and some of them use it for a job board and everything, right? So we have plugin for everything and we can customize it and we use it.

\n\n\n\n

So when it comes to the traditional CMS, we call that as monolithic. I hope I’m not using too much jargon here. Monolithic in the sense it has everything into it. So for example, if you go to a website, the header, footer, the sidebar, and the content that you see and the forms and everything that comes from the same CMS itself. So it is going to be, let’s say, in the case of WordPress, it’s built mostly with PHP and JavaScript.

\n\n\n\n

So everything is going to be PHP template with a bit of JavaScript and CSS to it. But when you say on the contrast, headless CMS, it means, so you can consider that as a, I would not say person. Maybe something like, you can imagine something that doesn’t have a head. So in the sense the body is the same, head is different.

\n\n\n\n

So you can imagine that as, you are going to use the same admin panel and you are going to have the same WordPress features. You can add the content, you can add menus, you can edit anything, you can add users, all that stuff. But when I view the website, so it’s not going to be your theme. So it’s not the typical way of how WordPress gets rendered.

\n\n\n\n

So instead we will be decoupling it. So that is WordPress admin will stay on another site. It can be on a subdomain or a sub folder, but the front end is going to be a different platform. So it’s going to be hosted in a, mostly a JavaScript based stuff. So you can use either React based frameworks like React itself or Gatsby, Next.js or Remix, or anything that you like.

\n\n\n\n

And also you can either go in another route as well. So you can make it like a fully static website, or you can render it on every time as a server side rendering as well. So every call will go to the server and renders.

\n\n\n\n

Okay, so now we can call that a small intro about headless. You may already know this one. It became a buzzword a couple of years ago, right? But now everyone wants to go as headless. I see that company goes headless, or my competitor goes headless. So I want to go that way. But, unpopular opinion. Maybe you might hear some other people say that too. Headless is not for everyone, or I would say not for every use case.

\n\n\n\n

It depends on how much content that you publish. What are your goals and what you want to achieve. So headless is good, it’s performant, it’s fast, secure, and it gives you more freedom and flexibility, especially in terms of performance it’s really good. But I would say it’s not the something like you should go headless. It’s not the answer.

\n\n\n\n

[00:09:10] Nathan Wrigley: So essentially you’re saying that there are scenarios where this is desirable, but there’s going to be other scenarios where WordPress, in the traditional sense of the word. The regular WordPress that you download, perhaps use a hosting company and it’s all driven by PHP. The normal way of doing WordPress. That might be the best solution for lots of people.

\n\n\n\n

Okay, so we’ve got our WordPress website, which we can interact with, and then the content that comes out of that website is pushed to something else. And probably we’ll get into what the options are there. But let’s take the use case of a company which comes to you and says, okay, we’ve heard this buzzword. We think that we want to go headless.

\n\n\n\n

What are the benefits of going headless? Let’s forget about all the problems that might be associated with it. Can we just iterate through the things that you will gain if you manage to pull off a headless WordPress website. Now, I know there’s going to be all sorts of different scenarios there, but maybe just pick out the low hanging fruit. Some of the things which you believe are really beneficial.

\n\n\n\n

[00:10:17] Lax Mariappan: Yeah. The first and foremost, or the popular one, is the performance. So WordPress uses PHP templates. We will do everything with PHP and Javascript and also a little bit of caching to render our traditional CMS like traditional pages. If you use a normal WordPress installation with a theme. So that’s how it’s get rendered.

\n\n\n\n

So there you can see it depends on the hosting company as well, and also how much plugins that you use and how you configured them. So that affects the performance of a site. But when it comes to headless everything is going to be bundled, and there will be how a normal JavaScript based application gets rendered.

\n\n\n\n

So it’s going to be a modern web application where you have control over, for example, if your page doesn’t use certain CSS classes, those CSS will not get loaded for that page. So I would say the assets that are loaded, it will be less. And the images will be more optimized. In either case, like in traditional too you can optimize images, but it’s like the performance is the first one, I would say.

\n\n\n\n

It’s going to be both developers will love it and also the site owners, and also, let’s say marketers, Everyone will like the performance aspect of it. And in terms of headless, I would say developers will like it, especially in terms of, so you can repurpose the content. So if you are having a CMS, WordPress as a headless CMS, you can use that same endpoint, get the data and display it in a different formats quickly.

\n\n\n\n

Other than a WordPress theme. So for example, if you’re using a WordPress theme, you have to create multiple templates. So this is a template for mobile, and this is something that, for example, if you want to use it for a landing page, you may have to do some small or extra changes. But when it comes to headless, you can just customize it in a way that you want to.

\n\n\n\n

For example, I want to have a landing page. I don’t want certain stuff to be there. So you can turn on, off certain components, that’s it. So it’s like you can render the blocks and render the content faster. So developers and designers will like it. And also, in terms of the security, that’s where I’m more interested in cybersecurity especially. When people say WordPress sites are not secure, that triggers me actually. Yeah, I do get angry.

\n\n\n\n

So it’s like, you don’t have to worry about that. So you don’t have to worry about changing your login page url. Adding captcha to your login form, all that stuff. Because that URL is going to be safe and secure. No one knows where you are hosted your CMS.

\n\n\n\n

[00:12:49] Nathan Wrigley: Can I just interrupt there? So could you explain that, because I imagine there’s a bunch of people scratching their head at this point. Because normally, let’s say you have a website, it’s example.com. You’re going to go to example.com/wp-admin, and there is your login page. But there’s something in between here. I’m not sure that we explained that quite. So just explain why the login is secure. Explain where it is and why it’s not normal WordPress.

\n\n\n\n

[00:13:19] Lax Mariappan: Yeah, so I mean, normal WebPress is also secure but people can guess it, right? Say example.com/wp-admin, so they know. They can see from the source code and the page source, they can see oh, this looks like a WordPress site. And then they can guess the admin url. So slash wp-admin, it’ll redirect them to the login page, right.

\n\n\n\n

But when it comes to headless, the example.com will be hosted somewhere, and the front end that you see will be different. So for example, let’s say CMS is your WordPress installation, all WP. So you can call that like wp.example.com. So that’s where your WordPress stays in. But when you go to the example.com, that’s your front end, so that’s just JavaScript and html. So it’s like, if somebody wants to hack your site or somebody wants to, just guess what will be the admin url. So they cannot.

\n\n\n\n

[00:14:10] Nathan Wrigley: It’s a difficult concept to understand if you haven’t encountered this before. But what you’ve got basically is a WordPress website, which is the container for the content, but it isn’t the website and we’re not used to that in traditional WordPress. You go to example.com/ wp-admin, get redirected, log in, do all the things, and click publish, and as soon as you click publish, it will be present on the website. That’s not the way that this is working because the WordPress website is completely decoupled from the thing which is presenting it to the world, right?

\n\n\n\n

[00:14:48] Lax Mariappan: Yeah. Yeah. Completely decoupled.

\n\n\n\n

[00:14:50] Nathan Wrigley: So given that, there’s no connection between, okay, here’s my website at example.com and where I might log in. And because of that there isn’t the capability to just guess the login page and then bruteforce an attack and so on. So in terms of security, it offers that benefit. The thing which people are most worried about, somebody getting your admin password going in and spoiling your site. That’s highly unlikely because they simply won’t know where to look.

\n\n\n\n

[00:15:23] Lax Mariappan: Yeah. And also, so for example certain normal pages like comments, so that’s where we get a lot of spam, right? So comments will go to comments.php. When you submit a form without any data, or maybe if it’s spam data, it just goes there, right? But when it comes to headless, we will be using some extra customization for the comments and everything.

\n\n\n\n

So it’s not going to be the data will store as comments in the database, and it’ll be, you can view them as comments in the admin panel. But when you are viewing it in the site, so you are reading a blog post, you have a comment form, so that form is HTML and JavaScript. So that’s not how a typical, a normal WordPress form, normal comments form.

\n\n\n\n

So that’s where you will get less spam as well. So you don’t have to worry about that too. Like people submitting spam data and also any other form. So that’s another thing. And you don’t have to worry about any other security related stuff, because it’s just static.

\n\n\n\n

So people cannot do anything or manipulate data. So it’s going to be just HTML stuff. Whatever they can do is just view the data. So I would say in the headless, so if you are viewing some pages or we are in a archive page and post archive, news archive, any archive page or any other page that does the data and fetches the data from the database, all that stuff.

\n\n\n\n

So all that stuff will be protected routes. So people cannot easily guess. Sometimes you might encounter database related attacks, right. So you may hear cross site scripting attack or any other stuff like, somebody trying to get data either they pull your data or they want to insert some other data to the database. That’s not the case.

\n\n\n\n

Everything is going to be static, like just html, and it’s only read only. So people are not going to input any data. And the input will be just maybe a comments form, contact us form, something like that. And that will be handled. It depends on what form provider you are using, or how you configure it, but still it’s more secure that way.

\n\n\n\n

[00:17:25] Nathan Wrigley: So just to reiterate the point one more time, just in case anybody hasn’t been paying attention. We have our WordPress website. It is used by the developers, by the content creators, by the editors. They do their normal work inside of WordPress, but the thing which is being viewed on the front end by the population at large is completely separate.

\n\n\n\n

You’re just sucking the data out of WordPress and putting it into whatever you like. The security’s fairly obvious, you’ve explained that really well. The performance, obviously, if all that you are showing is static html, essentially. That’s going to load really, really quickly. Nothing needs to be built at the time that the page is viewed and so on and so forth. It’s already been created.

\n\n\n\n

This all sounds amazing and of course that raises the question, why aren’t we all doing it? And you have given us, in the show notes you’ve given me, three different things which we perhaps should talk about, and some of them, you explained the problem and then we’ll get to the solution.

\n\n\n\n

So the first one that you talk about is dependency hell, you’ve described it as. And, I’m guessing that having a headless site is not straightforward. We’re very used in WordPress to, novices can install WordPress incredibly quickly. You basically upload a zip file and unpack it and connect it to a database, and these days, you know, you go to a hosting company and not even that. You just click a button and, wow, there’s your WordPress website 30 seconds later.

\n\n\n\n

I’m guessing that this is not the case for headless. There must be all sorts of complex layers of things going on in the background, and you say that in many cases it can become very difficult. Dependency hell. So describe the problem of all the dependencies.

\n\n\n\n

[00:19:13] Lax Mariappan: So when you have a WordPress installation, we will be installing plugins, right? You might be, if you are using WordPress for a while, you are already aware of the jQuery migrate plugin. All that stuff. So WordPress uses jQuery even now. So jQuery is a dependency that WordPress requires. WordPress depends on jQuery in admin panel, and also on the front end.

\n\n\n\n

So if you want to get rid of jQuery, it’s kind of, WordPress may not be the same, if you want to eliminate that. Because WordPress depends on it. So it’s something like, let’s say you cannot say that as a oxygen, but it’s something that we all need it. So we need that to survive. So WordPress needs jQuery to work normally.

\n\n\n\n

So similar case, when you are building a headless site, you will be requiring a lot of frameworks, libraries, and also packages. So for example, if I’m going to choose Next.js as my front end platform, front end framework. So Next.js is built with React. If I want to use Next.js, I may want to use some other Next.js related libraries.

\n\n\n\n

So it is something like if you are on Android, you may want to add extra apps on your phone. If you are an iPhone, you’ll be adding some more extra apps to extend, right? It’s the same case. Similar to plugins. Instead of that plugins, we will be adding packages. So that packages helps the developers to add extra features that we need.

\n\n\n\n

So the problem here comes in is, everything gets stacked in and one will be dependent on another. So, for example, if someone is installing a package like for SEO, and maybe that package will require something else. And let’s say if Nathan is maintaining SEO package and I installed it, and for example, for whatever reason, Nathan becomes a musician and he doesn’t, he is not interested in SEO anymore.

\n\n\n\n

So he may not be more active in maintaining that dependency, maintaining that plugin or that package. So what happens is I’ll be waiting for him to fix the bug or some errors. Or I will waiting for him to upgrade to the lightest version. But it’s not the case, right? So, my Next.js package will be waiting for Nathan, so it’s like I’m depending on him, but he’s not available. So in that case, I have to go and do that work as well. So that adds to our development timeline.

\n\n\n\n

And then, so this is just one package and one scenario. So this happens with multiple packages and stuff. And this is not just Node or NPM packages. It also happens to WordPress stuff as well. So, for example, let’s say we have a popular forms plugin, or we have a popular slider or any other plugin.

\n\n\n\n

So you will install that plugin and you want that plugin to work with headless. So how we are using headless, it’s the data is stored in the WordPress, and we want to get the data through either Rest API. It’s a method that we, you know, you go to a url, you ask the WordPress, hey, give me this data and it’s going to give. Or you’ll be using GraphQL. It’s the same. You go to an endpoint and you’re going to say, hey, I’m looking for this post. I want five posts from this date. So it’s going to give that data as well.

\n\n\n\n

So either you use Rest API or GraphQL. The problem is a plugin that you are using, your popular forms plugin, your popular slider, or any other plugin that you’re using. LMS plugin, E-commerce plugin or any plugin, like a payment gateway. So you have a plugin and you want to use it with headless. So that plugin should work with the Rest API or Graph QL. So if that doesn’t work, if that doesn’t give you the flexibility, and then you are still stuck there.

\n\n\n\n

Because you cannot go and create everything on your own, right? So we cannot reinvent all the wheels. We don’t have time to create everything from scratch. So that’s where it’s like that becomes a bottleneck. So you are like, hey, I found the plugin. I started working on it. It works up to this mark, but it’s not a hundred percent. So it’s like it does its job 80%. Now I have to go fill in that 20%. It adds to the budget, it adds to the development timeline. So that’s the dependency hell.

\n\n\n\n

[00:23:15] Nathan Wrigley: Yeah. So in the case of all of the technology, which is in the background if you like, which we haven’t really talked about too much, but like you said, the things which you are requiring from third party developers. There’s a dependency there, and it’s very similar to the dependency that you may have on plugins, you know, you want them to be updated and so on, but you are adding extra dependencies. And of course, the more dependencies you’ve got, the more costly, time consuming it is.

\n\n\n\n

I’m guessing that most of the things that you are depending on, in addition to WordPress and you described what a few of those were, you could, I suppose, do some due diligence and figure out which projects have been well maintained, updated frequently, and so on. And I guess in the open source world, much of the dependencies that you’re using will be open sourced, so you could fork them. But again, you are creating probably a large amount of work for yourself and your team.

\n\n\n\n

[00:24:13] Lax Mariappan: Yeah that’s true. Well said. So it’s like, since it is open source, it’s good. Like lot of reviewers. We have a lot of eyes on the code, and you can fork it. You have the freedom to do whatever you want. But still you are looking for a solution and that becomes a problem. You have to fix that as well. And that adds to the, another dependency, another dependency. It becomes a cycle that you cannot escape sometimes.

\n\n\n\n

[00:24:36] Nathan Wrigley: I guess this is a bit like a seesaw. You know, on the one hand you described all of the benefits, performance, security, and so on, of headless. And then on the other side is, is all of the things that we are now describing. You know, the dependencies and so on. You’ve got to weigh up at the beginning of the project whether one thing is worth all of the time and effort that may be required to do it.

\n\n\n\n

And I’m guessing in many cases, certainly at the enterprise level, the answer’s going to be yes, because the budget is there, we can put enough bodies to work to make all of this happen, and if we need to fork things, there’s enough people on the team that can do that and maintain the project, which has fallen into disuse. But for a little project the seasaw may tip heavily against something like headless just because of the things that you’ve described there.

\n\n\n\n

Okay. So that was our first thing, dependency hell. The second thing that you wanted to talk about was the fact that in the WordPress world, especially in the last five or six years or so, we are really used to what you see is what you get, WYSIWYG. You save something in WordPress, you publish something and you have almost a hundred percent certainty of what it’s going to look like. The backend looks like the front end, especially with things like page builders and so on. But you say that that’s not always the case with headless solutions. Why is that?

\n\n\n\n

[00:25:55] Lax Mariappan: We will be creating custom blocks. So, either there are a popular way of building now custom blocks is with ACF. So you all might be aware of and using it, even though you are not a programmer, you might be using it, right? So ACF is easy to install and create some custom fields. So you can use ACF to block, to build blocks for the site.

\n\n\n\n

So those blocks can be used or you can build your own custom blocks. You can use any block starters like, frameworks that are available now. Or you can just follow our, WordPress comes with packages that you can on build command, so you can just build your block in a matter of seconds.

\n\n\n\n

But still, all this stuff. So for example, if you are having custom blocks, I’m not talking about just normal blocks, like where you add a paragraph or image or something very simple. That is easy to build and that’s easy to see. That’s different. But I’m here talking about something complex.

\n\n\n\n

So for example, you can imagine that as an Elementor widget or, some other items that it comes with the page builders. So, let’s say a slider, maybe tabs, accordions, all that stuff, right? So that can be added through the blocks itself. But you cannot preview them, because when you add them in the admin panel and we add them in the content. Those content gets, you know, you can choose like, oh, this is the tab title, this is the content.

\n\n\n\n

And you can keep adding the content, but you don’t know how it’s going to render in the front end. But let’s say if you are using some, there are a lot of free blocks and also even premium blocks available. So if you are using a block to build them, and then using the normal WordPress installation. Or you can use WordPress with the full site editing, the modern themes, or the hybrid themes, like old plus full site editing themes.

\n\n\n\n

Still they both work well. Like you can preview, oh, okay, this is the tab I added this content. I can’t view this one. But when it comes to ACF blocks or other certain custom built blocks, you cannot preview them.

\n\n\n\n

So when a editor or a user adds content, they may get lost. So I have a slider. I want to add three, four images to it. I may get lost. Oh, what’s the third image? What I have added there, and how it looks? Is the images correct? Is the text rendered properly or should I reduce any title or text or anything, right? So all this stuff becomes a little tricky. And also sometimes it becomes a pain for the content writers, content editors, and also the site owners.

\n\n\n\n

[00:28:24] Nathan Wrigley: So in the normal, traditional WordPress, let’s say we’re creating a page, we add a page, and we use whatever tool it is that we want to use for that. We add in some blocks. We are perhaps using Elementor, whatever it may be. And we click publish and then we are able to immediately view that because WordPress is working in the traditional sense of the word. The page gets pushed through the templating engine and it’s rendered with its template and we can see it right away.

\n\n\n\n

But because that’s not happening here. And the mechanism for rendering that page is entirely different. You can’t necessarily view it immediately. Have I kind of encapsulated that? What you are doing in the backend, because it’s decoupled with the presentation layer on the front end, you can’t necessarily always see it?

\n\n\n\n

[00:29:16] Lax Mariappan: Yeah, so that’s the challenge. So the solution here is to customize the way you built. So for example, we can give them a preview button so they can preview what are the slides, and how they look. And they can see that immediately in the editor itself. Like when they are adding content in the block editor, they can see it.

\n\n\n\n

And also the other way is to have a button, a preview button. So that will preview before the content gets published. So, you can change the workflow. So if somebody hits, instead of publish, you can have like a preview button or keep it as a draft. So that way it’s like nothing goes to the front end without your approval or preview, right? So you have to preview it and see, oh, make sure everything looks correct, and then you can say, hey, I want to publish it. Yes, confirm, publish it, and then it goes to the frontend.

\n\n\n\n

[00:30:04] Nathan Wrigley: That’s fascinating. That’s really ingenious. So, because we can’t necessarily see it on the frontend, you and your team have built a custom preview system. So on a block by block basis, you can see what that block will look like when it’s rendered. So in the example of your slider, presumably where we’ve got three or four fields. We’ve uploaded maybe some text, we’ve uploaded an image, and it’s just a bunch of fields. Normally we’d click publish and we’d go to the page and preview the page and we’d see it right away. But in your scenario, you are going to hit a button inside the block to show what that block and that block alone will look like. Have I understood that?

\n\n\n\n

[00:30:48] Lax Mariappan: Yeah, that’s what we did. Because the users, they are used to the traditional WordPress. And especially that was with classic editor, I mean the old editor. So if you insert an image, they can see it’s an image. And if you insert something, you can see. And we are all used to the page builder era, right? So if you add a accordion, you can see how the accordion is going to look.

\n\n\n\n

But when it comes to headless, all this stuff is going to differ. So, the tabs, accordions, sliders, and also anything else, any other custom stuff that we built, we added a preview button, and when you click on the preview, you can see that right away.

\n\n\n\n

Then you can make sure like, oh, the colors are correct, the image is correct, and everything renders properly. Because sometimes if you are not looking at the content and adding content, you might miss some data, right? So you might have missed a small setting that says full width, or you know, boxed. So then you feel like, oh, why this looks so awful. Oh, I’ve missed this full width button. So that’s how the preview button works.

\n\n\n\n

[00:31:49] Nathan Wrigley: So if I’m looking at the block and it’s a, let’s stick with the slider just for the sake of it, and I’ve uploaded my images and whatever fields were required and I click the preview. Does it literally happen inside that block? Or is this some kind of modal which pops up and shows things? Or is it, is it literally taking over the block itself?

\n\n\n\n

[00:32:09] Lax Mariappan: Ah, it’ll be within the block. Like it will replace, so for example, if you have a block and you are adding some content to it, and when you click on the preview, it’ll replace where you are adding the content, right? It’ll replace the form. Form of the block where you are saying like, hey, this is the title, this is the subheading, this is the description. Instead of that, it’ll just render the titles, heading and description.

\n\n\n\n

[00:32:32] Nathan Wrigley: Right, and then you toggle that off again once you’re, once you’re happy. So, ah, that’s really interesting. So the workflow there is really very different. And I’m presuming that after a period of time, the people who are editing, creating this content, that just becomes part of the process? They just understand that, okay, rather than viewing the whole page or whatever it may be, post whatever, I’m just viewing this little bit, and I’ve done it several times now and I’m confident that if it looks right inside the block preview, then I can click publish, wait for everything to happen, and hopefully that page will go live. And, it’s just a different workflow that you have to get used to. But once you’ve done it several times, it’s, familiar and normal.

\n\n\n\n

[00:33:14] Lax Mariappan: Yeah, it becomes part of the workflow. And also, like we discussed earlier, your site will be like, CMS.example.com. And the front end will be on example.com. Sorry, every time you have to go to example.com/about, example.com slash contact us. Instead of that we will have a preview button. So, you can preview each block and you, if you, or feel like, hey, I want to see how the whole page looks like, you can click that preview, and that will take you, or that will show you immediately, oh, this is how the front end, like example.com/the page will look like.

\n\n\n\n

[00:33:45] Nathan Wrigley: Yeah, that’s a good point. We’re so used to the preview button being connected to the URL in question, because it’s being rendered by WordPress. You click the preview page button or whatever it may be, and it takes you to the correct place. In this case, there’s no connection between what the URL will be and where you currently are, so yeah, that’s fascinating.

\n\n\n\n

Just as a bit of an aside. We haven’t got into this, but I think it would be a good topic to discuss for a couple of minutes. If WordPress is separated from the presentation layer, this sort of headless notion. How often does the website get regenerated, if you know what I mean? So for example, if we click publish in our headless WordPress website, what is typical there? Are you going to generate the page immediately and store it as static html? Or do some clients have different expectations there? You know, for example, if you are a, a site which needs to publish things regularly, perhaps you need that capability.

\n\n\n\n

I click publish. I want that page to be live within a matter of moments. Or it may be that you’ve got a website where it doesn’t really matter if the pages are not built, I don’t know, three hours, six hours a day, whatever it may be. Do different clients have different expectations there?

\n\n\n\n

[00:34:56] Lax Mariappan: Yeah, that depends on how the publication frequency is. If you want to publish immediately, we can do. If you are okay with publishing the changes after two, three hours, still we can do. So it’s about how you want to set, how you want to build the things.

\n\n\n\n

So here, few things to consider. You can go with static, fully static website. That’s just static and only when a page gets updated. So for example, you have a hundred page. All of them are static and those pages will not be regenerated. So if you change just the about page and only that 99 pages will remain the same. Only that about page will get regenerated again. You can go that route.

\n\n\n\n

And also you can go with, every time in the page gets rendered, you can go server side rendering. So every time that’s new, so you can go that route as well. So that depends on how you want to render the data and everything has pros and cons. The normal way is like how Next.Js does now. Because it is like, keep everything static and if you want to render something, you can still regenerate the specific page.

\n\n\n\n

So this way it’s like you don’t have to build everything all the time. So you can build what has changed in the WordPress. You can see that in the headless frontend. And also you don’t have to wait for it. So, for example, if I go make some change and click update and you can see that immediately.

\n\n\n\n

[00:36:21] Nathan Wrigley: Really interesting, because there is no exact way of doing this is there? You can just build it in whichever way you think is most beneficial, or whatever the client needs. You know, if, if it’s a newspaper website where, really I need to click publish, and within a few moments I need that page to be live because the content that we’re creating is tremendously important to be fresh and new and so on. But it may be that, yeah, you don’t have that expectation and you’re quite happy to have it work in a different way and publish on a, a much less frequent basis. I can’t really imagine a scenario where anybody would say no, I’d rather it was published less frequently, but maybe there are scenarios where that’s beneficial. I don’t know.

\n\n\n\n

Okay, and the last point that you wanted to talk about was, the whole conversation has proven to be really interesting, but it’s pretty clear that there’s a lot more work involved in this kind of website. And so your first point was about the fact that the dependencies, lots of dependencies. Your second point that was that you don’t always get to see what you see is what you get in operation. And the third one is basically the amount of time it takes, the amount of resources it takes. You’ve described this as headless asks for more. Tell us about that.

\n\n\n\n

[00:37:34] Lax Mariappan: Yeah, so when it comes to creating a normal WordPress, like a standard WordPress theme. So what you do is like, you start with your prototyping tool. Like it can be Figma, Adobe XD or anything. So you have your design ready, right? You are creating mock-ups, discuss with the client, and then create a mock-up and then find the variations, all that stuff. And you are settling in, hey, this is my design. And now I’m going to create the theme.

\n\n\n\n

So, I want to create this many templates. I want to create this many menus, all that stuff. When it comes to traditional stuff, it’s like, you don’t have to consider too many things. So it’s kind of straightforward process and like designers and developers can, the engineers can work hand in hand. And it’s, you can follow Agile like, build stuff, reiterate and just deliver it.

\n\n\n\n

So that’s how that works. But when it comes to headless, so you have to consider a lot of things. I would say the first thing is the knowledge or, you know, expertise. With WebDev Studios, we are, I would say kind of one of pioneers and also experts in WordPress plus headless stuff. So we have launched, it’s a open source like we have Next.js starter template. So if you want to try out Next.js a headless frontend for your WebPress site, you can just take a look at WDS Next.js starter. It’s free and it’s in GitHub, so you can just start using it.

\n\n\n\n

So, expertise comes one, like whether you should be, have sound knowledge in that. So you can go and fix stuff. You know what you are doing and you know what to expect and all that stuff. But this requires something like, for example, I am a backend engineer. I have limited React knowledge. I’m now catching up with React, Next.js, all that stuff. But I would, I would not say I’m an expert at it. I build stuff, I still use Next.js every day, but it’s like, I won’t say I’m an expert at it.

\n\n\n\n

So expertise is one. So your team should have sound knowledge in the framework or anything that you do. Or even if you don’t have sound knowledge, let’s say if you are doing something like, something very new, like Remix got released only one or two years ago, right?

\n\n\n\n

So if you want to go use Remix, You should be an expert in React and you should play around with React. So that’s the time. So my point is like time, it asks for expertise and it asks for time. So when it comes to just normal WordPress theme, probably you might finish the theme, let’s say, in a few weeks, or at least a few days even sometimes. With page builders finish it in few days or few weeks, right?

\n\n\n\n

But maybe if you are building it from scratch and you are doing a lot of customization, it may take a while. But when it comes to headless, may take even longer. So more expertise, more time, and all this adds up to more budget.

\n\n\n\n

This may sound like, oh, well should I do all this stuff? It’s kind of worth it. So you don’t have to, for example, if you have your, the front end components ready you may be having your storybook, like where you want to see how the button should look like, how the elements, how the panels are. Let’s say how each component will look like and how they render, all that stuff, right? So when you have all these parts ready, you can go from, for example, today I’m using Next.js, sooner I can move to something else, like I can use Remix. Or I can use something else that’s going to be hot in the market in future.

\n\n\n\n

But when it comes to the typical WordPress, you are going to change everything from scratch. So if you want to add a new theme, so maybe if you want to change the look and feel, that’s different. So everything has pros and cons, but the short answer is the headless CMS ask for more.

\n\n\n\n

[00:41:13] Nathan Wrigley: Yeah. It does sound like not only do you need more time to develop all of this for the reasons you’ve just described. It’s more complicated, so it takes more time. There’s more moving parts, shall we say. And it may also be that you need to spend some of that time not just building the thing, but learning how all of this hangs together, because there’s an awful lot going on in the backend here. And if you don’t have expertise in that, presumably things could go pretty wrong.

\n\n\n\n

With that just before we end. You’ve obviously decided at WebDevStudios that this is an approach. I don’t know if you build the majority of your sites in this way or subset or a proportion of them, not sure. But, typically what is the amount of time longer it would take to get a website out? Let’s say, for example, that if you were just going to use WordPress as is a normal WordPress website, and you built an exact same website, but did it headless. And let’s imagine a site with, I don’t know, several different custom post types.

\n\n\n\n

It’s got hundreds of pages. I’m just kind of making up something off the top of my head. But typically, you know, does it take twice as long, three times as long, 50% longer? What, what are we looking at?

\n\n\n\n

[00:42:28] Lax Mariappan: I’m going to answer just like other engineers do. It depends. But it’s like, I would say it takes a long, maybe you can say, maybe you can say double, but it should not take more than double or something. So that’s where I would say start with more of research. So you should not change frameworks or libraries in between. Like once you started as React, go with React. And if your team is, they are very comfortable and they’re knowledgeable in React, use that. If you are going to use Vue.js or Astro or any other framework. When you start with something and you can go with it.

\n\n\n\n

So, it is a matter of discovering what the client needs and where the goals meet. How we can achieve it. And once we are very clear on that, you can start developing. And during the development phase itself, we can see what are the possible, you know, the bottlenecks or what causes the issue, what could be a problem, and we can figure out other different approaches and solutions.

\n\n\n\n

So, for example, you don’t have to let’s say, PayPal is not the only payment provider right now, right? The payment gateway. So we are using so many different stuff and they do the payment integration quickly. But before those days, let’s say 10, 15 years ago that case was different, so now we have more options.

\n\n\n\n

So similarly, you don’t have to create a form and you don’t have to wait for someone to, the third party or some other open source in a package or something to be ready. So either you can build something on your own if you have time and budget, or you can fork something and then you can adjust to it.

\n\n\n\n

Or the other way is, I would say you can go with some existing third party or SaaS or any other solution, which is just already there and you can see how you can use it with WordPress. So these are the stuff that can reduce your development time.

\n\n\n\n

So when you say if you are, I don’t know exact hours or something, let’s say a thousand hours. So if you say a thousand hours for a normal WebPress installation, so headless may take a little longer, 1,500 or 2000 or anything. But it depends on what the client wants and what framework you choose and your expertise, like, I mean, the whole team’s expertise. And also how well we plan, organize, and go.

\n\n\n\n

So sometimes it’s like just the client takes so long to respond, or sometimes it’s just like, even the client is clueless or what’s happening. So that adds up to some stuff. And I would like to also highlight, when you hear all this stuff, somebody listening is, they will be scratching their head like, so headless is yay or nay.

\n\n\n\n

So, recently, I cannot say the client name and stuff, but I would say, how we figured this out and how it is kind of helpful. So we had to publish more than 20 websites. That’s for a single client. And all of them are different, and all of them are headless, but that’s for a single parent company.

\n\n\n\n

So what happened is, we had the architecture ready, right? So we, we know what happens when you publish. We have everything ready. I mean, the back end and the front end ready. So things become more easier that way. The development time is actually just for one site and then other sites, it’s just like, it was fast.

\n\n\n\n

But we had enough configuration and enough options we given to the client. So every site is not going to look exactly the same. They have their own customizations. But still it’s like amount of development time is the same or is actually less when you compare to traditional. But it depends. It depends on what’s the use case? How, what you are trying to build and everything.

\n\n\n\n

[00:45:52] Nathan Wrigley: Yeah, it really does sound, there were so many good perspectives at the beginning where, you mentioned performance and so on where this is definitely going to be worth it. I guess if the client is willing and the budget is available and the expertise is there, then this sounds like an incredible option. Steep learning curve probably, but a lot of benefits on the backside of that.

\n\n\n\n

Lax, just before we round it up, if somebody has been thinking about playing with headless and they’ve listened to this and they think, okay, I’d like to take that a bit further. Couple of things, firstly, where can they get in touch with you? But also have you got any guidance about resources that they may find useful?

\n\n\n\n

So that could be a website or a book or whatever it may be. So let’s start off with resources and then we’ll turn to you to finish it off. So what resources do you recommend to learn about headless in general?

\n\n\n\n

[00:46:49] Lax Mariappan: In general it’s like you can start with WP Engine has their own blog. They have stuff about headless WordPress and they also have some of packages and stuff they maintain. They have Atlas. It’s a platform they are planning to go full fledged on headless stuff. And also you can read about GraphQL, WP GraphQL. Their team is more active and they share a ton of stuff on how to customize and maintain stuff with headless.

\n\n\n\n

And also you can, like a shameless plug. So I’d also highlight about our WebDevStudios blog. So you can see a lot of headless related articles, tips, and tricks. If you want to play around like, you know, you don’t have to spend something to test it out. So you can go with a lot of free starter templates.

\n\n\n\n

So we have, WDS has like WebDevStudios has a starter template. We have Next.js starter. So that’s a headless thing. All you need is your WordPress, and then you can install that on a locally in your laptop or machine, and then you can just test it out, how it looks, compare the performance and everything.

\n\n\n\n

And also, like other developers and writers have their own stuff. Like Colby Fayock is a popular WordPress developer. He has his own Next.js starter. So you can just simply Google WordPress headless starter, and you can find a lot of starter templates. If you are a developer, go this route or if you are a, you know, site owner or you are just hobbyist, you want to just try or understand a little bit more?

\n\n\n\n

You can still do that reading the resources, right? You can actually check our blog as well. WebDevStudios blog. We have, I would say a couple of headless related stuff. That’s one of the popular article last year. Why headless WordPress is trending. So you can see why it is trending, what to expect. You can read more details in that blog.

\n\n\n\n

[00:48:40] Nathan Wrigley: Thank you very much. And then finally, just to finish this off. Where could people get in touch with you? Are you available on social media? Maybe an email address? Whatever you’re comfortable with sharing.

\n\n\n\n

[00:48:50] Lax Mariappan: Sure. You can find me on, you know, Lax Mariappan. I’m on all the social media like Twitter, Instagram, Facebook, and everywhere you can find me. So you can reach out to me as an email as well, laxman.0903@gmail.com. Anywhere like GitHub everywhere is the same. Luckily I got my name on all the social media, so you can find it.

\n\n\n\n

[00:49:10] Nathan Wrigley: Lax Mariappan, thank you so much for chatting to me today. I really appreciate.

\n\n\n\n

[00:49:16] Lax Mariappan: Thanks Nathan. It’s been great. So I’ve been listening to WP Tavern Podcast for a while. Especially, I like to catch up with what’s going on. The new stuff with WordPress. So it’s good to be on the show,

\n\n\n\n

[00:49:28] Nathan Wrigley: Well, you are most welcome. It’s been a really interesting and informative episode. Cheers.

\n\n\n\n

[00:49:34] Lax Mariappan: Cheers. Thank you.

\n
\n\n\n\n

On the podcast today, we have Lax Mariappan.

\n\n\n\n

Lax is a web developer based in the Philippines. He’s an Open Source enthusiast, and lover of all things WordPress. Lax has been tinkering with websites since high school, but it all changed when he discovered WordPress in 2010. Lax currently works as a Backend Engineer at WebDevStudios.

\n\n\n\n

We talk today about Headless WordPress, and it’s a complex topic. Headless is the concept of decoupling the WordPress admin from the frontend of the site. WordPress will continue to work as expected, but the presentation layer will be done by a different technology. React, Gatsby and Remix being some popular choices.

\n\n\n\n

This implementation of WordPress is complex, requiring technical knowledge above and beyond that needed for a more typical WordPress install, but it has its benefits.

\n\n\n\n

Lax talks through all of this in great detail. How keeping on top of all the additional dependencies Headless WordPress requires can be time consuming. How it can create difficulties for content editors who don’t always get to see what their work will actually look like in real time. Why this approach to WordPress can take more time and resources during the build.

\n\n\n\n

Lax explains how these problems typically crop up, and how it’s possible to plan ahead and build in solutions for all the problems that you might encounter.

\n\n\n\n

If you’ve ever thought about going Headless with WordPress, then the podcast today is for you.

\n\n\n\n

Useful links.

\n\n\n\n

React Library

\n\n\n\n

Gatsby

\n\n\n\n

Remix

\n\n\n\n

WebDevStudio Next.js WordPress Starter

\n\n\n\n

GraphQL

\n\n\n\n

WPGraphQL

\n\n\n\n

WebDevStudio Blog

\n\n\n\n

Colby Fayock’s website

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 15:00:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:14:\"Nathan Wrigley\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:39;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:71:\"Do The Woo Community: 95% of Websites Have an Issue with Color Contrast\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74180\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://dothewoo.io/color-contrast/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:379:\"

Even just by getting your color contrast right, which is very easy, anyone can do it. You just use a contrast checker.

\n

>> The post 95% of Websites Have an Issue with Color Contrast appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 10:43:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:40;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:86:\"WPTavern: WordPress Performance Team Working Towards Unbundling Performance Lab Plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140668\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:97:\"https://wptavern.com/wordpress-performance-team-working-towards-unbundling-performance-lab-plugin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4476:\"

WordPress’ Performance Team met this week with the express purpose of responding to Matt Mullenweg’s recent request to stop adding functionality to the Performance Lab plugin which could otherwise work as a standalone plugin.

\n\n\n\n

At the end of December 2022, the Performance Team published instructions for how to test the new SQLite implementation, which was bundled into the Performance Lab plugin as a module. Mullenweg commented on the post, indicating he saw the SQLite functionality as better suited to becoming a standalone community plugin:

\n\n\n\n
\n

Can we please make this its own community plugin, hopefully to become a canonical one, and stop putting additional things like this into Performance Lab — it feels like we’re stuffing things into PL unnecessarily.

\n\n\n\n

In mid-October I have requested that we stop this unnecessary bundling before with @tweetythierry around WebP, which was put into Performance Lab, so it is disappointing that another large function like SQLite was bundled into Performance Lab plugin.

\n
\n\n\n\n

In an effort to galvanize a base of testers for upcoming performance features, the Performance Team has leaned towards bundling new performance-related functionality into the plugin. Although they are already developed as self-contained modules so they can be easily extracted as individual plugins, the concern is that their visibility would be greatly reduced. The Performance Lab plugin has more than 30,000 active installs. Any standalone plugin would take time to build up to a user base, whereas functionality added to Performance Lab has an instant audience.

\n\n\n\n

“Agreed that there are definitely valid use cases for stand alone plugins, remaining mindful of some of the advantages of a single hub plugin such as development/maintenance, adoption, promotion, developer onboarding/contribution etc. which the Performance Lab facilitates well today as a central performance focus community hub plugin,” Performance Team contributor Thierry Muller said in response to the unbundling request.

\n\n\n\n

Muller outlined three different options contributors discussed in this week’s Performance Team meeting:

\n\n\n\n
\n
    \n
  • Option 1: Keep PL as is, but additionally deploy modules as individual plugins
  • \n\n\n\n
  • Option 2: Make PL a “wrapper” focused on central infrastructure and recommendation of individual plugins
  • \n\n\n\n
  • Option 3: Deprecate PL completely in favor of individual plugins
  • \n
\n
\n\n\n\n

Option 3 seems to be the least attractive to those who participated in this week’s discussion, as it introduces more hurdles for discoverability. Performance Team contributor Felix Arntz noted that one benefit of option 1 is the plugin would continue to work as-is for the 30K people who currently have it installed and that option 2 “would require a complex migration that users likely would not understand.”

\n\n\n\n

WordPress developer Jonny Harris suggested that having each functionality in its own plugin helps with testing but also asked what defines a module.

\n\n\n\n

“Would the current Site Health checks all be together, for example?” Harris asked. “SQLite and WebP are clearly their own modules, but what about smaller things?”

\n\n\n\n

Arntz suggested contributors continue the discussion regarding the scope of how the current modules could be distributed as plugins. He suggested every module could become its own plugin where some modules become standalone plugins and others would be grouped together into a few “topic specific” plugins.

\n\n\n\n

Contributors are discussing the different approaches in more detail on a GitHub issue and will be voting on the best approach. The vote will be open until Friday, January 20, 2023.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 03:34:14 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:41;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:49:\"HeroPress: Why small can be just the right choice\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:56:\"https://heropress.com/?post_type=heropress-essays&p=5014\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:150:\"https://heropress.com/essays/why-small-can-be-just-the-right-choice/#utm_source=rss&utm_medium=rss&utm_campaign=why-small-can-be-just-the-right-choice\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:7334:\"\"Pull\nHere is Ellen reading her own story aloud.\n\n\n\n

I feel honoured to write an essay for HeroPress. While thinking about what I should write about, I wanted to make sure it will be helpful to others.

\n\n\n\n

Of course, everyone’s goals are different. My partner Manuel and I started to create WordPress products, because we saw the opportunity to build a small business and keep it a business we both felt comfortable to work in over the years. And that’s what we did. We love to travel and searched for a way to live the nomad lifestyle long before the term was even a thing. We travelled and worked on our blog and themes. And don’t get me wrong, it was not easy in the beginning. We had to build an audience first, so we wrote blog posts about everything we learned while keeping financially afloat with small client projects. We put endless hours of work into our blog, before even dreaming of one day earning income just with our themes. But we loved every minute of it.

\n\n\n\n
\n

We worked from Thailand, Sri Lanka and New Zealand, and we felt creative and free.

\n
\n\n\n\n

We went to WordCamps and creative conferences along the way and met so many new people with similar values and goals.

\n\n\n\n

Having these experiences formed our way of thinking about the way we wanted to work moving forward. The benefits of being a small team of two seemed so obvious to us. We could make decisions fast and react to new trends without asking anyone for permission. As long as we built something others liked, we would always be ok. So that’s what we focused on. We built one theme after the other and loved the creative freedom this work gave us. The positive feedback and listening to the stories our customers shared on how our themes helped them reach their goals kept us going.

\n\n\n\n

Living abroad

\n\n\n\n

As we could work remotely from any location. We didn’t need an office or a local team. Keeping our business so flexible allowed us to move from Germany to New Zealand in 2015. After about two years working towards it, we were able to apply for a business visa and eventually for permanent residency four years later. Living away from family is never easy, but the opportunity to live in another country surely teaches us so many valuable lessons we would never want to miss. It’s a true gift, all made possible by our small WordPress business.

\n\n\n\n

Reacting to changes

\n\n\n\n

Fast-forward to 2018 and the WordCamp Tokyo, where we first got the chance to dig deeper into the Gutenberg project during contributor day. We knew changes were coming, and we needed to react with our business. Even before, we felt that building one theme after the other felt a bit tiresome and not like the most effective way for WordPress users to build their site design. We were never convinced by the page builder solutions, as it just seemed too bloated and untrue to WordPress core to bring a wow effect to us. We love to keep things flexible and minimal, and adding an entire framework on top of WordPress never felt like a great idea to us.

\n\n\n\n
\n

So here comes this Gutenberg thing, a promise to a more flexible, component based way of creating designs for WordPress.

\n
\n\n\n\n

We felt like this is meant to be for us. So once home from the WordCamp we started to build blocks and explore how this new WordPress would work. We did not realize back then how big these changes would become and how much it would impact our work and our business.

\n\n\n\n

But it felt good to build something new and to try to find a better solution to offer for our theme customers. We struggled to gain footage for quite some time, as there were just so many new technical things to figure out and so much was unclear. But we still never doubted that we are on the right track, as with every new release the opportunities seem to get better and more stable.

\n\n\n\n

And just now we are just about to relaunch our business websites with a brand-new block theme that is solely built with our blocks, WooCommerce blocks and WordPress core blocks. It finally feels like all the work comes together and themes and the Gutenberg project are ready to be merged into one and released for production.

\n\n\n\n

Opportunity to pivot

\n\n\n\n

During all these changes, we had the time to think about the future of our WordPress business and what we want our road ahead to look like. Many others around us have sold their independent businesses or took a job at one of the big WordPress businesses. I feel like it’s also a natural path of WordPress and all of us growing up.

\n\n\n\n
\n

For us, we feel like we are just getting started again, finally having found a way to have fun creating for WordPress again.

\n
\n\n\n\n

Building one of our last classic themes, we felt like we had lost the fun in designing for WordPress. We felt like themes were stuck, being either too inflexible and or way too bloated to be any good. It felt like we were trying to build, squeeze out a solution into a product that technically was never meant to be this way.

\n\n\n\n

Block themes, the site editor, patterns, and blocks come as a chance for us to do it better. It’s a big shift and a difficult project to pull off, for sure. WordPress is used by so many people in so many ways. But block themes make WordPress lighter, and they don’t stand in the way of other add-ons as much as classic themes felt they were. It’s amazing how we can take all the components apart and mix and match them together. There are still missing pieces, but we are getting there.

\n\n\n\n

For us, we are taking this shift that we are sort of making together with WordPress, as an opportunity to make things better. We always felt like we wanted to offer more support and help to our customers. But we never found the time. So with our upcoming relaunch, we are taking the chance to change that. We will offer new services and are exploring more ways to offer our customers what they actually need. It feels like a breath of fresh air to us, and we haven’t had so much fun with WordPress in a long time.

\n\n\n\n
\n

It’s funny, who would have thought that a piece of software can impact your life in such a big way.

\n
\n\n\n\n

WordPress has impacted where we live, who our friends are and which destinations we like to visit. We feel more open-minded because of WordPress, we believe in the power of open source projects and we believe that a group of people from all over the world can build something meaningful together.

\n

The post Why small can be just the right choice appeared first on HeroPress.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Wed, 11 Jan 2023 01:15:30 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Ellen Bauer\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:42;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:63:\"WordPress.org blog: WordPress is Turning 20: Let’s Celebrate!\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://wordpress.org/news/?p=14155\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:74:\"https://wordpress.org/news/2023/01/wordpress-is-turning-20-lets-celebrate/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:1474:\"

2023 marks the 20th year of WordPress. Where would we all be without WordPress? Just think of that! While many technologies, software stacks, and fashion trends have come and gone throughout the past two decades, WordPress has thrived. This is due to the fantastic work and contributions of the WordPress community, comprised of thousands of contributors; and millions of users who have embraced the four freedoms of WordPress and the mission to democratize publishing.

\n\n\n\n

Let’s celebrate!

\n\n\n\n

Throughout the beginning of 2023, leading up to the official anniversary date of WordPress’s launch (May 27, 2003), a number of different events will celebrate this important milestone, reflect on the journey, and look toward the future.

\n\n\n\n

Please join in!

\n\n\n\n

Over the next few months, be sure to check WordPress’s official social media accounts along with the official anniversary website for updates on how you can be involved in this exciting celebration by contributing content, collecting cool anniversary swag, and much more. 

\n\n\n\n

Use the hashtag #WP20 on social media so the community can follow along.

\n\n\n\n

If you have something planned to celebrate that you would like to be considered for inclusion on the official website, please use this form to share the details.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 21:38:49 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:11:\"Dan Soschin\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:43;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:88:\"Do The Woo Community: WooCommerce, Payments and Crypto with Keala Gaines and Dave Lockie\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74249\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:52:\"https://dothewoo.io/woocommerce-payments-and-crypto/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:416:\"

Keala Gaines from WooCommerce and Dave Lockie from Automattic chat about the relationship between WooCommerce and Crypto.

\n

>> The post WooCommerce, Payments and Crypto with Keala Gaines and Dave Lockie appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 10:11:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:44;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"WPTavern: Gutenberg Times to Host Webinar on How to Use New WordPress Layout Features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:30:\"https://wptavern.com/?p=140874\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:96:\"https://wptavern.com/gutenberg-times-to-host-webinar-on-how-to-use-new-wordpress-layout-features\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:4790:\"

Gutenberg Times will be hosting a live Q&A webinar titled “Layout, Layout, Layout” on January 11, 2023, at 05:00 PM in Eastern Time (US and Canada) via Zoom. This event is open to WordPress users of all experience levels who are interested to learn more about how to use WordPress’ layout features when building sites with blocks.

\n\n\n\n

Host Birgit Pauli-Haack will be joined by WordPress veterans Isabel Brison, Andrew Serong, and Justin Tadlock. Brison will be demonstrating different layout scenarios during the presentation, and attendees will be able to participate with questions.

\n\n\n\n

Any user who has attempted to layout a design in WordPress has likely tried out container blocks that offer layout settings. These blocks include Columns, the Cover block, and the generic Group block.

\n\n\n\n

The event will cover how to manipulate layouts by defining the width of post content, arranging blocks horizontally or vertically, right or left aligned, and inside container blocks.

\n\n\n\n

“In terms of block styling, Layout is a complex feature because it affects child blocks in ways that go beyond CSS inheritance,” Pauli-Haack said.

\n\n\n\n

WordPress 6.1 introduced more layout controls and flexibility in the block editor, but Pauli-Haack said the dev note on updated layout support was written more for developers.

\n\n\n\n

“Feedback from users through the FSE program and other connections revealed that handling the layout settings for container blocks is not particularly intuitive and takes some trial and error to find the right combination,” she said. “The Live Q & A will bring a better understanding to users and #nocode site builders.”

\n\n\n\n

When Pauli-Haack started the Live Q & A’s in 2018, she routinely brought in guests who were building the block editor, with the intention of having users meet them and discuss features like full-site editing, block themes, case studies, and discuss challenges.

\n\n\n\n

“Since then, quite a few initiatives of the official WordPress project have come to life,” she said. “There is the highly successful Full Site Editing outreach program, spearheaded by Anne McCarthy, who now holds regular Hallway Hangouts with community members and contributors.”

\n\n\n\n

People are also learning the ins and outs of site editing through the efforts of the training team, which began creating courses and lesson plans and hosting workshops on Meetup.com in 2021. These are also recorded and uploaded to WordPress.tv and YouTube. WordPress.org also launched a blog for developers in November 2022. With all these new learning opportunities, Pauli-Haack is changing the focus for her live events.

\n\n\n\n

“For the Gutenberg Times Live Q & As, I am now looking at topics and discussions about more complex concepts, more case studies, and technology on the cutting edge,” she said. Most recently, the show featured the developers and digital strategies of the Pew Research Center, a high profile site that was built with a block-first approach.

\n\n\n\n

“We are also in planning phase to hold a Live Q & A with the developers of GiveWP who are using Gutenberg as a framework to build the next generation of their popular donations plugin with the components and scripts that Gutenberg uses, but outside the post or site editor,” Pauli-Haack said.

\n\n\n\n

She also has another Live Q & A planned with the WordPress VIP design team that works on design systems for companies that need a streamlined way to stay within their design standards. Pauli-Haack intends to talk with them about a plugin they created that lets designers automatically create a website’s theme.json file with all the styling pulled directly from Figma designs.

\n\n\n\n

The upcoming Layouts webinar is free but attendees need to register to get the zoom link. An archive of all the past Live Q & A events is available on the Gutenberg Times website. The best way to stay informed about future events is to subscribe to Gutenberg Times’ Weekend Edition, as subscribers get an early invitation for the next Live Q & A’s.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Tue, 10 Jan 2023 03:37:42 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Sarah Gooding\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:45;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:23:\"Matt: State of the Word\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:22:\"https://ma.tt/?p=75018\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:42:\"https://ma.tt/2023/01/state-of-the-word-2/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:365:\"

A few weeks ago, but what feels like a lifetime ago, I was in New York City with a few dozen extra special people from around the WordPress world. Alongside Josepha and the community we presented this review of how WordPress did in 2022, and vision for what’s coming:

\n\n\n\n
\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 23:25:18 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:4:\"Matt\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:46;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:85:\"Post Status: Support Inclusion in Tech with Winstina Hughes — Post Status Draft 136\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:32:\"https://poststatus.com/?p=146189\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:92:\"https://poststatus.com/support-inclusion-in-tech-with-winstina-hughes-post-status-draft-136/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:52731:\"

In this episode, Winstina Hughes joins Cory Miller to talk about the  Support Inclusion in Tech project created to champion diversity, equity, and inclusion in the WordPress community by providing assistance to WordCamp speakers for travel and hotels.

\n\n\n
\n\n\n\n

Estimated reading time: 59 minutes

\n
\n\n\n\n\n\n\n\n

Transcript

\n\n\n\n

Winstina Hughes  is a long-term community member and organizer within WordPress. She joins Cory Miller to discuss Support Inclusion in Tech, an effort to increase representation of minority and underrepresented speakers at WordCamp by providing needed financial support. This offers everyone in the WordPress community the chance to share their expertise and contribute resources so everyone has the opportunity to engage.

\n\n\n\n

Top Takeaways:

\n\n\n\n
    \n
  • Creativity is our common bond. WordPress is the playground where we all came to tinker and build for fun or business. It is the software magic where we discover what new things we can do each day, how to make ideas become reality, and how we might leverage what we learn to create a better world.
  • \n\n\n\n
  • Ripple effect of inclusion: When you provide the ability for a large group of people to participate, travel, and network, the impact extends beyond the WordPress community to create the bigger changes we want to see in the world. This is our community magic.
  • \n\n\n\n
  • Fifth Freedom in WordPress: We are all familiar with The Four Freedoms of WordPress. This is the 5th – full participation. Removing the financial barrier will bring us closer to the reality of being a truly inclusive community.
  • \n
\n\n\n\n
\n\n
\n\n\n\n
\n
\n

\"🙏\" Episode Partner: Gravity Forms

\n\n\n\n

Gravity Forms is a powerful form builder for WordPress and the #1 choice for businesses and web professionals across the globe. Its vast array of features, intuitive drag-and-drop form editor, and extensive ecosystem of add-ons, ensure customers can design beautiful, intelligent, and accessible forms for any project requirement.

\n
\n\n\n\n\n
\n\n\n\n

\"🔗\" Mentioned in the show:

\n\n\n\n\n\n\n\n

\"🐦\" You can follow Post Status and our guests on Twitter:

\n\n\n\n\n\n\n\n

The Post Status Draft podcast is geared toward WordPress professionals, with interviews, news, and deep analysis. \"📝\"

Browse our archives, and don’t forget to subscribe via iTunes, Google Podcasts, YouTube, Stitcher, Simplecast, or RSS. \"🎧\"

\n\n\n\n

Transcript

\n\n\n\n

Welcome to back to Post Status draft. I\'m with a good friend of mine when Winstina hughes. I met with Winstina a couple years ago in the post status community. We\'ve got to meet in person, talk numerous times, and, um, I\'m excited about what we\'re gonna be talking about here. Um, she\'s got a new, a project called support inclusion in tech.com and we\'re gonna dive into that today.

\n\n\n\n

But, uh, hi, Winstina. Hi. And pumped to finally have you on,

\n\n\n\n

Winstina Hughes: I\'m excited to be with you.

\n\n\n\n

Cory Miller: Could you tell us what you do in WordPress?

\n\n\n\n

Winstina Hughes: Okay. Um, What do I do in WordPress? every time I speak with, you know, every time I have one of these, um, you know, opportunities to speak with someone in the community, I end up like re repeating the question.

\n\n\n\n

Um, cuz it really helps me. I am a community member, um, and I\'m also, you know, a, an organizer, um, a meetup organizer and a board camp organizer. I started, um, going to [00:01:00] meetups in New York City and I transitioned into, Speaking, um, at Word Camp, New York City, and then I was invited to become a meetup organizer.

\n\n\n\n

And so, um, my, you know, my participation in the community was, um, you know, like in the early, um, you know, 2010s. And then around 2015, 2016, um, I started, you know, speaking at, at New York City, and then I became an organizer. I meet up organizer. In 2018, I led my first word camp and my only word, camp , hundred twenties, um, a budget of 120,000, a team of 18.

\n\n\n\n

Uh, it was an amazing experience. They were wonderful people and it was. Really tiring .

\n\n\n\n

Cory Miller: Yeah. You know, over the years, Winstina, I\'ve had so many dear friends that have been Word Camp organizers and really I go, oh my God, I love you so much because of what you\'re doing for the community. But I also go, I hope you [00:02:00] still like word this afterwards because it\'s a such a labor of love that I think, um, so often we don\'t really give the credit and thanks to the people, That do this voluntarily.

\n\n\n\n

Yeah, like you\'re talking about all the stuff you\'re done. So anyway, I wanna say thank you because I\'ve said it so many times to dear friends over the years going, thank you for what you\'re doing. I\'ve always shied away from it because it\'s so much work and I see all the passion and energy that you and other organizers have and I\'m really thankful cuz I think that is so critical to the entire community to have these, and now we\'re talking in 2022.

\n\n\n\n

But we hear WordCamps are back. You and I got to see each other in San Diego at Word Camp US. Yes, yes, yes. So

\n\n\n\n

Winstina Hughes: anyway, so Word, word camp Us. I was a co-organizer for Word Camp US this year. Um, and so yeah, you\'re right. Like we had a chance to teach other again there, and that was like, yay. That was awesome.

\n\n\n\n

Cory Miller: It was, yeah.[00:03:00]

\n\n\n\n

Yeah. A absolutely. Well, okay, so what drew you to, okay, how did you start with WordPress? Were you using WordPress for, uh, your own website, somebody else\'s website? How\'d you get started with the actual software?

\n\n\n\n

Winstina Hughes: So I started with WordPress in 2006, 2007. Um, I had a college course that was . Yeah, I, I had a college course.

\n\n\n\n

Um, and our professor required us to add, um, you know, the work that we\'d done, uh, into a wordpress.com blog. Um, it was a geographic information systems class. And, uh, we were looking at public health data at the census block level. Um, and so we were actually, you know, looking to see. You know, where, um, there were instances of like, um, I don\'t wanna say disease, but you know, like different illnesses.

\n\n\n\n

And so what what\'s really interesting is that you can, that schools get access to that data and you can actually like, You can [00:04:00] essentially imagine, and I don\'t wanna go too far deep into it, but imagine you have like, you know, Google Maps, right? And like when you have Google Maps open, you can do street view.

\n\n\n\n

So Google Maps lets you like go from that whole, um, like that map into like street view where you jump in as a person. So, uh, this data essentially took you away from just the geographic element, um, and the typography and like really. The census, you know, track level, like essentially, um, you know, looking at neighborhoods and, you know, the instances of disease in those neighborhoods.

\n\n\n\n

And so he, you know, he gave that to us as our final assignment. Um, you know, we did some like, uh, some heat mapping to show where there were greater concentrations of a particular type of illness, , right. Um, or, um, you know, disease or, you know, Uh, I\'m not exactly sure like what, what we [00:05:00] were calling it, but that\'s what our assignment was.

\n\n\n\n

And, uh, he asked us to, you know, take like a picture of the map and to post it in wordpress.com and that\'s how it all started with that , with that assignment. Um, so we were you.

\n\n\n\n

Cory Miller: We were using WordPress at the same time. That\'s the same year I started with WordPress when you started. I did not know you went that, that far back with WordPress.

\n\n\n\n

So I love that. Uh, yeah, I do. Thank you. And then you said like in 2010 you started actually, uh, getting involved with community events. And this is relevant to us talking about support, inclusion and tech. So what drew you to start participating in volunteering and contributing to WordPress?

\n\n\n\n

Winstina Hughes: So I went to New York City Meetups, um, and, uh, WordPress, New York City, uh, is the one that\'s in closest proximity to where I lived.

\n\n\n\n

I could just take the train in. Um, and it was, it was great. Like I, I really felt, um, that the community there was, was [00:06:00] open, like the organizers were open and, and they were welcoming. Um, Dana, rendy, uh, those were organizers at the time, Steve Bruner, who was an organizer. Was he is the organizer, , he started it and he\'s, he\'s kept it, you know, um, really like strong, like, since its inception.

\n\n\n\n

Um, and so like just going to these events and meeting these, these like wonderful generous people, these kind people, um, you know, meeting Kevin Cre, Christiana there as well. Um, and you know, just that environ. Was what led me to continue attending events. Um, and they really encouraged me to submit a talk to speak at New York City, um, ward Camp, New York City.

\n\n\n\n

And I submitted a talk to speak there and, you know, since that time I\'ve been more engaged in. Event organizing component, [00:07:00] um, or part of the community. So it moved beyond just, you know, um, Like learning, you know, to use Word Pro, you know, building sites and breaking them, uh, the best, right? Yeah. Like, that\'s the best way.

\n\n\n\n

That\'s the only way you can really learn. I mean, I, I started, you know, with different hosting plans, I\'ve had like four or five, like I have multiple domains. Like I think when you\'re in our space, you got a chance to really create. And, um, and that\'s what I was able to do and what I\'m able to continue doing, and.

\n\n\n\n

Now moved from just creating and building with WordPress to assisting with supporting, you know, our community through events like meetups and, uh, word camp organizing and supporting inclusion in tech is, is an extension of, um, of this work, this contribution that I\'ve been doing. It, it, it pieces together so many different elements that I\'ve come to, like I\'ve come to see and I\'ve come to understand [00:08:00] and. It\'s, it\'s a solution that I propose to, um, some current challenges that, um, I\'ve heard being expressed. Yeah,

\n\n\n\n

Cory Miller: I, uh, I wanna scroll back for a second and say that when you\'re talking about create, I sometimes it, for as long as you and I have been in WordPress sometimes forget that magic of being able to create something on the web or in the, in the world.

\n\n\n\n

See this cool tool called WordPress, so I appreciate that. I think that\'s what we rally around in the WordPress community and particularly to post status is helping build tools and projects and things on top of this magical thing we call WordPress. So that was a, when you said create, I was like, it\'s just little tingle of magic came up of that\'s, that\'s why we, I think that\'s our common bond in

\n\n\n\n

Winstina Hughes: WordPress.

\n\n\n\n

I agree. I agree. And I think that, uh, when we create as community members, um, and not necessarily [00:09:00] just as. Business owners or, or, um, you know, those who are providing like services. That\'s a component of creating. But, you know, in the middle of doing all that, I think, you know, I mean, I like to sit down and just literally play and see, you know, what could I do with it today?

\n\n\n\n

and, um, I entered a com competition, um, held by Sustainable New Jersey, um, right around the time I completed graduate school. And there were municipalities that were seeking, um, solutions for challenges that they had. And there was the city of East Orange and they wanted like a marketplace, um, and a place for their planning department to, you know, add their documents and also something for their green team.

\n\n\n\n

And when I saw this, I was like, I could use WordPress and e-commerce. So I created like a WooCommerce marketplace for them to sell, you know, for residents that would sell their products and services. And I demoed it. Um, and then I also had a website and also a Buddy press site. Um, and the buddy press site would be for their green team members.

\n\n\n\n

And I think that [00:10:00] like, when, when we create with WordPress, like we\'re able to like see like, you know, These asks and really apply like our knowledge of what we know the c m s can do and then provide a solution. And the city was actually really happy with the solution. Um, and I made it to the finals of the competition.

\n\n\n\n

Um, but there was another, uh, but there were other teams that that won it. Um, but it was, it was really exciting to show what WordPress, you know, software and what WooCommerce can. Uh, that\'s the

\n\n\n\n

Cory Miller: dream. Um, that, that\'s so awesome. Thank you for sharing that backstory. As much as we\'ve talked and stuff, I haven\'t got the chance to ask those questions and, um, it\'s a good reminder for me about, you know, I think if you go long enough in the community, you start to, well, I, I\'ll say I start to.

\n\n\n\n

Forget some of these nuances, [00:11:00] like being able to go, here\'s a project idea, this could be done in WordPress, you know? And that the tools are mostly freely available. Yes. And you can start and build something online.

\n\n\n\n

Winstina Hughes: Exactly. Yeah. You just, you know, download . Yeah. Yeah. Well, yeah.

\n\n\n\n

Cory Miller: So that leads me to support inclusion and tech. And you mentioned you saw a problem or problems and challenges in our community that you wanted to help make some a solution to toward it that became support, inclusion and tech. But can you talk about that a little bit? Cause I know my, my understanding and you continue to help me expand my understanding of all this is it\'s not just one particular country with DEI, it\'s a global thing. But could you talk a little bit about the problems and challenges that you saw in the space.

\n\n\n\n

Okay.

\n\n\n\n

Winstina Hughes: Absolutely. Um, absolutely. So I, you know, really wanna, like, I wanna hold true to like, um, to how [00:12:00] I, um, shared it on my website. Um, but really the backstory is that. There was a conversation that erupted on Twitter, um, about the need for more diversity on Word Camp, um, organizing teams. And this started, uh, due to, um, you know, uh, some, some thoughts that were expressed about Word Camp Europe, uh, where WordCamp Europe\'s organizing team, um, not being.

\n\n\n\n

Very reflective, um, of, you know, more ethnicities or a wider range of them. Um, it was a really difficult conversation that was happening. And my take on it really is that it\'s not where camp you\'re specific, right? Like, I mean, let\'s, you know, let\'s really step back and think about the fact that, you know, there\'s so many ethnicities around the world that have a ch [00:13:00] like it\'s really.

\n\n\n\n

When you\'re in the minority as a group, Really up to the group that\'s in the majority to weave you into those experiences and those opportunities. Um, and when that doesn\'t happen, then you have groups that don\'t have an opportunity to be, to participate and to be involved and, you know, support inclusion and tech.

\n\n\n\n

I mean, considering this was a conversation about word camps and our participation in them. Support, inclusion and tech really seeks to assist us in solving the challenge of, um, not having as much, you know, ethnic or, um, or just diverse representation within the Word camp experience. It doesn\'t seek to, you know, um, it doesn\'t, it doesn\'t seek, you know, to like solve, um, Like these, you know, the world that we [00:14:00] live in.

\n\n\n\n

And it doesn\'t seek to solve like, um, you know, diversity and inclusion outside of the WordPress space. Um, but I believe that in, in providing these, um, these opportunities within our community, since we\'re so large, that the ripple effects can extend well beyond the WordPress community. I believe that when you, Absolut.

\n\n\n\n

When you provide such a large group of people, the ability to, um, to participate in work camps, um, the ability to travel to them, the ability to network to them with, when you attend, um, the ability to like, you know, seek, um, you know, out more relationships, friendships, professional relationships. Then there\'s this ripple that extends outside of our community and I think.

\n\n\n\n

That level of empowerment can extend outside of WordPress and those ripples can assist us in diversity inclusion beyond, [00:15:00] um, you know, our, our involvement in WordPress. But you know, this, this particular solution is intended to solve the challenge that I saw, you know, um, being expressed, you know, within our community.

\n\n\n\n

And so the thought is really, Since, you know, since there\'s a take on it. And there\'s, it\'s a, I mean, it\'s an, it\'s an honest one, right? We don\'t see enough people of color. We don\'t see, um, enough, you know, people of, um, other minority groups, um, you know, uh, from other parts of the world. Um, You know, we are seeing an equal, more equal balance of, um, men and women.

\n\n\n\n

Uh, you know, but when it extends beyond that into like, you know, more representation in terms of like, you know, a wide range of religions, which ties to ethnicity often. Um, and when you\'re looking at representation in terms of those of us who, um, have like neuro [00:16:00] diversion, you know, um, you know, like, uh, characteristics and those of us who, um, you know, who we choose to love, , you know, the what society, um, you know, asks of us , right?

\n\n\n\n

Like, and um, and when we choose to hold true to that or when we\'re dealing with the physical limitations, um, that, you know, that we were born with or when we\'re in minority. Groups, you know, that have a harder time, you know, uh, receiving opportunities, um, to participate and to increase, you know, their reach and even, um, you know, the professional opportunities that are available to them.

\n\n\n\n

You know, like this. What can we do to, um, to really like solve. To solve that. Mm-hmm. And I thought, what could I do within our community Yep. To, you know, to integrate, you know, like all of those of us who are, um, Either, you know, disadvantaged [00:17:00] or not as represented into WordPress programming and support, inclusion and tech, um, seeks to, you know, take away that financial barrier, which I believe is really what, you know, can limit our participation.

\n\n\n\n

We want to participate, we want to speak, but if we can\'t afford to speak , right? I mean, if we can\'t afford to travel to the conference and if we can\'t afford a place to stay at the conference, um, then. Like, why would we even think to apply to speak at the conference? Right? Like,

\n\n\n\n

Cory Miller: yeah. That\'s, that\'s really beautiful, Winston, because, um, there\'s a couple of takeaways I, I got from this.

\n\n\n\n

Number one is, I, I\'ve always believed, um, at least in my world, that WordPress has been. The, an inclusive place, ever growing inclusive community. That\'s like a mirror to my world, the way I want my physical world here in Oklahoma to be. And I have [00:18:00] so much learned from our community leadership, uh, over the years, um, that there\'s a cons.

\n\n\n\n

Consistent push and drive from the entire community and the leadership to be truly diverse, truly inclusive in all those words. And I, I learn a lot from this. Um, so the mirror I, and I do think WordPress is, our community is so powerful cuz we\'re distributed all over the world. So if we make change in our community, in our WordPress, That should be, that should be reflected.

\n\n\n\n

And I think that\'s another, we talked about the software magic. This is the community magic. Exactly. Uh, the other thing is, I, I love and I respect because I try to take too much on that, you said, Hey, here\'s something I\'m passionate about, being an organizer, being at these community events, how special and valuable they are to you and other people instead.

\n\n\n\n

I\'m gonna make this dent first. Yeah. Like, I\'m gonna, I\'m gonna take on this aspect first. You. Beautifully and clearly [00:19:00] shared. This is the thing I\'m trying to take on in this bigger, bigger, um, change that you wanna see. We wanna see in the world. Thank you. Okay, so we\'ve got this now we\'ve got a website. Um, you\'ve got a website up to kind of share this.

\n\n\n\n

Now. Take me through, if you would, I am, pretend for a second you\'re talking to someone that is in an underrepresented, uh, in, in tech. Of, um, situation. Mm-hmm. , how\'s the process to, to get on the, Hey, I want to go to these WordCamps. I want to speak, but I do need some assistance. What does that process look like for, for support inclusion and tech?

\n\n\n\n

Winstina Hughes: So support, inclusion and tech. Um, also weaves into other initiatives in order to, to assist our speakers. Um, and so when you\'re accepted, support inclusion tech, it become, moves into the position to, to assist you once you\'re accepted into [00:20:00] Word Camp. Um, as soon as you get that, you know, acceptance, you know, go to https://supportinclusionintech.com/.

\n\n\n\n

Um, and you\'re simply just, you know, gonna put in the word camp that you were accepted in. And then there are two components, um, in addition that they\'re suggested , right? Like you\'re encouraged to do this. Um, you know, uh, we\'re in the community of consent and so, um, you have, you know, um, You\'re gonna give, you know, the consent to be included in these other initiatives, um, you\'re not gonna be forced into it.

\n\n\n\n

Uh, there\'s underrepresented in tech and there\'s also the WordPress diversity, speaker channel. Um, both of those, uh, are ways of. Further supporting diversity and inclusion and representation within the WordPress space and creating, you know, um, you know, successful opportunities for us to, um, to, you know, to put together great speaker applications and then to also, um, you know, move beyond just submitting [00:21:00] them. Um, but to being accepted.

\n\n\n\n

The, the ask is that, you know, once you\'ve been accepted to camp and you\'re starting the process of, you know, receiving funding through supporting inclusion and tech, that you also participate in those other two initiatives as well. Um, because you know, in the process of doing that, it\'s further supporting the work that we\'re doing in the WordPress community. Exactly as you said, Corey, that, you know, the word WordPress leadership already has been putting in, um, you know, the work to, you know, to assist us in resolving the challenges that face society as a whole. And so there are initiatives that currently exist and those two in particular, I think.

\n\n\n\n

You know, are ways that we can continue to support underrepresented minority groups in the WordPress community. Um, and so in the process of, you know, uh, applying for the funding, uh, you\'re encouraged to, you know, to list yourself on underrepresented tech to join the, um, the, the diversity speaker channel [00:22:00] on make WordPress.

\n\n\n\n

Um, and then once you\'ve just put on, put that information in and you\'ve identified the type of support that you\'re seeking, um, you just like, and it starts from there. Like I start, um, you know, pairing you with, you know, with a partner that you know can, can step in and provide, you know, the funding for you.

\n\n\n\n

And so, you know, they\'re gonna cover your travel and they\'re gonna cover your hotel. Um, and that way in order for you to participate, you\'re not going to be paying anything really that you know, out of pocket. For that participation, um, in that WordCamp. And that\'s really the goal. Um, the goal is to remove the financial barrier to your participation.

\n\n\n\n

Cory Miller: Yeah, that\'s fantastic. By the way, I wanted to sidebar for a second and say underrepresented in tech, uh, by Allie and Michelle Frechette. If, if you\'re listening to this and, uh, you also as a be becoming a member of underrepresented in tech, get a free [00:23:00] uh, professional membership at post status?

\n\n\n\n

Winstina Hughes: Yes. Yes. I started, I and let\'s not also forget too, that like there are other opportunities as well as Post Status has been, um, you know, looking into as ways of increasing, you know, diversity and representation within the Post Status community. Um, so underrepresented tech and that membership, and I know that there\'s some other ways that you\'re working on it too, Corey.

\n\n\n\n

Um, you know, I think, I think when we can pull all our efforts together. We have a stronger community. Um, and you are, you know, you\'re, you\'re offering that and then supporting inclusion and tech, you know, encouraging, you know, speakers to, to register and to participate in those two other programs. Strengthen all our efforts. Yes. Um, and, and that\'s, you know, that\'s the process of it. And so once you\'ve submitted, you know, your. once you submitted that form, you know, just letting me know, like the speaker registration that you\'re seeking, the [00:24:00] support. Um, you\'re also gonna complete the blind directory listing and that blind directory really.

\n\n\n\n

That Blind directory listing has the word camp that you\'ll be speaking at. Um, and it has the type of support that you\'re seeking, whether it\'s just beach travel or hotel, or both, and that\'s it. Um, no one in the community, um, you know, needs to know who you are. They don\'t need to know what your need is. Um, they don\'t need to know where you come from.

\n\n\n\n

And they don\'t need to know what makes you underrepresented and what makes you a diverse speaker. Uh, it\'s simply a way for, um, for companies that are considering sponsoring to see that the need does exist. And it\'s also a way for our community to see that the need does exist, um, and that we do have members that are seeking the support.

\n\n\n\n

Um, that, that blind directory listing is, is just a way, you know, for our community to see that, um, that our need is there. Um, yeah, and it\'s also a way of, um, uh, [00:25:00] keeping everyone up to date on the work that\'s happening.

\n\n\n\n

Cory Miller: So I know we\'ll have two asks. The first ask is if, um, you need assistance, want assistance to go to a WordCamp to be sure to go to supportinclusionintech.com?

\n\n\n\n

Winstina Hughes: Yes. Once you\'ve been accepted, go to supportinclusionintech.com. Complete the form for speaker registration, and you\'ll, um, you\'ll be paired currently, um, with four companies, uh, that, um, that have partnered to work on this.

\n\n\n\n

Cory Miller: That comes to our second ask. Yeah, that\'s right. Okay. So tell me how, um, now this is very relevant for post status because we\'re a bunch of professional and business members in our community.

\n\n\n\n

So the second ask is, we need someone, one, participants, people that need and want assistance go and speak at Word Camps. And the second part of this is the sponsors and partners. Can you tell me a little bit more, more about that and [00:26:00] how that.

\n\n\n\n

Winstina Hughes: Okay, so starting off, partners are sponsors, , um, partners are the first, um, you know, companies that expressed an interest in supporting this project.

\n\n\n\n

Um, you know, this initiative. Uh, and so like that is my way of thanking you, um, by, you know, by acknowledging. that you came into this, um, wholehearted and opened armed. And so thank you to the four companies, um, that have done this, uh, that have stepped forward to say that they will support. Um, you know, it was really exciting once the call went out, um, from Word Camp US that they were seeking, uh, support for underrepresented.

\n\n\n\n

Speakers. It was really exciting because Master wp, um, stepped in at that time, you know, to say that they thought that this was a great project. And, you know, they\'re the fourth company they joined, um, GoDaddy, Post Status, and Yoast, um, you know, the original three that said that, you know, that they would love to support this initiative.

\n\n\n\n

And so, um, now we have, [00:27:00] we have four of them and there are also several companies as well that are providing in-kind donations. Um, and, you know, they\'re doing so, makes it possible for support, inclusion and tech, um, you know, to, to function, right? Because like, you have the website and then there are all these different like plugins that make it functional and make it possible, you know, for, um, for, for it to run and function the way that we need to.

\n\n\n\n

Um, so if your company that wants to. Sponsor speakers, you know, you just have to go to the site. Um, there is a section there for you to register your support, um, your register, your desire to support. It\'ll ask you, um, you know, to provide, you know, like a contact. Um, it\'ll ask you the type of, uh, How you want to provide this support.

\n\n\n\n

Um, would you prefer to reimburse speakers for their expenses or are you, um, ready and, you know, willing and able to pay for their, um, their travel and their hotel in advance of their trip? [00:28:00] Um, so, you know, once you\'ve identified your contact, you know, your contact is the type of support that you want to provide, you know, then, you know, we\'ll have an opportunity, I\'ll have an opportunity, you know, to really. Sit down with you and for us to have a conversation about like, you know what would be your process, you know, what would make it easy or for you to be a part of this initiative? Um, this isn\'t a cookie cutter means of support for, for companies, because you\'re all different.

\n\n\n\n

Um, how GoDaddy, you know, is providing support is different from how Post Status is providing support is different from how Yoss is providing support. And it\'s different from, you know, how, um, Master WP is and, uh, When I started this, and I, you know, I, and I wrote on my blog, like, really this proposal on https://winstinahughes.com/.

\n\n\n\n

I went into it, um, you know, with the understanding, personal understanding is that it\'s gonna [00:29:00] take a couple years to understand the needs of our community and the ways, you know, companies and our ecosystem can support these needs. And in the last six months, Exactly what I, you know, anticipated, um, is what I\'ve been able to, you know, to, to see.

\n\n\n\n

And, you know, currently, um, there have been three, you know, requests, um, for, you know, to participate, you know, um, for funding, for support, for camps and, um, two unique, you know, individuals have, have made those requests. Um, and you know, so right now it\'s a question of. You know, like assisting them, you know, with the process of how, you know, our four partners, you know, can support them in that way.

\n\n\n\n

Um, and I think that answers part of your question. Um, the second part of the question is like, so how is this financial component gonna work? Right? [00:30:00] Like, are companies giving me money? No, you\'re not , like, I\'m not receiving, you know, um, any of the money. Is the financial support that you\'re providing. Um, instead it\'s looking at your company\'s processes, um, you know, your, your financial processes, your accounting processes for you to, you know, step back and think like, how could we as a company provide this level of support?

\n\n\n\n

Um, you know, it could be that you already have an existing program. Yoast already has a diversity fund. Um, and so Yoast partnering with me is a way of, um, you know, kind of bringing the need that exists to them as well. Um, and so therefore they\'re able to like further serve the community, um, you know, through those who are expressing an interest through support inclusion and tech.

\n\n\n\n

Um, the way Post Status, you know, is seeking the support speakers too, is different from Yoast. Um, and, you know, uh, [00:31:00] Yoast has a budget, um, and.

\n\n\n\n

Has their own system and their own ways of support. Um, and so they also have a budget and then Master wp, they also have a budget. And so once that budget has been met, then you know the partners essentially gray out for that year. Um, and they become active the next year. Um, and so. , that is a way of making this sustainable.

\n\n\n\n

You know, you, you pledge how much you can support, um, speakers financially, and once that has been met, then your, I mean, your capacity for the year is, is, is met. And then next year, once you\'ve reallocated your budget, or not re reallocated, but once you\'ve defined, you know, your budget, um, for the year, then you would go, you know, back into the process of supporting [00:32:00] speaker.

\n\n\n\n

Cory Miller: And I wanted to say from personal experience here, that there\'s many way, there\'s, there\'s creative ways to support these speakers, uh, to go, you know, uh, you, you talked about hotel and flights. Yeah. And, um, I, I wanna, I wanted to say that one standard to say this is not an unapproachable. Opportunity to support d uh, diversity inclusion in tech.

\n\n\n\n

Um, this is very manageable for most members at Post Status, by the way. So, you know, flight costs, uh, depending on where it is in the world. Um, I think the first question you asked me was, what\'s your budget? Yeah. And that\'s a great way. So as you come in and click sponsor, just be thinking of these things with when st for how you can.

\n\n\n\n

Help support this amazing project. Um, and that there\'s creative ways to do that. And I, I think Winstina, most members, business members at Post Status can make a meaningful contribution in this way [00:33:00] through this, your project here. And I love the fact also, I know we talked about this too, you wanted to be real careful.

\n\n\n\n

You wanna say you want the support to go to the person as best as possible. A lot of nonprofits have overhead. You have graciously generated your time and your talent to this project, and I, I, I love the way you\'ve done it too, even though I go, gosh, Winston, I love that you have this passion. Um, but thank you so much for this.

\n\n\n\n

But I know you give of your own time. For this particular project, but as you talk to Ena, if you\'re listening to this now, there\'s creative options and ENA is so good at helping you, helping understand where you\'re at, and then pair it with people that need assistance.

\n\n\n\n

Winstina Hughes: Thank you. Yes, and that is, that is the goal.

\n\n\n\n

In terms of my contribution to the WordPress community, burnout is so real and because of the fact that I work full-time outside of the WordPress space, the WordPress ecosystem, um, I\'m really [00:34:00] cognizant of the fact that I need to perform well. And at a high level , right? Uhhuh , um, at work, you know, and in my personal life.

\n\n\n\n

And WordPress fits into, um, you know, into that. And so I\'ve been able to contribute in different capacities since I was in college and. Graduate school, first attending in college, um, or post-college in graduate school, moving into speaking and organizing, um, and now working, you know, professionally maintaining, you know, organizing as a meetup organizer and a WordCamp organizer, and understanding that this can really lead to burnout.

\n\n\n\n

You know, um, my ultimate decision is, you know, that for the next two years, I\'m not gonna be a WordCamp speaker, and I\'m also not gonna be a. Organizer, you know, this, these are the ways that I can, you know, I can continue to contribute. I can contribute through support, inclusion and tech. Um, you know, but really pair, pair down all the other ways that I could burn out.

\n\n\n\n

[00:35:00] And so by maintaining, Being a New York City meetup organizer and hosting at least a minimum of six meet meetups a year, and, um, really pivoting and concentrating my energy towards support, inclusion and tech. I can sustainably contribute to the community. And so this is a perfect opportunity to really share with you, um, that, you know, I want to meet with every speaker.

\n\n\n\n

You know, that expresses the interest for support. So as you submit your, you know, your speaker registration and you join the directory listing, I will, um, you know, I\'ll ask to meet with you, for us to have a conversation, for me to understand your needs and to share. what it is I understand and I\'ve learned over time, and also how our partners seek to support.

\n\n\n\n

So we\'ll have that conversation. It\'s gonna be on the weekend. I hope you graciously incorporate that into your schedule. Um, because, you know, I, I work during the week, [00:36:00] um, and so, you know, we\'ll meet once. Uh, hopefully within a week or two of your registering as soon as possible. Especially it\'s, it\'s, it\'s ideal, uh, not ideal.

\n\n\n\n

It\'s encouraged to register as soon as possible, um, because the closer you get to your ward camp, you\'re gonna. Most likely, um, be reimbursed if you apply much sooner, like a, like two or three months in advance. You know, there are companies that will be able to, you know, cover your, your, your costs, um, of participation in advance of your trip.

\n\n\n\n

If you are reaching out like three to two. You know, to the time of, of your support that the time that you need, then you\'re looking at being reimbursed for your expenses. And so like, you know, that\'s, that\'s something to, to keep in mind when it comes to registering, you know, for this is that companies will be able to assist you with removing this.

\n\n\n\n

It just might be [00:37:00] later. When your need is expressed closer to the time that you\'re speaking that it\'s more, it\'ll be a reimbursement instead. Um, and so that\'s something to keep in mind, the timing in which you submit your interest, and also the fact that, um, you know, that we\'ll be meeting on a weekend. Um, there\'s this speaker that just registered and he wanted to meet with me.

\n\n\n\n

Um, On Christmas, he\'s in another part of the world. I mean, you know, like, so yeah. Um, and so I just, you know, I just like, I think when, and I had a con, you know, I just like responded and let him know that it\'s, it\'s Christmas for me. I\'m, you know, I\'m a Christian and I\'m so celebrating my holiday today. Um, you know, and, you know, like, uh, let\'s, let\'s meet next week.

\n\n\n\n

Um, so, you know, uh, we\'ll have like, you know, we\'ll have these conversations and we\'ll, we\'ll see. And you know how. Um, you know, how you and I can, can have that conversation and [00:38:00] meet and how your need can be met. And I\'ll also meet with, you know, companies that wanna sponsor as well. And I wanna tell you, I want you to tell me what\'s realistic for you.

\n\n\n\n

Um, I want just, just to, just to give you a sense of how some of the companies are. In fact, um, you have, uh, of the four partner. You have one partner who seeks to provide support, um, you know, within the us. Um, as of our last conversation, you know, the desire is to support minority speakers, um, specifically people of color, um, specifically, you know, black Americans, um, to improve or those of black descent to improve, um, their numbers.

\n\n\n\n

WordCamps in the US. Um, our last conversation was, you know, this is the direction that they wanna go. This is the greatest impact that they think that they can achieve. Um, and [00:39:00] I\'m, I\'m so glad that I get to listen to what everyone. Hopes to do, you know? Um, because it gives me a sense too that our community is really thinking through, like, this is how we\'re gonna solve it, right?

\n\n\n\n

Like, this is how we\'re gonna make the dent that we wanna see. So this company already knows this is how we\'re gonna make the dent that we wanna see. And there, there. Process too, is that they\'re just gonna give you a blanket amount of money and they\'re not gonna micromanage how it is you spend it. Um, they just simply ask, you know, that you, not simply, the requirement is that you put it towards your WordCamp experience and that\'s where they are with it.

\n\n\n\n

Um, there\'s, you know, another company host of course, has an established diversity fund and they have processes already in place for the support. And so you\'re simply gonna go through the existing process that, um, Yoast has established and they have a generous fund. Um, and their support, um, is something [00:40:00] that they\'ve been offering the support for a long time, and they\'re very, um, they\'re really respected , you know, for that effort.

\n\n\n\n

And, um, I\'ve had an opportunity to like, you know, to speak with someone who has been a part of their support in the past or received it and they speak so highly of, of Yoast um, and that\'s, you know, Yoast has already thought it through and they\'ve already walked through. You know, Corey and I, you and I have spoken about, you know, the budget, you know, that you\'re, that post status is set aside and, and you\'ve already shared.

\n\n\n\n

You know, what is the need? Like, we\'re not micromanaging, right? Like, let us know what type of support that you need, and we\'re just gonna provide that to you. Um, and so like you\'re, you are already thinking about like, how can we make this happen? Like, you know, if you need to, you know, it\'s a flight, you know, wherever it is.

\n\n\n\n

It doesn\'t have to be domestic, right? Like, it doesn\'t have to be in the us it could be anywhere in the world. Um, and, and that\'s, you know, that\'s like, [00:41:00] Post Status is thinking, and then GoDaddy is currently working through their process. Um, and I do believe that because of the fact that they have teams around the world that GoDaddy\'s reach will also be of, um, I think GoDaddy\'s reach will also extend beyond like the domestic, you know, like within the US and they\'ll be able to provide support as well toward camps.

\n\n\n\n

you know, around the world. I\'m anticipating it\'s possible that, um, GoDaddy\'s like impact could, you know, be especially strong with, um, Uh, WordCamps, like Word Camp Asia or Word Camp US or Word Camp Europe. Um, you know, because they\'ll have team members there and when they have team members there that can help facilitate and smooth the process over for, for those that they\'re going to be supporting, but they\'re working through their processes to make this established as well.

\n\n\n\n

And so I think [00:42:00] that, you know, just by me sharing that, you can tell that, you know, each of, you know, my partners are, are working within. Um, you know, like their business processes and their financial processes and also their vision for impact. Um, and I think that\'s really important.

\n\n\n\n

Cory Miller: So to recap, here\'s what I\'ve heard.

\n\n\n\n

So support inclusion and tech.com is the bridge between those that want have the desire to share their exper experience and expertise at word camps, but need some financial assistance to get their flights and hotel. That\'s what f support inclusion and tech.com does. Second, as a participant, as someone.

\n\n\n\n

Um, if you first need to apply and get, uh, approved to speak at work camp, then come to support inclusion and tech.com and, um, sign up, have a conversation.

\n\n\n\n

Winstina Hughes: Mm-hmm. , once you\'ve been approved.

\n\n\n\n

Cory Miller: Have a conversation with we, Tina. [00:43:00] And then third, the third recap is our ask for, um, well buzzer asked in our community.

\n\n\n\n

Uh, if you\'re looking to speak to Word Camp, go to support inclu or go apply, get approved, come to support inclusion in tech. And then second for those businesses out there. You know, you have a heart, you wanna support this. That\'s our community. That\'s who WordPress is. Uh, go to support inclusion and tech.

\n\n\n\n

Click on the sponsor link and have a conversation with ena. Think about your budget. Think about what you wanna do, uh, when Cena is so creative in helping just make these connections happen so you can really make a difference in our community. Did I get it all right?

\n\n\n\n

Winstina Hughes: You did. You did get it right And, okay.

\n\n\n\n

And I think that support inclusion tech also. It goes through vetting process as well to confirm that those who are seeking assistance, you know, to participate actually have been accepted. And that\'s why, that\'s why the steps are what they are. Um, partners aren\'t gonna [00:44:00] question, oh, is this need real? You know, that vetting is gonna happen in advance.

\n\n\n\n

So when you receive a speaker interest, You know that this is someone who has been accepted a Word camp, and they understand the process and they\'re working within, you know, your, your policies and your procedures, um, in order for them to participate. So it removes all those questions. Um, you know, so that and that, yeah, that\'s a part of it.

\n\n\n\n

Cory Miller: Well, Winston, my friend, thank you so much for this important work, uh, holding the banner up. I know this takes a lot of time. I know you\'ve got a full-time gig. I know you\'ve got a life

\n\n\n\n

Winstina Hughes: more,

\n\n\n\n

Cory Miller: um, But I so much appreciate you post. I just appreciate you, our members do for doing this vitally important work and making a difference in our world that can, like we said, can be a reflection in all these thousands of communities we go out to, to say, how can I be more inclusive?

\n\n\n\n

[00:45:00] How can I make sure everybody is represented as at least an opportunity to be represented? So I really appreciate you, Winstina, and your work and also just ringing the bell with me and teaching me and sharing, um, how we can make, make that difference. So I appreciate. Thanks for being. I\'m thanks for being on.

\n\n\n\n

Winstina Hughes: Sorry. No, no. I mean, I absolutely, like, this gives me life and it makes me wanna show up in the world, you know, different and energy. I wanna exercise more like , you know, like this is, this is, this is really in a lot of ways just like giving me energy to contribute. And so, um, to like, just to be able to like, work with you, you\'re, you\'re, you know, I\'m, I think you\'re awesome

\n\n\n\n

You know that, ditto. You\'re, you have a beautiful family. You know, like your energy is like, you have such great energy and so just a chance to work with you and like the amazing people that I\'ve had a chance to, it, it just, it gives me life and it makes me want to live more, you know? So like, let\'s, let\'s [00:46:00] see what we can do to continue to support our community so that the four freedoms, you know, I think that it\'s, , it\'s creating a fifth freedom, which is, you know, for all of us to be able to participate in a truly inclusive, um, community.

\n\n\n\n

And, you know, that speaks a lot to what the co-founders of WordPress. I think, um, you know, what, what they created and, and where they want, um, what their vision is and, you know, from their vision where we\'re, um, going and or how we\'re evolving as a community. I mean, to have 40% plus of a reach on the.

\n\n\n\n

There\'s so many people around the world that are impacted by this project, you know? So, um, yeah, I love

\n\n\n\n

Cory Miller: that. Let\'s, let\'s add the fifth Freedom. I love that win. Coined by Win, and I love that leadership vision for our community. We need it. Thank you. Thank you, ma\'am. You have a good rest of your year and we\'ll see you in the next year.

\n\n\n\n

For everybody listening, thanks for listening. Tune in, go to support inclusion in [00:47:00] tech.com, and also Winstina Hughes is in our post Slack community. So you can go at Wednesday and you can ping her and, um, get the conversation started there. So thank you, Eena.

\n\n\n\n

Winstina Hughes: Thank you. Thank you, Brent.

\n

This article was published at Post Status — the community for WordPress professionals.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 19:17:12 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:13:\"Olivia Bisset\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:47;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:59:\"Do The Woo Community: Do the Woo is Headed to WordCamp Asia\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:28:\"https://dothewoo.io/?p=74283\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:58:\"https://dothewoo.io/do-the-woo-is-headed-to-wordcamp-asia/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:373:\"

We are looking forward to attending WordCamp Asia and also are proud to be a media partner this year.

\n

>> The post Do the Woo is Headed to WordCamp Asia appeared first on Do the Woo - a WooCommerce Builder Community .

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Mon, 09 Jan 2023 10:19:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:5:\"BobWP\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:48;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:99:\"Gutenberg Times: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:53:\"https://gutenbergtimes.com/?post_type=podcast&p=23141\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:114:\"https://gutenbergtimes.com/podcast/gutenberg-changelog-78-state-of-the-word-wordpress-6-2-gutenberg-14-8-and-14-9/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:41325:\"

Birgit Pauli-Haack and Hector Prieto talked State of the Word, Gutenberg releases 14.8 and 14.9, WordPress 6.2 and beyond. 

\n\n\n\n

Show Notes / Transcript

\n\n\n\n\n\n\n\n

Show Notes

\n\n\n\n

State of the Word

\n\n\n\n\n\n\n\n

Gutenberg Times Live Q & A

\n\n\n\n

Gutenberg Times Live Q & A: January 11th at 5 pm ET / 22:00 UTC Layout, Layout, Layout.

\n\n\n\n

Isabel Brison’s talk at WordCamp Asia

\n\n\n\n

WordPress and Gutenberg Releases

\n\n\n\n\n\n\n\n

Stay in Touch

\n\n\n\n
\n\n
\n\n\n\n

Transcript

\n\n\n\n

\n\n\n\n

Birgit Pauli-Haack: Hello, and welcome to the 78th episode of the Gutenberg Changelog podcast. In this first episode of 2023, I wish all our listeners a wonderful, happy, prosperous and healthy new year. In today’s episode, we will talk about Gutenberg releases 14.8, 14.9, WordPress 6.2 and beyond. I’m your host, Birgit Pauli-Haack, curator at the Gutenberg Times and WordPress developer advocate, a full-time contributor to WordPress Open Source project. My guest today is Hector Prieto, full-time contributor on the WordPress Core team, coordinating multiple WordPress and Gutenberg releases. And it’s a great pleasure to finally have you on the show, Hector. Having a conversation about Gutenberg and WordPress with you is a wonderful way for me to start this new year. Happy New Year, feliz año nuevo, Hector. How are you today?

\n\n\n\n

Hector Prieto: Happy New Year. Hi, Birgit. I’m excited to join you on the podcast. It’s my pleasure.

\n\n\n\n

Birgit Pauli-Haack: Oh, the pleasure is really all mine. Where are you right now? Did you have a great holiday break?

\n\n\n\n

Hector Prieto: I’m currently in Alicante in Spain, very close to the Mediterranean Sea. And today we have a lovely sunny winter day with nearly 20 degrees Celsius. I had a few days to recharge and spend time with the family. What about you, did you enjoy your holidays?

\n\n\n\n

Birgit Pauli-Haack: Yeah. Well, that’s some warm weather there in Alicante. I would love to have that. But here in Florida it’s balmy, too. It’s about 27 degrees, so we are in the air conditioning right now. Yes, my husband and I, we spent the week in Mexico City between Christmas and New Year’s. We saw some great art, powerful murals from the ’50s and ’70s and ’60s. And we had fantastic food and a fabulous New Year’s event. It was great, at a restaurant over the roofs of Mexico City, so we really liked it.

\n\n\n\n

Hector Prieto: Wow, sounds really nice.

\n\n\n\n

Birgit Pauli-Haack: Yeah. Well, Hector, as you are the first time on the show, maybe you can share briefly with our listeners your WordPress origin story. When did you come across WordPress the first time and what do you work on now?

\n\n\n\n

Hector Prieto: Well, my first time working with WordPress was around 2015 when I worked at the startup agency building sites. However, it wasn’t until 2020 that I first moved into the contributor space, and here we are. I am currently sponsored by Automattic to work full-time in Core in project management-related duties and supporting the development of WordPress.

\n\n\n\n

Birgit Pauli-Haack: That’s wonderful. Well, thank you. So 2015, that’s just about two years before Gutenberg was introduced into the community. Did you, at your agency, have Gutenberg on the radar already, or did you heed the call to learn JavaScript deeply?

\n\n\n\n

Hector Prieto: It wasn’t until 2018 that we started using Gutenberg for the first time, when it was first released in 5.0.

\n\n\n\n

Birgit Pauli-Haack: Yeah. Then the time between learning about WordPress and then starting contributing, that’s about five years. That’s pretty much the time that it took me to really embrace the contributing on WordPress, but I started at the Community Project in 2014. 

\n\n\n\n

Announcements

\n\n\n\n

All right, so there are a few announcements that were happening since the last podcast episode. If you haven’t watched it yet, the recording of Matt Mullenweg’s State of the Word is available on WordPress TV. The transcript and answers to the questions that didn’t make it into the recording can be read on the follow-up post, State of the Word Reflections. Josepha Haden Chomphosy kicked off the State of the Word with a reminder on the four freedoms of WordPress, that you are free to run the program, you’re free to study and change the code, you’re free to distribute your code and also redistribute WordPress.

\n\n\n\n

She also recorded a separate WP briefing with her reflections in episode 45, State of the Word Reflections in which she highlights, among other things, learn WordPress, that 12,000 students actually went through the courses and the workshops already since the inception. And she also highlighted the WordPress Playground, which is a tool to run WordPress in the browser. You don’t need a server, you don’t need a database. You can run it in the browser and test plugins and themes. I think that changes how we approach some of the discovery for WordPress. We talked about it on the show here as well, but it’s definitely something that will have so many ramifications in the WordPress space later on when it’s still very raw and very not production ready. It’s just an idea that has already a proof of concept. And then the recap posts from the community are linked in the Gutenberg Weekend Edition 239 from December 17th, and you can check it out from there.

\n\n\n\n

I also have a side note that the Pew Research Center received a shoutout for the politology quiz that they built with blocks and had one million people already taking it. Seth Rubenstein is the lead developer and was a guest on a Gutenberg Times Live Q&A last year. And he gave a great demonstration about their team’s work with the block editor, so as they went for the Gutenberg first approach building the website. The recording is available on the Gutenberg Times YouTube channel, and also we have a post here on the Gutenberg Times website as well. So as always, all these links are in the show notes of the 78th episode. So Hector, do you have any comments on this? What is your most exciting topic from the State of the Word? You had a few takeaways?

\n\n\n\n

Hector Prieto: Yeah, there were a handful of them. I would actually highlight everything, starting with WordPress Playground. It’s such amazing technology and it’s going to open so many doors. But if I had to pick something, maybe for me because it is the thing I’m the closest to, it was a great recap about the progress WordPress made in the site editing front during the last year, to the point nowadays we can create themes directly in the editor just with blocks and patterns. This brings us very close to wrapping phase two and starting exploration around phase three in 2023. So it’s great to see that all these progress.

\n\n\n\n

Birgit Pauli-Haack: Yeah, you’re right, you’re right. And it’s been such a long journey as well. I look back at some of the history on the Gutenberg Times and the Gutenberg podcasts, and we first started talking about full-site editing in January of 2020. That was even pre-pandemic, and we had quite a few developers on our live Q&A talking about the first concepts about that. So now, three years later, it’s almost finished and it’s really cool. There are still some things to be done, but I am really excited about the start of phase three of collaboration and I have been constantly trying to unify all the various tools and methods and interfaces to streamline my workflow to produce content for the web. And if I don’t have to use multiple tools to collaborate with people, I will have arrived on internet nirvana. Yeah, it’s a high calling of course, but yeah, we are all in a space where we could maybe make it happen. So I’m really excited about that.

\n\n\n\n

Hector Prieto: Yeah. Also, it’s worth noting that even when we move to phase three and we can call a wrap on phase two, phase two will not be fully finished because there’s always going to be things to do related to site editing improvements, new tools. So I can see contributors working in new features for phase three and also iterating on phase two items. Another big takeaway for me during State of the Word was seeing how much Gutenberg itself has matured. And it’s now been used in more projects such as Tumblr, bbPress, and even in some mobile apps like Day One. Also, let’s not forget how WordCamps have made a comeback after COVID hit and stopped all the in-person events. And we went from one single WordCamp in 2021 to up to 22 in the last year, in 2022. That’s amazing.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s a nice iteration of the numbers. 22 WordCamps in ’22.

\n\n\n\n

Hector Prieto: Exactly. Especially since the community is what makes WordPress what it is, it’s the most important part of WordPress. So that’s really good to see.

\n\n\n\n

Birgit Pauli-Haack: Absolutely. Having the first WordPress in-person event in WordCamp Europe, I realized how much I missed interacting with everybody else in the community and seeing new faces and interacting with old friends. I looked up the number of WordCamps that were done in 2019, in-person WordCamps, and there were 148, or 145, something like that. So there is quite a bit of time to go between 22 to 142 or something like that.

\n\n\n\n

But it’s coming back especially because all those WordPress meetups, the local meetups, are all coming back as well. I think there was a note in the State of the Word that out of the 500, 260 have already come back to in-person events. And we know that WordPress meetups are actually the prerequisite to actually have local WordCamp organizers together to organize a WordCamp. So yeah, it’s all coming back and I’m glad that it’s coming back because of the connection that you have in the community. Yeah.

\n\n\n\n

Hector Prieto: As I mentioned earlier, I came to the contributing space in 2020. It was during the pandemic, so actually my first WordCamp was the only WordCamp in 2021. And my second WordCamp was for computer ware in last year. So it was really nice and refreshing for me to meet all the other contributors. It is something special, for sure.

\n\n\n\n

Birgit Pauli-Haack: Absolutely, yeah. It was great to meet you, Hector, although we had so many meetings with people on Zoom. Yeah.

\n\n\n\n

Hector Prieto: Fun times.

\n\n\n\n

Birgit Pauli-Haack: Yeah.

\n\n\n\n

Hector Prieto: Well, circling back to State of the Word, I would also like to point out that, last but not least, it’s really cool to see how Openverse has grown since joined WordPress about a year and a half ago. And I’m super excited to see that coming, Openverse integration in WordPress that will allow users to directly search and add images from Openverse into their WordPress site without leaving the editor at all. That’s super cool.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s super cool. And I think it would also be really cool to have that also go back to if somebody uploads an image to WordPress and checks the check mark, also put it into Openverse. I think that part would really make it to a 360 kind of integration. I also love that there’s not only for images, but there is a lot of audio already uploaded to the Openverse that you can use on podcasts or on videos, and add free without having to think about royalties and buying for it and all that. Yeah, so free to the community.

\n\n\n\n

Hector Prieto: There’s so many possibilities there. The future is exciting.

\n\n\n\n

Birgit Pauli-Haack: Yeah, it’s really exciting. And I’m glad that it’s all happening in conjunction with WordPress. The same with the WordPress photos library, where people can just upload their photos and have it be it in the public domain and make it available to the broader community. It’s really cool.

\n\n\n\n

Hector Prieto: Yeah.

\n\n\n\n

WordPress 6.2

\n\n\n\n

Birgit Pauli-Haack: All right. So between Christmas and New Year’s, Hector, you published the release schedule proposal for 6.2. I think it was something we were all waiting for. Kind of, okay, how do we plan first quarter when we don’t know when the release is coming? So you provided. So if the release team concurs, what’s the plan? When will we see the first Beta?

\n\n\n\n

Hector Prieto: If the proposed plan is approved, the first Beta release will be on February 7th, which is 10 days before the first of our WordCamp Asia takes place.

\n\n\n\n

Birgit Pauli-Haack: Excellent. In the planning schedule, you also have a call for contributors to volunteer for the release squad. So if you, dear listeners, are inclined to take part in it and you already have a little experience in contributing, throw your hat in the ring by commenting on the release post on the scheduled proposal post. And also throw your hat in the ring also means for those who English is their second language, also means raise your hand, you want to volunteer to be part of it, and then the release team is coming together. When do you expect that you will have a final plan?

\n\n\n\n

Hector Prieto: The call for volunteers is open as we speak. Considering the end of the year vacation people are taking, contributors taking, I think we won’t have anything until end of next week or the following one. We’re leaving some extra time for people to come back from the holidays and chime in.

\n\n\n\n

Birgit Pauli-Haack: All right. Okay. Yeah, so there are only two more Gutenberg releases before the feature freeze, if I calculate that correctly. We better get started in reviewing all the great new features that are coming in, in a more consolidated way.

\n\n\n\n

Hector Prieto: Definitely. I encourage all of our listeners to start testing and giving feedback. It’s always super helpful. Also, compared to the past releases, the proposed 6.2 schedule both include a fourth Beta release compared to the previous three ones to leave some extra buffer time between WordCamp Asia and release candidate one, which will be on March 7th for a final release on March 28th.

\n\n\n\n

Birgit Pauli-Haack: Oh, okay. Yeah, so contributor day at WordCamp Asia is definitely going to be part of it and that is really cool to have. Maybe we need to organize some tables that do some testing there. I don’t know how far the work of Asia contributor day team is about that, but having that plan definitely gives us all focus on that contributor day. All right, cool. So to repeat that, final release could be March 28th, so that’s about three months from today. And we will have a 6.2 release, provided everything works out as we anticipate now.

\n\n\n\n

And I have a reminder for our listeners now for next week. The Gutenberg Times Live Q&A, Layout, Layout Layout will be happening on January 11th at 5:00 PM Eastern. That’s 22:00 UTC. And in this show, Isabel Brison, Andrew Serong, Justin Tadlock and I will discuss the opportunities and challenges for all the layout features for site builders. And we will be available for questions and answer them.

\n\n\n\n

And Isabel Brison will also give us a demo of the various layout scenarios to use. She has, with Andrew, been instrumental in building all the features into the site editor and the blocks, and it’s going to be a very interesting show. It’s also going to be a little preview on Isabel Brison’s talk at WordCamp Asia in February 2023. So join us, link us in the show notes, and don’t forget you need to register there and to be… We will have a recording, of course, with the show notes and as well as a transcript, but it’s always good to have your questions answered live by the experts on the panel, and we have some great experts there. 

\n\n\n\n

What’s Released

\n\n\n\n

So, that brings us to the latest Gutenberg releases. First, there’s Gutenberg 14.8. That was released in December 12th. Ryan Welcher was release lead and it had 167 PRs merged by 42 contributors, five of which were first contributors. So welcome to the project, first contributors. So Hector, what’s the most significant enhancement in this release?

\n\n\n\n

Hector Prieto: Well, Gutenberg 14.8, so several changes to the site editor user interface, and introduce something I’m super excited about, which is browse mode. Thanks to this first iteration of browse mode, users can switch between editing and browsing modes in the site editor, making it much easier to navigate through templates and template parts or even add new ones through the sidebar. It’s a feature that has been long awaited and it’s finally here and I’m super excited.

\n\n\n\n

Birgit Pauli-Haack: Yeah. And it helps you with where you land when you click on the site editor. You are now not landing into editing your homepage and so now you have a better entrance into the site editor. And I really like that because it gets you better settled into what you’re going to do.

\n\n\n\n

Hector Prieto: It makes for a nicer onboarding and it’s less dangerous, let’s say, because it’s much more difficult to break your design just as soon as you land on the site editor.

\n\n\n\n

Birgit Pauli-Haack: Yeah, totally. So the navigation block also had some enhancements, especially with the migration from the old menu. So if you have a location primary, it will now fall back to the navigation menu from the classic menu. That is really helpful on the transition. There are other fallback updates made that it also uses the most recently created menu from the classic theme when you start migrating to a block theme.

\n\n\n\n

Enhancements

\n\n\n\n

So that is definitely a good help for transitioning from a classic theme to a block theme. But also it kind of decreases the mental load that you don’t have to recreate all your menus when you switch out the theme, which is something that was sometimes really critical in the classic menu, in the classic theme space, where everybody had different menu locations. And so I don’t think that it’s completely solved yet, but this is definitely a first step.

\n\n\n\n

Hector Prieto: Yes, it’s a step on the right direction. We all know building menus is one of the most challenging aspects of building your site. And contributors are making a huge step for making the menu building process much easier.

\n\n\n\n

Birgit Pauli-Haack: Yeah, absolutely. Yeah. There’s also one that came with 14.8 that is for the query block. The parent block is they removed the color block support just because it was always clashing up against the other blocks that are in the query block for the post template for the title and the excerpt. You could kind of get lost in which color did we do, and where do we do that? So removing it from the wrapper query block is definitely a good choice because it removes some of that confusion of where colors are actually set.

\n\n\n\n

Hector Prieto: Exactly. Contributors have seen a few inconsistencies when adding the color to the wrapper query block, between the title, between the navigation links. So now the colors block supports are all in the inner blocks and there’s no space for confusion.

\n\n\n\n

Birgit Pauli-Haack: So what else do we see there? Yeah, I think that was it on the 14.8 release, on the highlights. There are certainly the sidebar tabs for the navigation blocks. There is great work on the experimentation that happened. So right now we have five areas of experimentation in the Gutenberg plugin and there is only two more freeze, two more releases to get them out of experimentation into the production of the Gutenberg plugin. One of them is the sidebar for the navigation block. The other one is the separated settings tab in the sidebar that separates the styles from the features. And then the others, I don’t recall right now. Hang on, I’m going to check them out. I just had it there and then I closed my browser because, I don’t know, sometimes I just randomly close browser tabs, which is a really good way to confuse myself.

\n\n\n\n

Hector Prieto: The type interface is making good progress and it’s something we would likely see out of experimental very soon.

\n\n\n\n

Birgit Pauli-Haack: Oh, yeah. And then is the global styles for custom CSS is actually in… We all wait for that, but it’s now in the experimental stage and need to be switched on through experiments menu item on the Gutenberg plugin. And then the other one is the color randomizer utility that lets you mix the current color palette randomly and change it out. That’s kind of a funky way of handling your website to do a randomized color palette, but it certainly is a proof of concept of something bigger. Was there anything else in the 14.8 you want to mention, Hector?

\n\n\n\n

Hector Prieto: Well, there are a few other main highlights that you might have seen, our listeners might have seen in the release post. One of them is super interesting, which is the custom CSS rules for your site. There’s now a tiny CSS text field where you can add your custom CSS directly in the editor. As we all know, with great power comes responsibility. So it’s nice that you can add a custom CSS directly in the editor, but let’s not overuse the important.

\n\n\n\n

Birgit Pauli-Haack: Yeah, that’s definitely a way to… But that was before, so site editors or site users or site owners who used the custom CSS piece found that that was the missing piece to actually sign on to the full site editing, because they couldn’t do those very fast changes like changing a font size somewhere or changing a space somewhere or change the color of a border very easily by just using the developer tools, identifying the marker, the selector, and then just change the color in a custom CSS. Yeah, it opens up the capabilities for that.

\n\n\n\n

You need definitely have file editing capabilities on the server and that sometimes was not available to anybody. But those who used it, they really missed it in the file site editing, in the site editing features, so that is really a good thing. And there is also a… It’s not yet released and it’s not merged yet in, but I know that Carolina Nymark is actually working on custom CSS for single blocks. And I think that’s also a good way to, in the paradigm of getting atomic design going, that that’s probably a better approach than having custom CSS being pulled in for every site page or page with the custom CSS. Rather do it per block.

\n\n\n\n

Either way, it’s kind of a interesting feature that people want to have some control or at least go back to that what they are used to do and figure out how they can change it. Well, that definitely was a changelog of 14.8. I don’t think we’ve forgotten anything. I think we, in the release post by Ryan, it was a reorganized…. Oh, the style book is definitely something that was in 4.8. We haven’t talked about it. So do you want to talk about it, what that does?

\n\n\n\n

Hector Prieto: Oh yeah, definitely. The style book is a super cool new feature, which is extension of the style site editor. The style book in a nutshell gives you an overview of all the available blocks you have in a single place so that you can easily browse all the blocks you have available and play with their design.

\n\n\n\n

Birgit Pauli-Haack: When Gutenberg first came out, there were quite a few initiatives where you could have a unit test for blocks, where I think Rich Tabor actually had a plugin and I also worked with some of our clients back then when that we had a list of all the blocks in a page and then looked at it, how the theme works with it. And that was kind of a block unit testing in design. And with the additional features that come with site editing, it was a hard time to figure out what is a change in color on the paragraph block will have additional ramification throughout the site, or when you change style variations.

\n\n\n\n

So I’m really glad that the style book, that’s a menu item in the site editor. You can go there and then see all the blocks that you have. And you get an access to the style variations of your theme so you can select them and then see how the blocks change. And that is so powerful that you don’t have this save and surprise effect anymore. You really look at it and say, “Oh yeah, I like it.” And you also see where the style variations may not be entirely working for your site because there are some things that are left out. So this is so powerful for the experience with the block editor.

\n\n\n\n

Hector Prieto: For our listeners to picture it in their mind, it’s like having a page with demo content with all the blocks. You have it registered either core blocks or third party blocks. So as soon as you install it, applying that provides blocks, all these third party blocks will appear in the style book. And you will be able to see all of them together, play with the global styles, play with the accelerations, and see how they affect all these first party and third party blocks in a single place as if you have demo content page but automatically generated for you.

\n\n\n\n

Birgit Pauli-Haack: Yeah. So it really offsets the need for these block unit testings and it’s very, very powerful. Yeah, I so agree. I think we’ve got it all now. Let’s move on to the next release, which was 14.9. And it has at the time of this recording not been released, but it will come out any hour now. For those who use the Gutenberg plugin on their sites, it’s the first Gutenberg release for 2023. 132 PRs by 46 contributors. Again, five new contributors in there. Congrats for your merge of your PR, and welcome to the project. Thank you so much for your contributions, for all of them.

\n\n\n\n

Hector Prieto: It’s refreshing to see all these new contributors, even in these more maintenance oriented releases that happen during holidays. So congratulations to you all, and welcome.

\n\n\n\n

Birgit Pauli-Haack: What are the highlights? What did you see, or what’s in the release?

\n\n\n\n

Hector Prieto: There are a few changes. They’re mostly iterative, building on top of past features and enhancements. One of them, one very cool, is a new push to global styles button that appears in the cyber blocks, which allows users to, once they edit the blocks’ style and they like it and they say, “Hey, I like this how this is looking or how this image is looking. I would like all my image blocks to look like this.” It allows them to push those styles to global styles so that they automatically affect all the blocks of that type.

\n\n\n\n

Birgit Pauli-Haack: Right. And that’s also why it’s good to have this style book handy so you can actually see if you made a mistake or something like that and said, “Oh no, I didn’t consider this, so let’s do one more time.” Yeah. So that’s a great feature. Yeah, absolutely.

\n\n\n\n

Hector Prieto: Also, for those who like building patterns, now when registering patterns, there’s a new property that allows you to specify in which template a pattern makes sense. Let’s suppose, for example, we are building a 404 pattern. Previously it would be released everywhere, so it would appear everywhere in all kinds of templates. Now you can limit it to only appear on a 404 template, so it doesn’t bring noise to other templates where it doesn’t make sense. So this is going to improve pattern discoverability in general as patterns.

\n\n\n\n

Birgit Pauli-Haack: And it also improves separation of concerns. As you said, it will not show up on every page where even if it’s not suitable for the pattern. But it also themes can then, or plugin can now create custom post types and that all, and just make those patterns available for certain custom post types. I think that is definitely a missing piece that has now been added to it. Excellent. Yeah, I’m really excited about that.

\n\n\n\n

Hector Prieto: Yes, there’s a minor update following up on Gutenberg 14.5. So we are thinking, we’re looking at two months ago, three months ago, when the list view and the document outline were merged in a single panel. We have seen there are a few improvements that can be made in the design. So now, for example, the word count has been moved to the top of the outline for more clarity.

\n\n\n\n

Birgit Pauli-Haack: Yeah, there was some confusion. Where is it now? Yeah. And then you didn’t see it at first because when you hit on update, you post and then the little notification ball totally covered that piece. So it took a while till that goes away so you see the word count and the time to read and also the block count. There were a few pieces missing. I don’t know why they’re missing, but they probably don’t seem to be very important for content creators to see. And the outline, having the other one on the list here in one it definitely makes sense to have that.

\n\n\n\n

So if you haven’t seen that yet, it was in Gutenberg 14.5. It will come to 6.2, so checking it out through the Gutenberg plugin is definitely worth trying, worth a look so your site owners or the clients are prepared to find it in a different space. What I’m also very excited about is that there is now an option to import widgets from the sidebars into template parts. And that is in the whole idea of transitioning from a classic theme to block themes or make a site be better prepared to move to a site, to full site editing block theme. This is definitely a step forward. Any additional thoughts on that?

\n\n\n\n

Hector Prieto: Oh yeah, absolutely. This is a very important milestone towards block adoption because it allows users to migrate from classic widgets to native blocks. It’s worth noting that it doesn’t work on template focus mode yet, it’s only available for the block inspector. But this is definitely a step on the right direction to increase block adoption.

\n\n\n\n

Birgit Pauli-Haack: Excellent, yeah. And George Mamadashvili, who heads… That’s his PR. He also has a nice video on how he demonstrates how it’s going to work. So I hear quite a few people celebrating this piece to make the transition over. Another one is, this is minor thing, but the configurable settings for the fluid typography in the theme JSONs now has a minimum font size, so it can be anchored on the smallest font size. So the fluidity then can increase the font size on a bigger screen. There was a hard coded value of 14 pixels before, with no way to change. And now you can have the minimum font size, like 16 pixels or 18 pixels depending on your site needs. That’s a minor thing, but I think it is something that quite a few designers were missing.

\n\n\n\n

Hector Prieto: Yes, absolutely. It’s a minor improvement, but we’ve seen lots of these minor improvements in the last, I don’t know, four or five Gutenberg releases building on top of free typography. And when you look at them altogether combined, you can see huge improvements on how the feature is becoming more and more powerful by the day.

\n\n\n\n

Birgit Pauli-Haack: Yeah, absolutely, absolutely. I think that was at 14 point… No, one thing is still really important from the release and that is the adding shadow presets support for the theme JSON. So you can do box shadows on your blocks or wrapper blocks, and that is now available for designers of themes. There is no user interface for that yet. But as we said repeatedly here on the podcast, things will be…

\n\n\n\n

Hector Prieto: It will come.

\n\n\n\n

Birgit Pauli-Haack: Hmm?

\n\n\n\n

Hector Prieto: It will come.

\n\n\n\n

Birgit Pauli-Haack: But it’s important to make it work for the theme developers first. Before you have all the added implementation for site owners that want to change it, you first need to know how it’s actually working so you can see where the pieces are that need to be surfaced in a user interface. So there is a new setting object called shadow, and then you can add different palettes to it for natural and crisp and sharp and soft shadows. And the PR has quite a few information about how that’s implemented. It gives you quite a few use cases on how you can do the shadow boxes for the buttons, for cover block, for menu block. If you have a sticky menu, then you can put a little shadow underneath it to see the difference between the page and the menu. So there are quite a few design use cases to try that out.

\n\n\n\n

Hector Prieto: I’m curious to see what designers come up with thanks to this new setting. I can see lots of 3D buttons and shadow buttons and all these cool things.

\n\n\n\n

Birgit Pauli-Haack: You could even do the outlines of the shadows, kind of, if you have an outline… Yeah, there are some great designs out there right now. So, from the changelog we are on, anything else that you wanted to talk about here that we missed? I know that Tonya Mark has updated the tracking issue for the web fonts API. And what’s merged in this release is the change of architecture to use the Core’s dependencies API with the web fonts API. And there’s a call for testing out there, or it will be out there, and making sure that how you use it. She has an update where she asked how you can help. And that is if you have an idea about naming the API, should it be webfonts, or web fonts, two words, or just the fonts API, which I tend to be the fonts API, but there is a renaming before everything gets into the Core that we’ll be name things right.

\n\n\n\n

And then the other one is a call to test the new architecture and share feedback on your testing reports and using the web fonts API. I’m not quite sure how the planning is because it seems to be still blocked furthermore through additional architectural work. Hector, do you think that that will come with 6.2, or is it now a little late for 6.2?

\n\n\n\n

Hector Prieto: Well, Tony and the other contributors are making their best to have this feature land in 6.2, so I’m pretty positive it can make it in 6.2. And the best way to ensure it can land in that version is to help with testing and with feedback. That will help unlock the architecture redesign and the renaming and everything that’s currently being discussed right now.

\n\n\n\n

Birgit Pauli-Haack: All right. Okay. Yeah, if you all are contributing to things, dear listeners, help getting that over the finish line. It definitely needs some testing. So I think that’s the end of talking about Gutenberg 14.9. We’re coming also up on the hour, so I think we can go to closing things. Are there anything that you want to point out that are on the roadmap for 6.2 that you want to have our listeners know? And if not, how can the listeners get in contact with you, where to best meet you online?

\n\n\n\n

Hector Prieto: Well, you can reach out to me in WordPress Slack. Handle is Prieto. I guess it will be written in the show notes. So please feel free to ping me there or in GitHub or in Track. I’m using the handle everywhere, so that’s easy. I would just like to circle back to the 6.2 planning and reminding everyone the call for volunteers is open. So if you’re interested in participating in the squad, you are more than welcome. We will assist you if it’s your first time. If you’re an assistant contributor, you are also welcome and we can learn from you. So everybody’s welcome, that’s the long story short.

\n\n\n\n

Birgit Pauli-Haack: Yeah. And the only thing that I want to remind you is about the next week’s Gutenberg Live Q&A with Isabel Brison, Andrew Serong and Justin Tadlock on Layout, Layout, Layout. January 11th at 5:00 PM Eastern and 20:22 UTC. That’s 10:00 PM on UTC. And as always, the show notes will be published on gutenbergtimes.com/podcast. This is episode 78. And if you have questions and suggestions or news you want us to include, send them to changelog@gutenbergtimes.com. That’s changelog@gutenbergtimes.com. So thank you so much, Hector, for joining me here for the first Changelog podcast in 2023 to spend the time on preparation as well as in the show. Thank you all for listening and goodbye and again, Happy New Year.

\n\n\n\n

Hector Prieto: Thank you for having me and see you soon. Happy New Year, everybody.

\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 07 Jan 2023 19:14:10 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:19:\"Gutenberg Changelog\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}i:49;a:6:{s:4:\"data\";s:13:\"\n \n \n \n \n \n \n\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";s:5:\"child\";a:2:{s:0:\"\";a:5:{s:5:\"title\";a:1:{i:0;a:5:{s:4:\"data\";s:121:\"Gutenberg Times: 209 Block Themes, Query Block Variations, Forms with Blocks, Block Art and more – Weekend Edition #240\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"guid\";a:1:{i:0;a:5:{s:4:\"data\";s:35:\"https://gutenbergtimes.com/?p=23042\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:4:\"link\";a:1:{i:0;a:5:{s:4:\"data\";s:124:\"https://gutenbergtimes.com/209-block-themes-query-block-variations-forms-with-blocks-block-art-and-more-weekend-edition-240/\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:11:\"description\";a:1:{i:0;a:5:{s:4:\"data\";s:27679:\"

Howdy,

\n\n\n\n

Tomorrow is the 5-year anniversary of Gutenberg Times. It feels like I just started yesterday to be fascinated by the possibilities of the block editor. For many people, it actually was just yesterday that they dipped their toes into the world of the new thing. Not you of course. You have been a wonderful subscriber and reader for a while now, and I am infinitely grateful for your support. Thank you!

\n\n\n\n

Welcome to all new subscribers this year. So glad you are here.

\n\n\n\n

Let’s dive into the sixth year together, and learn what will be next for the block editor and what other people make with it and for it. The ecosystem seems to keep expanding quite a bit with the block editor.

\n\n\n\n

Wishing you and yours a fabulous 2023. May you be prosperous, happy, and healthy!

\n\n\n\n

Yours, 💕
Birgit

\n\n\n\n

PS: Reminder: Hope to see you next week at the Gutenberg Times Live Q & A. Get your seats now for January 11, 2023, at 5pm / ET 22:00 UTC

\n\n\n\n\n\n\n\n\n\n

Developing Gutenberg and WordPress

\n\n\n\n

Hector Prieto published the WordPress 6.2 Planning Schedule Proposal, and it’s also a call for volunteers for the release squad. The 6.2 release squad will then decide on the final release schedule. For now, Feature Freeze and Beta 1 would be on February 7th, 2023. Tthere are four Beta releases planned before release candidate 1 will be available on March 7th, and a final release on March 28th, 2023.

\n\n\n\n
\n\n\n\n

Reminder: January 10, 2023, at 9:30 ET / 14:30 UTC: Hallway Hangout: Performance Considerations for Block Themes Anne McCarthy wrote: “At a high level, we’ll go through general intros (what each person does/focuses on), current work underway to address performance, what work is being done specifically for block themes, and general open Q&A. Hallway hangouts are meant to be casual and collaborative so come prepared with a kind, curious mind along with any questions or items you want to demo/discuss.”

\n\n\n\n

From the WordPress Developer Blog

\n\n\n\n

Justin Tadlock published a tutorial for building a book review grid with a Query Loop block variation. WordPress 6.1 introduced an extension to the Query Loop block, which allows plugin developers to filter existing functionality in core WordPress rather than building custom blocks to query posts. This tutorial shows how to build a WordPress plugin that display a list of book review posts including post_meta` data, using a block variation for the Query Loop and set up rendering it on the front end.

\n\n\n\n

Nick Diego tweeted: I always knew the Query Loop block was incredibly powerful, but I had never explored integrating post metadata into custom block variations! Learn how in this fantastic article by @justintadlock on the new WordPress Developer Blog.

\n\n\n\n
\n\n\n\n

Micheal Burridge composed a Roundup post to review 2022 from a block developer’s perspective in is post. You’ll find a select list of resources, to get started or to catch up on the development from the last 12 months, via the Make Blog, WordPress TV and the Learn WordPress site.

\n\n\n\n

Gutenberg plugin releases

\n\n\n\n

Gutenberg 14.8 was released on December 22, 2022, and release lead Ryan Welcher highlighted in his post What’s new in Gutenberg 14.8? (21 December)

\n\n\n\n\n\n\n\n

Sarah Gooding reported on the release as well via the WPTavern: Gutenberg 14.8 Overhauls Site Editor Interface, Adds Style Book

\n\n\n\n
\n\n\n\n

Gutenberg 14.9 is the first release of 2023, and release lead Justin Tadlock pointed out a few new features in his post What’s new in Gutenberg 14.9? (4 January):

\n\n\n\n\n\n\n\n

On the WPTavern, Sarah Gooding took the version for spin and reported on the new magic: Gutenberg 14.9’s New Magic: Push Block Changes to Global Styles

\n\n\n\n
\n\n\n\n

In the upcoming Gutenberg Changelog episode 78, Hector Prieto was my guest. He is a full-time core contributor and coordinator of multi-release WordPress and Gutenberg releases. We discussed Gutenberg 14.8 and 14.9 as well as 6.2 release schedule proposal and other topics. The episode will hit your favorite podcast app over the weekend.

\n\n\n\n\n\n\n\n
\n

🎙️ New episode: Gutenberg Changelog #78 -State of the Word, WordPress 6.2, Gutenberg 14.8 and 14.9 with Birgit Pauli-Haack and special guest Hector Prieto

\n
\n\n\n\n

Plugins, Themes, and Tools for #nocode site builders and owners

\n\n\n\n
\n\n\n\n

Sarah Gooding wrote about Block Protocol Announces New WordPress Plugin Coming in 2023 It will allow users to embed interactive blocks that are compatible with Gutenberg, and will include blocks for drawing, GitHub pull request overview, timer, calculation, and more. The plugin will also include new blocks powered by OpenAI DALL-E and GPT .

\n\n\n\n

The Block Protocol project is open source and designed to be an open protocol, and WordPress hopes to integrate more with it in the future.

\n\n\n\n
\n\n\n\n

In the latest WPTavern Jukebox podcast episode, Damon Cook, developer advocate at WPEngine, discussed with Nathan Wrigley the future of website styling in WordPress. Wrigley wrote in the introduction: ” Block-based themes are revolutionizing website styling. You’re going to be able to change any aspect of your website from the UI that you’re familiar with. The hope is that it’ll make styling more accessible to a wider audience.

\n\n\n\n

Damon talks about the fact that we’re in a period of flux right now. The documentation and tooling needed to work with website styles is maturing, but is by no means complete.”

\n\n\n\n
\n\n\n\n

Torsten Landsiedel scratched his personal itch and built the plugin Ignore block name in search, after finding that the WordPress built-in search included in the findings posts where the search keywords are in the HTML comments of blocks, and with that skews, the search result less relevant. It’s particular helpful when your blog is about working with the block editor or about content creation with WordPress. Landsiedel feels that the block editor makes the shortcomings of the built-in search feature worse because blocks contain full words, and not just HTML tags. It’s been a long-standing issue, that this plugin now solves.

\n\n\n\n
\n\n\n\n

209 Block Themes are now available in the WordPress repository with new submissions by Themeisle, sparklewpthemes, olivethemes, deothemes, sonalsinha21, Blockify, hamidxazad, WPZOOM.

\n\n\n\n\n\n\n\n

Ana Segota of Anariel Design also announced Yuna, a block theme for Nonprofits that comes with 100+ Design Patterns, you can add to your page with a simple drag and drop. Use built-in options to arrange and style them any way you want. It also includes built-in styles for the popular GiveWP donations plugin and is also ready to house your ecommerce store.

\n\n\n\n

Making Block Art

\n\n\n\n

Curious about some art behind Matt Mullenweg during State of the Word? Below are those pieces designed for the Museum of Block Art which represent the creativity that Gutenberg blocks inspire. Be sure to stop by and experience the museum’s digital interactive exhibit.

\n\n\n\n

You can see

\n\n\n\n\n\n\n\n\n\n\n\n

Anne McCarthy, instigator and curator of the Museum of Block Art, wrote an insightful blog post about how she approached making art with the Block Editor. Take a look Behind the scenes of creating art with WordPress.

\n\n\n\n

Chuck Grimmett has more examples of WP Block Art on his blog.

\n\n\n\n
\n\n\n\n

Rich Tabor and Courtney Portnoy discussed The creative side of blocks on WordPressTV. Rich Tabor walks the viewers through one of his block art creations. It’s quite inspiring to watch Tabor’s exploratory creative process using the block editor. I learned quite a few things about the power of the various color features: gradient, nested group blocks, and how to replace the theme’s primary and secondary colors for the whole site. You’ll also get an introduction to the Museum of Block Art, where Rich and other block artists showcase their creations. (also mentioned in GT 239)

\n\n\n\n

Form Plugins working with Blocks

\n\n\n\n

Two plugins emerged that take advantage of the block editor and its components and scripts so site owners and builders can use them to create forms.

\n\n\n\n

Munir Kamal, created a block integration for the popular CF7 Forms. It’s aptly names CF Blocks and available in the WordPress repository. He wrote in the description: “With CF7 Blocks, you can easily create and customize contact forms within the familiar block editor interface. No more fiddling with short codes or HTML – just drag and drop blocks to build your forms exactly how you want them.” Sounds spectacular, doesn’t it?

\n\n\n\n

In here article New CF7 Blocks Plugin Brings Blocks to Contact Form 7, Sarah Gooding, took a more in-depth look and shares her findings.

\n\n\n\n
\n\n\n\n

On Twitter, JR Tashjian developer at GoDaddy, introduced OmniForm, the next-generation Form Builder for your website. Sign up for early access now and be among the first to try it. The plan is to make the plugin available in the WordPress plugin directory at the end of January, with early access provided to users the week prior. Tashjian continues: “OmniForm embraces the block editor to the fullest extent and unlike any solution right now. The block editor is the future of editing in WordPress and building any kind of form will be no different from creating a post or page.” Tall order. Looking forward to doing some testing, too.

\n\n\n\n

Theme Development for Full Site Editing and Blocks

\n\n\n\n

Anne McCarthy has a new video up on YouTube: Building a site with WordPress 5.9 vs. WordPress 6.2 (in progress features) – To better show what’s changed with the Site Editor from when it was first introduced in WordPress 5.9, this video goes through both a demo of the original state and a brief look at what’s in place today and what’s to come, especially as 6.2 looks to wrap up much of the work around site editing/phase 2 of Gutenberg. Keep in mind that WordPress 6.2 is not out yet and much of what’s being shown is very much a work in progress with big opportunities to provide feedback along the way. Either way, I hope you enjoy taking a peak back and a look forward.

\n\n\n\n
\n\n\n\n\n

 “Keeping up with Gutenberg – Index 2022” 
A chronological list of the WordPress Make Blog posts from various teams involved in Gutenberg development: Design, Theme Review Team, Core Editor, Core JS, Core CSS, Test, and Meta team from Jan. 2021 on. Updated by yours truly. The index 2020 is here

\n\n\n\n\n

In this video tutorial, Jonathan Bossenger gives you an Introduction to theme.json. You will learn how the theme.js file works, and how you can control these settings and styles.

\n\n\n\n
\n\n\n\n

Daisy Olsen started a new Live Stream schedule and will show off Block Themes in WordPress every Friday at 10:30 am ET / 15:30 UTC on Twitch.

\n\n\n\n

The inaugural show took place Friday, January 6th, 2023 with the topic: Building a Block Theme. It’s a great opportunity to follow along with Daisy and ask questions along the way.

\n\n\n\n\n\n\n\n

Building Blocks and Tools for the Block editor.

\n\n\n\n

Munir Kamal takes you on a journey of From WordPress to the World: Intro to the Standalone Gutenberg Block Editor. In his new plugin, Kamal made the journey and found a few challenges along the way, overcame them and new put it all together for others to follow. Using the app ‘Isolated block editor, from the public repo, maintained by Automattic. Matt Mullenweg in the State of the Word emphasized that the block editor is also used outside of WordPress, with Tumblr, Day One app and with bbPress instance.

\n\n\n\n
\n\n\n\n

The team working on GiveWP went on a similar route on the revamp of the highly popular donation plugin. Post Status recently posted an article about that: The Future of GiveWP and the Block Editor

\n\n\n\n

GiveWP will hold a Town hall event about the new version on January 25th, 2023 at 10am PT / 18:00 UTC – in case someone is interested. Learn more Town Hall: GiveWP Design Mode and What’s Next for 3.0

\n\n\n\n


Kyle Johnson, JavaScript developer at GiveWP will present his talk: Using Gutenberg as a Development Foundation, Not Just a Block Builder at WordCamp Birmingham on February 4th, 2023. As far as we know, the talks will be recorded, but not livestreamed. So, they will show up on WordPress TV in the weeks after the WordCamp.

\n\n\n\n
\n\n\n\n

Mohammed Noufal of Hubspot wrote about How to Create Custom Blocks in WordPress, providing answers to the questions: why use a custom block, how to make Custom Block Templates and how to use custom blocks on your site.

\n\n\n\n
\n\n\n\n

Jonathan Bossenger‘s last section of his series: Let’s code: developing blocks without React!  is now also available on WordPress TV. Let’s code: developing blocks without React! – Review. If you followed along over the past few weeks, you would have learned to build a small WordPress block using plain (vanilla) JavaScript. In this session, we will review everything we’ve learned so far, by rebuilding the entire block from scratch.

\n\n\n\n

The other editions for the series are in order of broadcast/

\n\n\n\n\n\n\n\n\n

Need a plugin .zip from Gutenberg’s master branch?
Gutenberg Times provides daily build for testing and review.
Have you been using it? Hit reply and let me know.

\n\n\n\n

\"GitHub

\n\n\n\n\n

Upcoming WordPress events

\n\n\n\n

January 10, 2023 – 9:30 ET / 14:30 UTC
Hallway Hangout: Performance Considerations for Block Themes with Anne McCarthy

\n\n\n\n

January 11, 2023 – 5 pm ET / 22:00 UTC
Gutenberg Times Live Q & A: Layout, layout, layout
Panel discussion with Isabel Brison, Andrew Serong, Justin Tadlock and Birgit Pauli-Haack

\n\n\n\n

February 4 + 5, 2023
WordCamp Birmingham, AL

\n\n\n\n

February 17 – 19, 2023
WordCamp Asia 2023 

\n\n\n\n

Learn WordPress Online Meetups

\n\n\n\n

January 17, 2023 – 3pm / 20:00 UTC
Patterns, reusable blocks and block locking

\n\n\n\n

January 19, 2023 – 7 pm ET / 24:00 UTC
Let’s make custom templates in the Site Editor!

\n\n\n\n

January 31, 2023 – 3pm ET / 20:00 UTC
Creating a photography website with the block editor

\n\n\n\n
\n\n\n\n\n

Featured Image:

\n\n\n\n
\n\n\n\n

Don’t want to miss the next Weekend Edition?

\n\n\n\n

We hate spam, too and won’t give your email address to anyone except Mailchimp to send out our Weekend Edition

Thanks for subscribing.
\n\n\n\n
\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}s:7:\"pubDate\";a:1:{i:0;a:5:{s:4:\"data\";s:31:\"Sat, 07 Jan 2023 14:25:00 +0000\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}s:32:\"http://purl.org/dc/elements/1.1/\";a:1:{s:7:\"creator\";a:1:{i:0;a:5:{s:4:\"data\";s:18:\"Birgit Pauli-Haack\";s:7:\"attribs\";a:0:{}s:8:\"xml_base\";s:0:\"\";s:17:\"xml_base_explicit\";b:0;s:8:\"xml_lang\";s:0:\"\";}}}}}}}}}}}}}}}}s:4:\"type\";i:128;s:7:\"headers\";O:42:\"Requests_Utility_CaseInsensitiveDictionary\":1:{s:7:\"\0*\0data\";a:8:{s:6:\"server\";s:5:\"nginx\";s:4:\"date\";s:29:\"Wed, 25 Jan 2023 13:14:29 GMT\";s:12:\"content-type\";s:8:\"text/xml\";s:13:\"last-modified\";s:29:\"Wed, 25 Jan 2023 13:00:35 GMT\";s:4:\"vary\";s:15:\"Accept-Encoding\";s:15:\"x-frame-options\";s:10:\"SAMEORIGIN\";s:16:\"content-encoding\";s:4:\"gzip\";s:4:\"x-nc\";s:9:\"HIT ord 1\";}}s:5:\"build\";s:14:\"20211220193300\";}','no'),(143,'_transient_timeout_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1674695670','no'),(144,'_transient_feed_mod_d117b5738fbd35bd8c0391cda1f2b5d9','1674652470','no'),(145,'_transient_timeout_dash_v2_88ae138922fe95674369b1cb3d215a2b','1674695670','no'),(146,'_transient_dash_v2_88ae138922fe95674369b1cb3d215a2b','','no'),(147,'recently_activated','a:0:{}','yes'),(148,'can_compress_scripts','1','no'),(149,'_site_transient_update_plugins','O:8:\"stdClass\":5:{s:12:\"last_checked\";i:1674652484;s:8:\"response\";a:0:{}s:12:\"translations\";a:0:{}s:9:\"no_update\";a:2:{s:19:\"akismet/akismet.php\";O:8:\"stdClass\":10:{s:2:\"id\";s:21:\"w.org/plugins/akismet\";s:4:\"slug\";s:7:\"akismet\";s:6:\"plugin\";s:19:\"akismet/akismet.php\";s:11:\"new_version\";s:5:\"5.0.2\";s:3:\"url\";s:38:\"https://wordpress.org/plugins/akismet/\";s:7:\"package\";s:56:\"https://downloads.wordpress.org/plugin/akismet.5.0.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:60:\"https://ps.w.org/akismet/assets/icon-256x256.png?rev=2818463\";s:2:\"1x\";s:60:\"https://ps.w.org/akismet/assets/icon-128x128.png?rev=2818463\";}s:7:\"banners\";a:1:{s:2:\"1x\";s:61:\"https://ps.w.org/akismet/assets/banner-772x250.jpg?rev=479904\";}s:11:\"banners_rtl\";a:0:{}s:8:\"requires\";s:3:\"5.0\";}s:9:\"hello.php\";O:8:\"stdClass\":10:{s:2:\"id\";s:25:\"w.org/plugins/hello-dolly\";s:4:\"slug\";s:11:\"hello-dolly\";s:6:\"plugin\";s:9:\"hello.php\";s:11:\"new_version\";s:5:\"1.7.2\";s:3:\"url\";s:42:\"https://wordpress.org/plugins/hello-dolly/\";s:7:\"package\";s:60:\"https://downloads.wordpress.org/plugin/hello-dolly.1.7.2.zip\";s:5:\"icons\";a:2:{s:2:\"2x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-256x256.jpg?rev=2052855\";s:2:\"1x\";s:64:\"https://ps.w.org/hello-dolly/assets/icon-128x128.jpg?rev=2052855\";}s:7:\"banners\";a:2:{s:2:\"2x\";s:67:\"https://ps.w.org/hello-dolly/assets/banner-1544x500.jpg?rev=2645582\";s:2:\"1x\";s:66:\"https://ps.w.org/hello-dolly/assets/banner-772x250.jpg?rev=2052855\";}s:11:\"banners_rtl\";a:0:{}s:8:\"requires\";s:3:\"4.6\";}}s:7:\"checked\";a:3:{s:19:\"akismet/akismet.php\";s:5:\"5.0.2\";s:19:\"datadog/datadog.php\";s:5:\"0.0.0\";s:9:\"hello.php\";s:5:\"1.7.2\";}}','no'),(152,'theme_mods_twentytwentythree','a:1:{s:18:\"custom_css_post_id\";i:-1;}','yes'),(157,'finished_updating_comment_type','1','yes'),(171,'_transient_timeout_global_styles_twentytwentythree','1674661327','no'),(172,'_transient_global_styles_twentytwentythree','body{--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #ffffff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--color--base: #ffffff;--wp--preset--color--contrast: #000000;--wp--preset--color--primary: #9DFF20;--wp--preset--color--secondary: #345C00;--wp--preset--color--tertiary: #F6F6F6;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--duotone--dark-grayscale: url(\'#wp-duotone-dark-grayscale\');--wp--preset--duotone--grayscale: url(\'#wp-duotone-grayscale\');--wp--preset--duotone--purple-yellow: url(\'#wp-duotone-purple-yellow\');--wp--preset--duotone--blue-red: url(\'#wp-duotone-blue-red\');--wp--preset--duotone--midnight: url(\'#wp-duotone-midnight\');--wp--preset--duotone--magenta-yellow: url(\'#wp-duotone-magenta-yellow\');--wp--preset--duotone--purple-green: url(\'#wp-duotone-purple-green\');--wp--preset--duotone--blue-orange: url(\'#wp-duotone-blue-orange\');--wp--preset--font-size--small: clamp(0.875rem, 0.875rem + ((1vw - 0.48rem) * 0.24), 1rem);--wp--preset--font-size--medium: clamp(1rem, 1rem + ((1vw - 0.48rem) * 0.24), 1.125rem);--wp--preset--font-size--large: clamp(1.75rem, 1.75rem + ((1vw - 0.48rem) * 0.24), 1.875rem);--wp--preset--font-size--x-large: 2.25rem;--wp--preset--font-size--xx-large: clamp(4rem, 4rem + ((1vw - 0.48rem) * 11.538), 10rem);--wp--preset--font-family--dm-sans: \"DM Sans\", sans-serif;--wp--preset--font-family--ibm-plex-mono: \'IBM Plex Mono\', monospace;--wp--preset--font-family--inter: \"Inter\", sans-serif;--wp--preset--font-family--system-font: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;--wp--preset--font-family--source-serif-pro: \"Source Serif Pro\", serif;--wp--preset--spacing--30: clamp(1.5rem, 5vw, 2rem);--wp--preset--spacing--40: clamp(1.8rem, 1.8rem + ((1vw - 0.48rem) * 2.885), 3rem);--wp--preset--spacing--50: clamp(2.5rem, 8vw, 4.5rem);--wp--preset--spacing--60: clamp(3.75rem, 10vw, 7rem);--wp--preset--spacing--70: clamp(5rem, 5.25rem + ((1vw - 0.48rem) * 9.096), 8rem);--wp--preset--spacing--80: clamp(7rem, 14vw, 11rem);}body { margin: 0;--wp--style--global--content-size: 650px;--wp--style--global--wide-size: 1200px; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) { padding-right: 0; padding-left: 0; }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding) > .alignfull { margin-right: 0; margin-left: 0; }.has-global-padding > .alignfull:where(:not(.has-global-padding)) > :where([class*=\"wp-block-\"]:not(.alignfull):not([class*=\"__\"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding :where(.has-global-padding) > .alignfull:where(:not(.has-global-padding)) > :where([class*=\"wp-block-\"]:not(.alignfull):not([class*=\"__\"]),p,h1,h2,h3,h4,h5,h6,ul,ol) { padding-right: 0; padding-left: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: 1.5rem; }body { --wp--style--block-gap: 1.5rem; }body .is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-flow > * + *{margin-block-start: 1.5rem;margin-block-end: 0;}body .is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}body .is-layout-constrained > * + *{margin-block-start: 1.5rem;margin-block-end: 0;}body .is-layout-flex{gap: 1.5rem;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body{background-color: var(--wp--preset--color--base);color: var(--wp--preset--color--contrast);font-family: var(--wp--preset--font-family--system-font);font-size: var(--wp--preset--font-size--medium);line-height: 1.6;--wp--style--root--padding-top: var(--wp--preset--spacing--40);--wp--style--root--padding-right: var(--wp--preset--spacing--30);--wp--style--root--padding-bottom: var(--wp--preset--spacing--40);--wp--style--root--padding-left: var(--wp--preset--spacing--30);}a:where(:not(.wp-element-button)){color: var(--wp--preset--color--contrast);text-decoration: underline;}a:where(:not(.wp-element-button)):hover{text-decoration: none;}a:where(:not(.wp-element-button)):focus{text-decoration: underline dashed;}a:where(:not(.wp-element-button)):active{color: var(--wp--preset--color--secondary);text-decoration: none;}h1, h2, h3, h4, h5, h6{font-weight: 400;line-height: 1.4;}h1{font-size: clamp(2.719rem, 2.719rem + ((1vw - 0.48rem) * 1.742), 3.625rem);line-height: 1.2;}h2{font-size: clamp(2.625rem, calc(2.625rem + ((1vw - 0.48rem) * 8.4135)), 3.25rem);line-height: 1.2;}h3{font-size: var(--wp--preset--font-size--x-large);}h4{font-size: var(--wp--preset--font-size--large);}h5{font-size: var(--wp--preset--font-size--medium);font-weight: 700;text-transform: uppercase;}h6{font-size: var(--wp--preset--font-size--medium);text-transform: uppercase;}.wp-element-button, .wp-block-button__link{background-color: var(--wp--preset--color--primary);border-radius: 0;border-width: 0;color: var(--wp--preset--color--contrast);font-family: inherit;font-size: inherit;line-height: inherit;padding: calc(0.667em + 2px) calc(1.333em + 2px);text-decoration: none;}.wp-element-button:visited, .wp-block-button__link:visited{color: var(--wp--preset--color--contrast);}.wp-element-button:hover, .wp-block-button__link:hover{background-color: var(--wp--preset--color--contrast);color: var(--wp--preset--color--base);}.wp-element-button:focus, .wp-block-button__link:focus{background-color: var(--wp--preset--color--contrast);color: var(--wp--preset--color--base);}.wp-element-button:active, .wp-block-button__link:active{background-color: var(--wp--preset--color--secondary);color: var(--wp--preset--color--base);}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-base-color{color: var(--wp--preset--color--base) !important;}.has-contrast-color{color: var(--wp--preset--color--contrast) !important;}.has-primary-color{color: var(--wp--preset--color--primary) !important;}.has-secondary-color{color: var(--wp--preset--color--secondary) !important;}.has-tertiary-color{color: var(--wp--preset--color--tertiary) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-base-background-color{background-color: var(--wp--preset--color--base) !important;}.has-contrast-background-color{background-color: var(--wp--preset--color--contrast) !important;}.has-primary-background-color{background-color: var(--wp--preset--color--primary) !important;}.has-secondary-background-color{background-color: var(--wp--preset--color--secondary) !important;}.has-tertiary-background-color{background-color: var(--wp--preset--color--tertiary) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-base-border-color{border-color: var(--wp--preset--color--base) !important;}.has-contrast-border-color{border-color: var(--wp--preset--color--contrast) !important;}.has-primary-border-color{border-color: var(--wp--preset--color--primary) !important;}.has-secondary-border-color{border-color: var(--wp--preset--color--secondary) !important;}.has-tertiary-border-color{border-color: var(--wp--preset--color--tertiary) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;}.has-xx-large-font-size{font-size: var(--wp--preset--font-size--xx-large) !important;}.has-dm-sans-font-family{font-family: var(--wp--preset--font-family--dm-sans) !important;}.has-ibm-plex-mono-font-family{font-family: var(--wp--preset--font-family--ibm-plex-mono) !important;}.has-inter-font-family{font-family: var(--wp--preset--font-family--inter) !important;}.has-system-font-font-family{font-family: var(--wp--preset--font-family--system-font) !important;}.has-source-serif-pro-font-family{font-family: var(--wp--preset--font-family--source-serif-pro) !important;}','no'),(173,'_transient_timeout_global_styles_svg_filters_twentytwentythree','1674661327','no'),(174,'_transient_global_styles_svg_filters_twentytwentythree','','no'); /*!40000 ALTER TABLE `wp_options` ENABLE KEYS */; UNLOCK TABLES; @@ -632,7 +634,7 @@ CREATE TABLE `wp_posts` ( LOCK TABLES `wp_posts` WRITE; /*!40000 ALTER TABLE `wp_posts` DISABLE KEYS */; -INSERT INTO `wp_posts` VALUES (1,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost:9999/?p=1',0,'post','',1),(2,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost:9999/?page_id=2',0,'page','',0),(3,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','

Who we are

Suggested text: Our website address is: http://localhost:9999.

Comments

Suggested text: When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

Suggested text: If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Cookies

Suggested text: If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Suggested text: Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Who we share your data with

Suggested text: If you request a password reset, your IP address will be included in the reset email.

How long we retain your data

Suggested text: If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

Suggested text: If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where your data is sent

Suggested text: Visitor comments may be checked through an automated spam detection service.

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost:9999/?page_id=3',0,'page','',0),(4,1,'2023-01-25 13:14:19','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2023-01-25 13:14:19','0000-00-00 00:00:00','',0,'http://localhost:9999/?p=4',0,'post','',0); +INSERT INTO `wp_posts` VALUES (1,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','\n

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

\n','Hello world!','','publish','open','open','','hello-world','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost/?p=1',0,'post','',1),(2,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','\n

This is an example page. It\'s different from a blog post because it will stay in one place and will show up in your site navigation (in most themes). Most people start with an About page that introduces them to potential site visitors. It might say something like this:

\n\n\n\n

Hi there! I\'m a bike messenger by day, aspiring actor by night, and this is my website. I live in Los Angeles, have a great dog named Jack, and I like piña coladas. (And gettin\' caught in the rain.)

\n\n\n\n

...or something like this:

\n\n\n\n

The XYZ Doohickey Company was founded in 1971, and has been providing quality doohickeys to the public ever since. Located in Gotham City, XYZ employs over 2,000 people and does all kinds of awesome things for the Gotham community.

\n\n\n\n

As a new WordPress user, you should go to your dashboard to delete this page and create new pages for your content. Have fun!

\n','Sample Page','','publish','closed','open','','sample-page','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost/?page_id=2',0,'page','',0),(3,1,'2023-01-25 13:12:51','2023-01-25 13:12:51','

Who we are

Suggested text: Our website address is: http://localhost.

Comments

Suggested text: When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection.

An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

Suggested text: If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Cookies

Suggested text: If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year.

If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser.

When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed.

If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Suggested text: Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website.

These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Who we share your data with

Suggested text: If you request a password reset, your IP address will be included in the reset email.

How long we retain your data

Suggested text: If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue.

For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

Suggested text: If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where your data is sent

Suggested text: Visitor comments may be checked through an automated spam detection service.

','Privacy Policy','','draft','closed','open','','privacy-policy','','','2023-01-25 13:12:51','2023-01-25 13:12:51','',0,'http://localhost/?page_id=3',0,'page','',0),(4,1,'2023-01-25 13:14:19','0000-00-00 00:00:00','','Auto Draft','','auto-draft','open','open','','','','','2023-01-25 13:14:19','0000-00-00 00:00:00','',0,'http://localhost/?p=4',0,'post','',0); /*!40000 ALTER TABLE `wp_posts` ENABLE KEYS */; UNLOCK TABLES; @@ -806,7 +808,7 @@ CREATE TABLE `wp_users` ( LOCK TABLES `wp_users` WRITE; /*!40000 ALTER TABLE `wp_users` DISABLE KEYS */; -INSERT INTO `wp_users` VALUES (1,'test','$P$Bj7U/Ktsr0VatG1e1O5qcdeQxCy25G0','test','test@gmail.com','http://localhost:9999','2023-01-25 13:12:51','',0,'test'); +INSERT INTO `wp_users` VALUES (1,'test','$P$Bj7U/Ktsr0VatG1e1O5qcdeQxCy25G0','test','test@gmail.com','http://localhost','2023-01-25 13:12:51','',0,'test'); /*!40000 ALTER TABLE `wp_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; diff --git a/tests/Frameworks/WordPress/Version_6_1/wp-config.php b/tests/Frameworks/WordPress/Version_6_1/wp-config.php index 75772b2dbd..d7b13fda40 100644 --- a/tests/Frameworks/WordPress/Version_6_1/wp-config.php +++ b/tests/Frameworks/WordPress/Version_6_1/wp-config.php @@ -20,7 +20,7 @@ // ** Database settings - You can get this info from your web host ** // /** The name of the database for WordPress */ -define( 'DB_NAME', 'test' ); +define( 'DB_NAME', 'wp61' ); /** Database username */ define( 'DB_USER', 'test' ); diff --git a/tests/Integration/ResponseStatusCodeTest.php b/tests/Integration/ResponseStatusCodeTest.php index defa5a635f..8ade570ae5 100644 --- a/tests/Integration/ResponseStatusCodeTest.php +++ b/tests/Integration/ResponseStatusCodeTest.php @@ -37,7 +37,7 @@ function () { [ SpanAssertion::build('web.request', 'web.request', 'web', 'GET /success')->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/success', + 'http.url' => 'http://localhost/success', 'http.status_code' => '200', ]), ] @@ -62,7 +62,7 @@ function () { SpanAssertion::build('web.request', 'web.request', 'web', 'GET /error')->withExactTags( [ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error', + 'http.url' => 'http://localhost/error', 'http.status_code' => '500', ] )->setError(), diff --git a/tests/Integrations/AMQP/AMQPTest.php b/tests/Integrations/AMQP/V2/AMQPTest.php similarity index 98% rename from tests/Integrations/AMQP/AMQPTest.php rename to tests/Integrations/AMQP/V2/AMQPTest.php index d8819b7c9b..81d3f772c8 100644 --- a/tests/Integrations/AMQP/AMQPTest.php +++ b/tests/Integrations/AMQP/V2/AMQPTest.php @@ -1,6 +1,6 @@ inCli( - __DIR__ . '/scripts/send.php', + __DIR__ . '/../scripts/send.php', [ 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_CLI_ENABLED' => 'true', - ] + ], + [], + self::$autoloadPath ); list($receiveTraces, $output) = $this->inCli( - __DIR__ . '/scripts/receive.php', + __DIR__ . '/../scripts/receive.php', [ 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', 'DD_TRACE_CLI_ENABLED' => 'true', ], [], - '', + self::$autoloadPath, true ); @@ -924,24 +928,26 @@ public function testDistributedTracingIsNotPropagatedIfDisabled() self::putEnv('DD_TRACE_DEBUG_PRNG_SEED=42'); // Not necessary, but makes it easier to debug locally $sendTraces = $this->inCli( - __DIR__ . '/scripts/send.php', + __DIR__ . '/../scripts/send.php', [ 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_DISTRIBUTED_TRACING' => 'false' - ] + ], + [], + self::$autoloadPath ); list($receiveTraces, $output) = $this->inCli( - __DIR__ . '/scripts/receive.php', + __DIR__ . '/../scripts/receive.php', [ 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', 'DD_TRACE_CLI_ENABLED' => 'true' ], [], - '', + self::$autoloadPath, true ); diff --git a/tests/Integrations/AMQP/V2/composer.json b/tests/Integrations/AMQP/V2/composer.json new file mode 100644 index 0000000000..f141a1fdc5 --- /dev/null +++ b/tests/Integrations/AMQP/V2/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php-amqplib/php-amqplib": "^v2.6.2" + } +} diff --git a/tests/Integrations/AMQP/V3_5/AMQPTest.php b/tests/Integrations/AMQP/V3_5/AMQPTest.php new file mode 100644 index 0000000000..34869f8498 --- /dev/null +++ b/tests/Integrations/AMQP/V3_5/AMQPTest.php @@ -0,0 +1,7 @@ + 'false', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_EXEC_ENABLED' => 'false', - ], [], 'about'); + ], [], 'about', false, null, false); $this->assertFlameGraph( $traces, diff --git a/tests/Integrations/CLI/Symfony/V5_2/CommonScenariosTest.php b/tests/Integrations/CLI/Symfony/V5_2/CommonScenariosTest.php index 0eaeb36480..6496046189 100644 --- a/tests/Integrations/CLI/Symfony/V5_2/CommonScenariosTest.php +++ b/tests/Integrations/CLI/Symfony/V5_2/CommonScenariosTest.php @@ -153,7 +153,7 @@ public function testLongRunningCommandWithoutRootSpan() 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_EXEC_ENABLED' => 'false', - ], [], 'about'); + ], [], 'about', false, null, false); $this->assertFlameGraph( $traces, diff --git a/tests/Integrations/CLI/Symfony/V6_2/CommonScenariosTest.php b/tests/Integrations/CLI/Symfony/V6_2/CommonScenariosTest.php index d35ad4cd20..3e60c64c8c 100644 --- a/tests/Integrations/CLI/Symfony/V6_2/CommonScenariosTest.php +++ b/tests/Integrations/CLI/Symfony/V6_2/CommonScenariosTest.php @@ -15,7 +15,7 @@ protected static function getConsoleScript() public function testThrowCommand() { - list($traces) = $this->inCli(self::getConsoleScript(), [ + list($traces) = $this->inCli(static::getConsoleScript(), [ 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', @@ -88,7 +88,7 @@ public function testThrowCommand() public function testCommand() { - list($traces) = $this->inCli(self::getConsoleScript(), [ + list($traces) = $this->inCli(static::getConsoleScript(), [ 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', @@ -148,12 +148,12 @@ public function testCommand() public function testLongRunningCommandWithoutRootSpan() { - list($traces) = $this->inCli(self::getConsoleScript(), [ + list($traces) = $this->inCli(static::getConsoleScript(), [ 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_EXEC_ENABLED' => 'false', - ], [], 'about'); + ], [], 'about', false, null, false); $this->assertFlameGraph( $traces, diff --git a/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php b/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php index 1e84c4fa9a..e0003a9fe2 100644 --- a/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php +++ b/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php @@ -50,7 +50,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'simple', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', @@ -76,7 +76,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'simple_view', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', @@ -111,7 +111,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'error', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', @@ -153,7 +153,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Parameterized', 'cakephp.route.action' => 'customAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.url' => 'http://localhost/parameterized/paramValue', 'http.status_code' => '200', 'http.route' => '/parameterized/:param', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/CakePHP/V3_10/CommonScenariosTest.php b/tests/Integrations/CakePHP/V3_10/CommonScenariosTest.php index 48ae8f80d4..824be58538 100644 --- a/tests/Integrations/CakePHP/V3_10/CommonScenariosTest.php +++ b/tests/Integrations/CakePHP/V3_10/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Simple', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -77,7 +77,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Simple_view', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -112,7 +112,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Error', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -154,7 +154,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Parameterized', 'cakephp.route.action' => 'customAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.url' => 'http://localhost/parameterized/paramValue', 'http.status_code' => '200', 'http.route' => '/parameterized/:param', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/CakePHP/V4_5/CommonScenariosTest.php b/tests/Integrations/CakePHP/V4_5/CommonScenariosTest.php index 84fa30ac6d..0bf6523e75 100644 --- a/tests/Integrations/CakePHP/V4_5/CommonScenariosTest.php +++ b/tests/Integrations/CakePHP/V4_5/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Simple', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -77,7 +77,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Simple_view', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -113,7 +113,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Error', 'cakephp.route.action' => 'index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'http.route' => '/{controller}', Tag::SPAN_KIND => 'server', @@ -155,7 +155,7 @@ public function provideSpecs() 'cakephp.route.controller' => 'Parameterized', 'cakephp.route.action' => 'customAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.url' => 'http://localhost/parameterized/paramValue', 'http.status_code' => '200', 'http.route' => '/parameterized/{param}', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/CodeIgniter/V2_2/CommonScenariosTest.php b/tests/Integrations/CodeIgniter/V2_2/CommonScenariosTest.php index 649e479861..3beaef738f 100644 --- a/tests/Integrations/CodeIgniter/V2_2/CommonScenariosTest.php +++ b/tests/Integrations/CodeIgniter/V2_2/CommonScenariosTest.php @@ -50,7 +50,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple?key=value&', + Tag::HTTP_URL => 'http://localhost/simple?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'Simple::index', Tag::SPAN_KIND => 'server', @@ -75,7 +75,7 @@ public function provideSpecs() 'GET /simple_view' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple_view?key=value&', + Tag::HTTP_URL => 'http://localhost/simple_view?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'Simple_View::index', Tag::SPAN_KIND => 'server', @@ -109,7 +109,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/error?key=value&', + Tag::HTTP_URL => 'http://localhost/error?key=value&', // CodeIgniter's error handler does not adjust the status code Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'Error_::index', @@ -138,7 +138,7 @@ public function provideSpecs() 'GET /parameterized/paramValue' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/parameterized/paramValue', + Tag::HTTP_URL => 'http://localhost/parameterized/paramValue', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'Parameterized::customAction', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/CodeIgniter/V2_2/ExitTest.php b/tests/Integrations/CodeIgniter/V2_2/ExitTest.php index c243b5c8ef..1c6d893032 100644 --- a/tests/Integrations/CodeIgniter/V2_2/ExitTest.php +++ b/tests/Integrations/CodeIgniter/V2_2/ExitTest.php @@ -38,7 +38,7 @@ public function testScenario() 'GET /exits' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/exits', + Tag::HTTP_URL => 'http://localhost/exits', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'Exits::index', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/CodeIgniter/V2_2/NoCI_ControllertTest.php b/tests/Integrations/CodeIgniter/V2_2/NoCI_ControllertTest.php index 7f57ba39f3..4951837ea5 100644 --- a/tests/Integrations/CodeIgniter/V2_2/NoCI_ControllertTest.php +++ b/tests/Integrations/CodeIgniter/V2_2/NoCI_ControllertTest.php @@ -38,7 +38,7 @@ public function testScenario() 'GET /health_check/ping' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/health_check/ping', + Tag::HTTP_URL => 'http://localhost/health_check/ping', Tag::HTTP_STATUS_CODE => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'codeigniter', diff --git a/tests/Integrations/Custom/Autoloaded/CommonScenariosTest.php b/tests/Integrations/Custom/Autoloaded/CommonScenariosTest.php index a226f5b8d1..0607a95a2f 100644 --- a/tests/Integrations/Custom/Autoloaded/CommonScenariosTest.php +++ b/tests/Integrations/Custom/Autoloaded/CommonScenariosTest.php @@ -47,7 +47,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', ]), ], @@ -59,7 +59,7 @@ public function provideSpecs() 'GET /simple_view' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', ]), ], @@ -71,7 +71,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', ])->setError(), ], diff --git a/tests/Integrations/Custom/Autoloaded/CompileTimeDisabledTest.php b/tests/Integrations/Custom/Autoloaded/CompileTimeDisabledTest.php index 75d9ca48e6..90a5b07460 100644 --- a/tests/Integrations/Custom/Autoloaded/CompileTimeDisabledTest.php +++ b/tests/Integrations/Custom/Autoloaded/CompileTimeDisabledTest.php @@ -16,6 +16,7 @@ protected static function getEnvs() { return array_merge(parent::getEnvs(), [ 'DD_TRACE_MEASURE_COMPILE_TIME' => '0', + 'DD_TRACE_MEASURE_PEAK_MEMORY_USAGE' => '0', ]); } @@ -26,12 +27,14 @@ protected function ddSetUp() * For the compile-time metrics specifically, this goofs things up, so let's disable. */ self::putenv('DD_TRACE_MEASURE_COMPILE_TIME=0'); + self::putenv('DD_TRACE_MEASURE_PEAK_MEMORY_USAGE=0'); \dd_trace_internal_fn('ddtrace_reload_config'); } protected function ddTearDown() { self::putenv('DD_TRACE_MEASURE_COMPILE_TIME'); + self::putenv('DD_TRACE_MEASURE_PEAK_MEMORY_USAGE'); dd_trace_internal_fn('ddtrace_reload_config'); parent::ddTearDown(); } @@ -44,5 +47,7 @@ public function testScenario() }); self::assertFalse(isset($traces[0][0]['metrics']['php.compilation.total_time_ms'])); + self::assertFalse(isset($traces[0][0]['metrics']['php.memory.peak_usage_bytes'])); + self::assertFalse(isset($traces[0][0]['metrics']['php.memory.peak_real_usage_bytes'])); } } diff --git a/tests/Integrations/Custom/Autoloaded/CompileTimeEnabledTest.php b/tests/Integrations/Custom/Autoloaded/CompileTimeEnabledTest.php index 98de286e2e..32bd515bff 100644 --- a/tests/Integrations/Custom/Autoloaded/CompileTimeEnabledTest.php +++ b/tests/Integrations/Custom/Autoloaded/CompileTimeEnabledTest.php @@ -16,6 +16,7 @@ protected static function getEnvs() { return array_merge(parent::getEnvs(), [ 'DD_TRACE_MEASURE_COMPILE_TIME' => '1', + 'DD_TRACE_MEASURE_PEAK_MEMORY_USAGE' => '1', ]); } @@ -26,12 +27,14 @@ protected function ddSetUp() * For the compile-time metrics specifically, this goofs things up, so let's disable. */ self::putenv('DD_TRACE_MEASURE_COMPILE_TIME=0'); + self::putenv('DD_TRACE_MEASURE_PEAK_MEMORY_USAGE=0'); \dd_trace_internal_fn('ddtrace_reload_config'); } protected function ddTearDown() { self::putenv('DD_TRACE_MEASURE_COMPILE_TIME'); + self::putenv('DD_TRACE_MEASURE_PEAK_MEMORY_USAGE'); dd_trace_internal_fn('ddtrace_reload_config'); parent::ddTearDown(); } @@ -44,5 +47,7 @@ public function testScenario() }); self::assertTrue(isset($traces[0][0]['metrics']['php.compilation.total_time_ms'])); + self::assertTrue(isset($traces[0][0]['metrics']['php.memory.peak_usage_bytes'])); + self::assertTrue(isset($traces[0][0]['metrics']['php.memory.peak_real_usage_bytes'])); } } diff --git a/tests/Integrations/Custom/Autoloaded/FatalErrorTest.php b/tests/Integrations/Custom/Autoloaded/FatalErrorTest.php index 59cebaeac8..c500b44c88 100644 --- a/tests/Integrations/Custom/Autoloaded/FatalErrorTest.php +++ b/tests/Integrations/Custom/Autoloaded/FatalErrorTest.php @@ -44,7 +44,7 @@ public function testScenario() 'GET /fatal' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/fatal', + 'http.url' => 'http://localhost/fatal', 'http.status_code' => '200', ]) ->setError("E_ERROR", "Intentional E_ERROR") diff --git a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php index f6fa461dc5..7532739549 100644 --- a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php +++ b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php @@ -53,11 +53,7 @@ public function testInstrumentation() $this->resetRequestDumper(); $this->call(GetSpec::create("autoloaded", "/simple")); - $response = $this->retrieveDumpedData(function ($request) { - return (strpos($request["uri"] ?? "", "/telemetry/") === 0) - && (strpos($request["body"] ?? "", "spans_created") !== false) - ; - }, true); + $response = $this->retrieveDumpedData($this->untilTelemetryRequest("spans_created")); $this->assertGreaterThanOrEqual(3, $response); $payloads = $this->readTelemetryPayloads($response); @@ -96,10 +92,16 @@ public function testInstrumentation() $this->call(GetSpec::create("autoloaded", "/pdo")); - $response = $this->retrieveDumpedData(function ($request) { - return (strpos($request["uri"] ?? "", "/telemetry/") === 0) - && (strpos($request["body"] ?? "", "spans_created") !== false) - ; + $found_telemetry = false; + $found_app_integrations_change = false; + $response = $this->retrieveDumpedData(function ($request) use (&$found_telemetry, &$found_app_integrations_change) { + if (strpos($request["uri"] ?? "", "/telemetry/") === 0 && strpos($request["body"] ?? "", "spans_created") !== false) { + $found_telemetry = true; + } + if (strpos($request["body"] ?? "", "app-integrations-change") !== false) { + $found_app_integrations_change = true; + } + return $found_telemetry && $found_app_integrations_change; }, true); $this->assertGreaterThanOrEqual(3, $response); diff --git a/tests/Integrations/Custom/Autoloaded/TraceSearchConfigTest.php b/tests/Integrations/Custom/Autoloaded/TraceSearchConfigTest.php index e1f3615e9f..e248b80380 100644 --- a/tests/Integrations/Custom/Autoloaded/TraceSearchConfigTest.php +++ b/tests/Integrations/Custom/Autoloaded/TraceSearchConfigTest.php @@ -40,7 +40,7 @@ public function testScenario() 'GET /simple' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', ])->withExactMetrics([ '_dd1.sr.eausr' => 0.3, diff --git a/tests/Integrations/Custom/NotAutoloaded/HttpHeadersConfiguredTest.php b/tests/Integrations/Custom/NotAutoloaded/HttpHeadersConfiguredTest.php index c50f725c4f..c33d5d5b98 100644 --- a/tests/Integrations/Custom/NotAutoloaded/HttpHeadersConfiguredTest.php +++ b/tests/Integrations/Custom/NotAutoloaded/HttpHeadersConfiguredTest.php @@ -38,7 +38,7 @@ public function testSelectedHeadersAreIncluded() $tags = [ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/', + 'http.url' => 'http://localhost/', 'http.status_code' => 200, 'http.request.headers.first-header' => 'some value: with colon', 'http.request.headers.forth-header' => '123', diff --git a/tests/Integrations/Custom/NotAutoloaded/HttpHeadersNotConfiguredTest.php b/tests/Integrations/Custom/NotAutoloaded/HttpHeadersNotConfiguredTest.php index 825a968d63..10e8133267 100644 --- a/tests/Integrations/Custom/NotAutoloaded/HttpHeadersNotConfiguredTest.php +++ b/tests/Integrations/Custom/NotAutoloaded/HttpHeadersNotConfiguredTest.php @@ -44,7 +44,7 @@ public function testSelectedHeadersAreIncluded() 'GET /' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/', + 'http.url' => 'http://localhost/', 'http.status_code' => 200, ]), ] diff --git a/tests/Integrations/Custom/NotAutoloaded/IncomingUserInfoTest.php b/tests/Integrations/Custom/NotAutoloaded/IncomingUserInfoTest.php index f704bef5f8..d8f145eaef 100644 --- a/tests/Integrations/Custom/NotAutoloaded/IncomingUserInfoTest.php +++ b/tests/Integrations/Custom/NotAutoloaded/IncomingUserInfoTest.php @@ -22,7 +22,7 @@ protected static function getEnvs() public function testSelectedHeadersAreIncluded() { $traces = $this->tracesFromWebRequest(function () { - $response = $this->sendRequest('GET', self::HOST_WITH_CREDENTIALS . ':' . self::PORT); + $response = $this->sendRequest('GET', self::HOST_WITH_CREDENTIALS); }); $this->assertFlameGraph( @@ -35,7 +35,7 @@ public function testSelectedHeadersAreIncluded() 'GET /' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/', + 'http.url' => 'http://localhost/', 'http.status_code' => 200, ]), ] diff --git a/tests/Integrations/DeferredLoading/composer.json b/tests/Integrations/DeferredLoading/composer.json new file mode 100644 index 0000000000..5b58b88b4d --- /dev/null +++ b/tests/Integrations/DeferredLoading/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "predis/predis": "^1.1" + } +} diff --git a/tests/Integrations/DeferredLoading/index.php b/tests/Integrations/DeferredLoading/index.php index cfa39551ea..11767cb556 100644 --- a/tests/Integrations/DeferredLoading/index.php +++ b/tests/Integrations/DeferredLoading/index.php @@ -1,6 +1,6 @@ query("SHOW TABLES LIKE 'cache%'"); while ($table = $cacheTables->fetchColumn()) { //fwrite(STDERR, "Truncating table $table" . PHP_EOL); diff --git a/tests/Integrations/Drupal/V9_5/CommonScenariosTest.php b/tests/Integrations/Drupal/V9_5/CommonScenariosTest.php index 1334f0981e..2273f2b598 100644 --- a/tests/Integrations/Drupal/V9_5/CommonScenariosTest.php +++ b/tests/Integrations/Drupal/V9_5/CommonScenariosTest.php @@ -4,6 +4,8 @@ class CommonScenariosTest extends \DDTrace\Tests\Integrations\Drupal\V8_9\CommonScenariosTest { + public static $database = "drupal95"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Drupal/Version_9_5/index.php'; diff --git a/tests/Integrations/Elasticsearch/V1/ElasticSearchIntegrationTest.php b/tests/Integrations/Elasticsearch/V1/ElasticSearchIntegrationTest.php index 656239bdd0..355145b351 100644 --- a/tests/Integrations/Elasticsearch/V1/ElasticSearchIntegrationTest.php +++ b/tests/Integrations/Elasticsearch/V1/ElasticSearchIntegrationTest.php @@ -38,6 +38,8 @@ function array_filter_recursive(callable $keep_fn, array $input) */ class ElasticSearchIntegrationTest extends IntegrationTestCase { + protected static $lockedResource = "elasticsearch"; + const HOST2 = 'elasticsearch2_integration'; const HOST7 = 'elasticsearch7_integration'; @@ -104,7 +106,7 @@ public function testDelete() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -112,7 +114,7 @@ public function testDelete() $traces = $this->isolateTracer(function () use ($client) { $this->assertSame('array', gettype($client->delete([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', ]))); }); @@ -122,7 +124,7 @@ public function testDelete() 'Elasticsearch.Client.delete', 'elasticsearch', 'elasticsearch', - 'delete index:my_index type:my_type' + 'delete index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -143,7 +145,7 @@ public function testExists() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -151,7 +153,7 @@ public function testExists() $traces = $this->isolateTracer(function () use ($client) { $this->assertTrue($client->exists([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', ])); }); @@ -161,7 +163,7 @@ public function testExists() 'Elasticsearch.Client.exists', 'elasticsearch', 'elasticsearch', - 'exists index:my_index type:my_type' + 'exists index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -182,7 +184,7 @@ public function testExplain() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -193,7 +195,7 @@ public function testExplain() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('explanation', $client->explain([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => [ 'query' => [ @@ -208,7 +210,7 @@ public function testExplain() 'Elasticsearch.Client.explain', 'elasticsearch', 'elasticsearch', - 'explain index:my_index type:my_type' + 'explain index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -233,7 +235,7 @@ public function testGet() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -241,7 +243,7 @@ public function testGet() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('found', $client->get([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', ])); }); @@ -251,7 +253,7 @@ public function testGet() 'Elasticsearch.Client.get', 'elasticsearch', 'elasticsearch', - 'get index:my_index type:my_type' + 'get index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -274,7 +276,7 @@ public function testIndex() $traces = $this->isolateTracer(function () use ($client) { $response = $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -286,7 +288,7 @@ public function testIndex() 'Elasticsearch.Client.index', 'elasticsearch', 'elasticsearch', - 'index index:my_index type:my_type' + 'index index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -310,10 +312,10 @@ public function testLimitedTracer() { $client = $this->client(); $traces = $this->isolateLimitedTracer(function () use ($client) { - $client->indices()->delete(['index' => 'my_index']); + $client->indices()->delete(['index' => 'my_index7']); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -321,7 +323,7 @@ public function testLimitedTracer() $docs = $client->search([ 'scroll' => '1s', 'size' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -361,16 +363,16 @@ public function testLimitedTracer() public function testScroll() { $client = $this->client(); - $client->indices()->delete(['index' => 'my_index']); + $client->indices()->delete(['index' => 'my_index7']); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->index([ 'id' => 2, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'second'], ]); @@ -381,7 +383,7 @@ public function testScroll() $docs = $client->search([ 'scroll' => '1s', 'size' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -456,14 +458,14 @@ public function testSearch() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->indices()->flush(); $traces = $this->isolateTracer(function () use ($client) { $client->search([ - 'index' => 'my_index', + 'index' => 'my_index7', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -477,7 +479,7 @@ public function testSearch() 'Elasticsearch.Client.search', 'elasticsearch', 'elasticsearch', - 'search index:' . 'my_index' + 'search index:' . 'my_index7' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -503,14 +505,14 @@ public function testPerformRequest() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->indices()->flush(); $traces = $this->isolateTracer(function () use ($client) { $client->search([ - 'index' => 'my_index', + 'index' => 'my_index7', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -520,7 +522,7 @@ public function testPerformRequest() }); $this->assertFlameGraph($traces, [ - SpanAssertion::exists('Elasticsearch.Client.search', 'search index:my_index') + SpanAssertion::exists('Elasticsearch.Client.search', 'search index:my_index7') ->withChildren([ SpanAssertion::build( 'Elasticsearch.Endpoint.performRequest', @@ -528,7 +530,7 @@ public function testPerformRequest() 'elasticsearch', 'performRequest' )->withExactTags([ - 'elasticsearch.url' => '/my_index/_search', + 'elasticsearch.url' => '/my_index7/_search', 'elasticsearch.method' => \PHP_VERSION_ID >= 70300 ? 'POST' : 'GET', 'elasticsearch.params' => '[]', 'elasticsearch.body' => '{"query":{"match_all":{}}}', @@ -553,7 +555,7 @@ public function testUpdate() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -561,7 +563,7 @@ public function testUpdate() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('_type', $client->update([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index7', 'type' => 'my_type', 'body' => ['doc' => ['my' => 'body']], ])); @@ -572,7 +574,7 @@ public function testUpdate() 'Elasticsearch.Client.update', 'elasticsearch', 'elasticsearch', - 'update index:my_index type:my_type' + 'update index:my_index7 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' diff --git a/tests/Integrations/Elasticsearch/V1/composer.json b/tests/Integrations/Elasticsearch/V1/composer.json new file mode 100644 index 0000000000..d6c78aace2 --- /dev/null +++ b/tests/Integrations/Elasticsearch/V1/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "elasticsearch/elasticsearch": "1.2.*", + "symfony/event-dispatcher": "~2.7" + } +} diff --git a/tests/Integrations/Elasticsearch/V7/ElasticSearchIntegrationTest.php b/tests/Integrations/Elasticsearch/V7/ElasticSearchIntegrationTest.php new file mode 100644 index 0000000000..ad64598c2c --- /dev/null +++ b/tests/Integrations/Elasticsearch/V7/ElasticSearchIntegrationTest.php @@ -0,0 +1,7 @@ +client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -110,7 +112,7 @@ public function testDelete() $traces = $this->isolateTracer(function () use ($client) { $this->assertSame('deleted', $client->delete([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', ])["result"]); }); @@ -120,7 +122,7 @@ public function testDelete() 'Elasticsearch.Client.delete', 'elasticsearch', 'elasticsearch', - 'delete index:my_index type:my_type' + 'delete index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -138,7 +140,7 @@ public function testExists() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -146,7 +148,7 @@ public function testExists() $traces = $this->isolateTracer(function () use ($client) { $this->assertTrue($client->exists([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', ])->asBool()); }); @@ -156,7 +158,7 @@ public function testExists() 'Elasticsearch.Client.exists', 'elasticsearch', 'elasticsearch', - 'exists index:my_index type:my_type' + 'exists index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -173,7 +175,7 @@ public function testExplain() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -184,7 +186,7 @@ public function testExplain() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('explanation', $client->explain([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => [ 'query' => [ @@ -199,7 +201,7 @@ public function testExplain() 'Elasticsearch.Client.explain', 'elasticsearch', 'elasticsearch', - 'explain index:my_index type:my_type' + 'explain index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -218,7 +220,7 @@ public function testGet() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -226,7 +228,7 @@ public function testGet() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('found', $client->get([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', ])); }); @@ -236,7 +238,7 @@ public function testGet() 'Elasticsearch.Client.get', 'elasticsearch', 'elasticsearch', - 'get index:my_index type:my_type' + 'get index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -256,7 +258,7 @@ public function testIndex() $traces = $this->isolateTracer(function () use ($client) { $response = $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -268,7 +270,7 @@ public function testIndex() 'Elasticsearch.Client.index', 'elasticsearch', 'elasticsearch', - 'index index:my_index type:my_type' + 'index index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -286,10 +288,10 @@ public function testLimitedTracer() { $client = $this->client(); $traces = $this->isolateLimitedTracer(function () use ($client) { - $client->indices()->delete(['index' => 'my_index']); + $client->indices()->delete(['index' => 'my_index8']); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -297,7 +299,7 @@ public function testLimitedTracer() $docs = $client->search([ 'scroll' => '1s', 'size' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -337,16 +339,16 @@ public function testLimitedTracer() public function testScroll() { $client = $this->client(); - $client->indices()->delete(['index' => 'my_index']); + $client->indices()->delete(['index' => 'my_index8']); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->index([ 'id' => 2, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'second'], ]); @@ -357,7 +359,7 @@ public function testScroll() $docs = $client->search([ 'scroll' => '1s', 'size' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -436,14 +438,14 @@ public function testSearch() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->indices()->flush(); $traces = $this->isolateTracer(function () use ($client) { $client->search([ - 'index' => 'my_index', + 'index' => 'my_index8', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -457,7 +459,7 @@ public function testSearch() 'Elasticsearch.Client.search', 'elasticsearch', 'elasticsearch', - 'search index:' . 'my_index' + 'search index:' . 'my_index8' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' @@ -476,14 +478,14 @@ public function testPerformRequest() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); $client->indices()->flush(); $traces = $this->isolateTracer(function () use ($client) { $client->search([ - 'index' => 'my_index', + 'index' => 'my_index8', 'body' => [ 'query' => [ 'match_all' => new \stdClass(), @@ -493,7 +495,7 @@ public function testPerformRequest() }); $this->assertFlameGraph($traces, [ - SpanAssertion::exists('Elasticsearch.Client.search', 'search index:my_index') + SpanAssertion::exists('Elasticsearch.Client.search', 'search index:my_index8') ->withChildren([ SpanAssertion::exists('Elastic.Transport.Serializer.JsonSerializer.serialize', 'Elastic.Transport.Serializer.JsonSerializer.serialize'), SpanAssertion::build( @@ -502,7 +504,7 @@ public function testPerformRequest() 'elasticsearch', 'performRequest' )->withExactTags([ - 'elasticsearch.url' => '/my_index/_search', + 'elasticsearch.url' => '/my_index8/_search', 'elasticsearch.method' => 'POST', 'elasticsearch.body' => '{"query":{"match_all":{}}}', Tag::SPAN_KIND => 'client', @@ -519,7 +521,7 @@ public function testUpdate() $client = $this->client(); $client->index([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['my' => 'body'], ]); @@ -527,7 +529,7 @@ public function testUpdate() $traces = $this->isolateTracer(function () use ($client) { $this->assertArrayHasKey('_type', $client->update([ 'id' => 1, - 'index' => 'my_index', + 'index' => 'my_index8', 'type' => 'my_type', 'body' => ['doc' => ['my' => 'body']], ])); @@ -538,7 +540,7 @@ public function testUpdate() 'Elasticsearch.Client.update', 'elasticsearch', 'elasticsearch', - 'update index:my_index type:my_type' + 'update index:my_index8 type:my_type' )->withExactTags([ Tag::SPAN_KIND => 'client', Tag::COMPONENT => 'elasticsearch' diff --git a/tests/Integrations/Elasticsearch/V8/composer.json b/tests/Integrations/Elasticsearch/V8/composer.json new file mode 100644 index 0000000000..d248e533b2 --- /dev/null +++ b/tests/Integrations/Elasticsearch/V8/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "elasticsearch/elasticsearch": "~8.5" + } +} diff --git a/tests/Integrations/Frankenphp/CommonScenariosTest.php b/tests/Integrations/Frankenphp/CommonScenariosTest.php index 64bebda421..c87894fff8 100644 --- a/tests/Integrations/Frankenphp/CommonScenariosTest.php +++ b/tests/Integrations/Frankenphp/CommonScenariosTest.php @@ -46,7 +46,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'frankenphp' @@ -60,7 +60,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'frankenphp' diff --git a/tests/Integrations/Guzzle/V5/composer.json b/tests/Integrations/Guzzle/V5/composer.json new file mode 100644 index 0000000000..255565722b --- /dev/null +++ b/tests/Integrations/Guzzle/V5/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "guzzlehttp/guzzle": "~5.0" + } +} diff --git a/tests/Integrations/Guzzle/V5/guzzle_in_distributed_web_request.php b/tests/Integrations/Guzzle/V5/guzzle_in_distributed_web_request.php index 6b6f1e949c..082e0b3334 100644 --- a/tests/Integrations/Guzzle/V5/guzzle_in_distributed_web_request.php +++ b/tests/Integrations/Guzzle/V5/guzzle_in_distributed_web_request.php @@ -1,6 +1,7 @@ inWebServer( function ($execute) { - $execute(GetSpec::create('GET', '/guzzle_in_web_request.php')); + $execute(GetSpec::create('GET', '/guzzle_in_web_request.php?version=' . basename(dirname(str_replace("\\", "/", static::class))))); }, __DIR__ . '/guzzle_in_web_request.php', [ @@ -516,6 +516,8 @@ public function testMultiExec() }, [ 'start', 'metrics.php.compilation.total_time_ms', + 'metrics.php.memory.peak_usage_bytes', + 'metrics.php.memory.peak_real_usage_bytes', 'meta.error.stack', 'meta._dd.p.tid', 'meta.curl.appconnect_time_us', diff --git a/tests/Integrations/Guzzle/V6/composer.json b/tests/Integrations/Guzzle/V6/composer.json new file mode 100644 index 0000000000..3913318017 --- /dev/null +++ b/tests/Integrations/Guzzle/V6/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "guzzlehttp/guzzle": "~6.0" + } +} diff --git a/tests/Integrations/Guzzle/V6/guzzle_in_distributed_web_request.php b/tests/Integrations/Guzzle/V6/guzzle_in_distributed_web_request.php index 7dabf8f65d..dea0e9363c 100644 --- a/tests/Integrations/Guzzle/V6/guzzle_in_distributed_web_request.php +++ b/tests/Integrations/Guzzle/V6/guzzle_in_distributed_web_request.php @@ -1,6 +1,7 @@ 'swoole_test_app', 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_DEBUG' => 'true' + 'DD_TRACE_DEBUG' => 'true', + 'PHP_INI_SCAN_DIR' => ':' . dirname(self::getAppIndexScript()), ]); } @@ -105,7 +98,7 @@ public function testScenarioGetReturnString() 'App\Http\Controllers\CommonSpecsController@simple simple_route' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple?key=value&', + Tag::HTTP_URL => 'http://localhost/simple?key=value&', Tag::HTTP_ROUTE => 'simple', Tag::HTTP_STATUS_CODE => '200', Tag::SPAN_KIND => 'server', @@ -172,7 +165,7 @@ public function testScenarioGetWithView() 'App\Http\Controllers\CommonSpecsController@simple_view unnamed_route' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple_view?key=value&', + Tag::HTTP_URL => 'http://localhost/simple_view?key=value&', Tag::HTTP_ROUTE => 'simple_view', Tag::HTTP_STATUS_CODE => '200', Tag::SPAN_KIND => 'server', @@ -256,7 +249,7 @@ public function testScenarioGetWithException() 'App\Http\Controllers\CommonSpecsController@error unnamed_route' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/error?key=value&', + Tag::HTTP_URL => 'http://localhost/error?key=value&', Tag::HTTP_ROUTE => 'error', Tag::HTTP_STATUS_CODE => '500', Tag::SPAN_KIND => 'server', @@ -322,7 +315,7 @@ public function testScenarioGetToMissingRoute() 'GET /does_not_exist' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/does_not_exist?key=value&', + Tag::HTTP_URL => 'http://localhost/does_not_exist?key=value&', Tag::HTTP_STATUS_CODE => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'laravel', diff --git a/tests/Integrations/Laravel/V10_x/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V10_x/AutomatedLoginEventsTest.php index 31e6fbde5c..c616c67b47 100644 --- a/tests/Integrations/Laravel/V10_x/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V10_x/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel10"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_10_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V10_x/CommonScenariosTest.php b/tests/Integrations/Laravel/V10_x/CommonScenariosTest.php index 6221d289c7..d626ca2f8e 100644 --- a/tests/Integrations/Laravel/V10_x/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V10_x/CommonScenariosTest.php @@ -4,6 +4,8 @@ class CommonScenariosTest extends \DDTrace\Tests\Integrations\Laravel\V9_x\CommonScenariosTest { + public static $database = "laravel10"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_10_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V4/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V4/AutomatedLoginEventsTest.php index 6ba787cca0..cc2e76bf25 100644 --- a/tests/Integrations/Laravel/V4/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V4/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel42"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_4_2/public/index.php'; diff --git a/tests/Integrations/Laravel/V4/CommonScenariosTest.php b/tests/Integrations/Laravel/V4/CommonScenariosTest.php index 1cda1b25c6..825f249773 100644 --- a/tests/Integrations/Laravel/V4/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V4/CommonScenariosTest.php @@ -9,6 +9,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "laravel42"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_4_2/public/index.php'; @@ -47,7 +49,7 @@ public function provideSpecs() 'laravel.route.name' => 'simple_route', 'laravel.route.action' => 'HomeController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', 'http.route' => 'simple', 'some.key1' => 'value', @@ -136,7 +138,7 @@ public function provideSpecs() 'laravel.route.name' => 'error', 'laravel.route.action' => 'HomeController@error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'http.route' => 'error', 'some.key1' => 'value', @@ -184,7 +186,7 @@ public function provideSpecs() 'laravel.route.name' => 'unnamed_route', 'laravel.route.action' => 'HomeController@dynamicRoute', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/dynamic_route/dynamic01/static/dynamic02', + 'http.url' => 'http://localhost/dynamic_route/dynamic01/static/dynamic02', 'http.status_code' => '200', 'http.route' => 'dynamic_route/{param01}/static/{param02?}', 'some.key1' => 'value', diff --git a/tests/Integrations/Laravel/V4/EloquentTest.php b/tests/Integrations/Laravel/V4/EloquentTest.php index 03476d4e94..e4b9698a33 100644 --- a/tests/Integrations/Laravel/V4/EloquentTest.php +++ b/tests/Integrations/Laravel/V4/EloquentTest.php @@ -10,6 +10,8 @@ class EloquentTest extends WebFrameworkTestCase { + public static $database = "laravel42"; + use SpanAssertionTrait; protected static function getAppIndexScript() @@ -119,6 +121,6 @@ public function testDelete() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel42', 'test', 'test'); } } diff --git a/tests/Integrations/Laravel/V4/PathParamsTest.php b/tests/Integrations/Laravel/V4/PathParamsTest.php index bb47e3c409..ff9b25c868 100644 --- a/tests/Integrations/Laravel/V4/PathParamsTest.php +++ b/tests/Integrations/Laravel/V4/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "laravel42"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_4_2/public/index.php'; diff --git a/tests/Integrations/Laravel/V4/TraceSearchConfigTest.php b/tests/Integrations/Laravel/V4/TraceSearchConfigTest.php index c8eb8ddc46..3139466297 100644 --- a/tests/Integrations/Laravel/V4/TraceSearchConfigTest.php +++ b/tests/Integrations/Laravel/V4/TraceSearchConfigTest.php @@ -9,6 +9,8 @@ class TraceSearchConfigTest extends WebFrameworkTestCase { + public static $database = "laravel42"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_4_2/public/index.php'; @@ -39,7 +41,7 @@ public function testScenario() 'laravel.route.name' => 'simple_route', 'laravel.route.action' => 'HomeController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', 'http.route' => 'simple', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V5_7/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V5_7/AutomatedLoginEventsTest.php index 9145056a95..32d2f3dc1e 100644 --- a/tests/Integrations/Laravel/V5_7/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V5_7/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel57"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_7/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_7/CommonScenariosTest.php b/tests/Integrations/Laravel/V5_7/CommonScenariosTest.php index 2ef0269557..d638ab09ec 100644 --- a/tests/Integrations/Laravel/V5_7/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V5_7/CommonScenariosTest.php @@ -7,6 +7,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "laravel57"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_7/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_7/EloquentTest.php b/tests/Integrations/Laravel/V5_7/EloquentTest.php index 1f5ad77bf6..6c7de63ae2 100644 --- a/tests/Integrations/Laravel/V5_7/EloquentTest.php +++ b/tests/Integrations/Laravel/V5_7/EloquentTest.php @@ -10,6 +10,8 @@ class EloquentTest extends WebFrameworkTestCase { + public static $database = "laravel57"; + use SpanAssertionTrait; protected static function getAppIndexScript() @@ -138,6 +140,6 @@ public function testDelete() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel57', 'test', 'test'); } } diff --git a/tests/Integrations/Laravel/V5_7/PathParamsTest.php b/tests/Integrations/Laravel/V5_7/PathParamsTest.php index 7c1eb922ab..414cdf9eab 100644 --- a/tests/Integrations/Laravel/V5_7/PathParamsTest.php +++ b/tests/Integrations/Laravel/V5_7/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "laravel57"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_7/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_7/PipelineTracingTest.php b/tests/Integrations/Laravel/V5_7/PipelineTracingTest.php index 6781709b0d..40eb25037b 100644 --- a/tests/Integrations/Laravel/V5_7/PipelineTracingTest.php +++ b/tests/Integrations/Laravel/V5_7/PipelineTracingTest.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class PipelineTracingTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel57"; protected static function getAppIndexScript() { diff --git a/tests/Integrations/Laravel/V5_7/QueueTest.php b/tests/Integrations/Laravel/V5_7/QueueTest.php index c3c83d03f1..d0c5314012 100644 --- a/tests/Integrations/Laravel/V5_7/QueueTest.php +++ b/tests/Integrations/Laravel/V5_7/QueueTest.php @@ -4,6 +4,8 @@ class QueueTest extends \DDTrace\Tests\Integrations\Laravel\V5_8\QueueTest { + public static $database = "laravel57"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_7/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_7/TraceSearchConfigTest.php b/tests/Integrations/Laravel/V5_7/TraceSearchConfigTest.php index d0da6140c1..db975b80f6 100644 --- a/tests/Integrations/Laravel/V5_7/TraceSearchConfigTest.php +++ b/tests/Integrations/Laravel/V5_7/TraceSearchConfigTest.php @@ -9,6 +9,8 @@ class TraceSearchConfigTest extends WebFrameworkTestCase { + public static $database = "laravel57"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_7/public/index.php'; @@ -44,7 +46,7 @@ public function testScenario() 'laravel.route.name' => 'simple_route', 'laravel.route.action' => 'App\Http\Controllers\CommonSpecsController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', 'http.route' => 'simple', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V5_8/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V5_8/AutomatedLoginEventsTest.php index 2753cbf9bf..0d8ea732e6 100644 --- a/tests/Integrations/Laravel/V5_8/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V5_8/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel58"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_8/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_8/CommonScenariosTest.php b/tests/Integrations/Laravel/V5_8/CommonScenariosTest.php index 9efb7c6386..042c7c42a3 100644 --- a/tests/Integrations/Laravel/V5_8/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V5_8/CommonScenariosTest.php @@ -2,13 +2,10 @@ namespace DDTrace\Tests\Integrations\Laravel\V5_8; -use DDTrace\Tag; -use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\WebFrameworkTestCase; -use DDTrace\Tests\Frameworks\Util\Request\RequestSpec; - class CommonScenariosTest extends \DDTrace\Tests\Integrations\Laravel\V5_7\CommonScenariosTest { + public static $database = "laravel58"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_8/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_8/EloquentTest.php b/tests/Integrations/Laravel/V5_8/EloquentTest.php index d3105283cb..47a2799c02 100644 --- a/tests/Integrations/Laravel/V5_8/EloquentTest.php +++ b/tests/Integrations/Laravel/V5_8/EloquentTest.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class EloquentTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel58"; protected static function getAppIndexScript() { @@ -140,6 +137,6 @@ public function testDelete() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel58', 'test', 'test'); } } diff --git a/tests/Integrations/Laravel/V5_8/PathParamsTest.php b/tests/Integrations/Laravel/V5_8/PathParamsTest.php index 419ee976eb..358d2a7453 100644 --- a/tests/Integrations/Laravel/V5_8/PathParamsTest.php +++ b/tests/Integrations/Laravel/V5_8/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "laravel58"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_8/public/index.php'; diff --git a/tests/Integrations/Laravel/V5_8/QueueTest.php b/tests/Integrations/Laravel/V5_8/QueueTest.php index 46da9e80e6..b09b790716 100644 --- a/tests/Integrations/Laravel/V5_8/QueueTest.php +++ b/tests/Integrations/Laravel/V5_8/QueueTest.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class QueueTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel58"; protected static function getAppIndexScript() { @@ -234,7 +231,7 @@ protected function resetQueue() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=' . static::$database, 'test', 'test'); } protected function spanEventJobProcessing() diff --git a/tests/Integrations/Laravel/V5_8/TraceSearchConfigTest.php b/tests/Integrations/Laravel/V5_8/TraceSearchConfigTest.php index 13582b3645..a3bfa259e9 100644 --- a/tests/Integrations/Laravel/V5_8/TraceSearchConfigTest.php +++ b/tests/Integrations/Laravel/V5_8/TraceSearchConfigTest.php @@ -9,6 +9,8 @@ class TraceSearchConfigTest extends WebFrameworkTestCase { + public static $database = "laravel58"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_5_8/public/index.php'; @@ -44,7 +46,7 @@ public function testScenario() 'laravel.route.name' => 'simple_route', 'laravel.route.action' => 'App\Http\Controllers\CommonSpecsController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', 'http.route' => 'simple', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php index 580018f9c7..7d5db973ca 100644 --- a/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V8_x/CommonScenariosTest.php b/tests/Integrations/Laravel/V8_x/CommonScenariosTest.php index 25c224ad23..10c8cb17cd 100644 --- a/tests/Integrations/Laravel/V8_x/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V8_x/CommonScenariosTest.php @@ -10,6 +10,8 @@ class CommonScenariosTest extends \DDTrace\Tests\Integrations\Laravel\V5_7\CommonScenariosTest { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V8_x/EloquentTest.php b/tests/Integrations/Laravel/V8_x/EloquentTest.php index 0e6098b647..e6462ff79c 100644 --- a/tests/Integrations/Laravel/V8_x/EloquentTest.php +++ b/tests/Integrations/Laravel/V8_x/EloquentTest.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class EloquentTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel8"; protected static function getAppIndexScript() { @@ -140,6 +137,6 @@ public function testDelete() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel8', 'test', 'test'); } } diff --git a/tests/Integrations/Laravel/V8_x/HttpHideRouteTest.php b/tests/Integrations/Laravel/V8_x/HttpHideRouteTest.php index 1a1cfa645b..e629b94e0f 100644 --- a/tests/Integrations/Laravel/V8_x/HttpHideRouteTest.php +++ b/tests/Integrations/Laravel/V8_x/HttpHideRouteTest.php @@ -10,6 +10,8 @@ class HttpHideRouteTest extends WebFrameworkTestCase { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; @@ -35,4 +37,4 @@ public function testDefaultPath() { } } } -} \ No newline at end of file +} diff --git a/tests/Integrations/Laravel/V8_x/InternalExceptionsTest.php b/tests/Integrations/Laravel/V8_x/InternalExceptionsTest.php index e393fde9e2..b27527c8b6 100644 --- a/tests/Integrations/Laravel/V8_x/InternalExceptionsTest.php +++ b/tests/Integrations/Laravel/V8_x/InternalExceptionsTest.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class InternalExceptionsTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel8"; protected static function getAppIndexScript() { @@ -46,7 +43,7 @@ public function testNotImplemented() 'laravel.route.name' => 'not-implemented', 'laravel.route.action' => 'App\Http\Controllers\InternalErrorController@notImplemented', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/not-implemented', + 'http.url' => 'http://localhost/not-implemented', 'http.status_code' => '501', 'http.route' => 'not-implemented', TAG::SPAN_KIND => 'server', @@ -106,7 +103,7 @@ public function testUnauthorized() 'laravel.route.name' => 'unauthorized', 'laravel.route.action' => 'App\Http\Controllers\InternalErrorController@unauthorized', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/unauthorized', + 'http.url' => 'http://localhost/unauthorized', 'http.status_code' => '403', 'http.route' => 'unauthorized', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V8_x/PathParamsTest.php b/tests/Integrations/Laravel/V8_x/PathParamsTest.php index c9d1183386..1a6f81bc2c 100644 --- a/tests/Integrations/Laravel/V8_x/PathParamsTest.php +++ b/tests/Integrations/Laravel/V8_x/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V8_x/QueueTest.php b/tests/Integrations/Laravel/V8_x/QueueTest.php index a8e34083e7..e0c66ed88c 100644 --- a/tests/Integrations/Laravel/V8_x/QueueTest.php +++ b/tests/Integrations/Laravel/V8_x/QueueTest.php @@ -2,22 +2,14 @@ namespace DDTrace\Tests\Integrations\Laravel\V8_x; -use DDTrace\Log\Logger; use DDTrace\Tag; -use DDTrace\Tests\Common\SnapshotTestTrait; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\SpanChecker; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; -use Illuminate\Support\Facades\Artisan; class QueueTest extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; - use SnapshotTestTrait; + public static $database = "laravel8"; protected static function getAppIndexScript() { @@ -349,7 +341,7 @@ protected function resetQueue() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel8', 'test', 'test'); } protected function spanEventJobProcessing() diff --git a/tests/Integrations/Laravel/V8_x/QueueTestNotDistributed.php b/tests/Integrations/Laravel/V8_x/QueueTestNotDistributed.php index cc901923a5..72a1a3f751 100644 --- a/tests/Integrations/Laravel/V8_x/QueueTestNotDistributed.php +++ b/tests/Integrations/Laravel/V8_x/QueueTestNotDistributed.php @@ -4,15 +4,12 @@ use DDTrace\Tag; use DDTrace\Tests\Common\SpanAssertion; -use DDTrace\Tests\Common\SpanAssertionTrait; -use DDTrace\Tests\Common\TracerTestTrait; use DDTrace\Tests\Common\WebFrameworkTestCase; use DDTrace\Tests\Frameworks\Util\Request\GetSpec; class QueueTestNotDistributed extends WebFrameworkTestCase { - use TracerTestTrait; - use SpanAssertionTrait; + public static $database = "laravel8"; protected static function getAppIndexScript() { @@ -91,7 +88,7 @@ protected function resetQueue() protected function connection() { - return new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + return new \PDO('mysql:host=mysql_integration;dbname=laravel8', 'test', 'test'); } protected function getCommonTags( diff --git a/tests/Integrations/Laravel/V8_x/RouteCachingTest.php b/tests/Integrations/Laravel/V8_x/RouteCachingTest.php index 269936d567..1a357f8a4c 100644 --- a/tests/Integrations/Laravel/V8_x/RouteCachingTest.php +++ b/tests/Integrations/Laravel/V8_x/RouteCachingTest.php @@ -10,6 +10,8 @@ class RouteCachingTest extends WebFrameworkTestCase { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; @@ -40,7 +42,7 @@ public function testNotCached() 'laravel.route.name' => 'unnamed_route', 'laravel.route.action' => 'App\Http\Controllers\RouteCachingController@unnamed', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/unnamed-route', + 'http.url' => 'http://localhost/unnamed-route', 'http.status_code' => '200', 'http.route' => 'unnamed-route', TAG::SPAN_KIND => 'server', @@ -88,7 +90,7 @@ public function testCached() 'laravel.route.name' => 'unnamed_route', 'laravel.route.action' => 'App\Http\Controllers\RouteCachingController@unnamed', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/unnamed-route', + 'http.url' => 'http://localhost/unnamed-route', 'http.status_code' => '200', 'http.route' => 'unnamed-route', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V8_x/TraceSearchConfigTest.php b/tests/Integrations/Laravel/V8_x/TraceSearchConfigTest.php index 1ea5d475ab..4c98522fc8 100644 --- a/tests/Integrations/Laravel/V8_x/TraceSearchConfigTest.php +++ b/tests/Integrations/Laravel/V8_x/TraceSearchConfigTest.php @@ -9,6 +9,8 @@ class TraceSearchConfigTest extends WebFrameworkTestCase { + public static $database = "laravel8"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_8_x/public/index.php'; @@ -44,7 +46,7 @@ public function testScenario() 'laravel.route.name' => 'simple_route', 'laravel.route.action' => 'App\Http\Controllers\CommonSpecsController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', 'http.route' => 'simple', TAG::SPAN_KIND => 'server', diff --git a/tests/Integrations/Laravel/V9_x/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V9_x/AutomatedLoginEventsTest.php index 7b9407ce15..89fafef9d8 100644 --- a/tests/Integrations/Laravel/V9_x/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V9_x/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "laravel9"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_9_x/public/index.php'; diff --git a/tests/Integrations/Laravel/V9_x/CommonScenariosTest.php b/tests/Integrations/Laravel/V9_x/CommonScenariosTest.php index 682bcc0d37..aa15066f69 100644 --- a/tests/Integrations/Laravel/V9_x/CommonScenariosTest.php +++ b/tests/Integrations/Laravel/V9_x/CommonScenariosTest.php @@ -7,6 +7,8 @@ class CommonScenariosTest extends \DDTrace\Tests\Integrations\Laravel\V5_7\CommonScenariosTest { + public static $database = "laravel9"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Laravel/Version_9_x/public/index.php'; diff --git a/tests/Integrations/Logs/BaseLogsTest.php b/tests/Integrations/Logs/BaseLogsTest.php index b42485a8fa..3e72b215db 100644 --- a/tests/Integrations/Logs/BaseLogsTest.php +++ b/tests/Integrations/Logs/BaseLogsTest.php @@ -8,15 +8,20 @@ class BaseLogsTest extends \DDTrace\Tests\Common\IntegrationTestCase { + protected static function logFile() + { + return "/tmp/test-" . substr(static::class, strrpos(static::class, '\\') + 1) . '.log'; + } + protected function ddSetUp() { parent::ddSetUp(); - shell_exec('rm -f /tmp/test.log'); + shell_exec('rm -f ' . static::logFile()); } protected function getTestFileContents(): string { - $filename = '/tmp/test.log'; + $filename = static::logFile(); $handle = fopen($filename, 'r'); $contents = fread($handle, filesize($filename)); fclose($handle); diff --git a/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php b/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php index e6deb9c4b1..bfb5963137 100644 --- a/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php +++ b/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php @@ -23,7 +23,7 @@ protected function ddSetUp() protected function getLogger($jsonFormatter = false) { $logger = new Logger(); - $writer = new Stream('/tmp/test.log'); + $writer = new Stream(static::logFile()); if ($jsonFormatter) { $writer->setFormatter(new Json()); diff --git a/tests/Integrations/Logs/LaminasLogV2/composer.json b/tests/Integrations/Logs/LaminasLogV2/composer.json new file mode 100644 index 0000000000..f16f36b9ef --- /dev/null +++ b/tests/Integrations/Logs/LaminasLogV2/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "laminas/laminas-log": "~2.0" + } +} diff --git a/tests/Integrations/Logs/MonologV1/MonologV1Test.php b/tests/Integrations/Logs/MonologV1/MonologV1Test.php index c84cd24d7a..0c17cd9311 100644 --- a/tests/Integrations/Logs/MonologV1/MonologV1Test.php +++ b/tests/Integrations/Logs/MonologV1/MonologV1Test.php @@ -12,7 +12,7 @@ class MonologV1Test extends BaseLogsTest protected function getLogger($jsonFormatter = false) { $logger = new Logger('test'); - $streamHandler = new StreamHandler('/tmp/test.log'); + $streamHandler = new StreamHandler(static::logFile()); if ($jsonFormatter) { $streamHandler->setFormatter(new JsonFormatter()); diff --git a/tests/Integrations/Logs/MonologV1/composer.json b/tests/Integrations/Logs/MonologV1/composer.json new file mode 100644 index 0000000000..4928c06f8b --- /dev/null +++ b/tests/Integrations/Logs/MonologV1/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "monolog/monolog": "~1.0" + } +} diff --git a/tests/Integrations/Logs/MonologV2/composer.json b/tests/Integrations/Logs/MonologV2/composer.json new file mode 100644 index 0000000000..fa7c066e21 --- /dev/null +++ b/tests/Integrations/Logs/MonologV2/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "monolog/monolog": "~2.0" + } +} diff --git a/tests/Integrations/Logs/MonologV3/composer.json b/tests/Integrations/Logs/MonologV3/composer.json new file mode 100644 index 0000000000..5156e664e6 --- /dev/null +++ b/tests/Integrations/Logs/MonologV3/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "monolog/monolog": "~3.0" + } +} diff --git a/tests/Integrations/Lumen/V10_0/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V10_0/TraceSearchConfigTest.php index 57f6771713..26ed1d4328 100644 --- a/tests/Integrations/Lumen/V10_0/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V10_0/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Lumen/V5_2/CommonScenariosTest.php b/tests/Integrations/Lumen/V5_2/CommonScenariosTest.php index a0ddf044af..b3bcfc9a1c 100644 --- a/tests/Integrations/Lumen/V5_2/CommonScenariosTest.php +++ b/tests/Integrations/Lumen/V5_2/CommonScenariosTest.php @@ -50,7 +50,7 @@ public function provideSpecs() )->withExactTags([ 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simpleView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' @@ -110,7 +110,7 @@ public function provideSpecs() )->withExactTags([ 'lumen.route.action' => 'App\Http\Controllers\ExampleController@error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' @@ -159,7 +159,7 @@ protected function getSimpleTrace() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' @@ -188,7 +188,7 @@ protected function getErrorTrace() )->withExactTags([ 'lumen.route.action' => 'App\Http\Controllers\ExampleController@error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' diff --git a/tests/Integrations/Lumen/V5_2/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V5_2/TraceSearchConfigTest.php index fc20a908f0..1245798370 100644 --- a/tests/Integrations/Lumen/V5_2/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V5_2/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' diff --git a/tests/Integrations/Lumen/V5_6/CommonScenariosTest.php b/tests/Integrations/Lumen/V5_6/CommonScenariosTest.php index bee117face..87d5afcba1 100644 --- a/tests/Integrations/Lumen/V5_6/CommonScenariosTest.php +++ b/tests/Integrations/Lumen/V5_6/CommonScenariosTest.php @@ -50,7 +50,7 @@ public function provideSpecs() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen', @@ -75,7 +75,7 @@ public function provideSpecs() )->withExactTags([ 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simpleView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen', @@ -120,7 +120,7 @@ public function provideSpecs() )->withExactTags([ 'lumen.route.action' => 'App\Http\Controllers\ExampleController@error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', TAG::COMPONENT => 'lumen' diff --git a/tests/Integrations/Lumen/V5_6/DeprecatedResourceNameTest.php b/tests/Integrations/Lumen/V5_6/DeprecatedResourceNameTest.php index 55e89c648f..4fdcf2dd03 100644 --- a/tests/Integrations/Lumen/V5_6/DeprecatedResourceNameTest.php +++ b/tests/Integrations/Lumen/V5_6/DeprecatedResourceNameTest.php @@ -43,7 +43,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Lumen/V5_6/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V5_6/TraceSearchConfigTest.php index cf690079c4..864c7eb320 100644 --- a/tests/Integrations/Lumen/V5_6/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V5_6/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Lumen/V5_8/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V5_8/TraceSearchConfigTest.php index b635b4dd35..b02f5039e6 100644 --- a/tests/Integrations/Lumen/V5_8/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V5_8/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Lumen/V8_1/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V8_1/TraceSearchConfigTest.php index 49d8820d2b..170e013f37 100644 --- a/tests/Integrations/Lumen/V8_1/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V8_1/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Lumen/V9_0/TraceSearchConfigTest.php b/tests/Integrations/Lumen/V9_0/TraceSearchConfigTest.php index 01e856ac9c..8819068029 100644 --- a/tests/Integrations/Lumen/V9_0/TraceSearchConfigTest.php +++ b/tests/Integrations/Lumen/V9_0/TraceSearchConfigTest.php @@ -44,7 +44,7 @@ public function testScenario() 'lumen.route.name' => 'simple_route', 'lumen.route.action' => 'App\Http\Controllers\ExampleController@simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'lumen', diff --git a/tests/Integrations/Magento/V2_3/CommonScenariosTest.php b/tests/Integrations/Magento/V2_3/CommonScenariosTest.php index c1c34c740c..bfd39174eb 100644 --- a/tests/Integrations/Magento/V2_3/CommonScenariosTest.php +++ b/tests/Integrations/Magento/V2_3/CommonScenariosTest.php @@ -4,6 +4,8 @@ class CommonScenariosTest extends \DDTrace\Tests\Integrations\Magento\V2_4\CommonScenariosTest { + public static $database = "magento23"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Magento/Version_2_3/pub/index.php'; diff --git a/tests/Integrations/Magento/V2_4/CommonScenariosTest.php b/tests/Integrations/Magento/V2_4/CommonScenariosTest.php index 7a543ba61a..14eea63e32 100644 --- a/tests/Integrations/Magento/V2_4/CommonScenariosTest.php +++ b/tests/Integrations/Magento/V2_4/CommonScenariosTest.php @@ -7,6 +7,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "magento24"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Magento/Version_2_4/pub/index.php'; diff --git a/tests/Integrations/Memcache/MemcacheTest.php b/tests/Integrations/Memcache/MemcacheTest.php index ef0244a087..06289a79fb 100644 --- a/tests/Integrations/Memcache/MemcacheTest.php +++ b/tests/Integrations/Memcache/MemcacheTest.php @@ -10,6 +10,8 @@ final class MemcacheTest extends IntegrationTestCase { + protected static $lockedResource = "memcache"; + /** * @var \Memcache */ diff --git a/tests/Integrations/Memcached/MemcachedTest.php b/tests/Integrations/Memcached/MemcachedTest.php index 80a2470d43..7c07ff96cf 100644 --- a/tests/Integrations/Memcached/MemcachedTest.php +++ b/tests/Integrations/Memcached/MemcachedTest.php @@ -10,6 +10,8 @@ final class MemcachedTest extends IntegrationTestCase { + protected static $lockedResource = "memcache"; + /** * @var \Memcached */ diff --git a/tests/Integrations/Mongo/MongoTest.php b/tests/Integrations/Mongo/MongoTest.php index 1d616f9c00..ae1e83398e 100644 --- a/tests/Integrations/Mongo/MongoTest.php +++ b/tests/Integrations/Mongo/MongoTest.php @@ -12,6 +12,8 @@ class MongoTest extends IntegrationTestCase { + protected static $lockedResource = "mongodb"; + const HOST = 'mongodb_integration'; const PORT = '27017'; const USER = 'test'; diff --git a/tests/Integrations/MongoDB/MongoDBTest.php b/tests/Integrations/MongoDB/MongoDBTest.php index dc14ca2d9c..8e467c03d2 100644 --- a/tests/Integrations/MongoDB/MongoDBTest.php +++ b/tests/Integrations/MongoDB/MongoDBTest.php @@ -26,6 +26,8 @@ class AnObject class MongoDBTest extends IntegrationTestCase { + protected static $lockedResource = "mongodb"; + const HOST = 'mongodb_integration'; const PORT = '27017'; const USER = 'test'; diff --git a/tests/Integrations/MongoDB/composer.json b/tests/Integrations/MongoDB/composer.json new file mode 100644 index 0000000000..14ff1e3bda --- /dev/null +++ b/tests/Integrations/MongoDB/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "mongodb/mongodb": "1.*" + } +} diff --git a/tests/Integrations/Mysqli/MysqliTest.php b/tests/Integrations/Mysqli/MysqliTest.php index 705faa6b5b..62be833d17 100644 --- a/tests/Integrations/Mysqli/MysqliTest.php +++ b/tests/Integrations/Mysqli/MysqliTest.php @@ -9,7 +9,7 @@ class MysqliTest extends IntegrationTestCase { private static $host = 'mysql_integration'; - private static $db = 'test'; + public static $database = 'mysqlitest'; private static $port = '3306'; private static $user = 'test'; private static $password = 'test'; @@ -39,7 +39,7 @@ protected function envsToCleanUpAtTearDown() public function testProceduralConnect() { $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); $mysqli->close(); }); @@ -96,7 +96,7 @@ public function testProceduralConnectDontReportError() public function testConstructorConnect() { $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->close(); }); @@ -109,7 +109,7 @@ public function testConstructorConnect() public function testProceduralQuery() { $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, 'SELECT * from tests'); $mysqli->close(); }); @@ -130,7 +130,7 @@ public function testProceduralQueryPeerServiceEnabled() { $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true']); $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, 'SELECT * from tests'); $mysqli->close(); }); @@ -154,7 +154,7 @@ public function testProceduralExecuteQuery() } $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_execute_query($mysqli, 'SELECT * from tests WHERE 1 = ?', [1]); $mysqli->close(); }); @@ -175,7 +175,7 @@ public function testProceduralExecuteQueryPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true']); $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_execute_query($mysqli, 'SELECT * from tests WHERE 1 = ?', [1]); $mysqli->close(); }); @@ -191,7 +191,7 @@ public function testProceduralQueryRealConnect() { $traces = $this->isolateTracer(function () { $mysqli = \mysqli_init(); - \mysqli_real_connect($mysqli, self::$host, self::$user, self::$password, self::$db); + \mysqli_real_connect($mysqli, self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, 'SELECT * from tests'); $mysqli->close(); }); @@ -215,7 +215,7 @@ public function testProceduralQueryRealConnectPeerServiceEnabled() $traces = $this->isolateTracer(function () { $mysqli = \mysqli_init(); - \mysqli_real_connect($mysqli, self::$host, self::$user, self::$password, self::$db); + \mysqli_real_connect($mysqli, self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, 'SELECT * from tests'); $mysqli->close(); }); @@ -236,7 +236,7 @@ public function testProceduralQueryRealConnectPeerServiceEnabled() public function testConstructorQuery() { $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -258,7 +258,7 @@ public function testConstructorQueryPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true']); $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -279,7 +279,7 @@ public function testEmptyConstructorQuery() { $traces = $this->isolateTracer(function () { $mysqli = new \mysqli(); - $mysqli->real_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli->real_connect(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -304,7 +304,7 @@ public function testEmptyConstructorQueryPeerServiceEnabled() $traces = $this->isolateTracer(function () { $mysqli = new \mysqli(); - $mysqli->real_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli->real_connect(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -327,7 +327,7 @@ public function testProceduralCommit() { $query = "INSERT INTO tests (id, name) VALUES (100, 'Tom')"; $traces = $this->isolateTracer(function () use ($query) { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, $query); \mysqli_commit($mysqli); $mysqli->close(); @@ -345,7 +345,7 @@ public function testProceduralCommit() public function testConstructorPreparedStatement() { $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $stmt = $mysqli->prepare("INSERT INTO tests (id, name) VALUES (?, ?)"); $id = 100; $name = 100; @@ -369,7 +369,7 @@ public function testConstructorPreparedStatementPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true']); $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $stmt = $mysqli->prepare("INSERT INTO tests (id, name) VALUES (?, ?)"); $id = 100; $name = 100; @@ -393,7 +393,7 @@ public function testProceduralSelectDbPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true', 'DD_TRACE_GENERATE_ROOT_SPAN=true']); $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_select_db($mysqli, 'information_schema'); \mysqli_query($mysqli, 'SELECT * from columns limit 1'); $mysqli->close(); @@ -422,7 +422,7 @@ public function testConstructorSelectDbPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true', 'DD_TRACE_GENERATE_ROOT_SPAN=true']); $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->select_db('information_schema'); $mysqli->query('SELECT * from columns limit 1'); $mysqli->close(); @@ -449,7 +449,7 @@ public function testConstructorSelectDbPeerServiceEnabled() public function testLimitedTracerConstructorQuery() { $traces = $this->isolateLimitedTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -460,7 +460,7 @@ public function testLimitedTracerConstructorQuery() public function testLimitedTracerProceduralCommit() { $traces = $this->isolateLimitedTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); \mysqli_query($mysqli, "INSERT INTO tests (id, name) VALUES (100, 'From Test')"); \mysqli_commit($mysqli); $mysqli->close(); @@ -473,7 +473,7 @@ public function testLimitedTracerProceduralCommit() public function testLimitedTracerConstructorPreparedStatement() { $traces = $this->isolateLimitedTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $stmt = $mysqli->prepare("INSERT INTO tests (id, name) VALUES (?, ?)"); $id = 100; $name = 100; @@ -489,7 +489,7 @@ public function testLimitedTracerConstructorPreparedStatement() public function testProceduralPreparedStatement() { $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); $stmt = \mysqli_prepare($mysqli, "INSERT INTO tests (id, name) VALUES (?, ?)"); $id = 100; $name = 100; @@ -503,13 +503,9 @@ public function testProceduralPreparedStatement() $this->assertFlameGraph($traces, [ SpanAssertion::exists('mysqli_connect', 'mysqli_connect'), SpanAssertion::build('mysqli_prepare', 'mysqli', 'sql', 'INSERT INTO tests (id, name) VALUES (?, ?)') - ->withExactTags(array_merge(self::baseTags(), [ - '_dd.base_service' => 'phpunit', - ])), + ->withExactTags(self::baseTags()), SpanAssertion::build('mysqli_stmt_execute', 'mysqli', 'sql', 'INSERT INTO tests (id, name) VALUES (?, ?)') - ->withExactTags(array_merge(self::baseTags(), [ - '_dd.base_service' => 'phpunit', - ])), + ->withExactTags(self::baseTags()), ], true, false); } @@ -518,7 +514,7 @@ public function testProceduralPreparedStatementPeerServiceEnabled() $this->putEnvAndReloadConfig(['DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED=true']); $traces = $this->isolateTracer(function () { - $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$db); + $mysqli = \mysqli_connect(self::$host, self::$user, self::$password, self::$database); $stmt = \mysqli_prepare($mysqli, "INSERT INTO tests (id, name) VALUES (?, ?)"); $id = 100; $name = 100; @@ -571,7 +567,7 @@ public function testNoFakeServices() ]); $traces = $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query('SELECT * from tests'); $mysqli->close(); }); @@ -591,7 +587,7 @@ public function testServiceMappedSplitByDomain() self::putEnv('DD_TRACE_DB_CLIENT_SPLIT_BY_INSTANCE=true'); self::putEnv('DD_SERVICE_MAPPING=mysqli:my-mysqli'); $traces = $this->isolateTracer(function () { - new \mysqli(self::$host, self::$user, self::$password, self::$db); + new \mysqli(self::$host, self::$user, self::$password, self::$database); }); $this->assertSpans($traces, [ @@ -611,11 +607,11 @@ private function baseTags($expectDbName = true, $expectPeerService = false) ]; if ($expectDbName) { - $tags['db.name'] = 'test'; + $tags['db.name'] = self::$database; } if ($expectPeerService) { - $tags['peer.service'] = 'test'; + $tags['peer.service'] = self::$database; $tags['_dd.peer.service.source'] = 'db.name'; } @@ -625,7 +621,7 @@ private function baseTags($expectDbName = true, $expectPeerService = false) private function setUpDatabase() { $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query(" CREATE TABLE tests ( id integer not null primary key, @@ -641,7 +637,7 @@ private function setUpDatabase() private function clearDatabase() { $this->isolateTracer(function () { - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $mysqli->query("DROP TABLE IF EXISTS tests"); $mysqli->commit(); $mysqli->close(); @@ -661,7 +657,7 @@ private function queryDatabaseAllAssociative($table, $wheres) $conditions[] = "$key = '$value'"; } $inlineWhere = $conditions ? 'WHERE ' . implode('AND', $conditions) : ''; - $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$db); + $mysqli = new \mysqli(self::$host, self::$user, self::$password, self::$database); $result = $mysqli->query("SELECT * FROM $table $inlineWhere"); if (false === $result) { $message = mysqli_error($mysqli); diff --git a/tests/Integrations/Nette/V2_4/NetteTest.php b/tests/Integrations/Nette/V2_4/NetteTest.php index ee4e82802b..6d1d565389 100644 --- a/tests/Integrations/Nette/V2_4/NetteTest.php +++ b/tests/Integrations/Nette/V2_4/NetteTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' @@ -93,7 +93,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'simpleView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' @@ -147,7 +147,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'errorView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' diff --git a/tests/Integrations/Nette/V3_0/NetteTest.php b/tests/Integrations/Nette/V3_0/NetteTest.php index 678516411b..81edcdd1fe 100644 --- a/tests/Integrations/Nette/V3_0/NetteTest.php +++ b/tests/Integrations/Nette/V3_0/NetteTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' @@ -93,7 +93,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'simpleView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' @@ -147,7 +147,7 @@ public function provideSpecs() 'nette.route.presenter' => 'Homepage', 'nette.route.action' => 'errorView', 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'nette' diff --git a/tests/Integrations/OpenAI/OpenAITest.php b/tests/Integrations/OpenAI/OpenAITest.php index cf5f1c668d..2a4851fc5b 100644 --- a/tests/Integrations/OpenAI/OpenAITest.php +++ b/tests/Integrations/OpenAI/OpenAITest.php @@ -29,7 +29,7 @@ private function checkErrors() protected function ddSetUp() { - // Note: Remember that DD_DOGSTATSD_URL=http://127.0.0.1:9876 is set in the Makefile call + // Note: Remember that DD_DOGSTATSD_URL=http://request-replayer:80 is set in the Makefile call ini_set("log_errors", 1); ini_set("error_log", __DIR__ . "/openai.log"); self::putEnvAndReloadConfig([ diff --git a/tests/Integrations/OpenAI/composer.json b/tests/Integrations/OpenAI/composer.json new file mode 100644 index 0000000000..6bbc027b03 --- /dev/null +++ b/tests/Integrations/OpenAI/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "openai-php/client": "@stable", + "guzzlehttp/guzzle": "^7.8.1", + "guzzlehttp/psr7": "^2.6.2" + } +} diff --git a/tests/Integrations/PCNTL/PCNTLTest.php b/tests/Integrations/PCNTL/PCNTLTest.php index a10c94d465..3df2bc579d 100644 --- a/tests/Integrations/PCNTL/PCNTLTest.php +++ b/tests/Integrations/PCNTL/PCNTLTest.php @@ -5,12 +5,16 @@ use DDTrace\Tests\Common\IntegrationTestCase; use DDTrace\Tests\Common\SpanAssertion; -const ACCEPTABLE_TEST_EXECTION_TIME_S = 1.4; - final class PCNTLTest extends IntegrationTestCase { + private static $acceptable_test_execution_time = 2; + protected function ddSetUp() { + if (!\dd_trace_env_config("DD_TRACE_SIDECAR_TRACE_SENDER")) { + self::$acceptable_test_execution_time = 1.4; + } + $this->resetRequestDumper(); parent::ddSetUp(); } @@ -34,7 +38,7 @@ public function testDoesNoHangAtShutdownWhenDisabled($scriptPath) ] ); $end = \microtime(true); - $this->assertLessThan(ACCEPTABLE_TEST_EXECTION_TIME_S, $end - $start); + $this->assertLessThan(self::$acceptable_test_execution_time, $end - $start); } /** @@ -59,7 +63,7 @@ public function testDoesNoHangAtShutdownWhenEnabled($scriptPath) true ); $end = \microtime(true); - $this->assertLessThan(ACCEPTABLE_TEST_EXECTION_TIME_S, $end - $start); + $this->assertLessThan(self::$acceptable_test_execution_time, $end - $start); if (\dd_trace_env_config("DD_TRACE_SIDECAR_TRACE_SENDER")) { \dd_trace_synchronous_flush(); } @@ -84,9 +88,12 @@ public function testCliShortRunningTracingWhenEnabled() __DIR__ . '/scripts/synthetic.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', - ] + ], + [], + '', + false, + $this->untilNumberOfTraces(2) ); $this->assertCount(2, $requests); @@ -112,7 +119,6 @@ public function testAccessingTracerAfterForkIsUnproblematic() __DIR__ . '/scripts/access-tracer-after-fork.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_DEBUG' => 'false', ], @@ -137,9 +143,12 @@ public function testCliShortRunningMainSpanAreGenerateBeforeAndAfter() __DIR__ . '/scripts/short-running.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', - ] + ], + [], + '', + false, + $this->untilNumberOfTraces(2) ); $this->assertCount(2, $requests); @@ -165,9 +174,12 @@ public function testCliShortRunningMultipleForks() __DIR__ . '/scripts/short-running-multiple.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', - ] + ], + [], + '', + false, + $this->untilNumberOfTraces(6) ); $this->assertCount(6, $requests); @@ -204,7 +216,6 @@ public function testCliShortRunningMultipleNestedForks() __DIR__ . '/scripts/short-running-multiple-nested.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', ] ); @@ -238,32 +249,34 @@ public function testCliShortRunningMultipleNestedForks() public function testCliLongRunningMultipleForksAutoFlush() { - list($requests) = $this->inCli( + $this->inCli( __DIR__ . '/scripts/long-running-autoflush.php', [ 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'true', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', - 'DD_TRACE_AGENT_FLUSH_INTERVAL' => 0, - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, - ] - ); - $this->assertCount(4, $requests); - - for ($i = 0; $i < 4; $i += 2) { - $this->assertFlameGraph([$requests[$i]], [ - SpanAssertion::exists('curl_exec', '/httpbin_integration/ip'), - ]); - - $this->assertFlameGraph([$requests[$i + 1]], [ - SpanAssertion::exists('long_running_entry_point')->withChildren([ - SpanAssertion::exists('curl_exec', '/httpbin_integration/get'), - SpanAssertion::exists('curl_exec', '/httpbin_integration/headers'), - SpanAssertion::exists('curl_exec', '/httpbin_integration/user-agent'), + 'DD_TRACE_AGENT_FLUSH_INTERVAL' => 333, + ], + [], + '', + false, + $this->until( + $this->untilSpan(SpanAssertion::exists('curl_exec', '/httpbin_integration/child-0')), + $this->untilSpan(SpanAssertion::exists('curl_exec', '/httpbin_integration/child-1')), + $this->untilSpan(SpanAssertion::exists('long_running_entry_point')->withChildren([ + SpanAssertion::exists('curl_exec', '/httpbin_integration/entry_point-0'), + SpanAssertion::exists('curl_exec', '/httpbin_integration/main_process-0'), + SpanAssertion::exists('curl_exec', '/httpbin_integration/end_entry_point-0'), SpanAssertion::exists('pcntl_fork'), - ]), - ]); - } + ])), + $this->untilSpan(SpanAssertion::exists('long_running_entry_point')->withChildren([ + SpanAssertion::exists('curl_exec', '/httpbin_integration/entry_point-1'), + SpanAssertion::exists('curl_exec', '/httpbin_integration/main_process-1'), + SpanAssertion::exists('curl_exec', '/httpbin_integration/end_entry_point-1'), + SpanAssertion::exists('pcntl_fork'), + ])) + ) + ); } public function testCliLongRunningMultipleForksManualFlush() @@ -278,8 +291,11 @@ public function testCliLongRunningMultipleForksManualFlush() 'DD_TRACE_CLI_ENABLED' => 'true', 'DD_TRACE_AUTO_FLUSH_ENABLED' => 'false', 'DD_TRACE_GENERATE_ROOT_SPAN' => 'false', - 'DD_TRACE_SHUTDOWN_TIMEOUT' => 5000, - ] + ], + [], + '', + false, + $this->untilNumberOfTraces(6) ); $this->assertCount(6, $requests); diff --git a/tests/Integrations/PCNTL/scripts/long-running-autoflush.php b/tests/Integrations/PCNTL/scripts/long-running-autoflush.php index b13c29bc90..876ff59dd9 100644 --- a/tests/Integrations/PCNTL/scripts/long-running-autoflush.php +++ b/tests/Integrations/PCNTL/scripts/long-running-autoflush.php @@ -10,26 +10,26 @@ }); for ($iteration = 0; $iteration < ITERATIONS; $iteration++) { - long_running_entry_point(); + long_running_entry_point($iteration); } -function long_running_entry_point() +function long_running_entry_point($iteration) { - call_httpbin('get'); + call_httpbin('entry_point-'.$iteration); $forkPid = pcntl_fork(); if ($forkPid > 0) { // Main - call_httpbin('headers'); + call_httpbin('main_process-'.$iteration); } else if ($forkPid === 0) { // Child - call_httpbin('ip'); + call_httpbin('child-'.$iteration); exit(0); } else { error_log('Error'); exit(-1); } - call_httpbin('user-agent'); + call_httpbin('end_entry_point-'.$iteration); pcntl_waitpid($forkPid, $childStatus); } diff --git a/tests/Integrations/PDO/PDOTest.php b/tests/Integrations/PDO/PDOTest.php index 31ef5357e1..690acb8f01 100644 --- a/tests/Integrations/PDO/PDOTest.php +++ b/tests/Integrations/PDO/PDOTest.php @@ -8,7 +8,7 @@ final class PDOTest extends IntegrationTestCase { - const MYSQL_DATABASE = 'test'; + public static $database = 'pdotest'; const MYSQL_USER = 'test'; const MYSQL_PASSWORD = 'test'; const MYSQL_HOST = 'mysql_integration'; @@ -733,45 +733,39 @@ private function ensureActiveQueriesErrorCanHappen() private function setUpDatabase() { - $this->isolateTracer(function () { - $pdo = $this->pdoInstance(); + $pdo = $this->pdoInstance(); + $pdo->beginTransaction(); + $pdo->exec(" + CREATE TABLE tests ( + id integer not null primary key AUTO_INCREMENT, + name varchar(100) + ) + "); + if (PHP_VERSION_ID >= 80000 && !$pdo->inTransaction()) { + // CREATE TABLE causes an implicit commit on PHP 8 + // @see https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html $pdo->beginTransaction(); - $pdo->exec(" - CREATE TABLE tests ( - id integer not null primary key AUTO_INCREMENT, - name varchar(100) - ) - "); - if (PHP_VERSION_ID >= 80000 && !$pdo->inTransaction()) { - // CREATE TABLE causes an implicit commit on PHP 8 - // @see https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html - $pdo->beginTransaction(); - } - $pdo->exec("INSERT INTO tests (id, name) VALUES (1, 'Tom')"); + } + $pdo->exec("INSERT INTO tests (id, name) VALUES (1, 'Tom')"); - $pdo->commit(); - $pdo = null; - }); + $pdo->commit(); } private function clearDatabase() { - $this->isolateTracer(function () { - $pdo = $this->pdoInstance(); - $pdo->beginTransaction(); - $pdo->exec("DROP TABLE tests"); - if (PHP_VERSION_ID < 80000) { - // DROP TABLE causes an implicit commit on PHP 8 - // @see https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html - $pdo->commit(); - } - $pdo = null; - }); + $pdo = $this->pdoInstance(); + $pdo->beginTransaction(); + $pdo->exec("DROP TABLE tests"); + if (PHP_VERSION_ID < 80000) { + // DROP TABLE causes an implicit commit on PHP 8 + // @see https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html + $pdo->commit(); + } } public function mysqlDns() { - return "mysql:host=" . self::MYSQL_HOST . ";dbname=" . self::MYSQL_DATABASE; + return "mysql:host=" . self::MYSQL_HOST . ";dbname=" . self::$database; } protected function baseTags($expectPeerService = false) @@ -779,7 +773,7 @@ protected function baseTags($expectPeerService = false) $tags = [ 'db.engine' => 'mysql', 'out.host' => self::MYSQL_HOST, - 'db.name' => self::MYSQL_DATABASE, + 'db.name' => self::$database, 'db.user' => self::MYSQL_USER, 'span.kind' => 'client', Tag::COMPONENT => 'pdo', @@ -787,7 +781,7 @@ protected function baseTags($expectPeerService = false) ]; if ($expectPeerService) { - $tags['peer.service'] = self::MYSQL_DATABASE; + $tags['peer.service'] = self::$database; $tags['_dd.peer.service.source'] = 'db.name'; } diff --git a/tests/Integrations/PHPRedis/V3/PHPRedisClusterTest.php b/tests/Integrations/PHPRedis/V3/PHPRedisClusterTest.php index 1b1c3ae3a6..bf2da378cc 100644 --- a/tests/Integrations/PHPRedis/V3/PHPRedisClusterTest.php +++ b/tests/Integrations/PHPRedis/V3/PHPRedisClusterTest.php @@ -10,6 +10,8 @@ class PHPRedisClusterTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const CONNECTION_1 = 'CONNECTION_1'; const CONNECTION_1_AS_ARG = 'CONNECTION_1_AS_ARG'; const A_STRING = 'A_STRING'; @@ -131,11 +133,19 @@ public function dataProviderTestMethodsSpansOnly() /** * @dataProvider dataProviderTestMethodsSimpleSpan */ - public function testMethodsOnlySpan($method, $arg) + public function testMethodsOnlySpan($method, $arg, $canFail = false) { $redis = $this->redis; - $traces = $this->isolateTracer(function () use ($redis, $method, $arg) { - null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + $hasError = false; + $traces = $this->isolateTracer(function () use ($redis, $method, $arg, $canFail, &$hasError) { + try { + null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + } catch (\RedisClusterException $e) { + if (!$canFail) { + throw $e; + } + $hasError = true; + } }); $this->assertFlameGraph($traces, [ @@ -143,8 +153,13 @@ public function testMethodsOnlySpan($method, $arg) "RedisCluster.$method", 'phpredis', 'redis', - "RedisCluster.$method" - )->withExactTags($this->baseTags()), + "RedisCluster.$method", + [], + null, + $hasError + ) + ->withExactTags($this->baseTags()) + ->skipTagsLike('/^error\..*/'), ]); } @@ -154,8 +169,8 @@ public function dataProviderTestMethodsSimpleSpan() 'ping' => ['ping', null], 'echo' => ['echo', 'hey'], 'save' => ['save', null], - 'bgRewriteAOF' => ['bgRewriteAOF', null], - 'flushAll' => ['flushAll', null], + 'bgRewriteAOF' => ['bgRewriteAOF', null, true], + 'flushAll' => ['flushAll', null, true], 'flushDb' => ['flushDb', null], ]; } diff --git a/tests/Integrations/PHPRedis/V3/PHPRedisTest.php b/tests/Integrations/PHPRedis/V3/PHPRedisTest.php index 34c413547f..d182c6ed1a 100644 --- a/tests/Integrations/PHPRedis/V3/PHPRedisTest.php +++ b/tests/Integrations/PHPRedis/V3/PHPRedisTest.php @@ -10,6 +10,8 @@ class PHPRedisTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const A_STRING = 'A_STRING'; const ARRAY_COUNT_1 = 'ARRAY_COUNT_1'; const ARRAY_COUNT_2 = 'ARRAY_COUNT_2'; diff --git a/tests/Integrations/PHPRedis/V4/PHPRedisClusterTest.php b/tests/Integrations/PHPRedis/V4/PHPRedisClusterTest.php index 906ba3e22f..e341a508ab 100644 --- a/tests/Integrations/PHPRedis/V4/PHPRedisClusterTest.php +++ b/tests/Integrations/PHPRedis/V4/PHPRedisClusterTest.php @@ -10,6 +10,8 @@ class PHPRedisClusterTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const CONNECTION_1 = 'CONNECTION_1'; const CONNECTION_1_AS_ARG = 'CONNECTION_1_AS_ARG'; const A_STRING = 'A_STRING'; @@ -131,11 +133,19 @@ public function dataProviderTestMethodsSpansOnly() /** * @dataProvider dataProviderTestMethodsSimpleSpan */ - public function testMethodsOnlySpan($method, $arg) + public function testMethodsOnlySpan($method, $arg, $canFail = false) { $redis = $this->redis; - $traces = $this->isolateTracer(function () use ($redis, $method, $arg) { - null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + $hasError = false; + $traces = $this->isolateTracer(function () use ($redis, $method, $arg, $canFail, &$hasError) { + try { + null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + } catch (\RedisClusterException $e) { + if (!$canFail) { + throw $e; + } + $hasError = true; + } }); $this->assertFlameGraph($traces, [ @@ -143,8 +153,13 @@ public function testMethodsOnlySpan($method, $arg) "RedisCluster.$method", 'phpredis', 'redis', - "RedisCluster.$method" - )->withExactTags($this->baseTags()), + "RedisCluster.$method", + [], + null, + $hasError + ) + ->withExactTags($this->baseTags()) + ->skipTagsLike('/^error\..*/'), ]); } @@ -154,8 +169,8 @@ public function dataProviderTestMethodsSimpleSpan() 'ping' => ['ping', null], 'echo' => ['echo', 'hey'], 'save' => ['save', null], - 'bgRewriteAOF' => ['bgRewriteAOF', null], - 'flushAll' => ['flushAll', null], + 'bgRewriteAOF' => ['bgRewriteAOF', null, true], + 'flushAll' => ['flushAll', null, true], 'flushDb' => ['flushDb', null], ]; } diff --git a/tests/Integrations/PHPRedis/V4/PHPRedisTest.php b/tests/Integrations/PHPRedis/V4/PHPRedisTest.php index 8162c9f76e..54142c087a 100644 --- a/tests/Integrations/PHPRedis/V4/PHPRedisTest.php +++ b/tests/Integrations/PHPRedis/V4/PHPRedisTest.php @@ -10,6 +10,8 @@ class PHPRedisTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const A_STRING = 'A_STRING'; const A_FLOAT = 'A_FLOAT'; const ARRAY_COUNT_1 = 'ARRAY_COUNT_1'; diff --git a/tests/Integrations/PHPRedis/V5/PHPRedisClusterTest.php b/tests/Integrations/PHPRedis/V5/PHPRedisClusterTest.php index ac7eee8d0b..26b01a61c4 100644 --- a/tests/Integrations/PHPRedis/V5/PHPRedisClusterTest.php +++ b/tests/Integrations/PHPRedis/V5/PHPRedisClusterTest.php @@ -10,6 +10,8 @@ class PHPRedisClusterTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const CONNECTION_1 = 'CONNECTION_1'; const CONNECTION_1_AS_ARG = 'CONNECTION_1_AS_ARG'; const A_STRING = 'A_STRING'; @@ -150,11 +152,19 @@ public function dataProviderTestMethodsSpansOnly() /** * @dataProvider dataProviderTestMethodsSimpleSpan */ - public function testMethodsOnlySpan($method, $arg) + public function testMethodsOnlySpan($method, $arg, $canFail = false) { $redis = $this->redis; - $traces = $this->isolateTracer(function () use ($redis, $method, $arg) { - null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + $hasError = false; + $traces = $this->isolateTracer(function () use ($redis, $method, $arg, $canFail, &$hasError) { + try { + null === $arg ? $redis->$method($this->connection1) : $redis->$method($this->connection1, $arg); + } catch (\RedisClusterException $e) { + if (!$canFail) { + throw $e; + } + $hasError = true; + } }); $this->assertFlameGraph($traces, [ @@ -162,8 +172,13 @@ public function testMethodsOnlySpan($method, $arg) "RedisCluster.$method", 'phpredis', 'redis', - "RedisCluster.$method" - )->withExactTags($this->baseTags()), + "RedisCluster.$method", + [], + null, + $hasError + ) + ->withExactTags($this->baseTags()) + ->skipTagsLike('/^error\..*/'), ]); } @@ -173,8 +188,8 @@ public function dataProviderTestMethodsSimpleSpan() 'ping' => ['ping', null], 'echo' => ['echo', 'hey'], 'save' => ['save', null], - 'bgRewriteAOF' => ['bgRewriteAOF', null], - 'flushAll' => ['flushAll', null], + 'bgRewriteAOF' => ['bgRewriteAOF', null, true], + 'flushAll' => ['flushAll', null, true], 'flushDb' => ['flushDb', null], ]; } diff --git a/tests/Integrations/PHPRedis/V5/PHPRedisTest.php b/tests/Integrations/PHPRedis/V5/PHPRedisTest.php index f8e1d043be..68271da19d 100644 --- a/tests/Integrations/PHPRedis/V5/PHPRedisTest.php +++ b/tests/Integrations/PHPRedis/V5/PHPRedisTest.php @@ -17,6 +17,8 @@ class CustomRedisClass extends \Redis class PHPRedisTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + const A_STRING = 'A_STRING'; const A_FLOAT = 'A_FLOAT'; const ARRAY_COUNT_1 = 'ARRAY_COUNT_1'; diff --git a/tests/Integrations/Predis/PredisTest.php b/tests/Integrations/Predis/PredisTest.php index 8bc7f9ed4c..f7e5d0e703 100644 --- a/tests/Integrations/Predis/PredisTest.php +++ b/tests/Integrations/Predis/PredisTest.php @@ -9,6 +9,8 @@ final class PredisTest extends IntegrationTestCase { + protected static $lockedResource = "redis"; + private $host = 'redis_integration'; private $port = '6379'; diff --git a/tests/Integrations/Predis/composer.json b/tests/Integrations/Predis/composer.json new file mode 100644 index 0000000000..5b58b88b4d --- /dev/null +++ b/tests/Integrations/Predis/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "predis/predis": "^1.1" + } +} diff --git a/tests/Integrations/Roadrunner/V2/CommonScenariosTest.php b/tests/Integrations/Roadrunner/V2/CommonScenariosTest.php index d7f888da37..efe303b35d 100644 --- a/tests/Integrations/Roadrunner/V2/CommonScenariosTest.php +++ b/tests/Integrations/Roadrunner/V2/CommonScenariosTest.php @@ -46,7 +46,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'roadrunner' @@ -60,7 +60,7 @@ public function provideSpecs() 'GET /simple_view' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'roadrunner' @@ -74,7 +74,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'error.stack' => '#0 {main}', Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/SQLSRV/SQLSRVTest.php b/tests/Integrations/SQLSRV/SQLSRVTest.php index a52857bb57..2410e00913 100644 --- a/tests/Integrations/SQLSRV/SQLSRVTest.php +++ b/tests/Integrations/SQLSRV/SQLSRVTest.php @@ -11,7 +11,7 @@ class SQLSRVTest extends IntegrationTestCase { private static $host = 'sqlsrv_integration'; private static $port = '1433'; - private static $database = 'master'; + private static $db = 'master'; private static $user = 'sa'; private static $password = 'Password12!'; @@ -397,17 +397,14 @@ public function testConnectPrepareStatement() $this->assertFlameGraph($traces, [ SpanAssertion::exists('sqlsrv_connect'), SpanAssertion::build('sqlsrv_prepare', 'sqlsrv', 'sql', $query) - ->withExactTags( - array_merge(self::baseTags($query), [ - '_dd.base_service' => 'phpunit', - ])), + ->withExactTags(self::baseTags($query)), SpanAssertion::build('sqlsrv_execute', 'sqlsrv', 'sql', $query) - ->withExactTags(array_merge(self::baseTags($query), [ - '_dd.base_service' => 'phpunit', - ])) + ->withExactTags(self::baseTags($query)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, + '_dd.agent_psr' => 1.0, + '_sampling_priority_v1' => 1.0, ]) ], true, false); } @@ -445,7 +442,7 @@ private function createConnection() self::$host . ', ' . self::$port, [ 'PWD' => self::$password, - 'Database' => self::$database, + 'Database' => self::$db, 'UID' => self::$user, 'TrustServerCertificate' => true ] @@ -484,7 +481,7 @@ private static function baseTags($query = null, $expectPeerService = false) Tag::SPAN_KIND => 'client', Tag::COMPONENT => SQLSRVIntegration::NAME, Tag::DB_SYSTEM => SQLSRVIntegration::SYSTEM, - Tag::DB_INSTANCE => self::$database, + Tag::DB_INSTANCE => self::$db, Tag::DB_USER => self::$user, Tag::TARGET_HOST => self::$host, Tag::TARGET_PORT => self::$port, diff --git a/tests/Integrations/Slim/V3_12/CommonScenariosTest.php b/tests/Integrations/Slim/V3_12/CommonScenariosTest.php index 0525516853..712538bc1f 100644 --- a/tests/Integrations/Slim/V3_12/CommonScenariosTest.php +++ b/tests/Integrations/Slim/V3_12/CommonScenariosTest.php @@ -49,7 +49,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.controller' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -74,7 +74,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.controller' => 'App\SimpleViewController::index', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -109,7 +109,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.controller' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -137,7 +137,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.controller' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.url' => 'http://localhost/parameterized/paramValue', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', diff --git a/tests/Integrations/Slim/V4/CommonScenariosTest.php b/tests/Integrations/Slim/V4/CommonScenariosTest.php index 22c14b1f56..1a248bafbe 100644 --- a/tests/Integrations/Slim/V4/CommonScenariosTest.php +++ b/tests/Integrations/Slim/V4/CommonScenariosTest.php @@ -112,7 +112,7 @@ public function provideSpecs() 'slim.route.name' => 'simple-route', 'slim.route.handler' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -140,7 +140,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.handler' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -177,7 +177,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.handler' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', @@ -211,7 +211,7 @@ public function provideSpecs() )->withExactTags([ 'slim.route.handler' => 'Closure::__invoke', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.url' => 'http://localhost/parameterized/paramValue', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'slim', diff --git a/tests/Integrations/Swoole/CommonScenariosTest.php b/tests/Integrations/Swoole/CommonScenariosTest.php index 684c393da9..04e23cf013 100644 --- a/tests/Integrations/Swoole/CommonScenariosTest.php +++ b/tests/Integrations/Swoole/CommonScenariosTest.php @@ -65,7 +65,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'swoole' @@ -79,7 +79,7 @@ public function provideSpecs() 'GET /simple_view' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'swoole' @@ -93,7 +93,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:' . self::PORT . '/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', 'error.stack' => "#0 [internal function]: {closure}()\n#1 {main}", Tag::SPAN_KIND => 'server', diff --git a/tests/Integrations/Symfony/V3_0/CommonScenariosTest.php b/tests/Integrations/Symfony/V3_0/CommonScenariosTest.php index 3874d14d77..4f9d14068d 100644 --- a/tests/Integrations/Symfony/V3_0/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V3_0/CommonScenariosTest.php @@ -43,7 +43,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -80,7 +80,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -126,7 +126,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -192,7 +192,7 @@ public function provideSpecs() )->withExactTags([ 'symfony.route.action' => 'Symfony\Bundle\TwigBundle\Controller\ExceptionController@showAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_0/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V3_0/TraceSearchConfigTest.php index 360eb17d57..82be10bfa6 100644 --- a/tests/Integrations/Symfony/V3_0/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V3_0/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_3/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V3_3/AutomatedLoginEventsTest.php index 73d2fb36e8..3ce67daa57 100644 --- a/tests/Integrations/Symfony/V3_3/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Symfony/V3_3/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "symfony33"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_3_3/web/index.php'; diff --git a/tests/Integrations/Symfony/V3_3/CommonScenariosTest.php b/tests/Integrations/Symfony/V3_3/CommonScenariosTest.php index 8e8135cfe3..7e6d402ec9 100644 --- a/tests/Integrations/Symfony/V3_3/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V3_3/CommonScenariosTest.php @@ -43,7 +43,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -81,7 +81,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -128,7 +128,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -196,7 +196,7 @@ public function provideSpecs() )->withExactTags([ 'symfony.route.action' => 'Symfony\Bundle\TwigBundle\Controller\ExceptionController@showAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_3/PathParamsTest.php b/tests/Integrations/Symfony/V3_3/PathParamsTest.php index f1b82a2576..d0c2d1228f 100644 --- a/tests/Integrations/Symfony/V3_3/PathParamsTest.php +++ b/tests/Integrations/Symfony/V3_3/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "symfony33"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_3_3/web/index.php'; diff --git a/tests/Integrations/Symfony/V3_3/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V3_3/TraceSearchConfigTest.php index 7a7d09fa5c..2c9478e5a3 100644 --- a/tests/Integrations/Symfony/V3_3/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V3_3/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_4/AutofinishedTracesSymfony34Test.php b/tests/Integrations/Symfony/V3_4/AutofinishedTracesSymfony34Test.php index dc12f68b93..5e69751943 100644 --- a/tests/Integrations/Symfony/V3_4/AutofinishedTracesSymfony34Test.php +++ b/tests/Integrations/Symfony/V3_4/AutofinishedTracesSymfony34Test.php @@ -37,7 +37,7 @@ public function testEndpointThatExitsWithNoProcess() 'symfony.route.action' => 'AppBundle\Controller\HomeController@actionBeingTerminatedByExit', 'symfony.route.name' => 'terminated_by_exit', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/terminated_by_exit', + 'http.url' => 'http://localhost/terminated_by_exit', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_4/CommonScenariosTest.php b/tests/Integrations/Symfony/V3_4/CommonScenariosTest.php index 478e0f340d..4973645565 100644 --- a/tests/Integrations/Symfony/V3_4/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V3_4/CommonScenariosTest.php @@ -50,7 +50,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -88,7 +88,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -137,7 +137,7 @@ public function provideSpecs() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -192,7 +192,7 @@ public function provideSpecs() ->withExactTags([ 'symfony.route.action' => 'Symfony\Bundle\TwigBundle\Controller\ExceptionController@showAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_4/TemplateEnginesTest.php b/tests/Integrations/Symfony/V3_4/TemplateEnginesTest.php index 91fd087a37..910c479081 100644 --- a/tests/Integrations/Symfony/V3_4/TemplateEnginesTest.php +++ b/tests/Integrations/Symfony/V3_4/TemplateEnginesTest.php @@ -30,7 +30,7 @@ public function testAlternateTemplatingEngine() 'symfony.route.action' => 'AppBundle\Controller\HomeController@indexAction', 'symfony.route.name' => 'alternate_templating', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/alternate_templating', + 'http.url' => 'http://localhost/alternate_templating', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V3_4/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V3_4/TraceSearchConfigTest.php index 37664d4627..b7ffd3eba3 100644 --- a/tests/Integrations/Symfony/V3_4/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V3_4/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'AppBundle\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_0/CommonScenariosTest.php b/tests/Integrations/Symfony/V4_0/CommonScenariosTest.php index 4586d2344b..e93bc42506 100644 --- a/tests/Integrations/Symfony/V4_0/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V4_0/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -89,7 +89,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -136,7 +136,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -204,7 +204,7 @@ public function provideSpecs() )->withExactTags([ 'symfony.route.action' => 'Symfony\Bundle\TwigBundle\Controller\ExceptionController@showAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_0/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V4_0/TraceSearchConfigTest.php index e5d213e98f..1cacc0f82a 100644 --- a/tests/Integrations/Symfony/V4_0/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V4_0/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_2/CommonScenariosTest.php b/tests/Integrations/Symfony/V4_2/CommonScenariosTest.php index c67f0a7563..f2a703c53d 100644 --- a/tests/Integrations/Symfony/V4_2/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V4_2/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -89,7 +89,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -136,7 +136,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -202,7 +202,7 @@ public function provideSpecs() )->withExactTags([ 'symfony.route.action' => 'Symfony\Bundle\TwigBundle\Controller\ExceptionController@showAction', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_2/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V4_2/TraceSearchConfigTest.php index 53cfc7923d..5e7d684b29 100644 --- a/tests/Integrations/Symfony/V4_2/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V4_2/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_4/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V4_4/AutomatedLoginEventsTest.php index 6d103b407c..c54ff95387 100644 --- a/tests/Integrations/Symfony/V4_4/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Symfony/V4_4/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "symfony44"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_4_4/public/index.php'; diff --git a/tests/Integrations/Symfony/V4_4/CommonScenariosTest.php b/tests/Integrations/Symfony/V4_4/CommonScenariosTest.php index 94ececacf8..5ef29b708f 100644 --- a/tests/Integrations/Symfony/V4_4/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V4_4/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -91,7 +91,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -138,7 +138,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -192,7 +192,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V4_4/MessengerTest.php b/tests/Integrations/Symfony/V4_4/MessengerTest.php new file mode 100644 index 0000000000..cc58c62907 --- /dev/null +++ b/tests/Integrations/Symfony/V4_4/MessengerTest.php @@ -0,0 +1,90 @@ + '1', + 'DD_TRACE_CLI_ENABLED' => '1', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_DEBUG' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_PHPREDIS_ENABLED' => 'false' // We are NOT testing the phpredis integration + ]); + } + + public function testAsyncSuccess() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky number', '/lucky/number'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_DEBUG' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v4_4.messenger_test.test_async_success_consumer', + true + ); + } + + public function testAsyncFailure() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky fail', '/lucky/fail'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v4_4.messenger_test.test_async_failure_consumer', + true + ); + } +} diff --git a/tests/Integrations/Symfony/V4_4/PathParamsTest.php b/tests/Integrations/Symfony/V4_4/PathParamsTest.php index a32171670c..0b0ba229ce 100644 --- a/tests/Integrations/Symfony/V4_4/PathParamsTest.php +++ b/tests/Integrations/Symfony/V4_4/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "symfony44"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_4_4/public/index.php'; diff --git a/tests/Integrations/Symfony/V4_4/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V4_4/TraceSearchConfigTest.php index 9183677fd5..a751f93854 100644 --- a/tests/Integrations/Symfony/V4_4/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V4_4/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_0/CommonScenariosTest.php b/tests/Integrations/Symfony/V5_0/CommonScenariosTest.php index 4116ee1725..4877a3df41 100644 --- a/tests/Integrations/Symfony/V5_0/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V5_0/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -91,7 +91,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -138,7 +138,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -192,7 +192,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_0/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V5_0/TraceSearchConfigTest.php index 88945977d5..58e5e95683 100644 --- a/tests/Integrations/Symfony/V5_0/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V5_0/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_1/CommonScenariosTest.php b/tests/Integrations/Symfony/V5_1/CommonScenariosTest.php index 119786964d..4007c20974 100644 --- a/tests/Integrations/Symfony/V5_1/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V5_1/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -91,7 +91,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -138,7 +138,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -192,7 +192,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_1/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V5_1/TraceSearchConfigTest.php index 2f6f832c0e..9a6022439a 100644 --- a/tests/Integrations/Symfony/V5_1/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V5_1/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_2/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V5_2/AutomatedLoginEventsTest.php index edc72a4272..00972cd43d 100644 --- a/tests/Integrations/Symfony/V5_2/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Symfony/V5_2/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "symfony52"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_5_2/public/index.php'; diff --git a/tests/Integrations/Symfony/V5_2/CommonScenariosTest.php b/tests/Integrations/Symfony/V5_2/CommonScenariosTest.php index abd905e649..26e2a39be0 100644 --- a/tests/Integrations/Symfony/V5_2/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V5_2/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -89,7 +89,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -134,7 +134,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -184,7 +184,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V5_2/MessengerTest.php b/tests/Integrations/Symfony/V5_2/MessengerTest.php new file mode 100644 index 0000000000..3bf22a2327 --- /dev/null +++ b/tests/Integrations/Symfony/V5_2/MessengerTest.php @@ -0,0 +1,91 @@ + '1', + 'DD_TRACE_CLI_ENABLED' => '1', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_DEBUG' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_PHPREDIS_ENABLED' => 'false' // We are NOT testing the phpredis integration + ]); + } + + public function testAsyncSuccess() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky number', '/lucky/number'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_DEBUG' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v5_2.messenger_test.test_async_success_consumer', + true + ); + } + + public function testAsyncFailure() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky fail', '/lucky/fail'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v5_2.messenger_test.test_async_failure_consumer', + true + ); + } +} diff --git a/tests/Integrations/Symfony/V5_2/PathParamsTest.php b/tests/Integrations/Symfony/V5_2/PathParamsTest.php index d5a4de6a5b..2be773101d 100644 --- a/tests/Integrations/Symfony/V5_2/PathParamsTest.php +++ b/tests/Integrations/Symfony/V5_2/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "symfony52"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_5_2/public/index.php'; diff --git a/tests/Integrations/Symfony/V5_2/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V5_2/TraceSearchConfigTest.php index c86903991a..2e391cbb73 100644 --- a/tests/Integrations/Symfony/V5_2/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V5_2/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V6_2/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V6_2/AutomatedLoginEventsTest.php index e9d2bafa19..78f77e16ac 100644 --- a/tests/Integrations/Symfony/V6_2/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Symfony/V6_2/AutomatedLoginEventsTest.php @@ -9,6 +9,7 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "symfony62"; public function createUser($email) { $this->connection()->exec('insert into user (roles, email, password) VALUES ("{}", "'.$email.'", "$2y$13$WNnAxSuifzgXGx9kYfFr.eMaXzE50MmrMnXxmrlZqxSa21oiMyy0i")'); diff --git a/tests/Integrations/Symfony/V6_2/CommonScenariosTest.php b/tests/Integrations/Symfony/V6_2/CommonScenariosTest.php index 0aaa11a617..ec2400813c 100644 --- a/tests/Integrations/Symfony/V6_2/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V6_2/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -89,7 +89,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -134,7 +134,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -184,7 +184,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V6_2/ConsoleCommandTest.php b/tests/Integrations/Symfony/V6_2/ConsoleCommandTest.php index 5c812014d1..d43c4afd04 100644 --- a/tests/Integrations/Symfony/V6_2/ConsoleCommandTest.php +++ b/tests/Integrations/Symfony/V6_2/ConsoleCommandTest.php @@ -15,7 +15,7 @@ protected static function getConsoleScript() public function testScenario() { - list($traces) = $this->inCli(self::getConsoleScript(), [ + list($traces) = $this->inCli(static::getConsoleScript(), [ 'DD_TRACE_GENERATE_ROOT_SPAN' => 'true', 'DD_TRACE_EXEC_ENABLED' => 'false', ], [], 'about'); diff --git a/tests/Integrations/Symfony/V6_2/MessengerTest.php b/tests/Integrations/Symfony/V6_2/MessengerTest.php new file mode 100644 index 0000000000..c3c2adffc3 --- /dev/null +++ b/tests/Integrations/Symfony/V6_2/MessengerTest.php @@ -0,0 +1,92 @@ + '1', + 'DD_TRACE_CLI_ENABLED' => '1', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_DEBUG' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_PHPREDIS_ENABLED' => 'false' // We are NOT testing the phpredis integration + ]); + } + + public function testAsyncSuccess() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky number', '/lucky/number'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_DEBUG' => 'true', + 'DD_TRACE_PHPREDIS_ENABLED' => 'false' // We are NOT testing the phpredis integration + ], [], ['mess:cons', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v6_2.messenger_test.test_async_success_consumer', + true + ); + } + + public function testAsyncFailure() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky fail', '/lucky/fail'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_PHPREDIS_ENABLED' => 'false' // We are NOT testing the phpredis integration + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v6_2.messenger_test.test_async_failure_consumer', + true + ); + } +} diff --git a/tests/Integrations/Symfony/V6_2/PathParamsTest.php b/tests/Integrations/Symfony/V6_2/PathParamsTest.php index 3175d34218..659a5426c1 100644 --- a/tests/Integrations/Symfony/V6_2/PathParamsTest.php +++ b/tests/Integrations/Symfony/V6_2/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "symfony62"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_6_2/public/index.php'; diff --git a/tests/Integrations/Symfony/V6_2/TraceSearchConfigTest.php b/tests/Integrations/Symfony/V6_2/TraceSearchConfigTest.php index 85716ce0de..a48c7aafbd 100644 --- a/tests/Integrations/Symfony/V6_2/TraceSearchConfigTest.php +++ b/tests/Integrations/Symfony/V6_2/TraceSearchConfigTest.php @@ -43,7 +43,7 @@ public function testScenario() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php b/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php index 98e777fcf5..f84e3b7b50 100644 --- a/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Symfony/V7_0/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "symfony70"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/Symfony/Version_7_0/public/index.php'; diff --git a/tests/Integrations/Symfony/V7_0/CommonScenariosTest.php b/tests/Integrations/Symfony/V7_0/CommonScenariosTest.php index 641326f2ec..220c89f012 100644 --- a/tests/Integrations/Symfony/V7_0/CommonScenariosTest.php +++ b/tests/Integrations/Symfony/V7_0/CommonScenariosTest.php @@ -51,7 +51,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleAction', 'symfony.route.name' => 'simple', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -89,7 +89,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@simpleViewAction', 'symfony.route.name' => 'simple_view', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -134,7 +134,7 @@ public function provideSpecs() 'symfony.route.action' => 'App\Controller\CommonScenariosController@errorAction', 'symfony.route.name' => 'error', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', @@ -184,7 +184,7 @@ public function provideSpecs() 'GET /does_not_exist' )->withExactTags([ 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/does_not_exist?key=value&', + 'http.url' => 'http://localhost/does_not_exist?key=value&', 'http.status_code' => '404', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'symfony', diff --git a/tests/Integrations/Symfony/V7_0/MessengerTest.php b/tests/Integrations/Symfony/V7_0/MessengerTest.php new file mode 100644 index 0000000000..cf729fb468 --- /dev/null +++ b/tests/Integrations/Symfony/V7_0/MessengerTest.php @@ -0,0 +1,89 @@ + '1', + 'DD_TRACE_CLI_ENABLED' => '1', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_DEBUG' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + ]); + } + + public function testAsyncSuccess() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky number', '/lucky/number'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + 'DD_TRACE_DEBUG' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v7_0.messenger_test.test_async_success_consumer', + true + ); + } + + public function testAsyncFailure() + { + $this->tracesFromWebRequestSnapshot(function () { + $spec = GetSpec::create('Lucky fail', '/lucky/fail'); + $this->call($spec); + }, self::FIELDS_TO_IGNORE); + + list($consumerTraces) = $this->inCli(self::getConsoleScript(), [ + 'DD_TRACE_CLI_ENABLED' => 'true', + 'DD_TRACE_EXEC_ENABLED' => 'false', + 'DD_SERVICE' => 'symfony_messenger_test', + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS' => 'true', + 'DD_TRACE_SYMFONY_MESSENGER_MIDDLEWARES' => 'true', + ], [], ['messenger:consume', 'async', '--limit=1']); + + $this->snapshotFromTraces( + $consumerTraces, + self::FIELDS_TO_IGNORE, + 'tests.integrations.symfony.v7_0.messenger_test.test_async_failure_consumer', + true + ); + } +} diff --git a/tests/Integrations/WordPress/V4_8/AutomatedLoginEventsTest.php b/tests/Integrations/WordPress/V4_8/AutomatedLoginEventsTest.php index 89b60e8978..f17cd82ce6 100644 --- a/tests/Integrations/WordPress/V4_8/AutomatedLoginEventsTest.php +++ b/tests/Integrations/WordPress/V4_8/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "wp48"; + protected $users_table = 'wp_users'; protected static function getAppIndexScript() diff --git a/tests/Integrations/WordPress/V4_8/CommonScenariosTest.php b/tests/Integrations/WordPress/V4_8/CommonScenariosTest.php index 99ec806edb..393dfb33d4 100644 --- a/tests/Integrations/WordPress/V4_8/CommonScenariosTest.php +++ b/tests/Integrations/WordPress/V4_8/CommonScenariosTest.php @@ -10,6 +10,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "wp48"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_4_8/index.php'; @@ -18,7 +20,7 @@ protected static function getAppIndexScript() public static function ddSetUpBeforeClass() { parent::ddSetUpBeforeClass(); - $pdo = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + $pdo = new \PDO('mysql:host=mysql_integration;dbname=wp48', 'test', 'test'); $pdo->exec(file_get_contents(__DIR__ . '/../../../Frameworks/WordPress/Version_4_8/wp_2019-10-01.sql')); } diff --git a/tests/Integrations/WordPress/V4_8/PathParamsTest.php b/tests/Integrations/WordPress/V4_8/PathParamsTest.php index f7f4fc5327..01ab216279 100644 --- a/tests/Integrations/WordPress/V4_8/PathParamsTest.php +++ b/tests/Integrations/WordPress/V4_8/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "wp48"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_4_8/index.php'; diff --git a/tests/Integrations/WordPress/V5_5/AutomatedLoginEventsTest.php b/tests/Integrations/WordPress/V5_5/AutomatedLoginEventsTest.php index bea6d652d7..595227131f 100644 --- a/tests/Integrations/WordPress/V5_5/AutomatedLoginEventsTest.php +++ b/tests/Integrations/WordPress/V5_5/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "wp55"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_5/index.php'; diff --git a/tests/Integrations/WordPress/V5_5/CommonScenariosTest.php b/tests/Integrations/WordPress/V5_5/CommonScenariosTest.php index 7dddd3b840..86093271c4 100644 --- a/tests/Integrations/WordPress/V5_5/CommonScenariosTest.php +++ b/tests/Integrations/WordPress/V5_5/CommonScenariosTest.php @@ -11,6 +11,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "wp55"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_5/index.php'; @@ -19,7 +21,7 @@ protected static function getAppIndexScript() public static function ddSetUpBeforeClass() { parent::ddSetUpBeforeClass(); - $pdo = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + $pdo = new \PDO('mysql:host=mysql_integration;dbname=wp55', 'test', 'test'); $pdo->exec(file_get_contents(__DIR__ . '/../../../Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql')); } diff --git a/tests/Integrations/WordPress/V5_5/PathParamsTest.php b/tests/Integrations/WordPress/V5_5/PathParamsTest.php index 22878ce4c9..9081e57492 100644 --- a/tests/Integrations/WordPress/V5_5/PathParamsTest.php +++ b/tests/Integrations/WordPress/V5_5/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "wp55"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_5/index.php'; diff --git a/tests/Integrations/WordPress/V5_9/AutomatedLoginEventsTest.php b/tests/Integrations/WordPress/V5_9/AutomatedLoginEventsTest.php index f21b212d1e..aee17e961a 100644 --- a/tests/Integrations/WordPress/V5_9/AutomatedLoginEventsTest.php +++ b/tests/Integrations/WordPress/V5_9/AutomatedLoginEventsTest.php @@ -9,6 +9,8 @@ */ class AutomatedLoginEventsTest extends AutomatedLoginEventsTestSuite { + public static $database = "wp59"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_9/index.php'; diff --git a/tests/Integrations/WordPress/V5_9/CommonScenariosTest.php b/tests/Integrations/WordPress/V5_9/CommonScenariosTest.php index 767dea9a05..19a50fb1de 100644 --- a/tests/Integrations/WordPress/V5_9/CommonScenariosTest.php +++ b/tests/Integrations/WordPress/V5_9/CommonScenariosTest.php @@ -11,6 +11,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "wp59"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_9/index.php'; @@ -19,7 +21,7 @@ protected static function getAppIndexScript() public function ddSetUp() { parent::ddSetUp(); - $pdo = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + $pdo = new \PDO('mysql:host=mysql_integration;dbname=wp59', 'test', 'test'); $pdo->exec(file_get_contents(__DIR__ . '/../../../Frameworks/WordPress/Version_5_5/wp_2020-10-21.sql')); } diff --git a/tests/Integrations/WordPress/V5_9/PathParamsTest.php b/tests/Integrations/WordPress/V5_9/PathParamsTest.php index 1dbe1200bd..b7421ec643 100644 --- a/tests/Integrations/WordPress/V5_9/PathParamsTest.php +++ b/tests/Integrations/WordPress/V5_9/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "wp59"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_5_9/index.php'; diff --git a/tests/Integrations/WordPress/V6_1/CommonScenariosTest.php b/tests/Integrations/WordPress/V6_1/CommonScenariosTest.php index ef6b06b61f..793b74eb2c 100644 --- a/tests/Integrations/WordPress/V6_1/CommonScenariosTest.php +++ b/tests/Integrations/WordPress/V6_1/CommonScenariosTest.php @@ -7,6 +7,8 @@ class CommonScenariosTest extends WebFrameworkTestCase { + public static $database = "wp61"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_6_1/index.php'; @@ -15,7 +17,7 @@ protected static function getAppIndexScript() public function ddSetUp() { parent::ddSetUp(); - $pdo = new \PDO('mysql:host=mysql_integration;dbname=test', 'test', 'test'); + $pdo = new \PDO('mysql:host=mysql_integration;dbname=wp61', 'test', 'test'); $pdo->exec(file_get_contents(__DIR__ . '/../../../Frameworks/WordPress/Version_6_1/scripts/wp_initdb.sql')); } diff --git a/tests/Integrations/WordPress/V6_1/PathParamsTest.php b/tests/Integrations/WordPress/V6_1/PathParamsTest.php index 9dd5f30c46..e39bc5774a 100644 --- a/tests/Integrations/WordPress/V6_1/PathParamsTest.php +++ b/tests/Integrations/WordPress/V6_1/PathParamsTest.php @@ -9,6 +9,8 @@ */ class PathParamsTest extends PathParamsTestSuite { + public static $database = "wp61"; + protected static function getAppIndexScript() { return __DIR__ . '/../../../Frameworks/WordPress/Version_6_1/index.php'; diff --git a/tests/Integrations/Yii/V2_0/CommonScenariosTest.php b/tests/Integrations/Yii/V2_0/CommonScenariosTest.php index d9a3399364..d1f9bcafc5 100644 --- a/tests/Integrations/Yii/V2_0/CommonScenariosTest.php +++ b/tests/Integrations/Yii/V2_0/CommonScenariosTest.php @@ -49,7 +49,7 @@ public function provideSpecs() 'GET /simple' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple?key=value&', + Tag::HTTP_URL => 'http://localhost/simple?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'app\controllers\SimpleController::actionIndex', 'app.route.path' => '/simple', @@ -93,7 +93,7 @@ public function provideSpecs() 'GET /simple_view' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/simple_view?key=value&', + Tag::HTTP_URL => 'http://localhost/simple_view?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'app\controllers\SimpleController::actionView', 'app.route.path' => '/simple_view', @@ -140,7 +140,7 @@ public function provideSpecs() 'GET /error' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/error?key=value&', + Tag::HTTP_URL => 'http://localhost/error?key=value&', Tag::HTTP_STATUS_CODE => '500', 'app.endpoint' => 'app\controllers\SimpleController::actionError', 'app.route.path' => '/error', @@ -212,7 +212,7 @@ public function provideSpecs() 'GET /parameterized/?' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/parameterized/paramValue', + Tag::HTTP_URL => 'http://localhost/parameterized/paramValue', Tag::HTTP_STATUS_CODE => '200', 'app.endpoint' => 'app\controllers\SimpleController::actionParameterized', 'app.route.path' => '/parameterized/:value', diff --git a/tests/Integrations/Yii/V2_0/ModuleTest.php b/tests/Integrations/Yii/V2_0/ModuleTest.php index 69fc8e42ce..6b1a52b85b 100644 --- a/tests/Integrations/Yii/V2_0/ModuleTest.php +++ b/tests/Integrations/Yii/V2_0/ModuleTest.php @@ -39,7 +39,7 @@ public function testGet() 'GET /forum/?/?/?' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/forum/new-york/new-york/manhattan?key=value&', + Tag::HTTP_URL => 'http://localhost/forum/new-york/new-york/manhattan?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.route.path' => '/forum/:state/:city/:neighborhood', Tag::HTTP_ROUTE => '/forum/:state/:city/:neighborhood', diff --git a/tests/Integrations/Yii/V2_0/ParameterizedRouteTest.php b/tests/Integrations/Yii/V2_0/ParameterizedRouteTest.php index d91d67c221..915f3a698e 100644 --- a/tests/Integrations/Yii/V2_0/ParameterizedRouteTest.php +++ b/tests/Integrations/Yii/V2_0/ParameterizedRouteTest.php @@ -39,7 +39,7 @@ public function testGet() 'GET /homes/?/?/?' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/homes/new-york/new-york/manhattan?key=value&', + Tag::HTTP_URL => 'http://localhost/homes/new-york/new-york/manhattan?key=value&', Tag::HTTP_STATUS_CODE => '200', 'app.route.path' => '/homes/:state/:city/:neighborhood', Tag::HTTP_ROUTE => '/homes/:state/:city/:neighborhood', diff --git a/tests/Integrations/Yii/V2_0/YiiDetailsTest.php b/tests/Integrations/Yii/V2_0/YiiDetailsTest.php index 84d737236a..05532f12cd 100644 --- a/tests/Integrations/Yii/V2_0/YiiDetailsTest.php +++ b/tests/Integrations/Yii/V2_0/YiiDetailsTest.php @@ -40,7 +40,7 @@ public function testRootIndexRoute() 'GET /site/index' )->withExactTags([ Tag::HTTP_METHOD => 'GET', - Tag::HTTP_URL => 'http://localhost:9999/site/index', + Tag::HTTP_URL => 'http://localhost/site/index', Tag::HTTP_STATUS_CODE => '200', 'app.route.path' => '/site/index', Tag::HTTP_ROUTE => '/site/index', diff --git a/tests/Integrations/ZendFramework/V1/CommonScenariosTest.php b/tests/Integrations/ZendFramework/V1/CommonScenariosTest.php index cf21f2d3ff..3bbec5542a 100644 --- a/tests/Integrations/ZendFramework/V1/CommonScenariosTest.php +++ b/tests/Integrations/ZendFramework/V1/CommonScenariosTest.php @@ -14,6 +14,13 @@ protected static function getAppIndexScript() return __DIR__ . '/../../../Frameworks/ZendFramework/Version_1_12/public/index.php'; } + protected static function getEnvs() + { + return array_merge(parent::getEnvs(), [ + 'DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS' => 1, + ]); + } + /** * @dataProvider provideSpecs * @param RequestSpec $spec @@ -22,6 +29,8 @@ protected static function getAppIndexScript() */ public function testScenario(RequestSpec $spec, array $spanExpectations) { + $this->resetRequestDumper(); + $traces = $this->tracesFromWebRequest(function () use ($spec) { $this->call($spec); }); @@ -39,7 +48,7 @@ public function provideSpecs() 'zf1.action' => 'index', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", @@ -52,7 +61,7 @@ public function provideSpecs() 'zf1.action' => 'view', 'zf1.route_name' => 'my_simple_view_route', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", @@ -65,7 +74,7 @@ public function provideSpecs() 'zf1.action' => 'error', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", diff --git a/tests/Integrations/ZendFramework/V1/TraceSearchConfigTest.php b/tests/Integrations/ZendFramework/V1/TraceSearchConfigTest.php index 6626a13db9..67c1b4ea11 100644 --- a/tests/Integrations/ZendFramework/V1/TraceSearchConfigTest.php +++ b/tests/Integrations/ZendFramework/V1/TraceSearchConfigTest.php @@ -40,7 +40,7 @@ public function testScenario() 'zf1.action' => 'index', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'zendframework', diff --git a/tests/Integrations/ZendFramework/V1_21/CommonScenariosTest.php b/tests/Integrations/ZendFramework/V1_21/CommonScenariosTest.php index cb4e7e22d6..729d1a6137 100644 --- a/tests/Integrations/ZendFramework/V1_21/CommonScenariosTest.php +++ b/tests/Integrations/ZendFramework/V1_21/CommonScenariosTest.php @@ -39,7 +39,7 @@ public function provideSpecs() 'zf1.action' => 'index', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple?key=value&', + 'http.url' => 'http://localhost/simple?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", @@ -52,7 +52,7 @@ public function provideSpecs() 'zf1.action' => 'view', 'zf1.route_name' => 'my_simple_view_route', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple_view?key=value&', + 'http.url' => 'http://localhost/simple_view?key=value&', 'http.status_code' => '200', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", @@ -65,7 +65,7 @@ public function provideSpecs() 'zf1.action' => 'error', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/error?key=value&', + 'http.url' => 'http://localhost/error?key=value&', 'http.status_code' => '500', Tag::SPAN_KIND => "server", Tag::COMPONENT => "zendframework", diff --git a/tests/Integrations/ZendFramework/V1_21/TraceSearchConfigTest.php b/tests/Integrations/ZendFramework/V1_21/TraceSearchConfigTest.php index a86cfcf267..88040937ec 100644 --- a/tests/Integrations/ZendFramework/V1_21/TraceSearchConfigTest.php +++ b/tests/Integrations/ZendFramework/V1_21/TraceSearchConfigTest.php @@ -40,7 +40,7 @@ public function testScenario() 'zf1.action' => 'index', 'zf1.route_name' => 'default', 'http.method' => 'GET', - 'http.url' => 'http://localhost:9999/simple', + 'http.url' => 'http://localhost/simple', 'http.status_code' => '200', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'zendframework', diff --git a/tests/OpenTelemetry/Integration/API/TracerTest.php b/tests/OpenTelemetry/Integration/API/TracerTest.php index b7aa1d6ed8..4cc6da8c37 100644 --- a/tests/OpenTelemetry/Integration/API/TracerTest.php +++ b/tests/OpenTelemetry/Integration/API/TracerTest.php @@ -433,12 +433,7 @@ public function testRecordException() $span = $traces[0][0]; $this->assertSame('internal', $span['name']); $this->assertSame('test.span', $span['resource']); - $this->assertSame('exception message', $span['meta'][Tag::ERROR_MSG]); - $this->assertSame('RuntimeException', $span['meta'][Tag::ERROR_TYPE]); $this->assertNotEmpty($span['meta'][Tag::ERROR_STACK]); - $this->assertEquals(1, $span['error']); - - $this->markTestIncomplete("Span Events aren't yet supported"); } public function testSpanNameUpdate() diff --git a/tests/OpenTelemetry/Integration/Context/FiberTest.php b/tests/OpenTelemetry/Integration/Context/Fiber/FiberTest.php similarity index 75% rename from tests/OpenTelemetry/Integration/Context/FiberTest.php rename to tests/OpenTelemetry/Integration/Context/Fiber/FiberTest.php index 55697a32fb..cda6c178a4 100644 --- a/tests/OpenTelemetry/Integration/Context/FiberTest.php +++ b/tests/OpenTelemetry/Integration/Context/Fiber/FiberTest.php @@ -13,7 +13,7 @@ use OpenTelemetry\API\Trace\Span; use OpenTelemetry\API\Trace\SpanKind; use OpenTelemetry\Context\Context; -use OpenTelemetry\Context\ContextStorage; +use OpenTelemetry\Context\ExecutionContextAwareInterface; use OpenTelemetry\SDK\Trace\TracerProvider; use function DDTrace\close_span; use function DDTrace\start_span; @@ -31,78 +31,6 @@ public function ddSetUp(): void public function ddTearDown() { parent::ddTearDown(); - Context::setStorage(new ContextStorage()); // Reset OpenTelemetry context - } - - /** - * @throws \Throwable - */ - public function test_context_switching_ffi_observer() - { - $key = Context::createKey('-'); - $scope = Context::getCurrent() - ->with($key, 'main') - ->activate(); - - $fiber = new Fiber(function () use ($key) { - $scope = Context::getCurrent() - ->with($key, 'fiber') - ->activate(); - - $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); - - Fiber::suspend(); - - $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); - - $scope->detach(); - }); - - $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); - - $fiber->start(); - - $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); - - $fiber->resume(); - - $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); - - $scope->detach(); - } - - public function test_context_switching_ffi_observer_registered_on_startup() - { - $key = Context::createKey('-'); - - $fiber = new Fiber(function () use ($key) { - $scope = Context::getCurrent() - ->with($key, 'fiber') - ->activate(); - - $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); - - Fiber::suspend(); - - $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); - - $scope->detach(); - }); - - - $fiber->start(); - - $this->assertSame('main:', 'main:' . Context::getCurrent()->get($key)); - - $scope = Context::getCurrent() - ->with($key, 'main') - ->activate(); - - $fiber->resume(); - - $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); - - $scope->detach(); } public function testFiberInteroperabilityStackSwitch() diff --git a/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer.phpt b/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer.phpt new file mode 100644 index 0000000000..7caf00f006 --- /dev/null +++ b/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer.phpt @@ -0,0 +1,46 @@ +--TEST-- +Context switches on execution context switch. +--SKIPIF-- + +--ENV-- +OTEL_PHP_FIBERS_ENABLED=1 +--FILE-- +with($key, 'main') + ->activate(); + +$fiber = new Fiber(function() use ($key) { + $scope = Context::getCurrent() + ->with($key, 'fiber') + ->activate(); + + echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL; + + Fiber::suspend(); + echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL; + + $scope->detach(); +}); + +echo 'main:' . Context::getCurrent()->get($key), PHP_EOL; + +$fiber->start(); +echo 'main:' . Context::getCurrent()->get($key), PHP_EOL; + +$fiber->resume(); +echo 'main:' . Context::getCurrent()->get($key), PHP_EOL; + +$scope->detach(); +?> +--EXPECT-- +main:main +fiber:fiber +main:main +fiber:fiber +main:main diff --git a/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer_registered_on_startup.phpt b/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer_registered_on_startup.phpt new file mode 100644 index 0000000000..4de8eb1db9 --- /dev/null +++ b/tests/OpenTelemetry/Integration/Context/Fiber/test_context_switching_ffi_observer_registered_on_startup.phpt @@ -0,0 +1,45 @@ +--TEST-- +Fiber handler has to be loaded before fibers are used. +--SKIPIF-- + +--ENV-- +OTEL_PHP_FIBERS_ENABLED=1 +--FILE-- +with($key, 'fiber') + ->activate(); + + echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL; + + Fiber::suspend(); + echo 'fiber:' . Context::getCurrent()->get($key), PHP_EOL; + + $scope->detach(); +}); + +$fiber->start(); +echo 'main:' . Context::getCurrent()->get($key), PHP_EOL; + +$scope = Context::getCurrent() + ->with($key, 'main') + ->activate(); + +$fiber->resume(); +echo 'main:' . Context::getCurrent()->get($key), PHP_EOL; + +$scope->detach(); + +?> +--EXPECT-- +fiber:fiber +main: +fiber:fiber +main:main diff --git a/tests/OpenTelemetry/Integration/InternalTelemetryTest.php b/tests/OpenTelemetry/Integration/InternalTelemetryTest.php index 4023745f02..9df322e544 100644 --- a/tests/OpenTelemetry/Integration/InternalTelemetryTest.php +++ b/tests/OpenTelemetry/Integration/InternalTelemetryTest.php @@ -56,11 +56,7 @@ public function testInternalMetricWithOpenTelemetry() $this->executeCommand(); - $requests = $this->retrieveDumpedData(function ($request) { - return (strpos($request["uri"] ?? "", "/telemetry/") === 0) - && (strpos($request["body"] ?? "", "spans_created") !== false) - ; - }, true); + $requests = $this->retrieveDumpedData($this->untilTelemetryRequest("spans_created")); $payloads = $this->readTelemetryPayloads($requests); $isMetric = function (array $payload) { diff --git a/tests/OpenTelemetry/Integration/InteroperabilityTest.php b/tests/OpenTelemetry/Integration/InteroperabilityTest.php index 0a7eacc767..6de97dbdc0 100644 --- a/tests/OpenTelemetry/Integration/InteroperabilityTest.php +++ b/tests/OpenTelemetry/Integration/InteroperabilityTest.php @@ -3,6 +3,8 @@ namespace DDTrace\Tests\OpenTelemetry\Integration; use DDTrace\SpanLink; +use DDTrace\SpanEvent; +use DDTrace\ExceptionSpanEvent; use DDTrace\Tag; use DDTrace\Tests\Common\BaseTestCase; use DDTrace\Tests\Common\SpanAssertion; @@ -1175,4 +1177,149 @@ public function testSpanLinksInteroperabilityAddDuplicates() $this->assertNotSame($otelSpanLinks[1], $otelSpanLinks[3]); }); } + + public function testSpanEventsInteroperabilityFromDatadogSpan() + { + $traces = $this->isolateTracer(function () { + $span = start_span(); + $span->name = "dd.span"; + + $spanEvent = new SpanEvent( + "event-name", + [ + 'arg1' => 'value1', + 'int_array' => [3, 4], + 'string_array' => ["5", "6"] + ], + 1720037568765201300 + ); + $span->events[] = $spanEvent; + + /** @var en $OTelSpan */ + $otelSpan = Span::getCurrent(); + $otelSpanEvent = $otelSpan->toSpanData()->getEvents()[0]; + + $this->assertSame('event-name', $otelSpanEvent->getName()); + $this->assertSame([ + 'arg1' => 'value1', + 'int_array' => [3, 4], + 'string_array' => ["5", "6"] + ], $otelSpanEvent->getAttributes()->toArray()); + $this->assertSame(1720037568765201300, (int)$otelSpanEvent->getEpochNanos()); + + close_span(); + }); + + $this->assertCount(1, $traces[0]); + $this->assertSame("[{\"name\":\"event-name\",\"time_unix_nano\":1720037568765201300,\"attributes\":{\"arg1\":\"value1\",\"int_array\":[3,4],\"string_array\":[\"5\",\"6\"]}}]", $traces[0][0]['meta']['events']); + } + + public function testSpanEventsInteroperabilityFromOpenTelemetrySpan() + { + $traces = $this->isolateTracer(function () { + $otelSpan = self::getTracer()->spanBuilder("otel.span") + ->startSpan(); + $otelSpan->addEvent( + "event-name", + [ + 'arg1' => 'value1', + 'int_array' => [3, 4], + 'string_array' => ["5", "6"] + ], + 1720037568765201300 + ); + + $activeSpan = active_span(); + $spanEvent = $activeSpan->events[0]; + $this->assertSame("event-name", $spanEvent->name); + $this->assertSame([ + 'arg1' => 'value1', + 'int_array' => [3, 4], + 'string_array' => ["5", "6"] + ], $spanEvent->attributes); + $this->assertSame(1720037568765201300, (int)$spanEvent->timestamp); + + $otelSpan->end(); + }); + + $this->assertCount(1, $traces[0]); + $this->assertSame("[{\"name\":\"event-name\",\"time_unix_nano\":1720037568765201300,\"attributes\":{\"arg1\":\"value1\",\"int_array\":[3,4],\"string_array\":[\"5\",\"6\"]}}]", $traces[0][0]['meta']['events']); + } + + public function testOtelRecordExceptionAttributesSerialization() + { + $lastException = new \Exception("woof3"); + + $traces = $this->isolateTracer(function () use ($lastException) { + $otelSpan = self::getTracer()->spanBuilder("operation") + ->recordException(new \Exception("woof1"), [ + "string_val" => "value", + "exception.stacktrace" => "stacktrace1" + ]) + ->startSpan(); + + $otelSpan->addEvent("non_exception_event", ["exception.stacktrace" => "non-error"]); + $otelSpan->recordException($lastException, ["exception.message" => "message override"]); + + $otelSpan->end(); + }); + + $events = json_decode($traces[0][0]['meta']['events'], true); + $this->assertCount(3, $events); + + $event1 = $events[0]; + $this->assertSame('value', $event1['attributes']['string_val']); + $this->assertSame('woof1', $event1['attributes']['exception.message']); + $this->assertSame('stacktrace1', $event1['attributes']['exception.stacktrace']); + + $event2 = $events[1]; + $this->assertSame('non-error', $event2['attributes']['exception.stacktrace']); + + $event3 = $events[2]; + $this->assertSame('message override', $event3['attributes']['exception.message']); + + $this->assertSame(\DDTrace\get_sanitized_exception_trace($lastException), $traces[0][0]['meta']['error.stack']); + + $this->assertArrayNotHasKey('error.message', $traces[0][0]['meta']); + $this->assertArrayNotHasKey('error.type', $traces[0][0]['meta']); + $this->assertArrayNotHasKey('error', $traces[0][0]); + } + + public function testExceptionSpanEvents() + { + $traces = $this->isolateTracer(function () { + $span = start_span(); + $span->name = "dd.span"; + + $spanEvent = new ExceptionSpanEvent( + new \Exception("Test exception message"), + [ + 'arg1' => 'value1', + 'exception.stacktrace' => 'Stacktrace Override' + ] + ); + + $span->events[] = $spanEvent; + + /** @var Span $otelSpan */ + $otelSpan = Span::getCurrent(); + $otelSpanEvent = $otelSpan->toSpanData()->getEvents()[0]; + + $this->assertSame('exception', $otelSpanEvent->getName()); + $this->assertSame([ + 'exception.message' => 'Test exception message', + 'exception.type' => 'Exception', + 'exception.stacktrace' => 'Stacktrace Override', + 'arg1' => 'value1' + ], $otelSpanEvent->getAttributes()->toArray()); + + close_span(); + }); + $event = json_decode($traces[0][0]['meta']['events'], true)[0]; + + $this->assertSame('Test exception message', $event['attributes']['exception.message']); + $this->assertSame('Exception', $event['attributes']['exception.type']); + $this->assertSame('Stacktrace Override', $event['attributes']['exception.stacktrace']); + $this->assertSame('value1', $event['attributes']['arg1']); + } } diff --git a/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php b/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php index b849ce7e45..5d8a7406b7 100644 --- a/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php +++ b/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php @@ -109,6 +109,22 @@ public function test_add_link(): void $this->assertCount(2, $span->toSpanData()->getLinks()); } + /** + * @group trace-compliance + */ + public function test_add_link_after_span_creation(): void + { + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->addLink($this->sampledSpanContext) + ->startSpan() + ->addLink($this->sampledSpanContext); + + $this->assertCount(2, $span->toSpanData()->getLinks()); + } + public function test_add_link_invalid(): void { /** @var Span $span */ diff --git a/tests/OpenTelemetry/composer-1.json b/tests/OpenTelemetry/composer-1.json new file mode 100644 index 0000000000..3cd307c3b1 --- /dev/null +++ b/tests/OpenTelemetry/composer-1.json @@ -0,0 +1,9 @@ +{ + "name": "datadog/dd-trace-tests", + "require": { + "open-telemetry/sdk": "1.0.*", + "open-telemetry/extension-propagator-b3": "1.0.*", + "open-telemetry/opentelemetry-logger-monolog": "1.0.*" + }, + "minimum-stability": "stable" +} diff --git a/tests/OpenTelemetry/composer-beta.json b/tests/OpenTelemetry/composer-beta.json new file mode 100644 index 0000000000..17891594b8 --- /dev/null +++ b/tests/OpenTelemetry/composer-beta.json @@ -0,0 +1,9 @@ +{ + "name": "datadog/dd-trace-tests", + "require": { + "open-telemetry/sdk": "@beta", + "open-telemetry/extension-propagator-b3": "@beta", + "open-telemetry/opentelemetry-logger-monolog": "@beta" + }, + "minimum-stability": "beta" +} diff --git a/tests/OpenTracer1Unit/composer.json b/tests/OpenTracer1Unit/composer.json new file mode 100644 index 0000000000..f80f627d48 --- /dev/null +++ b/tests/OpenTracer1Unit/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "opentracing/opentracing": "^1.0" + } +} diff --git a/tests/OpenTracerUnit/GlobalTracerTest.php b/tests/OpenTracerUnit/GlobalTracerTest.php deleted file mode 100644 index 59fd6c6f94..0000000000 --- a/tests/OpenTracerUnit/GlobalTracerTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertInstanceOf( - '\DDTrace\OpenTracer\Tracer', - \OpenTracing\GlobalTracer::get() - ); - } -} diff --git a/tests/OpenTracerUnit/ScopeManagerTest.php b/tests/OpenTracerUnit/ScopeManagerTest.php deleted file mode 100644 index 178fa819df..0000000000 --- a/tests/OpenTracerUnit/ScopeManagerTest.php +++ /dev/null @@ -1,63 +0,0 @@ -assertNull($scopeManager->getActive()); - } - - public function testActivateSuccess() - { - $tracer = Tracer::make(new NoopTransport()); - $span = $tracer->startSpan(self::OPERATION_NAME); - $scopeManager = new ScopeManager(new DDScopeManager()); - $scopeManager->activate($span, false); - $this->assertSame( - $span->unwrapped(), - $scopeManager->getActive()->getSpan()->unwrapped() - ); - } - - public function testGetScopeReturnsNull() - { - $tracer = Tracer::make(new NoopTransport()); - $tracer->startSpan(self::OPERATION_NAME); - $this->assertNull($tracer->getScopeManager()->getActive()); - } - - public function testGetScopeSuccess() - { - $tracer = Tracer::make(new NoopTransport()); - $span = $tracer->startSpan(self::OPERATION_NAME); - $scope = $tracer->getScopeManager()->activate($span, false); - $this->assertSame( - $scope->unwrapped(), - $tracer->getScopeManager()->getActive()->unwrapped() - ); - } -} diff --git a/tests/OpenTracerUnit/ScopeTest.php b/tests/OpenTracerUnit/ScopeTest.php deleted file mode 100644 index 13867f5531..0000000000 --- a/tests/OpenTracerUnit/ScopeTest.php +++ /dev/null @@ -1,29 +0,0 @@ -prophesize('DDTrace\Contracts\Span'); - $span->finish()->shouldBeCalled(); - $ddScope = new DDScope(new ScopeManager(), $span->reveal(), true); - $scope = new Scope($ddScope); - $scope->close(); - } - - public function testScopeDoesNotFinishesSpanOnClose() - { - $span = $this->prophesize('DDTrace\Contracts\Span'); - $span->finish()->shouldNotBeCalled(); - $ddScope = new DDScope(new ScopeManager(), $span->reveal(), false); - $scope = new Scope($ddScope); - $scope->close(); - } -} diff --git a/tests/OpenTracerUnit/SpanContextTest.php b/tests/OpenTracerUnit/SpanContextTest.php deleted file mode 100644 index 58f4321d72..0000000000 --- a/tests/OpenTracerUnit/SpanContextTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertNull($context->unwrapped()->getParentId()); - } - - public function testCreateChildSpanContext() - { - $parentContext = new SpanContext( - DDSpanContext::createAsRoot([ - self::BAGGAGE_ITEM_KEY => self::BAGGAGE_ITEM_VALUE, - ]) - ); - $childContext = new SpanContext( - DDSpanContext::createAsChild($parentContext->unwrapped()) - ); - - $this->assertSame($childContext->unwrapped()->getTraceId(), $parentContext->unwrapped()->getTraceId()); - $this->assertSame($childContext->unwrapped()->getParentId(), $parentContext->unwrapped()->getSpanId()); - $this->assertSame(iterator_to_array($childContext), iterator_to_array($parentContext)); - } - - public function testGetBaggageItemsReturnsExpectedValues() - { - $context = new SpanContext( - DDSpanContext::createAsRoot([ - self::BAGGAGE_ITEM_KEY => self::BAGGAGE_ITEM_VALUE, - ]) - ); - - $this->assertSame(self::BAGGAGE_ITEM_VALUE, $context->getBaggageItem(self::BAGGAGE_ITEM_KEY)); - $this->assertNull($context->getBaggageItem(self::BAGGAGE_ITEM_KEY2)); - } - - public function testAddBaggageItemsReturnsExpectedContext() - { - $context = new SpanContext( - DDSpanContext::createAsRoot([ - self::BAGGAGE_ITEM_KEY => self::BAGGAGE_ITEM_VALUE, - ]) - ); - $newContext = $context->withBaggageItem(self::BAGGAGE_ITEM_KEY2, self::BAGGAGE_ITEM_VALUE2); - - $this->assertSame(self::BAGGAGE_ITEM_VALUE, $newContext->getBaggageItem(self::BAGGAGE_ITEM_KEY)); - $this->assertNull($context->getBaggageItem(self::BAGGAGE_ITEM_KEY2)); - $this->assertSame(self::BAGGAGE_ITEM_VALUE2, $newContext->getBaggageItem(self::BAGGAGE_ITEM_KEY2)); - } -} diff --git a/tests/OpenTracerUnit/SpanTest.php b/tests/OpenTracerUnit/SpanTest.php deleted file mode 100644 index 83ce510036..0000000000 --- a/tests/OpenTracerUnit/SpanTest.php +++ /dev/null @@ -1,165 +0,0 @@ -createSpan(); - $span->setTag(self::TAG_KEY, self::TAG_VALUE); - - $this->assertSame(self::OPERATION_NAME, $span->getOperationName()); - $this->assertSame(self::SERVICE, $span->unwrapped()->getService()); - $this->assertSame(self::RESOURCE, $span->unwrapped()->getResource()); - $this->assertSame(self::TAG_VALUE, $span->unwrapped()->getTag(self::TAG_KEY)); - } - - public function testOverwriteOperationNameSuccess() - { - $span = $this->createSpan(); - $span->overwriteOperationName(self::ANOTHER_NAME); - $this->assertSame(self::ANOTHER_NAME, $span->getOperationName()); - } - - public function testSpanTagsRemainImmutableAfterFinishing() - { - $span = $this->createSpan(); - $span->finish(); - - $span->setTag(self::TAG_KEY, self::TAG_VALUE); - $this->assertNull($span->unwrapped()->getTag(self::TAG_KEY)); - } - - public function testSpanTagWithErrorCreatesExpectedTags() - { - $span = $this->createSpan(); - $span->setTag(Tag::ERROR, new Exception(self::EXCEPTION_MESSAGE)); - - $this->assertTrue($span->unwrapped()->hasError()); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_MSG), self::EXCEPTION_MESSAGE); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_TYPE), 'Exception'); - } - - public function testSpanTagWithErrorBoolProperlyMarksError() - { - $span = $this->createSpan(); - - $span->setTag(Tag::ERROR, true); - $this->assertTrue($span->unwrapped()->hasError()); - - $span->setTag(Tag::ERROR, false); - $this->assertFalse($span->unwrapped()->hasError()); - } - - public function testLogWithErrorBoolProperlyMarksError() - { - $span = $this->createSpan(); - - $span->log([Tag::LOG_ERROR => true]); - $this->assertTrue($span->unwrapped()->hasError()); - - $span->log([Tag::LOG_ERROR => false]); - $this->assertFalse($span->unwrapped()->hasError()); - } - - public function testLogWithEventErrorMarksSpanWithError() - { - $span = $this->createSpan(); - - $span->log([Tag::LOG_EVENT => 'error']); - $this->assertTrue($span->unwrapped()->hasError()); - } - - public function testLogWithOtherEventDoesNotMarkSpanWithError() - { - $span = $this->createSpan(); - - $span->log([Tag::LOG_EVENT => 'some other event']); - $this->assertFalse($span->unwrapped()->hasError()); - - $span->log([Tag::LOG_ERROR => false]); - $this->assertFalse($span->unwrapped()->hasError()); - } - - public function testSpanLogWithErrorCreatesExpectedTags() - { - foreach ([Tag::LOG_ERROR, Tag::LOG_ERROR_OBJECT] as $key) { - $span = $this->createSpan(); - $span->log([$key => new Exception(self::EXCEPTION_MESSAGE)]); - - $this->assertTrue($span->unwrapped()->hasError()); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_MSG), self::EXCEPTION_MESSAGE); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_TYPE), 'Exception'); - } - } - - public function testSpanLogStackAddsExpectedTag() - { - $span = $this->createSpan(); - $span->log([Tag::LOG_STACK => self::DUMMY_STACK_TRACE]); - - $this->assertFalse($span->unwrapped()->hasError()); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_STACK), self::DUMMY_STACK_TRACE); - } - - public function testSpanLogMessageAddsExpectedTag() - { - $span = $this->createSpan(); - $span->log([Tag::LOG_MESSAGE => self::EXCEPTION_MESSAGE]); - - $this->assertFalse($span->unwrapped()->hasError()); - $this->assertEquals($span->unwrapped()->getTag(Tag::ERROR_MSG), self::EXCEPTION_MESSAGE); - } - - public function testAddCustomTagsSuccess() - { - $span = $this->createSpan(); - $span->setTag(Tag::SERVICE_NAME, self::ANOTHER_SERVICE); - $span->setTag(Tag::RESOURCE_NAME, self::ANOTHER_RESOURCE); - $span->setTag(Tag::SPAN_TYPE, self::ANOTHER_TYPE); - - $this->assertEquals(self::ANOTHER_SERVICE, $span->unwrapped()->getService()); - $this->assertEquals(self::ANOTHER_RESOURCE, $span->unwrapped()->getResource()); - $this->assertEquals(self::ANOTHER_TYPE, $span->unwrapped()->getType()); - } - - public function testAddTagsFailsForInvalidTagKey() - { - $this->setExpectedException( - '\DDTrace\Exceptions\InvalidSpanArgument', - 'Invalid key type in given span tags. Expected string, got integer.' - ); - $span = $this->createSpan(); - $span->setTag(1, self::TAG_VALUE); - } - - private function createSpan() - { - $spanData = \DDTrace\start_span(); - $spanData->name = self::OPERATION_NAME; - $spanData->service = self::SERVICE; - $spanData->resource = self::RESOURCE; - $span = new DDSpan($spanData, DDSpanContext::createAsRoot()); - return new Span($span); - } -} diff --git a/tests/OpenTracerUnit/TracerTest.php b/tests/OpenTracerUnit/TracerTest.php deleted file mode 100644 index 08997452ba..0000000000 --- a/tests/OpenTracerUnit/TracerTest.php +++ /dev/null @@ -1,315 +0,0 @@ -startSpan(self::OPERATION_NAME)->unwrapped(); - $this->assertSame("", $span->getTag(Tag::ENV)); - $this->assertSame("", $span->getTag(Tag::VERSION)); - } - - public function testTracerWithConstructorArg() - { - $tracer = new Tracer(\DDTrace\GlobalTracer::get()); - - $span = $tracer->startSpan(self::OPERATION_NAME)->unwrapped(); - $this->assertSame("", $span->getTag(Tag::ENV)); - $this->assertSame("", $span->getTag(Tag::VERSION)); - } - - public function testCreateSpanWithDefaultTags() - { - $tracer = Tracer::make(new NoopTransport()); - - $span = $tracer->startSpan(self::OPERATION_NAME)->unwrapped(); - $this->assertSame("", $span->getTag(Tag::ENV)); - $this->assertSame("", $span->getTag(Tag::VERSION)); - } - - public function testCreateSpanWithEnvAndVersionConfigured() - { - $this->putEnvAndReloadConfig(['DD_ENV=' . self::ENVIRONMENT, 'DD_VERSION=' . self::VERSION]); - $tracer = Tracer::make(new NoopTransport()); - - $span = $tracer->startSpan(self::OPERATION_NAME)->unwrapped(); - $this->assertSame(self::ENVIRONMENT, $span->getTag(Tag::ENV)); - $this->assertSame(self::VERSION, $span->getTag(Tag::VERSION)); - } - - public function testCreateSpanWithExpectedValues() - { - $tracer = Tracer::make(new NoopTransport()); - $startTime = 12345; - $span = $tracer - ->startSpan(self::OPERATION_NAME, [ - 'tags' => [ - self::TAG_KEY => self::TAG_VALUE - ], - 'start_time' => $startTime, - ]) - ->unwrapped(); - - $this->assertSame(self::OPERATION_NAME, $span->getOperationName()); - $this->assertSame(self::TAG_VALUE, $span->getTag(self::TAG_KEY)); - $this->assertSame($startTime, $span->getStartTime()); - } - - public function testStartSpanAsChild() - { - $context = DDSpanContext::createAsRoot(); - $tracer = Tracer::make(new NoopTransport()); - $span = $tracer - ->startSpan(self::OPERATION_NAME, [ - 'child_of' => $context, - ]) - ->unwrapped(); - $this->assertSame($context->getSpanId(), $span->getParentId()); - $this->assertNull($span->getTag(Tag::PID)); - } - - public function testStartSpanAsRootWithPid() - { - $tracer = Tracer::make(new NoopTransport()); - $span = $tracer->startSpan(self::OPERATION_NAME)->unwrapped(); - $metrics = $span->getMetrics(); - $this->assertArrayHasKey(Tag::PID, $metrics); - $this->assertEquals(getmypid(), $metrics[Tag::PID]); - } - - public function testStartActiveSpan() - { - $tracer = Tracer::make(new NoopTransport()); - $scope = $tracer->startActiveSpan(self::OPERATION_NAME); - $this->assertSame( - $scope->unwrapped(), - $tracer->getScopeManager()->getActive()->unwrapped() - ); - } - - public function testStartActiveSpanAsChild() - { - $tracer = Tracer::make(new NoopTransport()); - $parentScope = $tracer->startActiveSpan(self::OPERATION_NAME); - $parentSpan = $parentScope->getSpan(); - $parentSpan->setTag(Tag::SERVICE_NAME, 'parent_service'); - $childScope = $tracer->startActiveSpan(self::ANOTHER_OPERATION_NAME); - $this->assertSame( - $childScope->unwrapped(), - $tracer->getScopeManager()->getActive()->unwrapped() - ); - $this->assertSame( - $parentScope->getSpan()->unwrapped()->getSpanId(), - $childScope->getSpan()->unwrapped()->getParentId() - ); - $this->assertSame( - $parentScope->getSpan()->unwrapped()->getService(), - $childScope->getSpan()->unwrapped()->getService() - ); - } - - public function testInjectThrowsUnsupportedFormatException() - { - $this->setExpectedException('\DDTrace\Exceptions\UnsupportedFormat'); - $carrier = []; - - $tracer = Tracer::make(new NoopTransport()); - $tracer->inject( - new SpanContext(DDSpanContext::createAsRoot()), - self::FORMAT, - $carrier - ); - } - - public function testInjectCallsTheRightInjector() - { - $context = new SpanContext(DDSpanContext::createAsRoot()); - $carrier = []; - - $propagator = $this->prophesize('DDTrace\Propagator'); - $propagator->inject($context->unwrapped(), $carrier)->shouldBeCalled(); - $tracer = Tracer::make(new NoopTransport(), [self::FORMAT => $propagator->reveal()]); - $tracer->inject($context, self::FORMAT, $carrier); - } - - public function testExtractThrowsUnsupportedFormatException() - { - $this->setExpectedException('\DDTrace\Exceptions\UnsupportedFormat'); - $carrier = []; - $tracer = Tracer::make(new NoopTransport()); - $tracer->extract(self::FORMAT, $carrier); - } - - public function testExtractCallsTheRightExtractor() - { - $expectedContext = DDSpanContext::createAsRoot(); - $carrier = []; - - $propagator = $this->prophesize('DDTrace\Propagator'); - $propagator->extract($carrier)->shouldBeCalled()->willReturn($expectedContext); - $tracer = Tracer::make(new NoopTransport(), [self::FORMAT => $propagator->reveal()]); - $actualContext = $tracer->extract(self::FORMAT, $carrier); - $this->assertSame($expectedContext, $actualContext->unwrapped()); - } - - public function testOTSpanContextAsParent() - { - GlobalTracer::set(Tracer::make()); - - $tracer = GlobalTracer::get(); - - $header = <<extract(Formats\TEXT_MAP, $carrier); - $B = $tracer->startActiveSpan('B', ['child_of' => $context]); - - $otcontext = $B->getSpan()->getContext(); - self::assertInstanceOf('DDTrace\OpenTracer\SpanContext', $otcontext); - self::assertEquals('2409624703365403319', $otcontext->unwrapped()->getParentId()); - } - - public function testOTStartSpanOptions() - { - GlobalTracer::set(Tracer::make()); - $tracer = GlobalTracer::get(); - - $now = time(); - $scope = $tracer->startActiveSpan( - self::OPERATION_NAME, - \OpenTracing\StartSpanOptions::create([ - 'tags' => [ - \OpenTracing\Tags\SPAN_KIND => \OpenTracing\Tags\SPAN_KIND_MESSAGE_BUS_PRODUCER, - 'message_id' => 'some id' - ], - 'start_time' => $now, - ]) - ); - self::assertInstanceOf('DDTrace\OpenTracer\Scope', $scope); - $scope = $scope->unwrapped(); - $span = $scope->getSpan(); - self::assertSame(\OpenTracing\Tags\SPAN_KIND_MESSAGE_BUS_PRODUCER, $span->getTag(\OpenTracing\Tags\SPAN_KIND)); - self::assertSame($now, $span->getStartTime()); - } - - public function testOnlyFinishedTracesAreBeingSent() - { - self::markTestIncomplete(); - $transport = $this->prophesize('DDTrace\Transport'); - $tracer = Tracer::make($transport->reveal()); - $span = $tracer->startSpan(self::OPERATION_NAME); - $tracer->startSpan(self::ANOTHER_OPERATION_NAME, [ - 'child_of' => $span->unwrapped(), - ]); - $span->finish(); - - $span2 = $tracer->startSpan(self::OPERATION_NAME); - $span3 = $tracer->startSpan(self::ANOTHER_OPERATION_NAME, [ - 'child_of' => $span2->unwrapped(), - ]); - $span2->finish(); - $span3->finish(); - - $transport->send([ - [$span2->unwrapped(), $span3->unwrapped()], - ])->shouldBeCalled(); - - $tracer->flush(); - } - - public function testPrioritySamplingIsAssigned() - { - self::markTestIncomplete(); - $tracer = Tracer::make(new DebugTransport()); - $tracer->startSpan(self::OPERATION_NAME); - $this->assertSame( - PrioritySampling::AUTO_KEEP, - $tracer->unwrapped()->getPrioritySampling() - ); - } - - public function testPrioritySamplingInheritedFromDistributedTracingContext() - { - self::markTestIncomplete(); - $distributedTracingContext = new DDSpanContext('', '', '', [], true); - $distributedTracingContext->setPropagatedPrioritySampling(PrioritySampling::USER_REJECT); - $tracer = Tracer::make(new DebugTransport()); - $tracer->startSpan(self::OPERATION_NAME, [ - 'child_of' => $distributedTracingContext, - ]); - $this->assertSame( - PrioritySampling::USER_REJECT, - $tracer->unwrapped()->getPrioritySampling() - ); - } - - public function testUnfinishedSpansAreNotSentOnFlush() - { - $transport = new DebugTransport(); - $tracer = Tracer::make($transport); - $tracer->startActiveSpan('root'); - $tracer->startActiveSpan('child'); - - $tracer->flush(); - - $this->assertEmpty($transport->getTraces()); - } - - public function testUnfinishedSpansCanBeFinishedOnFlush() - { - self::markTestIncomplete(); - Configuration::replace(\Mockery::mock('\DDTrace\Configuration', [ - 'isAutofinishSpansEnabled' => true, - 'isPrioritySamplingEnabled' => false, - 'isDebugModeEnabled' => false, - ])); - - $transport = new DebugTransport(); - $tracer = Tracer::make($transport); - $tracer->startActiveSpan('root'); - $tracer->startActiveSpan('child'); - - $tracer->flush(); - $sent = $transport->getTraces(); - $this->assertSame('root', $sent[0][0]->getOperationName()); - $this->assertSame('child', $sent[0][1]->getOperationName()); - } -} diff --git a/tests/OpenTracing/InternalTelemetryTest.php b/tests/OpenTracing/InternalTelemetryTest.php index 2dc9f0143f..9ff657148e 100644 --- a/tests/OpenTracing/InternalTelemetryTest.php +++ b/tests/OpenTracing/InternalTelemetryTest.php @@ -28,7 +28,7 @@ private function readTelemetryPayloads($response) } // Filter the payloads from the trace background sender - return array_values(array_filter($telemetryPayloads, function($p) { return ($p["application"]["service_name"] ?? "") != "background_sender-php-service"; })); + return array_values(array_filter($telemetryPayloads, function($p) { return ($p["application"]["service_name"] ?? "") == "service_name"; })); } public function testInternalMetricWithOpenTracing() @@ -37,22 +37,25 @@ public function testInternalMetricWithOpenTracing() $this->executeCommand(); - $requests = $this->retrieveDumpedData(function ($request) { - return (strpos($request["uri"] ?? "", "/telemetry/") === 0) - && (strpos($request["body"] ?? "", "spans_created") !== false) - ; - }, true); + $requests = $this->retrieveDumpedData($this->untilTelemetryRequest("spans_created"), true); $payloads = $this->readTelemetryPayloads($requests); $isMetric = function (array $payload) { return 'generate-metrics' === $payload['request_type']; }; - $metrics = array_values(array_filter($payloads, $isMetric)); + $metricRequests = array_values(array_filter($payloads, $isMetric)); - $this->assertCount(1, $metrics); - $this->assertEquals("generate-metrics", $metrics[0]["request_type"]); - $this->assertEquals("tracers", $metrics[0]["payload"]["series"][0]["namespace"]); - $this->assertEquals("spans_created", $metrics[0]["payload"]["series"][0]["metric"]); - $this->assertEquals(["integration_name:opentracing"], $metrics[0]["payload"]["series"][0]["tags"]); + $this->assertCount(1, $metricRequests); + $this->assertEquals("generate-metrics", $metricRequests[0]["request_type"]); + + $metrics = []; + foreach ($metricRequests[0]['payload']['series'] as $serie) { + $metrics[$serie['metric']][] = $serie; + } + + $this->assertCount(1, $metrics['spans_created']); + $this->assertEquals("tracers", $metrics['spans_created'][0]["namespace"]); + $this->assertEquals("spans_created", $metrics['spans_created'][0]["metric"]); + $this->assertEquals(["integration_name:opentracing"], $metrics['spans_created'][0]["tags"]); } } diff --git a/tests/Sapi/CliServer/CliServer.php b/tests/Sapi/CliServer/CliServer.php index 7a97858ff1..9ce308b554 100644 --- a/tests/Sapi/CliServer/CliServer.php +++ b/tests/Sapi/CliServer/CliServer.php @@ -58,8 +58,6 @@ public function __construct($indexFile, $host, $port, array $envs = [], array $i public function start() { if (getenv('PHPUNIT_COVERAGE')) { - $this->inis['auto_prepend_file'] = __DIR__ . '/../../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $this->inis['zend_extension'] = $xdebugExtension; diff --git a/tests/Sapi/Frankenphp/FrankenphpServer.php b/tests/Sapi/Frankenphp/FrankenphpServer.php index 3ba424b75a..201ffe5100 100644 --- a/tests/Sapi/Frankenphp/FrankenphpServer.php +++ b/tests/Sapi/Frankenphp/FrankenphpServer.php @@ -52,8 +52,6 @@ public function __construct($indexFile, $host, $port, array $envs = [], array $i $this->envs = $envs; if (getenv('PHPUNIT_COVERAGE')) { - $inis['auto_prepend_file'] = __DIR__ . '/../../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $inis['zend_extension'] = $xdebugExtension; diff --git a/tests/Sapi/OctaneServer/OctaneServer.php b/tests/Sapi/OctaneServer/OctaneServer.php index a5c98c66f5..b1543f9d9a 100644 --- a/tests/Sapi/OctaneServer/OctaneServer.php +++ b/tests/Sapi/OctaneServer/OctaneServer.php @@ -58,13 +58,15 @@ public function __construct($artisanFile, $host, $port, array $envs = [], array public function start() { if (getenv('PHPUNIT_COVERAGE')) { - $this->inis['auto_prepend_file'] = __DIR__ . '/../../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $this->inis['zend_extension'] = $xdebugExtension; $this->inis['xdebug.mode'] = 'coverage'; } + $token = ini_get('datadog.trace.agent_test_session_token'); + if ($token != "") { + $this->envs["DD_TRACE_AGENT_TEST_SESSION_TOKEN"] = $token; + } $cmd = sprintf( PHP_BINARY . ' %s %s octane:start --server=swoole --host=%s --port=%d', @@ -94,7 +96,7 @@ public function stop() $this->process->stop(0); $cmd = sprintf( - PHP_BINARY . ' %s octane:stop', + PHP_BINARY . ' -d extension=swoole %s octane:stop', $this->artisanFile ); $process = new Process($cmd); diff --git a/tests/Sapi/Roadrunner/RoadrunnerServer.php b/tests/Sapi/Roadrunner/RoadrunnerServer.php index 1715ad8de3..e113fff07d 100644 --- a/tests/Sapi/Roadrunner/RoadrunnerServer.php +++ b/tests/Sapi/Roadrunner/RoadrunnerServer.php @@ -54,6 +54,11 @@ public function __construct($version, $workerFile, $host, $port, array $envs = [ $this->version = $version; $this->workerFile = $workerFile; + $token = ini_get('datadog.trace.agent_test_session_token'); + if ($token != "") { + $envs["DD_TRACE_AGENT_TEST_SESSION_TOKEN"] = $token; + } + $logPath = dirname($workerFile) . '/' . self::ERROR_LOG; switch (php_uname('m')) { @@ -67,8 +72,6 @@ public function __construct($version, $workerFile, $host, $port, array $envs = [ } if (getenv('PHPUNIT_COVERAGE')) { - $inis['auto_prepend_file'] = __DIR__ . '/../../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $inis['zend_extension'] = $xdebugExtension; diff --git a/tests/Sapi/SwooleServer/SwooleServer.php b/tests/Sapi/SwooleServer/SwooleServer.php index 15e0c29a45..5db3e79479 100644 --- a/tests/Sapi/SwooleServer/SwooleServer.php +++ b/tests/Sapi/SwooleServer/SwooleServer.php @@ -19,6 +19,11 @@ final class SwooleServer implements Sapi */ private $indexFile; + /** + * @var int + */ + private $port; + /** * @var array */ @@ -31,12 +36,14 @@ final class SwooleServer implements Sapi /** * @param string $indexFile + * @param int $port * @param array $envs * @param array $inis */ - public function __construct($indexFile, array $envs = [], array $inis = []) + public function __construct($indexFile, $port, array $envs = [], array $inis = []) { $this->indexFile = $indexFile; + $this->port = $port; $this->envs = $envs; $this->inis = $inis; } @@ -44,8 +51,6 @@ public function __construct($indexFile, array $envs = [], array $inis = []) public function start() { if (getenv('PHPUNIT_COVERAGE')) { - $this->inis['auto_prepend_file'] = __DIR__ . '/../../save_code_coverage.php'; - $xdebugExtension = glob(PHP_EXTENSION_DIR . '/xdebug*.so'); $xdebugExtension = end($xdebugExtension); $this->inis['zend_extension'] = $xdebugExtension; @@ -53,9 +58,10 @@ public function start() } $cmd = sprintf( - PHP_BINARY . ' %s %s', + PHP_BINARY . ' %s %s %d', new IniSerializer($this->inis), - $this->indexFile + $this->indexFile, + $this->port ); $envs = new EnvSerializer($this->envs); $processCmd = "$envs exec $cmd"; diff --git a/tests/Unit/Util/OrphansTest.php b/tests/Unit/Util/OrphansTest.php index fdddae81fb..58c61555f1 100644 --- a/tests/Unit/Util/OrphansTest.php +++ b/tests/Unit/Util/OrphansTest.php @@ -8,8 +8,6 @@ final class OrphansTest extends IntegrationTestCase { - use TracerTestTrait; - static function foo() { // no-op diff --git a/tests/WebServer.php b/tests/WebServer.php index 227d52bb3f..667c55d118 100644 --- a/tests/WebServer.php +++ b/tests/WebServer.php @@ -20,7 +20,7 @@ final class WebServer { const FCGI_HOST = '0.0.0.0'; - const FCGI_PORT = 9797; + const FCGI_PORT = 9797 - GLOBAL_PORT_OFFSET; const ERROR_LOG_NAME = 'dd_php_error.log'; @@ -131,6 +131,9 @@ public function start() if (!isset($this->envs['DD_TRACE_DEBUG'])) { $this->inis['datadog.trace.debug'] = 'true'; } + if (GLOBAL_AUTO_PREPEND_FILE) { + $this->inis['auto_prepend_file'] = GLOBAL_AUTO_PREPEND_FILE; + } $this->errorLogSize = (int)@filesize($this->defaultInis['error_log']); @@ -154,6 +157,7 @@ public function start() } elseif ($this->isSwoole) { $this->sapi = new SwooleServer( $this->indexFile, + $this->port, $this->envs, $this->inis ); diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index d3153bc92a..0000000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,26 +0,0 @@ -= 8) { - require __DIR__ . '/Common/MultiPHPUnitVersionAdapter_typed.php'; -} else { - require __DIR__ . '/Common/MultiPHPUnitVersionAdapter_untyped.php'; -} diff --git a/tests/bootstrap_common.php b/tests/bootstrap_common.php new file mode 100644 index 0000000000..94a4597cb9 --- /dev/null +++ b/tests/bootstrap_common.php @@ -0,0 +1,117 @@ += 8) { + require __DIR__ . '/Common/MultiPHPUnitVersionAdapter_typed.php'; + } else { + require __DIR__ . '/Common/MultiPHPUnitVersionAdapter_untyped.php'; + } + + function update_test_agent_session_token($token) { + if (defined('GLOBAL_AUTO_PREPEND_RSRC')) { + ini_set("datadog.trace.agent_test_session_token", $token); + ftruncate(GLOBAL_AUTO_PREPEND_RSRC, 0); + fseek(GLOBAL_AUTO_PREPEND_RSRC, 0); + fwrite(GLOBAL_AUTO_PREPEND_RSRC, " strlen(__DIR__)) { + if (file_exists("$path/vendor/autoload.php")) { + putenv("COMPOSER_ROOT_VERSION=1.0.0"); // silence composer + \DDTrace\Tests\Common\IntegrationTestCase::$autoloadPath = "$path/vendor/autoload.php"; + require_once \DDTrace\Tests\Common\IntegrationTestCase::$autoloadPath; + return; + } elseif (file_exists("$path/composer.json")) { + \DDTrace\Testing\trigger_error("Found $path/composer.json, but seems not installed", E_USER_ERROR); + } + $path = dirname($path); + } + }; + if (class_exists('PHPUnit\Runner\StandardTestSuiteLoader')) { + \DDTrace\hook_method('PHPUnit\Util\FileLoader', 'load', $hook); + \DDTrace\hook_method('PHPUnit\Runner\StandardTestSuiteLoader', 'load', $hook); + } elseif (method_exists('PHPUnit\Runner\TestSuiteLoader', 'loadSuiteClassFile')) { + \DDTrace\hook_method('PHPUnit\Runner\TestSuiteLoader', 'loadSuiteClassFile', $hook); + } else { + \DDTrace\hook_method('PHPUnit\Runner\TestSuiteLoader', 'load', $hook); + } +} diff --git a/tests/bootstrap_phpbench.php b/tests/bootstrap_phpbench.php new file mode 100644 index 0000000000..f02397139e --- /dev/null +++ b/tests/bootstrap_phpbench.php @@ -0,0 +1,7 @@ + string(15) "active_span.php" ["resource"]=> @@ -61,6 +61,9 @@ object(DDTrace\RootSpanData)#%d (20) { ["links"]=> array(0) { } + ["events"]=> + array(0) { + } ["peerServiceSources"]=> array(0) { } diff --git a/tests/ext/appsec/sca_flag_is_sent_01.phpt b/tests/ext/appsec/sca_flag_is_sent_01.phpt index 028f483e5d..10b3263b60 100644 --- a/tests/ext/appsec/sca_flag_is_sent_01.phpt +++ b/tests/ext/appsec/sca_flag_is_sent_01.phpt @@ -5,7 +5,9 @@ This configuration is used by the backend to display/charge customers --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/appsec/sca_flag_is_sent_02.phpt b/tests/ext/appsec/sca_flag_is_sent_02.phpt index c74dfeb528..c2bdfeb089 100644 --- a/tests/ext/appsec/sca_flag_is_sent_02.phpt +++ b/tests/ext/appsec/sca_flag_is_sent_02.phpt @@ -5,7 +5,9 @@ This configuration is used by the backend to display/charge customers --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/appsec/sca_flag_is_sent_03.phpt b/tests/ext/appsec/sca_flag_is_sent_03.phpt index 251dde5602..0ab15c9b3c 100644 --- a/tests/ext/appsec/sca_flag_is_sent_03.phpt +++ b/tests/ext/appsec/sca_flag_is_sent_03.phpt @@ -5,7 +5,9 @@ This configuration is used by the backend to display/charge customers --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/appsec/sca_flag_is_sent_04.phpt b/tests/ext/appsec/sca_flag_is_sent_04.phpt index 0b581833af..f92835f286 100644 --- a/tests/ext/appsec/sca_flag_is_sent_04.phpt +++ b/tests/ext/appsec/sca_flag_is_sent_04.phpt @@ -5,7 +5,9 @@ This configuration is used by the backend to display/charge customers --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/appsec/sca_flag_is_sent_05.phpt b/tests/ext/appsec/sca_flag_is_sent_05.phpt index a8dfdaae9f..54f9257b9f 100644 --- a/tests/ext/appsec/sca_flag_is_sent_05.phpt +++ b/tests/ext/appsec/sca_flag_is_sent_05.phpt @@ -5,7 +5,9 @@ This configuration is used by the backend to display/charge customers --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/appsec/sca_test.inc b/tests/ext/appsec/sca_test.inc index e8e45c0527..a64ae8e474 100644 --- a/tests/ext/appsec/sca_test.inc +++ b/tests/ext/appsec/sca_test.inc @@ -21,7 +21,10 @@ for ($i = 0; $i < 100; ++$i) { foreach (file(__DIR__ . '/'.$id.'-telemetry.out') as $l) { if ($l) { $json = json_decode($l, true); - if ($json && ($json["application"]["service_name"] ?? "") != "background_sender-php-service") { + if ($json) { + if ($json["application"]["service_name"] == "background_sender-php-service" || $json["application"]["service_name"] == "datadog-ipc-helper") { + continue; + } array_push($batches, ...($json["request_type"] == "message-batch" ? $json["payload"] : [$json])); } } diff --git a/tests/ext/autoload-php-files/dd_init_open_basedir.phpt b/tests/ext/autoload-php-files/dd_init_open_basedir.phpt index a04f9b4f04..12a5fabde3 100644 --- a/tests/ext/autoload-php-files/dd_init_open_basedir.phpt +++ b/tests/ext/autoload-php-files/dd_init_open_basedir.phpt @@ -1,7 +1,9 @@ --TEST-- Calling dd_init.php is confined to open_basedir settings --ENV-- -DD_TRACE_LOG_LEVEL=info,startup=off +DD_TRACE_AUTO_FLUSH_ENABLED=0 +DD_TRACE_LOG_LEVEL=info,startup=off,datadog_sidecar=off +DD_AUTOLOAD_NO_COMPILE=1 --INI-- open_basedir="{PWD}" datadog.trace.sources_path="{PWD}/.." diff --git a/tests/ext/autoload-php-files/default_spl_autoloader.phpt b/tests/ext/autoload-php-files/default_spl_autoloader.phpt new file mode 100644 index 0000000000..3ed5a422c5 --- /dev/null +++ b/tests/ext/autoload-php-files/default_spl_autoloader.phpt @@ -0,0 +1,17 @@ +--TEST-- +Execute the default spl_autoload implementation if spl_autoload_register() is called without args +--INI-- +datadog.trace.sources_path="{PWD}/.." +--FILE-- + +--EXPECT-- +bool(true) +Request start diff --git a/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt b/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt index 5dbe07e0d0..a5cc003137 100644 --- a/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt +++ b/tests/ext/autoload-php-files/error_get_last_is_unaffected.phpt @@ -1,8 +1,10 @@ --TEST-- Errors in ddtrace autoloader do not affect error_get_last() --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_AUTOLOAD_NO_COMPILE=1 +DD_APPSEC_ENABLED=0 --INI-- error_reporting=E_ALL datadog.trace.sources_path="{PWD}/.." diff --git a/tests/ext/autoload-php-files/file_not_found.phpt b/tests/ext/autoload-php-files/file_not_found.phpt index 50919d4a24..413b3d19ad 100644 --- a/tests/ext/autoload-php-files/file_not_found.phpt +++ b/tests/ext/autoload-php-files/file_not_found.phpt @@ -1,6 +1,7 @@ --TEST-- Do not fail when PHP code couldn't be loaded --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_AUTOLOAD_NO_COMPILE=1 --INI-- diff --git a/tests/ext/autoload-php-files/ignores_exceptions.phpt b/tests/ext/autoload-php-files/ignores_exceptions.phpt index 953a5fc77e..993d1bd08b 100644 --- a/tests/ext/autoload-php-files/ignores_exceptions.phpt +++ b/tests/ext/autoload-php-files/ignores_exceptions.phpt @@ -1,6 +1,7 @@ --TEST-- Request init hook ignores exceptions --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_AUTOLOAD_NO_COMPILE=1 --INI-- diff --git a/tests/ext/autoload-php-files/ignores_fatal_errors.phpt b/tests/ext/autoload-php-files/ignores_fatal_errors.phpt index 40b22ab8f6..e3a8593020 100644 --- a/tests/ext/autoload-php-files/ignores_fatal_errors.phpt +++ b/tests/ext/autoload-php-files/ignores_fatal_errors.phpt @@ -3,6 +3,7 @@ Request init hook ignores fatal errors --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_AUTOLOAD_NO_COMPILE=1 --INI-- diff --git a/tests/ext/autoload-php-files/legacy_autoloader.phpt b/tests/ext/autoload-php-files/legacy_autoloader.phpt new file mode 100644 index 0000000000..606edb1895 --- /dev/null +++ b/tests/ext/autoload-php-files/legacy_autoloader.phpt @@ -0,0 +1,23 @@ +--TEST-- +Execute __autoload() if present +--SKIPIF-- += 80000) die("skip: __autoload was removed in PHP 8") ?> +--INI-- +error_reporting=8191 +datadog.trace.sources_path="{PWD}/.." +--FILE-- + +--EXPECT-- +Autoload splautoload attempted! +bool(false) +Request start diff --git a/tests/ext/autoload-php-files/skip_default_autoloader.phpt b/tests/ext/autoload-php-files/skip_default_autoloader.phpt new file mode 100644 index 0000000000..bf25fa4eac --- /dev/null +++ b/tests/ext/autoload-php-files/skip_default_autoloader.phpt @@ -0,0 +1,15 @@ +--TEST-- +Do not execute the default spl_autoload implementation if no autoloader is specified +--INI-- +datadog.trace.sources_path="{PWD}/.." +--FILE-- + +--EXPECT-- +bool(false) +Request start diff --git a/tests/ext/autoload-php-files/splautoload.inc b/tests/ext/autoload-php-files/splautoload.inc new file mode 100644 index 0000000000..b324fc1363 --- /dev/null +++ b/tests/ext/autoload-php-files/splautoload.inc @@ -0,0 +1,5 @@ + ---INI-- -ddtrace.cgroup_file={PWD}/stubs/cgroup.docker --ENV-- DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_BGS_ENABLED=1 @@ -14,7 +12,9 @@ DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS=1 DD_TRACE_AGENT_FLUSH_INTERVAL=666 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 -DD_TRACE_AUTO_FLUSH_ENABLED=1 +--INI-- +ddtrace.cgroup_file={PWD}/stubs/cgroup.docker +datadog.trace.agent_test_session_token=background-sender/agent_headers_container_id --FILE-- ---INI-- -ddtrace.cgroup_file={PWD}/stubs/cgroup.empty --ENV-- DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_BGS_ENABLED=1 @@ -13,7 +11,9 @@ DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS=1 DD_TRACE_AGENT_FLUSH_INTERVAL=333 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 -DD_TRACE_AUTO_FLUSH_ENABLED=1 +--INI-- +ddtrace.cgroup_file={PWD}/stubs/cgroup.empty +datadog.trace.agent_test_session_token=background-sender/agent_headers_container_id_empty --FILE-- ---INI-- -ddtrace.cgroup_file={PWD}/stubs/cgroup.fargate.1.4 --ENV-- DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_BGS_ENABLED=1 @@ -14,7 +12,9 @@ DD_TRACE_AGENT_FLUSH_AFTER_N_REQUESTS=1 DD_TRACE_AGENT_FLUSH_INTERVAL=333 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 -DD_TRACE_AUTO_FLUSH_ENABLED=1 +--INI-- +ddtrace.cgroup_file={PWD}/stubs/cgroup.fargate.1.4 +datadog.trace.agent_test_session_token=background-sender/agent_headers_container_id_fargate --FILE-- replayRequest(); // clear + RequestReplayer::launchUnixProxy(str_replace("unix://", "", getenv("DD_TRACE_AGENT_URL"))); \DDTrace\start_span(); \DDTrace\close_span(); -$rr = new RequestReplayer(); - echo PHP_EOL; $headers = $rr->replayHeaders([ 'content-type', diff --git a/tests/ext/background-sender/agent_sampling.phpt b/tests/ext/background-sender/agent_sampling.phpt index 230bd02f5b..2896404e3f 100644 --- a/tests/ext/background-sender/agent_sampling.phpt +++ b/tests/ext/background-sender/agent_sampling.phpt @@ -10,7 +10,8 @@ DD_TRACE_AGENT_FLUSH_INTERVAL=333 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 DD_TRACE_SIDECAR_TRACE_SENDER=0 -DD_TRACE_AUTO_FLUSH_ENABLED=1 +--INI-- +datadog.trace.agent_test_session_token=background-sender/agent_sampling --FILE-- setResponse(["rate_by_service" => ["service:,env:" => 0]]); echo "Initial sampling: {$get_sampling()}\n"; $rr->setResponse(["rate_by_service" => ["service:,env:" => 0, "service:foo,env:none" => 1]]); +dd_trace_internal_fn("synchronous_flush"); \DDTrace\start_span(); \DDTrace\close_span(); @@ -39,6 +41,7 @@ echo "Generic sampling: {$get_sampling()}\n"; // reset it for other tests $rr->setResponse(["rate_by_service" => []]); +dd_trace_internal_fn("synchronous_flush"); $s = \DDTrace\start_span(); $s->service = "foo"; diff --git a/tests/ext/background-sender/agent_sampling_sidecar.phpt b/tests/ext/background-sender/agent_sampling_sidecar.phpt index 2f7dbb5d63..e33c298f11 100644 --- a/tests/ext/background-sender/agent_sampling_sidecar.phpt +++ b/tests/ext/background-sender/agent_sampling_sidecar.phpt @@ -11,43 +11,97 @@ DD_TRACE_AGENT_FLUSH_INTERVAL=333 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 DD_TRACE_SIDECAR_TRACE_SENDER=1 -DD_TRACE_AUTO_FLUSH_ENABLED=1 +--INI-- +datadog.trace.agent_test_session_token=background-sender/agent_sampling_sidecar --FILE-- replayRequest(); // cleanup possible leftover -$get_sampling = function() use ($rr) { +$expected = [1,0,1]; +$error = false; +$get_sampling = function() use ($rr, &$expected, &$error) { $root = json_decode($rr->waitForDataAndReplay()["body"], true); $spans = $root["chunks"][0]["spans"] ?? $root[0]; - return $spans[0]["metrics"]["_sampling_priority_v1"]; + $priority = $spans[0]["metrics"]["_sampling_priority_v1"]; + if ($priority != array_shift($expected)) { + $error = true; + } + return $priority; }; -$rr->setResponse(["rate_by_service" => ["service:,env:" => 0]]); +$rr->setResponse(["rate_by_service" => ["service:,env:" => 0, "service:agent_sampling_sidecar_test,env:first" => 1]]); \DDTrace\start_span(); \DDTrace\close_span(); echo "Initial sampling: {$get_sampling()}\n"; -$rr->setResponse(["rate_by_service" => ["service:,env:" => 0, "service:foo,env:none" => 1]]); +checkUpdated("service:agent_sampling_sidecar_test,env:first"); +$rr->setResponse(["rate_by_service" => ["service:,env:" => 0, "service:foo,env:none" => 1, "service:agent_sampling_sidecar_test,env:second" => 0]]); + +recordContents(); \DDTrace\start_span(); \DDTrace\close_span(); +recordContents(); + +checkUpdated("service:agent_sampling_sidecar_test,env:second"); echo "Generic sampling: {$get_sampling()}\n"; // reset it for other tests $rr->setResponse(["rate_by_service" => []]); +recordContents(); $s = \DDTrace\start_span(); $s->service = "foo"; $s->env = "none"; \DDTrace\close_span(); +recordContents(); echo "Specific sampling: {$get_sampling()}\n"; +if ($error && PHP_OS === "Linux") { + var_dump($contents); +} + ?> --EXPECTF-- [ddtrace] [info] Flushing trace of size 1 to send-queue for http://request-replayer:80 diff --git a/tests/ext/background-sender/background_sender_survives_setuid.phpt b/tests/ext/background-sender/background_sender_survives_setuid.phpt index 9f4d458494..9ebd6e70bb 100644 --- a/tests/ext/background-sender/background_sender_survives_setuid.phpt +++ b/tests/ext/background-sender/background_sender_survives_setuid.phpt @@ -8,6 +8,7 @@ To test this we will issue a setgroups() via the libc wrapper (which distributes + --ENV-- diff --git a/tests/ext/background-sender/sidecar_fallback.phpt b/tests/ext/background-sender/sidecar_fallback.phpt index 896605c7b5..8b9783795b 100644 --- a/tests/ext/background-sender/sidecar_fallback.phpt +++ b/tests/ext/background-sender/sidecar_fallback.phpt @@ -2,7 +2,7 @@ Send telemetry about the sidecar being disabled --SKIPIF-- - + replayRequest(); /* avoid cross-pollination */ ?> @@ -11,8 +11,11 @@ DD_AGENT_HOST=request-replayer DD_TRACE_AGENT_PORT=80 DD_TRACE_AGENT_FLUSH_INTERVAL=333 DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_ENABLED=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 DD_SERVICE=service +--INI-- +datadog.trace.agent_test_session_token=background-sender/sidecar_fallback --FILE-- +--ENV-- +DD_TRACE_LOG_LEVEL=warn,span=off,startup=off +DD_LOG_BACKTRACE=1 +DD_CRASHTRACKING_ENABLED=1 +--INI-- +datadog.trace.log_file=file://stdout +--FILE-- + +--EXPECTF-- +[ddtrace] [warning] Settings 'datadog.log_backtrace' and 'datadog.crashtracking_enabled' are mutually exclusive. Cannot enable the backtrace. +1 diff --git a/tests/ext/crashtracker_segfault.phpt b/tests/ext/crashtracker_segfault.phpt new file mode 100644 index 0000000000..5e4aae80d9 --- /dev/null +++ b/tests/ext/crashtracker_segfault.phpt @@ -0,0 +1,79 @@ +--TEST-- +Send crashtracker report when segmentation fault signal is raised and config enables it +--SKIPIF-- + +--ENV-- +DD_TRACE_LOG_LEVEL=0 +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +--INI-- +datadog.trace.agent_test_session_token=tests/ext/crashtracker_segfault.phpt +--FILE-- +replayRequest(); // cleanup possible leftover + +usleep(100000); // Let time to the sidecar to open the crashtracker socket + +posix_setrlimit(POSIX_RLIMIT_CORE, 0, 0); + +$php = getenv('TEST_PHP_EXECUTABLE'); +$args = getenv('TEST_PHP_ARGS')." ".getenv("TEST_PHP_EXTRA_ARGS"); +$cmd = $php." ".$args." -r 'posix_kill(posix_getpid(), 11);'"; +system($cmd); + +$rr->waitForRequest(function ($request) { + if ($request["uri"] != "/telemetry/proxy/api/v2/apmtelemetry") { + return false; + } + $body = json_decode($request["body"], true); + if ($body["request_type"] != "logs" || !isset($body["payload"][0]["message"])) { + return false; + } + + $payload = $body["payload"][0]; + $payload["message"] = json_decode($payload["message"], true); + $output = json_encode($payload, JSON_PRETTY_PRINT); + + echo $output; + + return true; +}); +?> +--EXPECTF-- +%A{ + "message": { + "additional_stacktraces": [], + "files": { +%A + }, + "metadata": { + "library_name": "dd-trace-php", + "library_version": "%s", + "family": "php", + "tags": [ +%A + ] + }, + "os_info": { +%A + }, + "span_ids": [], + "tags": [], + "trace_ids": [] + }, + "level": "ERROR", + "count": 1, + "stack_trace": "%s", + "tags": "%ssigname:SIGSEGV%s", + "is_sensitive": true +}%A diff --git a/tests/ext/crashtracker_segfault_disabled.phpt b/tests/ext/crashtracker_segfault_disabled.phpt new file mode 100644 index 0000000000..bb73bfdf5c --- /dev/null +++ b/tests/ext/crashtracker_segfault_disabled.phpt @@ -0,0 +1,55 @@ +--TEST-- +Don't send crashtracker report when segmentation fault signal is raised and config disables it +--SKIPIF-- + +--ENV-- +DD_TRACE_LOG_LEVEL=0 +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_CRASHTRACKING_ENABLED=0 +--INI-- +datadog.trace.agent_test_session_token=tests/ext/crashtracker_segfault_disabled.phpt +--FILE-- +replayRequest(); // cleanup possible leftover + +usleep(100000); // Let time to the sidecar to open the crashtracker socket + +$php = getenv('TEST_PHP_EXECUTABLE'); +$args = getenv('TEST_PHP_ARGS')." ".getenv("TEST_PHP_EXTRA_ARGS"); +$cmd = $php." ".$args." -r 'posix_kill(posix_getpid(), 11);'"; +system($cmd); + +$rr->waitForRequest(function ($request) { + if ($request["uri"] != "/telemetry/proxy/api/v2/apmtelemetry") { + return false; + } + $body = json_decode($request["body"], true); + if ($body["request_type"] != "logs" || !isset($body["payload"][0]["message"])) { + return false; + } + + $payload = $body["payload"][0]; + $payload["message"] = json_decode($payload["message"], true); + $output = json_encode($payload, JSON_PRETTY_PRINT); + + echo $output; + + return true; +}); + +?> +--EXPECTF-- +%A +Fatal error: Uncaught Exception: wait for replay timeout in %s +%A diff --git a/tests/ext/dd_trace_serialize_msgpack_error.phpt b/tests/ext/dd_trace_serialize_msgpack_error.phpt index 229215e630..6b8bb2e2c9 100644 --- a/tests/ext/dd_trace_serialize_msgpack_error.phpt +++ b/tests/ext/dd_trace_serialize_msgpack_error.phpt @@ -1,6 +1,7 @@ --TEST-- dd_trace_serialize_msgpack() error conditions --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off --FILE-- - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -56,6 +57,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/distributed_tracing/distributed_trace_conflicting_span_ids_tracecontext.phpt b/tests/ext/distributed_tracing/distributed_trace_conflicting_span_ids_tracecontext.phpt index 696ee326aa..e2c5261fc4 100644 --- a/tests/ext/distributed_tracing/distributed_trace_conflicting_span_ids_tracecontext.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_conflicting_span_ids_tracecontext.phpt @@ -8,7 +8,7 @@ DD_TRACE_PROPAGATION_STYLE=tracecontext DDTrace\consume_distributed_tracing_headers([ "traceparent" => "00-0000000000000000000000000000002a-0000000000000001-01", - "tracestate" => "dd=p:00000000000000bb;s:1", + "tracestate" => "dd=p:00000000000000bb;p:00000000000000bb;s:1", ]); $span = \DDTrace\start_span(); diff --git a/tests/ext/distributed_tracing/distributed_trace_inherit.phpt b/tests/ext/distributed_tracing/distributed_trace_inherit.phpt index eec5fb4034..7cbaebcf58 100644 --- a/tests/ext/distributed_tracing/distributed_trace_inherit.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_inherit.phpt @@ -1,6 +1,7 @@ --TEST-- Transmit distributed header information to spans --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 HTTP_X_DATADOG_TRACE_ID=42 HTTP_X_DATADOG_PARENT_ID=10 HTTP_X_DATADOG_ORIGIN=datadog @@ -59,13 +60,17 @@ array(2) { string(7) "datadog" } ["metrics"]=> - array(3) { + array(5) { ["process_id"]=> float(%f) ["_sampling_priority_v1"]=> float(3) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt b/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt index cbbc58c5d7..462cc6b1b1 100644 --- a/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt @@ -1,6 +1,7 @@ --TEST-- Setting a distributed tracing context if a span is already active --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 HTTP_X_DATADOG_TAGS=custom_tag=inherited HTTP_X_DATADOG_ORIGIN=datadog DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/do_not_check_if_class_or_function_exists_by_default.phpt b/tests/ext/do_not_check_if_class_or_function_exists_by_default.phpt index 39f8d177af..f094385b51 100644 --- a/tests/ext/do_not_check_if_class_or_function_exists_by_default.phpt +++ b/tests/ext/do_not_check_if_class_or_function_exists_by_default.phpt @@ -33,7 +33,7 @@ echo "no exception thrown" . PHP_EOL; --EXPECT-- TRUE TRUE -TRUE +FALSE TRUE TRUE no exception thrown diff --git a/tests/ext/dogstatsd/metrics_over_uds.phpt b/tests/ext/dogstatsd/metrics_over_uds.phpt index 6fe66694e3..f68da3719c 100644 --- a/tests/ext/dogstatsd/metrics_over_uds.phpt +++ b/tests/ext/dogstatsd/metrics_over_uds.phpt @@ -14,46 +14,7 @@ DD_VERSION=1.12 --FILE-- socket = socket_create(AF_UNIX, SOCK_DGRAM, 0))) { - $errorcode = socket_last_error(); - $errormsg = socket_strerror($errorcode); - die("Couldn't create socket: [$errorcode] $errormsg\n"); - } - - if (!socket_bind($this->socket, $path)) { - $errorcode = socket_last_error(); - $errormsg = socket_strerror($errorcode); - die("Could not bind socket : [$errorcode] $errormsg\n"); - } - - // On the CI, when this test is ran using "pecl run-tests" with sudo - // the Unix socket is owned by root while the sidecar process is ran as another user - chmod($path, 0777); - } - - public function dump($expected, $iter = 5000) { - $lines = []; - for ($i = 0; $i < $iter; ++$i) { - usleep(100); - if (socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port)) { - $lines[] = "$buf\n"; - if (count($lines) == $expected) { - sort($lines, SORT_STRING); - echo implode($lines); - return; - } - } - } - } - - public function close() { - socket_close($this->socket); - } -} +require __DIR__ . '/metrics_uds.inc'; $server = new UDSServer('/tmp/ddtrace-test-metrics_over_uds.socket'); diff --git a/tests/ext/dogstatsd/metrics_uds.inc b/tests/ext/dogstatsd/metrics_uds.inc new file mode 100644 index 0000000000..32050fdc35 --- /dev/null +++ b/tests/ext/dogstatsd/metrics_uds.inc @@ -0,0 +1,42 @@ +socket = socket_create(AF_UNIX, SOCK_DGRAM, 0))) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Couldn't create socket: [$errorcode] $errormsg\n"); + } + + if (!socket_bind($this->socket, $path)) { + $errorcode = socket_last_error(); + $errormsg = socket_strerror($errorcode); + die("Could not bind socket : [$errorcode] $errormsg\n"); + } + + // On the CI, when this test is ran using "pecl run-tests" with sudo + // the Unix socket is owned by root while the sidecar process is ran as another user + chmod($path, 0777); + } + + public function dump($expected, $iter = 5000) { + $lines = []; + for ($i = 0; $i < $iter; ++$i) { + usleep(100); + if (socket_recvfrom($this->socket, $buf, 2048, MSG_DONTWAIT, $remote_ip, $remote_port)) { + $lines[] = "$buf\n"; + if (count($lines) == $expected) { + sort($lines, SORT_STRING); + echo implode($lines); + return; + } + } + } + } + + public function close() { + socket_close($this->socket); + } +} diff --git a/tests/ext/extension_no_static_tls.phpt b/tests/ext/extension_no_static_tls.phpt index 6ae89ca2fd..d331e5acae 100644 --- a/tests/ext/extension_no_static_tls.phpt +++ b/tests/ext/extension_no_static_tls.phpt @@ -26,7 +26,7 @@ if (!file_exists('/proc/self/maps')) { die('skip: no /proc/self/maps'); } // 5. Determine in which compilation unit(s) this function is defined $maps = file_get_contents('/proc/self/maps'); -if (preg_match('@(?<=\\s)\\S*/ddtrace\\.so$@m', $maps, $m) != 1) { +if (preg_match('@(?<=\\s)\\S*/ddtrace[^/]*\\.so$@m', $maps, $m) != 1) { die('cannot find loaded ddtrace.so'); } diff --git a/tests/ext/extract_server_values.phpt b/tests/ext/extract_server_values.phpt index 6949d0b762..70ba2ec3d7 100644 --- a/tests/ext/extract_server_values.phpt +++ b/tests/ext/extract_server_values.phpt @@ -3,6 +3,7 @@ Test invalid $_SERVER values are properly ignored --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_HEADER_TAGS=0 HTTP_0=http_zero_header diff --git a/tests/ext/fibers/fiber_observer_bailout.phpt b/tests/ext/fibers/fiber_observer_bailout.phpt index 45ade74c5f..d14863ee91 100644 --- a/tests/ext/fibers/fiber_observer_bailout.phpt +++ b/tests/ext/fibers/fiber_observer_bailout.phpt @@ -5,6 +5,7 @@ Test fiber observing with bailout --INI-- memory_limit=100M --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- flushInterval = getenv('DD_TRACE_AGENT_FLUSH_INTERVAL') - ? (int) getenv('DD_TRACE_AGENT_FLUSH_INTERVAL') - : 5000; + ? (int) getenv('DD_TRACE_AGENT_FLUSH_INTERVAL') * 100 + : 50000; + + $this->maxIteration = (strncasecmp(PHP_OS, "WIN", 3) === 0) ? 500 : 200; } public function waitForFlush() { - usleep($this->flushInterval * 2 * 1000); + usleep($this->flushInterval * 2); + } + + public function waitForRequest($matcher) + { + $i = 0; + do { + if ($i++ == $this->maxIteration) { + throw new Exception("wait for replay timeout"); + } + usleep($this->flushInterval); + + $requests = $this->replayAllRequests(); + if (is_array($requests)) { + foreach ($requests as $request) { + if ($matcher($request)) { + return $request; + } + } + } + } while (true); } public function waitForDataAndReplay($ignoreTelemetry = true) { $i = 0; do { - if ($i++ == 100) { + if ($i++ == $this->maxIteration) { throw new Exception("wait for replay timeout"); } - usleep($this->flushInterval * 1000); + usleep($this->flushInterval); } while (empty($data = $this->replayRequest($ignoreTelemetry))); return $data; } @@ -46,13 +73,22 @@ class RequestReplayer { // Request replayer now returns as many requests as were sent during a session. // For the scope of the tests, we are returning the very first one. - $allRequests = json_decode(file_get_contents($this->endpoint . '/replay'), true); + $allRequests = $this->replayAllRequests(); if ($allRequests && $ignoreTelemetry) { $allRequests = array_values(array_filter($allRequests, function ($v) { return $v["uri"] != '/telemetry/proxy/api/v2/apmtelemetry'; })); } return $allRequests ? $allRequests[0] : []; } + public function replayAllRequests() + { + return json_decode(file_get_contents($this->endpoint . '/replay', false, stream_context_create([ + "http" => [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])), true); + } + public function replayHeaders($showOnly = []) { $request = $this->waitForDataAndReplay(); @@ -78,7 +114,10 @@ class RequestReplayer "http" => [ "method" => "POST", "content" => json_encode($array), - "header" => "Content-Type: application/json" + "header" => [ + "Content-Type: application/json", + "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ] ], ])); } diff --git a/tests/ext/inherit_meta_from_parent.phpt b/tests/ext/inherit_meta_from_parent.phpt index 66aea67fd5..46db2d7ef0 100644 --- a/tests/ext/inherit_meta_from_parent.phpt +++ b/tests/ext/inherit_meta_from_parent.phpt @@ -4,6 +4,8 @@ Inherit some global metadata from parent span datadog.trace.generate_root_span=0 datadog.env = badenv datadog.version = badversion +--ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 --FILE-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH=25 diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_copy_handle.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_copy_handle.phpt index a75671c5cf..09de7f66e4 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_copy_handle.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_copy_handle.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate after curl_copy_handle() --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_001.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_001.phpt index 6ad7a1b523..bba8baf561 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_001.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_001.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with existing headers set with curl_setopt --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec HTTP_X_DATADOG_ORIGIN=phpt-test diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_002.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_002.phpt index 654cde6513..f63d45dfbd 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_002.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_002.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with existing headers set with curl_setopt --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec HTTP_X_DATADOG_ORIGIN=phpt-test diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_003.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_003.phpt index dd4eb84044..971955fd9b 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_003.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_existing_headers_003.phpt @@ -10,6 +10,7 @@ Some libraries do not check the return stats when setting curl opts. The original headers should still be applied even when there is an error from setting the curl opts. --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=curl_exec HTTP_X_DATADOG_ORIGIN=phpt-test diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_001.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_001.phpt index 91b70610aa..5ba3863ad0 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_001.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_001.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with curl_multi_exec() --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_002.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_002.phpt index 455cd64170..6b4d9bc988 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_002.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_002.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate when curl_multi_init() is called before cu --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_003.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_003.phpt index b2c270c525..4d15eeab48 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_003.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_003.phpt @@ -4,6 +4,7 @@ Test CurlMulti during garbage collection --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_copy_handle.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_copy_handle.phpt index 92dca7cad5..810fe5fd70 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_copy_handle.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_copy_handle.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with curl_multi_exec() after curl_copy_han --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_001.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_001.phpt index 3066d66262..8d67dd86a6 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_001.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_001.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with curl_multi_exec() and headers set wit --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_002.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_002.phpt index 20ec53efdf..5eef54eff1 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_002.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_multi_exec_existing_headers_002.phpt @@ -4,6 +4,7 @@ Distributed tracing headers propagate with curl_multi_exec() and headers set wit --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off HTTP_X_DATADOG_ORIGIN=phpt-test --FILE-- diff --git a/tests/ext/integrations/source_code/001-git_metadata_injection_from_valid_files.phpt b/tests/ext/integrations/source_code/001/git_metadata_injection_from_valid_files.phpt similarity index 94% rename from tests/ext/integrations/source_code/001-git_metadata_injection_from_valid_files.phpt rename to tests/ext/integrations/source_code/001/git_metadata_injection_from_valid_files.phpt index 1cec81010f..064eec1f73 100644 --- a/tests/ext/integrations/source_code/001-git_metadata_injection_from_valid_files.phpt +++ b/tests/ext/integrations/source_code/001/git_metadata_injection_from_valid_files.phpt @@ -1,6 +1,7 @@ --TEST-- Basic Git Metadata Injection from valid .git files (Repository URL & Commit Sha) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_GIT_METADATA_ENABLED=1 --SKIPIF-- @@ -10,7 +11,7 @@ if (getenv('PHP_PEAR_RUNTESTS') === '1') die("skip: The pecl run-tests path is n --FILE-- - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -58,6 +59,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/integrations/source_code/git_metadata_injection_from_env.phpt b/tests/ext/integrations/source_code/git_metadata_injection_from_env.phpt index 46f1bec83a..1e86838aee 100644 --- a/tests/ext/integrations/source_code/git_metadata_injection_from_env.phpt +++ b/tests/ext/integrations/source_code/git_metadata_injection_from_env.phpt @@ -1,6 +1,7 @@ --TEST-- Basic Git Metadata Injection from env var (Repository URL & Commit Sha) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_GIT_REPOSITORY_URL=github.com/user/env_repo DD_GIT_COMMIT_SHA=123456 DD_TRACE_GENERATE_ROOT_SPAN=0 @@ -52,7 +53,7 @@ array(2) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%d) ["_dd.agent_psr"]=> @@ -61,6 +62,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/integrations/source_code/git_metadata_injection_from_global_tags.phpt b/tests/ext/integrations/source_code/git_metadata_injection_from_global_tags.phpt index 2201f2e9fa..c24f36e055 100644 --- a/tests/ext/integrations/source_code/git_metadata_injection_from_global_tags.phpt +++ b/tests/ext/integrations/source_code/git_metadata_injection_from_global_tags.phpt @@ -1,6 +1,7 @@ --TEST-- Basic Git Metadata Injection from global tags (Repository URL & Commit Sha) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TAGS=git.commit.sha:123456,git.repository_url:github.com/user/env_repo DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- @@ -55,7 +56,7 @@ array(2) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%d) ["_dd.agent_psr"]=> @@ -64,6 +65,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/integrations/source_code/git_metadata_injection_remove_credentials_from_env.phpt b/tests/ext/integrations/source_code/git_metadata_injection_remove_credentials_from_env.phpt index b2ab4e940c..c9588deee1 100644 --- a/tests/ext/integrations/source_code/git_metadata_injection_remove_credentials_from_env.phpt +++ b/tests/ext/integrations/source_code/git_metadata_injection_remove_credentials_from_env.phpt @@ -1,6 +1,7 @@ --TEST-- Remove credentials from repository URL --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_GIT_REPOSITORY_URL=https://u:t@github.com/user/repo_new DD_GIT_COMMIT_SHA=123456 DD_TRACE_GENERATE_ROOT_SPAN=0 @@ -52,7 +53,7 @@ array(2) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%d) ["_dd.agent_psr"]=> @@ -61,6 +62,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/integrations/source_code/repository_url_env_var.phpt b/tests/ext/integrations/source_code/repository_url_env_var.phpt index 12a04d6b11..467aa21c14 100644 --- a/tests/ext/integrations/source_code/repository_url_env_var.phpt +++ b/tests/ext/integrations/source_code/repository_url_env_var.phpt @@ -1,6 +1,7 @@ --TEST-- When DD_GIT_REPOSITORY_URL is specified, _dd.git.repository_url is injected --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_GIT_REPOSITORY_URL=github.com/user/env_repo DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- @@ -49,7 +50,7 @@ array(2) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%d) ["_dd.agent_psr"]=> @@ -58,6 +59,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> diff --git a/tests/ext/ip_collection_03.phpt b/tests/ext/ip_collection_03.phpt index 65dc888ec7..3c55dc4be1 100644 --- a/tests/ext/ip_collection_03.phpt +++ b/tests/ext/ip_collection_03.phpt @@ -1,6 +1,7 @@ --TEST-- Client IP should be collected if env DD_TRACE_CLIENT_IP_ENABLED is set to true --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 REMOTE_ADDR=127.0.0.1 DD_TRACE_CLIENT_IP_ENABLED=true diff --git a/tests/ext/limiter/002-limiter-reached.phpt b/tests/ext/limiter/002-limiter-reached.phpt index 1559bbb9bf..b3a1803ed7 100644 --- a/tests/ext/limiter/002-limiter-reached.phpt +++ b/tests/ext/limiter/002-limiter-reached.phpt @@ -3,6 +3,7 @@ rate limiter reached --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_RATE_LIMIT=10 --FILE-- diff --git a/tests/ext/live-debugger/debugger_log_probe.phpt b/tests/ext/live-debugger/debugger_log_probe.phpt new file mode 100644 index 0000000000..05385932bd --- /dev/null +++ b/tests/ext/live-debugger/debugger_log_probe.phpt @@ -0,0 +1,252 @@ +--TEST-- +Installing a live debugger log probe +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_DYNAMIC_INSTRUMENTATION_ENABLED=1 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/log_probe +--FILE-- + ["typeName" => "Bar", "methodName" => "foo"], + "captureSnapshot" => true, + "segments" => [ + ["json" => ["filter" => [["ref" => "value"], ["not" => ["isDefined" => ["getmember" => [["ref" => "@it"], "foo"]]]]]]], + ["str" => "\n"], + ["json" => ["filter" => [["ref" => "value"], ["instanceof" => [["ref" => "@it"], "bool"]]]]], + ["str" => "\n"], + ["json" => ["filter" => [["ref" => "value"], ["instanceof" => [["ref" => "this"], "Bar"]]]]], + ["str" => "\n"], + ], + ]); + + \DDTrace\start_span(); // submit span data +}); + +var_dump((new Bar)->foo(10, "20", [true])); + +$dlr = new DebuggerLogReplayer; +$log = $dlr->waitForDebuggerDataAndReplay(); +$log = json_decode($log["body"], true)[0]; +foreach ($log["debugger"]["snapshot"]["captures"] as &$capture) { + ksort($capture["arguments"]); +} +var_dump($log); + +?> +--CLEAN-- + +--EXPECTF-- +int(30) +array(5) { + ["service"]=> + string(22) "debugger_log_probe.php" + ["ddsource"]=> + string(11) "dd_debugger" + ["timestamp"]=> + int(%d) + ["debugger"]=> + array(1) { + ["snapshot"]=> + array(5) { + ["language"]=> + string(3) "php" + ["id"]=> + string(%d) "%s" + ["timestamp"]=> + int(%d) + ["captures"]=> + array(2) { + ["entry"]=> + array(1) { + ["arguments"]=> + array(3) { + ["arg1"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "10" + } + ["arg2"]=> + array(2) { + ["type"]=> + string(6) "string" + ["value"]=> + string(2) "20" + } + ["this"]=> + array(2) { + ["type"]=> + string(3) "Bar" + ["fields"]=> + array(1) { + ["prop"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(2) { + [0]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + [1]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "2" + } + } + } + } + } + } + } + ["return"]=> + &array(2) { + ["arguments"]=> + array(4) { + ["@return"]=> + array(2) { + ["type"]=> + string(3) "Bar" + ["fields"]=> + array(1) { + ["prop"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(2) { + [0]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + [1]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "2" + } + } + } + } + } + ["arg1"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "10" + } + ["arg2"]=> + array(2) { + ["type"]=> + string(6) "string" + ["value"]=> + string(2) "20" + } + ["this"]=> + array(2) { + ["type"]=> + string(3) "Bar" + ["fields"]=> + array(1) { + ["prop"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(2) { + [0]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + [1]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "2" + } + } + } + } + } + } + ["locals"]=> + array(1) { + ["value"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(1) { + [0]=> + array(2) { + ["type"]=> + string(4) "bool" + ["value"]=> + string(4) "true" + } + } + } + } + } + } + ["probe"]=> + array(2) { + ["id"]=> + string(1) "1" + ["location"]=> + array(2) { + ["method"]=> + string(3) "foo" + ["type"]=> + string(3) "Bar" + } + } + } + } + ["message"]=> + string(21) "[true] +[true] +[true] +" +} diff --git a/tests/ext/live-debugger/debugger_metric_probe.phpt b/tests/ext/live-debugger/debugger_metric_probe.phpt new file mode 100644 index 0000000000..af8039adec --- /dev/null +++ b/tests/ext/live-debugger/debugger_metric_probe.phpt @@ -0,0 +1,48 @@ +--TEST-- +Installing a live debugger metric probe +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_DOGSTATSD_URL=unix:///tmp/ddtrace-test-metric_probe.socket +DD_DYNAMIC_INSTRUMENTATION_ENABLED=1 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +DD_VERSION=1.2.3 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/metric_probe +--FILE-- + ["methodName" => "foo"], "metricName" => "foo", "kind" => "COUNT", "value" => ["json" => ["ref" => "@return"]]]); + return \DDTrace\start_span(); // submit span data +}); + +foo(); + +$server->dump(1); +$server->close(); + +?> +--CLEAN-- + +--EXPECT-- +dynamic.instrumentation.metric.probe.foo:123|c|#service:debugger_metric_probe.php,version:1.2.3,x-datadog-test-session-token:live-debugger/metric_probe diff --git a/tests/ext/live-debugger/debugger_span_decoration_probe.phpt b/tests/ext/live-debugger/debugger_span_decoration_probe.phpt new file mode 100644 index 0000000000..9320df9cb9 --- /dev/null +++ b/tests/ext/live-debugger/debugger_span_decoration_probe.phpt @@ -0,0 +1,146 @@ +--TEST-- +Installing a live debugger span decoration probe +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_DYNAMIC_INSTRUMENTATION_ENABLED=1 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/span_decoration_probe +--FILE-- +meta; + unset($meta["runtime-id"]); + return $meta; +} + +function &root($arg) { + $meta = &\DDTrace\root_span()->meta; + unset($meta["runtime-id"]); + return $arg; +} + +await_probe_installation(function() { + build_span_decoration_probe(["where" => ["methodName" => "foo"], "inBodyLocation" => "START", "decorations" => [ + ["tags" => [["name" => "bare", "value" => ["segments" => [["str" => "raw"]]]]]], + ["tags" => [["name" => "arg", "value" => ["segments" => [["json" => ["ref" => "arg"]]]]]], "when" => ["json" => ["instanceof" => [["index" => [["ref" => "arg"], "foo"]], "object"]]]], + ]]); + build_span_decoration_probe(["where" => ["methodName" => "foo"], "decorations" => [ + ["tags" => [["name" => "error", "value" => ["segments" => [["json" => ["ref" => "arg"]]]]]], "when" => ["json" => ["eq" => [["index" => [["ref" => "arg"], "val"]], "test"]]]], + ["tags" => [["name" => "valid", "value" => ["segments" => [["json" => ["filter" => [["ref" => "arg"], ["ne" => [["ref" => "@it"], 1]]]]]]]]], "when" => ["json" => ["ne" => [["getmember" => [["index" => [["ref" => "arg"], "foo"]], "var"]], 1]]]], + ]]); + + build_span_decoration_probe(["where" => ["methodName" => "root"], "inBodyLocation" => "EXIT", "targetSpan" => "ROOT", "decorations" => [ + ["tags" => [["name" => "ret", "value" => ["segments" => [["str" => "return: "], ["json" => ["ref" => "@return"]]]]]]], + ]]); + + \DDTrace\start_span(); // submit span data +}, 3); + +\DDTrace\start_span(); +var_dump(foo(["foo" => (object)["var" => 1, "val" => "test"], "val" => 123])); +\DDTrace\active_span()->meta = []; +$meta = foo(["foo" => (object)["var" => 2]]); +ksort($meta); +var_dump($meta); + +root(1); +var_dump(\DDTrace\root_span()->meta); + +$dlr = new DebuggerLogReplayer; +$log = $dlr->waitForDebuggerDataAndReplay(); +var_dump($log["uri"]); +var_dump(json_decode($log["body"], true)); + +?> +--CLEAN-- + +--EXPECTF-- +array(4) { + ["bare"]=> + string(3) "raw" + ["_dd.di.bare.probe_id"]=> + string(1) "1" + ["arg"]=> + string(50) "[foo => (stdClass){val: test, var: 1}, val => 123]" + ["_dd.di.arg.probe_id"]=> + string(1) "1" +} +array(6) { + ["_dd.di.arg.probe_id"]=> + string(1) "1" + ["_dd.di.bare.probe_id"]=> + string(1) "1" + ["_dd.di.valid.probe_id"]=> + string(1) "2" + ["arg"]=> + string(27) "[foo => (stdClass){var: 2}]" + ["bare"]=> + string(3) "raw" + ["valid"]=> + string(20) "[(stdClass){var: 2}]" +} +array(2) { + ["ret"]=> + string(9) "return: 1" + ["_dd.di.ret.probe_id"]=> + string(1) "3" +} +string(%d) "/debugger/v1/input?ddtags=debugger_version:1.%s,env:none,version:,runtime_id:%s-%s-%s-%s-%s,host_name:%s" +array(1) { + [0]=> + array(5) { + ["service"]=> + string(34) "debugger_span_decoration_probe.php" + ["ddsource"]=> + string(11) "dd_debugger" + ["timestamp"]=> + int(1%d) + ["debugger"]=> + array(1) { + ["snapshot"]=> + array(5) { + ["language"]=> + string(3) "php" + ["id"]=> + string(36) "%s-%s-%s-%s-%s" + ["timestamp"]=> + int(1%d) + ["probe"]=> + array(2) { + ["id"]=> + string(1) "2" + ["location"]=> + array(1) { + ["method"]=> + string(3) "foo" + } + } + ["evaluationErrors"]=> + array(1) { + [0]=> + array(2) { + ["expr"]=> + string(20) "arg["val"] == "test"" + ["message"]=> + string(77) "Could not fetch index "val" on arg (evaluated to [foo => (stdClass){var: 2}])" + } + } + } + } + ["message"]=> + string(32) "Evaluation errors for probe id 2" + } +} diff --git a/tests/ext/live-debugger/debugger_span_probe.phpt b/tests/ext/live-debugger/debugger_span_probe.phpt new file mode 100644 index 0000000000..b38af41d65 --- /dev/null +++ b/tests/ext/live-debugger/debugger_span_probe.phpt @@ -0,0 +1,39 @@ +--TEST-- +Installing a live debugger span probe +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_DYNAMIC_INSTRUMENTATION_ENABLED=1 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/span_probe +--FILE-- +name; +} + +await_probe_installation(function() { + build_span_probe(["where" => ["methodName" => "foo"]]); + + \DDTrace\start_span(); // submit span data +}); + +var_dump(foo()); + +?> +--CLEAN-- + +--EXPECT-- +string(15) "dd.dynamic.span" diff --git a/tests/ext/live-debugger/debugger_span_probe_class.phpt b/tests/ext/live-debugger/debugger_span_probe_class.phpt new file mode 100644 index 0000000000..04623d81af --- /dev/null +++ b/tests/ext/live-debugger/debugger_span_probe_class.phpt @@ -0,0 +1,85 @@ +--TEST-- +Installing a live debugger span probe on a class +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_DYNAMIC_INSTRUMENTATION_ENABLED=1 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/span_probe_class +--FILE-- +name} {$span->resource}"; + } +} + +function delayed() { + class Delayed { + static function foo() { + $span = \DDTrace\active_span(); + return "{$span->name} {$span->resource}"; + } + } +} + +await_probe_installation(function() { + build_span_probe(["where" => ["typeName" => "Bar", "methodName" => "foo"]]); + build_span_probe(["where" => ["typeName" => "Delayed", "methodName" => "foo"]]); + + \DDTrace\start_span(); // submit span data +}, 2); + +var_dump(Bar::foo()); + +delayed(); +var_dump(Delayed::foo()); + +$dlr = new DebuggerLogReplayer; +$ordered = []; +$events = 0; +$time = time(); +do { + $log = $dlr->waitForDiagnosticsDataAndReplay(); + foreach (json_decode($log["files"]["event"]["contents"], true) as $payload) { + $diagnostic = $payload["debugger"]["diagnostics"]; + $ordered[$diagnostic["probeId"]][$payload["timestamp"]][] = $diagnostic["status"]; + ++$events; + } +} while ($events < 5 && $time > time() - 10); +ksort($ordered); +foreach ($ordered as &$value) { + ksort($value); + foreach ($value as &$v) { + uasort($v, function($a, $b) { $map = ["RECEIVED" => 0, "INSTALLED" => 1, "EMITTING" => 2]; return $map[$a] - $map[$b]; }); + } +} +var_dump($log["uri"]); +var_dump($log["files"]["event"]["name"]); +foreach ($ordered as $id => $statuses) { + print "$id: " . implode(", ", array_merge(...$statuses)) . "\n"; +} + +?> +--CLEAN-- + +--EXPECTF-- +string(23) "dd.dynamic.span Bar.foo" +string(27) "dd.dynamic.span Delayed.foo" +string(%d) "/debugger/v1/diagnostics?ddtags=debugger_version:%s,env:none,version:,runtime_id:%s,host_name:%s" +string(10) "event.json" +1: INSTALLED, EMITTING +2: RECEIVED, INSTALLED, EMITTING diff --git a/tests/ext/live-debugger/exception-replay_001.phpt b/tests/ext/live-debugger/exception-replay_001.phpt new file mode 100644 index 0000000000..e8cdd472b7 --- /dev/null +++ b/tests/ext/live-debugger/exception-replay_001.phpt @@ -0,0 +1,233 @@ +--TEST-- +Test exception replay +--SKIPIF-- + + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_EXCEPTION_REPLAY_ENABLED=1 +DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/exception-replay_001 +--FILE-- + STDIN]); +} catch (Exception $e) { + $span = \DDTrace\start_span(); + $span->exception = $e; + \DDTrace\close_span(); +} + +$dlr = new DebuggerLogReplayer; +$log = $dlr->waitForDebuggerDataAndReplay(); +$log = json_decode($log["body"], true); +foreach ($log[1]["debugger"]["snapshot"]["captures"] as &$capture) { + ksort($capture["locals"]); +} +var_dump($log); + +?> +--CLEAN-- + +--EXPECTF-- +array(2) { + [0]=> + array(5) { + ["service"]=> + string(24) "exception-replay_001.php" + ["ddsource"]=> + string(11) "dd_debugger" + ["timestamp"]=> + int(%d) + ["debugger"]=> + array(1) { + ["snapshot"]=> + array(8) { + ["language"]=> + string(3) "php" + ["id"]=> + string(36) "%s" + ["timestamp"]=> + int(%d) + ["exceptionCaptureId"]=> + string(36) "%s" + ["exceptionHash"]=> + string(%d) "%s" + ["frameIndex"]=> + int(0) + ["captures"]=> + array(1) { + ["return"]=> + array(2) { + ["arguments"]=> + array(1) { + ["foo"]=> + array(2) { + ["type"]=> + string(8) "stdClass" + ["fields"]=> + array(1) { + ["val"]=> + array(2) { + ["type"]=> + string(6) "stream" + ["value"]=> + string(1) "1" + } + } + } + } + ["locals"]=> + array(1) { + ["localVar"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(1) { + [0]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + } + } + } + } + } + ["probe"]=> + array(2) { + ["id"]=> + string(0) "" + ["location"]=> + array(1) { + ["method"]=> + string(3) "foo" + } + } + } + } + ["message"]=> + NULL + } + [1]=> + array(5) { + ["service"]=> + string(24) "exception-replay_001.php" + ["ddsource"]=> + string(11) "dd_debugger" + ["timestamp"]=> + int(%d) + ["debugger"]=> + array(1) { + ["snapshot"]=> + array(8) { + ["language"]=> + string(3) "php" + ["id"]=> + string(36) "%s" + ["timestamp"]=> + int(%d) + ["exceptionCaptureId"]=> + string(36) "%s" + ["exceptionHash"]=> + string(16) "0547bb1d4e434257" + ["frameIndex"]=> + int(1) + ["captures"]=> + array(1) { + ["return"]=> + &array(1) { + ["locals"]=> + array(8) { + ["_COOKIE"]=> + array(1) { + ["type"]=> + string(5) "array" + } + ["_FILES"]=> + array(1) { + ["type"]=> + string(5) "array" + } + ["_GET"]=> + array(1) { + ["type"]=> + string(5) "array" + } + ["_POST"]=> + array(1) { + ["type"]=> + string(5) "array" + } + ["_SERVER"]=> + array(%d) { + ["type"]=> + string(5) "array" + ["entries"]=> + array(%d) { + %A + } + ["argc"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + ["argv"]=> + array(2) { + ["type"]=> + string(5) "array" + ["elements"]=> + array(1) { + [0]=> + array(2) { + ["type"]=> + string(6) "string" + ["value"]=> + string(%d) "%sexception-replay_001.php" + } + } + } + ["globalvar"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + } + } + } + ["probe"]=> + array(2) { + ["id"]=> + string(0) "" + ["location"]=> + array(0) { + } + } + } + } + ["message"]=> + NULL + } +} \ No newline at end of file diff --git a/tests/ext/live-debugger/exception-replay_002.phpt b/tests/ext/live-debugger/exception-replay_002.phpt new file mode 100644 index 0000000000..1880a57a5b --- /dev/null +++ b/tests/ext/live-debugger/exception-replay_002.phpt @@ -0,0 +1,1032 @@ +--TEST-- +Test exception replay capture limits +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_EXCEPTION_REPLAY_ENABLED=1 +DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS=1 +--INI-- +datadog.trace.agent_test_session_token=live-debugger/exception-replay_002 +--FILE-- +obj = $obj; + (new MyClass)->foo($obj); +} catch (Exception $e) { + $span = \DDTrace\start_span(); + $span->exception = $e; + \DDTrace\close_span(); +} + +$dlr = new DebuggerLogReplayer; +$log = $dlr->waitForDebuggerDataAndReplay(); +$log = json_decode($log["body"], true); + +function recursive_ksort(&$arr) { + if (is_array($arr)) { + ksort($arr); + array_walk($arr, 'recursive_ksort'); + } +} + +recursive_ksort($log[0]["debugger"]["snapshot"]["captures"]); +var_dump($log[0]); + +?> +--CLEAN-- + +--EXPECTF-- +array(5) { + ["service"]=> + string(24) "exception-replay_002.php" + ["ddsource"]=> + string(11) "dd_debugger" + ["timestamp"]=> + int(%d) + ["debugger"]=> + array(1) { + ["snapshot"]=> + array(8) { + ["language"]=> + string(3) "php" + ["id"]=> + string(36) "%s" + ["timestamp"]=> + int(%d) + ["exceptionCaptureId"]=> + string(36) "%s" + ["exceptionHash"]=> + string(%d) "%s" + ["frameIndex"]=> + int(0) + ["captures"]=> + array(1) { + ["return"]=> + array(2) { + ["arguments"]=> + array(2) { + ["foo"]=> + array(2) { + ["fields"]=> + array(1) { + ["obj"]=> + array(2) { + ["fields"]=> + array(1) { + ["obj"]=> + array(2) { + ["fields"]=> + array(1) { + ["obj"]=> + array(2) { + ["notCapturedReason"]=> + string(5) "depth" + ["type"]=> + string(8) "stdClass" + } + } + ["type"]=> + string(8) "stdClass" + } + } + ["type"]=> + string(8) "stdClass" + } + } + ["type"]=> + string(8) "stdClass" + } + ["this"]=> + array(3) { + ["fields"]=> + array(20) { + ["field10"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "10" + } + ["field11"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "11" + } + ["field12"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "12" + } + ["field13"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "13" + } + ["field14"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "14" + } + ["field15"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "15" + } + ["field16"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "16" + } + ["field17"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "17" + } + ["field18"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "18" + } + ["field19"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "19" + } + ["field2"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "2" + } + ["field20"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "20" + } + ["field21"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "21" + } + ["field3"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "3" + } + ["field4"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "4" + } + ["field5"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "5" + } + ["field6"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "6" + } + ["field7"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "7" + } + ["field8"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "8" + } + ["field9"]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "9" + } + } + ["notCapturedReason"]=> + string(10) "fieldCount" + ["type"]=> + string(7) "MyClass" + } + } + ["locals"]=> + array(2) { + ["localArray"]=> + array(3) { + ["elements"]=> + array(100) { + [0]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "0" + } + [1]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "1" + } + [2]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "2" + } + [3]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "3" + } + [4]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "4" + } + [5]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "5" + } + [6]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "6" + } + [7]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "7" + } + [8]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "8" + } + [9]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(1) "9" + } + [10]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "10" + } + [11]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "11" + } + [12]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "12" + } + [13]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "13" + } + [14]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "14" + } + [15]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "15" + } + [16]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "16" + } + [17]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "17" + } + [18]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "18" + } + [19]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "19" + } + [20]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "20" + } + [21]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "21" + } + [22]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "22" + } + [23]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "23" + } + [24]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "24" + } + [25]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "25" + } + [26]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "26" + } + [27]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "27" + } + [28]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "28" + } + [29]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "29" + } + [30]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "30" + } + [31]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "31" + } + [32]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "32" + } + [33]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "33" + } + [34]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "34" + } + [35]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "35" + } + [36]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "36" + } + [37]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "37" + } + [38]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "38" + } + [39]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "39" + } + [40]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "40" + } + [41]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "41" + } + [42]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "42" + } + [43]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "43" + } + [44]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "44" + } + [45]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "45" + } + [46]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "46" + } + [47]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "47" + } + [48]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "48" + } + [49]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "49" + } + [50]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "50" + } + [51]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "51" + } + [52]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "52" + } + [53]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "53" + } + [54]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "54" + } + [55]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "55" + } + [56]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "56" + } + [57]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "57" + } + [58]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "58" + } + [59]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "59" + } + [60]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "60" + } + [61]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "61" + } + [62]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "62" + } + [63]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "63" + } + [64]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "64" + } + [65]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "65" + } + [66]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "66" + } + [67]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "67" + } + [68]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "68" + } + [69]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "69" + } + [70]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "70" + } + [71]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "71" + } + [72]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "72" + } + [73]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "73" + } + [74]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "74" + } + [75]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "75" + } + [76]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "76" + } + [77]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "77" + } + [78]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "78" + } + [79]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "79" + } + [80]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "80" + } + [81]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "81" + } + [82]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "82" + } + [83]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "83" + } + [84]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "84" + } + [85]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "85" + } + [86]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "86" + } + [87]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "87" + } + [88]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "88" + } + [89]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "89" + } + [90]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "90" + } + [91]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "91" + } + [92]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "92" + } + [93]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "93" + } + [94]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "94" + } + [95]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "95" + } + [96]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "96" + } + [97]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "97" + } + [98]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "98" + } + [99]=> + array(2) { + ["type"]=> + string(3) "int" + ["value"]=> + string(2) "99" + } + } + ["notCapturedReason"]=> + string(14) "collectionSize" + ["type"]=> + string(5) "array" + } + ["localStr"]=> + array(4) { + ["size"]=> + string(3) "293" + ["truncated"]=> + bool(true) + ["type"]=> + string(6) "string" + ["value"]=> + string(255) "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 8" + } + } + } + } + ["probe"]=> + array(2) { + ["id"]=> + string(0) "" + ["location"]=> + array(2) { + ["method"]=> + string(3) "foo" + ["type"]=> + string(7) "MyClass" + } + } + } + } + ["message"]=> + NULL +} diff --git a/tests/ext/live-debugger/live_debugger.inc b/tests/ext/live-debugger/live_debugger.inc new file mode 100644 index 0000000000..5478f16108 --- /dev/null +++ b/tests/ext/live-debugger/live_debugger.inc @@ -0,0 +1,143 @@ + (string)++$probeId, + "language" => "php", + "evaluateAt" => "EXIT", + ] + $data; + return put_live_debugger_file($json); +} + +function put_live_debugger_file($json) { + $data = json_encode($json); + $path = "datadog/2/LIVE_DEBUGGING/" . sha1($data) . "/config"; + put_rc_file($path, $data); + return $path; +} + +class DebuggerLogReplayer { + private $rr; + private $outstandingLogs = []; + private $outstandingDiagnostics = []; + + public function __construct() { + require_once __DIR__ . '/../includes/request_replayer.inc'; + $this->rr = new RequestReplayer; + } + + public function waitForDebuggerDataAndReplay() + { + $i = 0; + do { + if ($i++ == $this->rr->maxIteration) { + throw new Exception("wait for replay timeout"); + } + usleep($this->rr->flushInterval); + } while (empty($data = $this->replayDebuggerData())); + return $data; + } + + public function replayDebuggerData() + { + if ($this->outstandingLogs) { + return array_shift($this->outstandingLogs); + } + + // Request replayer now returns as many requests as were sent during a session. + // For the scope of the tests, we are returning the very first one. + $allLogs = json_decode(file_get_contents($this->rr->endpoint . '/replay', false, stream_context_create([ + "http" => [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])), true); + if ($allLogs) { + $allLogs = array_values(array_filter($allLogs, function ($v) { return strpos($v["uri"], '/debugger/v1/input') === 0; })); + } + if ($allLogs) { + $this->outstandingLogs = $allLogs; + return array_shift($this->outstandingLogs); + } + return []; + } + + public function waitForDiagnosticsDataAndReplay() + { + $i = 0; + do { + if ($i++ == $this->rr->maxIteration) { + throw new Exception("wait for replay timeout"); + } + usleep($this->rr->flushInterval); + } while (empty($data = $this->replayDiagnosticsData())); + return $data; + } + + public function replayDiagnosticsData() + { + if ($this->outstandingDiagnostics) { + return array_shift($this->outstandingDiagnostics); + } + + // Request replayer now returns as many requests as were sent during a session. + // For the scope of the tests, we are returning the very first one. + $allDiagnostics = json_decode(file_get_contents($this->rr->endpoint . '/replay', false, stream_context_create([ + "http" => [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])), true); + if ($allDiagnostics) { + $allDiagnostics = array_values(array_filter($allDiagnostics, function ($v) { return strpos($v["uri"], '/debugger/v1/diagnostics') === 0; })); + } + if ($allDiagnostics) { + $this->outstandingDiagnostics = $allDiagnostics; + return array_shift($this->outstandingDiagnostics); + } + return []; + } +} + +function await_probe_installation($trigger, $num = 1) { + ini_set("datadog.trace.hook_limit", "0"); + + $last_id = \DDtrace\install_hook("_dummy", function () {}); + $ret = $trigger(); + + for ($i = 0; $i < 6000; ++$i) { + usleep(1000); + + $id = \DDtrace\install_hook("_dummy", function () {}); + if ($last_id + 1 != $id) { + $num -= $id - $last_id - 1; + if ($num <= 0) { + return $ret; + } + } + $last_id = $id; + } + + return $ret; +} \ No newline at end of file diff --git a/tests/ext/pcntl/pcntl_fork_long_running_autoflush.phpt b/tests/ext/pcntl/pcntl_fork_long_running_autoflush.phpt index d064760b10..f37763d85a 100644 --- a/tests/ext/pcntl/pcntl_fork_long_running_autoflush.phpt +++ b/tests/ext/pcntl/pcntl_fork_long_running_autoflush.phpt @@ -4,8 +4,9 @@ Long running autoflush --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=false -DD_TRACE_AUTO_FLUSH_ENABLED=true DD_TRACE_LOG_LEVEL=info,startup=off +--INI-- +datadog.trace.sources_path= --FILE-- --ENV-- -DD_TRACE_CLI_ENABLED=true DD_TRACE_GENERATE_ROOT_SPAN=false DD_TRACE_AUTO_FLUSH_ENABLED=false --FILE-- diff --git a/tests/ext/profiling/runtime_id_01.phpt b/tests/ext/profiling/runtime_id_01.phpt index 534d71dfa4..d883c870f7 100644 --- a/tests/ext/profiling/runtime_id_01.phpt +++ b/tests/ext/profiling/runtime_id_01.phpt @@ -2,7 +2,6 @@ runtime-id exists in meta when profiling is enabled --ENV-- DD_PROFILING_ENABLED=true -DD_TRACE_CLI_ENABLED=true --SKIPIF-- +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=remote-config/dynamic_config_auto_update +--FILE-- + true, +]); + +usleep(500000); + +var_dump(ini_get("datadog.logs_injection")); + +del_rc_file($path); + +usleep(500000); + +var_dump(ini_get("datadog.logs_injection")); + +?> +--CLEAN-- + +--EXPECT-- +string(1) "1" +string(5) "false" diff --git a/tests/ext/remote_config/dynamic_config_update.phpt b/tests/ext/remote_config/dynamic_config_update.phpt new file mode 100644 index 0000000000..2f29a0a026 --- /dev/null +++ b/tests/ext/remote_config/dynamic_config_update.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test dynamic config update +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS=0.01 +--INI-- +datadog.trace.agent_test_session_token=remote-config/dynamic_config_update +--FILE-- + 0.5, + "tracing_header_tags" => [["header" => "foo", "tag_name" => "bar"], ["header" => "other", "tag_name" => "baz"]], + "log_injection_enabled" => true, + "tracing_tags" => ["foo:bar", "baz:qux"], + "tracing_enabled" => true, + "tracing_sampling_rules" => [ + [ + "service" => "foo", + "resource" => "bar", + "provenance" => "customer", + "sample_rate" => 0, + ], + [ + "service" => "f?o", + "resource" => "b*r", + "name" => "*", + "provenance" => "dynamic", + "sample_rate" => 0, + "tags" => [ + ["key" => "vuz", "value_glob" => "v?"], + ], + ], + ], +]); + +// submit span data +\DDTrace\start_span(); +usleep(500000); + +var_dump(ini_get("datadog.trace.sample_rate")); +$tags = explode(",", ini_get("datadog.trace.header_tags")); +sort($tags); // rust has no stable sorting in hashmaps, but that's fine +var_dump(implode(",", $tags)); +var_dump(ini_get("datadog.logs_injection")); +var_dump(ini_get("datadog.tags")); +var_dump(ini_get("datadog.trace.enabled")); +var_dump(ini_get("datadog.trace.sampling_rules")); + +?> +--CLEAN-- + +--EXPECT-- +string(3) "0.5" +string(9) "foo,other" +string(1) "1" +string(15) "foo:bar,baz:qux" +string(1) "1" +string(187) "[{"service":"foo","resource":"bar","_provenance":"customer","sample_rate":0.0},{"name":"*","service":"f?o","resource":"b*r","tags":{"vuz":"v?"},"_provenance":"dynamic","sample_rate":0.0}]" diff --git a/tests/ext/remote_config/remote_config.inc b/tests/ext/remote_config/remote_config.inc new file mode 100644 index 0000000000..4aa1a326df --- /dev/null +++ b/tests/ext/remote_config/remote_config.inc @@ -0,0 +1,58 @@ + [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])); +} + +function put_rc_file($path, $contents, $service = null) { + $ctx = stream_context_create([ + 'http' => [ + 'method' => 'PUT', + "header" => [ + "Content-Type: application/json", + "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + 'content' => $contents + ] + ]); + file_get_contents(rc_base_url() . "/add-rc-config-file?path=" . rawurlencode($path) . "&service=" . rawurlencode($service ?? default_rc_service()), false, $ctx); +} + +function del_rc_file($path) { + file_get_contents(rc_base_url() . "/del-rc-config-file?path=" . rawurlencode($path), false, stream_context_create([ + "http" => [ + "header" => "X-Datadog-Test-Session-Token: " . ini_get("datadog.trace.agent_test_session_token"), + ], + ])); +} + +function default_rc_service() { + return (\DDTrace\root_span() ? \DDTrace\root_span()->service : ini_get("datadog.service")) ?: basename($_SERVER["argv"][0]); +} + +function put_dynamic_config_file($configs, $service = null, $env = null) { + $json = [ + "action" => "enable", + "service_target" => [ + "service" => $service ?? default_rc_service(), + "env" => $env ?? (ini_get("datadog.env") ?: "none"), + ], + "lib_config" => $configs, + ]; + $data = json_encode($json); + $path = "datadog/2/APM_TRACING/" . sha1($data) . "/config"; + put_rc_file($path, $data, $service); + return $path; +} diff --git a/tests/ext/request-replayer/CONFLICTS b/tests/ext/request-replayer/CONFLICTS deleted file mode 100644 index baa6044435..0000000000 --- a/tests/ext/request-replayer/CONFLICTS +++ /dev/null @@ -1 +0,0 @@ -all \ No newline at end of file diff --git a/tests/ext/request-replayer/dd_trace_exception_span_event.phpt b/tests/ext/request-replayer/dd_trace_exception_span_event.phpt new file mode 100644 index 0000000000..0f92f47a60 --- /dev/null +++ b/tests/ext/request-replayer/dd_trace_exception_span_event.phpt @@ -0,0 +1,56 @@ +--TEST-- +DDTrace\ExceptionSpanEvent serialization with overridden attributes +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +--INI-- +datadog.trace.agent_test_session_token=dd_trace_exception_span_event +--FILE-- +name = 'ExceptionClass.exceptionMethod'; + $exception = new \Exception("initial exception"); + $spanEvent = new ExceptionSpanEvent($exception, [ + "exception.message" => "override message", + "custom.attribute" => "custom value" + ]); + $span->events[] = $spanEvent; +}); + +$rr = new RequestReplayer(); +$rr->replayRequest(); // cleanup possible leftover + +try { + $exceptionClass = new ExceptionClass(); + $exceptionClass->exceptionMethod(); +} catch (\Exception $e) { + echo 'Caught exception: ' . $e->getMessage() . PHP_EOL; +} + +$replay = $rr->waitForDataAndReplay(); +$root = json_decode($replay["body"], true); +$spans = $root["chunks"][0]["spans"] ?? $root[0]; +$span = $spans[0]; + +var_dump($span['meta']['events']); +?> +--EXPECTF-- +Caught exception: Exception in method +string(%d) "[{"name":"exception","time_unix_nano":%d,"attributes":{"exception.message":"override message","exception.type":"Exception","exception.stacktrace":"#0 %s(%d): ExceptionClass->{closure}()\n#1 %s(%d): ExceptionClass->exceptionMethod()\n#2 {main}","custom.attribute":"custom value"}}]" diff --git a/tests/ext/request-replayer/dd_trace_span_event.phpt b/tests/ext/request-replayer/dd_trace_span_event.phpt new file mode 100644 index 0000000000..9516074caf --- /dev/null +++ b/tests/ext/request-replayer/dd_trace_span_event.phpt @@ -0,0 +1,48 @@ +--TEST-- +DDTrace\SpanEvent serialization with attributes +--SKIPIF-- + +--ENV-- +DD_AGENT_HOST=request-replayer +DD_TRACE_AGENT_PORT=80 +DD_TRACE_AGENT_FLUSH_INTERVAL=333 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +--INI-- +datadog.trace.agent_test_session_token=dd_trace_span_event +--FILE-- +name = 'TestClass.testMethod'; + $spanEvent = new SpanEvent("event-name", [ + 'arg1' => 'value1', + 'int_array' => [3, 4], + 'string_array' => ["5", "6"] + ], 1720037568765201300); + $span->events[] = $spanEvent; +}); + +$rr = new RequestReplayer(); +$test = new TestClass(); +$test->testMethod(); +$replay = $rr->waitForDataAndReplay(); +$root = json_decode($replay["body"], true); +$spans = $root["chunks"][0]["spans"] ?? $root[0]; +$span = $spans[0]; +var_dump($span['meta']['events']); +?> +--EXPECT-- +In testMethod +string(134) "[{"name":"event-name","time_unix_nano":1720037568765201300,"attributes":{"arg1":"value1","int_array":[3,4],"string_array":["5","6"]}}]" diff --git a/tests/ext/request-replayer/dd_trace_span_link_with_exception.phpt b/tests/ext/request-replayer/dd_trace_span_link_with_exception.phpt index abb893585c..6dbbca8840 100644 --- a/tests/ext/request-replayer/dd_trace_span_link_with_exception.phpt +++ b/tests/ext/request-replayer/dd_trace_span_link_with_exception.phpt @@ -6,9 +6,10 @@ Span Link serialization with non-null EG(exception) doesn't fail DD_AGENT_HOST=request-replayer DD_TRACE_AGENT_PORT=80 DD_TRACE_AGENT_FLUSH_INTERVAL=333 -DD_TRACE_AUTO_FLUSH_ENABLED=1 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_INSTRUMENTATION_TELEMETRY_ENABLED=0 +--INI-- +datadog.trace.agent_test_session_token=dd_trace_span_link_with_exception --FILE-- - array(6) { + array(8) { ["process_id"]=> float(%f) ["foo"]=> @@ -134,6 +135,10 @@ array(3) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> @@ -190,7 +195,7 @@ array(3) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -199,6 +204,10 @@ array(3) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox-prehook/exception_error_log.phpt b/tests/ext/sandbox-prehook/exception_error_log.phpt index 578accfcac..d43eee2c31 100644 --- a/tests/ext/sandbox-prehook/exception_error_log.phpt +++ b/tests/ext/sandbox-prehook/exception_error_log.phpt @@ -1,6 +1,7 @@ --TEST-- [Prehook Regression] Exception in tracing closure gets logged --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum --FILE-- diff --git a/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt b/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt index 61a3447334..75d2109bcb 100644 --- a/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt +++ b/tests/ext/sandbox-prehook/exceptions_and_errors_are_ignored_in_tracing_closure.phpt @@ -3,6 +3,7 @@ --ENV-- DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=mt_rand,mt_srand +DD_APPSEC_ENABLED=0 --INI-- error_reporting=E_ALL --FILE-- diff --git a/tests/ext/sandbox-prehook/keep_spans_in_limited_tracing_userland_functions.phpt b/tests/ext/sandbox-prehook/keep_spans_in_limited_tracing_userland_functions.phpt index 3618f50fa9..b1c568ee80 100644 --- a/tests/ext/sandbox-prehook/keep_spans_in_limited_tracing_userland_functions.phpt +++ b/tests/ext/sandbox-prehook/keep_spans_in_limited_tracing_userland_functions.phpt @@ -1,6 +1,7 @@ --TEST-- [Prehook Regression] Keep spans in limited mode (userland functions) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_SPANS_LIMIT=5 --FILE-- diff --git a/tests/ext/sandbox-regression/class_resolver_bailout_hook.phpt b/tests/ext/sandbox-regression/class_resolver_bailout_hook.phpt index cfaf9e457e..c7e2dfbdad 100644 --- a/tests/ext/sandbox-regression/class_resolver_bailout_hook.phpt +++ b/tests/ext/sandbox-regression/class_resolver_bailout_hook.phpt @@ -3,6 +3,7 @@ Assert bailouts are gracefully handled within class autoloading --SKIPIF-- = 70300 && PHP_VERSION_ID < 70400) die('skip: Bailing out in autoloaders is fundamentally broken in PHP 7.3'); ?> --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off --FILE-- - array(6) { + array(8) { ["process_id"]=> float(%f) ["foo"]=> @@ -141,6 +142,10 @@ array(5) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> @@ -223,7 +228,7 @@ array(5) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -232,6 +237,10 @@ array(5) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [4]=> @@ -262,7 +271,7 @@ array(5) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -271,6 +280,10 @@ array(5) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox/dd_trace_function_internal.phpt b/tests/ext/sandbox/dd_trace_function_internal.phpt index fc1acb3d62..83eedeba4c 100644 --- a/tests/ext/sandbox/dd_trace_function_internal.phpt +++ b/tests/ext/sandbox/dd_trace_function_internal.phpt @@ -1,6 +1,7 @@ --TEST-- DDTrace\trace_function() can trace internal functions with internal spans --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum --FILE-- @@ -51,7 +52,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -60,6 +61,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox/dd_trace_method.phpt b/tests/ext/sandbox/dd_trace_method.phpt index 5274ed6e67..2c56850083 100644 --- a/tests/ext/sandbox/dd_trace_method.phpt +++ b/tests/ext/sandbox/dd_trace_method.phpt @@ -1,6 +1,7 @@ --TEST-- DDTrace\trace_method() can trace with internal spans --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_TRACED_INTERNAL_FUNCTIONS=mt_rand --FILE-- @@ -136,7 +137,7 @@ array(3) { string(16) "%s" } ["metrics"]=> - array(6) { + array(8) { ["process_id"]=> float(%f) ["foo"]=> @@ -149,6 +150,10 @@ array(3) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> @@ -209,7 +214,7 @@ array(3) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -218,6 +223,10 @@ array(3) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox/dd_trace_method_alias.phpt b/tests/ext/sandbox/dd_trace_method_alias.phpt index cfa8f1a211..8dec540b51 100644 --- a/tests/ext/sandbox/dd_trace_method_alias.phpt +++ b/tests/ext/sandbox/dd_trace_method_alias.phpt @@ -1,6 +1,7 @@ --TEST-- dd_trace_method() is aliased to DDTrace\trace_method() --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- --INI-- datadog.trace.debug=1 +--ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 --FILE-- --EXPECTF-- [ddtrace] [warning] UnwindExit thrown in ddtrace's closure defined at %s:%d for x(): -[ddtrace] [span] Encoding span %d: trace_id=%s, name='die_in_sandbox.php', service='die_in_sandbox.php', resource: 'die_in_sandbox.php', type 'cli' with tags: runtime-id='%s', _dd.p.dm='-0', _dd.p.tid='%s'; and metrics: process_id='%d', _dd.agent_psr='1', _sampling_priority_v1='1', php.compilation.total_time_ms='%f' +[ddtrace] [span] Encoding span %d: trace_id=%s, name='die_in_sandbox.php', service='die_in_sandbox.php', resource: 'die_in_sandbox.php', type 'cli' with tags: runtime-id='%s', _dd.p.dm='-0', _dd.p.tid='%s'; and metrics: process_id='%d', _dd.agent_psr='1', _sampling_priority_v1='1', php.compilation.total_time_ms='%f', php.memory.peak_usage_bytes='%f', php.memory.peak_real_usage_bytes='%f' [ddtrace] [span] Encoding span %d: trace_id=%s, name='x', service='die_in_sandbox.php', resource: 'x', type 'cli' with tags: -; and metrics: - [ddtrace] [info] Flushing trace of size 2 to send-queue for %s diff --git a/tests/ext/sandbox/errors_are_flagged_from_userland.phpt b/tests/ext/sandbox/errors_are_flagged_from_userland.phpt index 2214480f58..1cd99e7172 100644 --- a/tests/ext/sandbox/errors_are_flagged_from_userland.phpt +++ b/tests/ext/sandbox/errors_are_flagged_from_userland.phpt @@ -1,6 +1,7 @@ --TEST-- Errors from userland will be flagged on span --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -64,6 +65,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox/exception_error_log.phpt b/tests/ext/sandbox/exception_error_log.phpt index df00bf8e1c..b2131bb1ca 100644 --- a/tests/ext/sandbox/exception_error_log.phpt +++ b/tests/ext/sandbox/exception_error_log.phpt @@ -1,6 +1,7 @@ --TEST-- Exception in tracing closure gets logged --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum --FILE-- diff --git a/tests/ext/sandbox/exception_is_defined.phpt b/tests/ext/sandbox/exception_is_defined.phpt index 8909a92771..364354d7f0 100644 --- a/tests/ext/sandbox/exception_is_defined.phpt +++ b/tests/ext/sandbox/exception_is_defined.phpt @@ -2,6 +2,7 @@ Exceptions in the tracing closure callback are always defined --ENV-- DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum +DD_APPSEC_ENABLED=0 --FILE-- + +--INI-- +opcache.enable=1 +opcache.enable_cli = 1 +opcache.jit_buffer_size=512M +opcache.jit=1255 +zend_extension=opcache.so +--FILE-- + function($args, $retval) { + var_dump($retval); + } +]); + +for ($i = 0; $i < 2; ++$i) { + foreach (gen() as $val) { + var_dump($val); + } +} + +?> +--EXPECT-- +int(1) +int(1) +int(2) +int(2) +int(1) +int(1) +int(2) +int(2) diff --git a/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_functions.phpt b/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_functions.phpt index 0eb7bb0e79..a5afa15814 100644 --- a/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_functions.phpt +++ b/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_functions.phpt @@ -1,6 +1,7 @@ --TEST-- Keep spans in limited mode (internal functions) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SPANS_LIMIT=5 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_TRACED_INTERNAL_FUNCTIONS=array_sum,mt_rand diff --git a/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_methods.phpt b/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_methods.phpt index 28049abed8..2e3bcadaab 100644 --- a/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_methods.phpt +++ b/tests/ext/sandbox/keep_spans_in_limited_tracing_internal_methods.phpt @@ -1,6 +1,7 @@ --TEST-- Keep spans in limited mode (internal methods) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SPANS_LIMIT=5 DD_TRACE_GENERATE_ROOT_SPAN=0 DD_TRACE_TRACED_INTERNAL_FUNCTIONS=DateTime::format,DateTime::setTime diff --git a/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_functions.phpt b/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_functions.phpt index de84897889..57d1ffabe0 100644 --- a/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_functions.phpt +++ b/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_functions.phpt @@ -1,6 +1,7 @@ --TEST-- Keep spans in limited mode (userland functions) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SPANS_LIMIT=5 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- diff --git a/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_methods.phpt b/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_methods.phpt index 59998e4730..127af1f30a 100644 --- a/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_methods.phpt +++ b/tests/ext/sandbox/keep_spans_in_limited_tracing_userland_methods.phpt @@ -1,6 +1,7 @@ --TEST-- Keep spans in limited mode (userland methods) --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SPANS_LIMIT=5 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- diff --git a/tests/ext/sandbox/manual_flush.phpt b/tests/ext/sandbox/manual_flush.phpt index 1842f6a6d3..4ac704754c 100644 --- a/tests/ext/sandbox/manual_flush.phpt +++ b/tests/ext/sandbox/manual_flush.phpt @@ -1,5 +1,5 @@ --TEST-- -Spans are automatically flushed when auto-flushing enabled +Spans are not automatically flushed when auto-flushing disabled --ENV-- DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off diff --git a/tests/ext/sandbox/new_static.phpt b/tests/ext/sandbox/new_static.phpt index d4b3836f32..7b6e916c82 100644 --- a/tests/ext/sandbox/new_static.phpt +++ b/tests/ext/sandbox/new_static.phpt @@ -1,5 +1,7 @@ --TEST-- New static instantiates from expected class +--ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 --FILE-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- --EXPECTF-- -object(DDTrace\RootSpanData)#%d (20) { +object(DDTrace\RootSpanData)#%d (21) { ["name"]=> string(3) "foo" ["resource"]=> @@ -57,6 +58,9 @@ object(DDTrace\RootSpanData)#%d (20) { ["links"]=> array(0) { } + ["events"]=> + array(0) { + } ["peerServiceSources"]=> array(0) { } @@ -87,7 +91,7 @@ object(DDTrace\RootSpanData)#%d (20) { ["gitMetadata"]=> NULL } -object(DDTrace\RootSpanData)#%d (20) { +object(DDTrace\RootSpanData)#%d (21) { ["name"]=> string(5) "dummy" ["resource"]=> @@ -120,6 +124,9 @@ object(DDTrace\RootSpanData)#%d (20) { ["links"]=> array(0) { } + ["events"]=> + array(0) { + } ["peerServiceSources"]=> array(0) { } @@ -135,7 +142,7 @@ object(DDTrace\RootSpanData)#%d (20) { NULL } ["active"]=> - object(DDTrace\RootSpanData)#%d (20) { + object(DDTrace\RootSpanData)#%d (21) { ["name"]=> string(3) "foo" ["resource"]=> @@ -168,6 +175,9 @@ object(DDTrace\RootSpanData)#%d (20) { ["links"]=> array(0) { } + ["events"]=> + array(0) { + } ["peerServiceSources"]=> array(0) { } @@ -231,7 +241,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -240,6 +250,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/sandbox/static_tracing_closures_will_not_bind_this.phpt b/tests/ext/sandbox/static_tracing_closures_will_not_bind_this.phpt index 29b6b4db9f..56a02c556f 100644 --- a/tests/ext/sandbox/static_tracing_closures_will_not_bind_this.phpt +++ b/tests/ext/sandbox/static_tracing_closures_will_not_bind_this.phpt @@ -1,6 +1,7 @@ --TEST-- Static tracing closures will not bind $this --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off --FILE-- +--ENV-- +DD_LOG_BACKTRACE=0 --FILE-- --XFAIL-- Code called in the segv handler is not signal safe, this will sometimes result in a segfault. ---ENV-- -DD_LOG_BACKTRACE=1 --FILE-- diff --git a/tests/ext/sidecar_disabled_when_telemetry_disabled.phpt b/tests/ext/sidecar_disabled_when_telemetry_disabled.phpt index b2cbd26a70..0f311a6408 100644 --- a/tests/ext/sidecar_disabled_when_telemetry_disabled.phpt +++ b/tests/ext/sidecar_disabled_when_telemetry_disabled.phpt @@ -6,7 +6,7 @@ Sidecar should be disabled when telemetry is disabled '1', diff --git a/tests/ext/single-span_sampling/check-sample-rate.phpt b/tests/ext/single-span_sampling/check-sample-rate.phpt index d61d87cc25..130e317fd2 100644 --- a/tests/ext/single-span_sampling/check-sample-rate.phpt +++ b/tests/ext/single-span_sampling/check-sample-rate.phpt @@ -3,6 +3,7 @@ Check sample rate is in effect --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SAMPLE_RATE=0 DD_SPAN_SAMPLING_RULES=[{"sample_rate":0.5,"max_per_second":10}] DD_TRACE_DEBUG_PRNG_SEED=30 diff --git a/tests/ext/single-span_sampling/limited-single-span-with-match.phpt b/tests/ext/single-span_sampling/limited-single-span-with-match.phpt index 8c95487d70..20df9b9f21 100644 --- a/tests/ext/single-span_sampling/limited-single-span-with-match.phpt +++ b/tests/ext/single-span_sampling/limited-single-span-with-match.phpt @@ -3,6 +3,7 @@ Test max_per_second single span limiting with multiple buckets --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SAMPLE_RATE=0 DD_SPAN_SAMPLING_RULES=[{"sample_rate":1,"max_per_second":2,"service":"a","name":"b"},{"sample_rate":1,"max_per_second":2,"service":"a"},{"sample_rate":1,"max_per_second":2,"name":"b"}] DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/single-span_sampling/limited-single-span.phpt b/tests/ext/single-span_sampling/limited-single-span.phpt index 6bf02b93c8..8ce5d712dc 100644 --- a/tests/ext/single-span_sampling/limited-single-span.phpt +++ b/tests/ext/single-span_sampling/limited-single-span.phpt @@ -3,6 +3,7 @@ Test max_per_second single span limiting --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SAMPLE_RATE=0 DD_SPAN_SAMPLING_RULES=[{"sample_rate":1,"max_per_second":10}] DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/single-span_sampling/name-matching-single-span.phpt b/tests/ext/single-span_sampling/name-matching-single-span.phpt index 3053b8305f..713dd552a2 100644 --- a/tests/ext/single-span_sampling/name-matching-single-span.phpt +++ b/tests/ext/single-span_sampling/name-matching-single-span.phpt @@ -1,6 +1,7 @@ --TEST-- Ingest all spans --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SAMPLE_RATE=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- diff --git a/tests/ext/single-span_sampling/read-single-span-sampling-config-from-file.phpt b/tests/ext/single-span_sampling/read-single-span-sampling-config-from-file.phpt index 3d844e136b..235bb2cdf8 100644 --- a/tests/ext/single-span_sampling/read-single-span-sampling-config-from-file.phpt +++ b/tests/ext/single-span_sampling/read-single-span-sampling-config-from-file.phpt @@ -1,6 +1,7 @@ --TEST-- Check sample rate is in effect --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_SAMPLE_RATE=0 DD_SPAN_SAMPLING_RULES=[{"sample_rate":1}] DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/span_stack/span_stack_clone.phpt b/tests/ext/span_stack/span_stack_clone.phpt index 176d7e6229..bcd1bf6481 100644 --- a/tests/ext/span_stack/span_stack_clone.phpt +++ b/tests/ext/span_stack/span_stack_clone.phpt @@ -1,6 +1,7 @@ --TEST-- Test cloning a span stack --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- diff --git a/tests/ext/span_stack/span_trace_swap.phpt b/tests/ext/span_stack/span_trace_swap.phpt index a6bdd2c6a9..fd58c9fd42 100644 --- a/tests/ext/span_stack/span_trace_swap.phpt +++ b/tests/ext/span_stack/span_trace_swap.phpt @@ -1,6 +1,7 @@ --TEST-- Test creating swapping traces --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- =')) @@ -77,7 +78,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -86,6 +87,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } @@ -118,7 +123,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -127,6 +132,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } @@ -159,7 +168,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -168,6 +177,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/start_span_with_all_properties.phpt b/tests/ext/start_span_with_all_properties.phpt index 6bf0c725ff..46254bd2bd 100644 --- a/tests/ext/start_span_with_all_properties.phpt +++ b/tests/ext/start_span_with_all_properties.phpt @@ -1,6 +1,7 @@ --TEST-- Set DDTrace\start_span() properties --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -91,6 +92,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } [1]=> @@ -123,7 +128,7 @@ array(2) { string(16) "%s" } ["metrics"]=> - array(5) { + array(7) { ["process_id"]=> float(%f) ["cc"]=> @@ -134,6 +139,10 @@ array(2) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/start_span_without_closing.phpt b/tests/ext/start_span_without_closing.phpt index a3af7e2bab..b1862207c3 100644 --- a/tests/ext/start_span_without_closing.phpt +++ b/tests/ext/start_span_without_closing.phpt @@ -1,6 +1,7 @@ --TEST-- Use DDTrace\close_span() on span started within internal span --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_LOG_LEVEL=info,startup=off DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- @@ -54,7 +55,7 @@ array(1) { string(16) "%s" } ["metrics"]=> - array(4) { + array(6) { ["process_id"]=> float(%f) ["_dd.agent_psr"]=> @@ -63,6 +64,10 @@ array(1) { float(1) ["php.compilation.total_time_ms"]=> float(%f) + ["php.memory.peak_usage_bytes"]=> + float(%f) + ["php.memory.peak_real_usage_bytes"]=> + float(%f) } } } diff --git a/tests/ext/startup_logging_json.phpt b/tests/ext/startup_logging_json.phpt index 524bbb3912..ad5dd0c516 100644 --- a/tests/ext/startup_logging_json.phpt +++ b/tests/ext/startup_logging_json.phpt @@ -2,6 +2,8 @@ Startup logging from JSON fetched at runtime --INI-- datadog.trace.sources_path= +--ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 --FILE-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/telemetry/config.phpt b/tests/ext/telemetry/config.phpt index 846b776520..06465c8cb8 100644 --- a/tests/ext/telemetry/config.phpt +++ b/tests/ext/telemetry/config.phpt @@ -3,7 +3,9 @@ Report user config telemetry --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 @@ -11,6 +13,7 @@ DD_TRACE_AUTOFINISH_SPANS=1 DD_INSTRUMENTATION_TELEMETRY_ENABLED=1 DD_AGENT_HOST= DD_AUTOLOAD_NO_COMPILE= +DD_TRACE_GIT_METADATA_ENABLED=0 --INI-- datadog.trace.agent_url="file://{PWD}/config-telemetry.out" --FILE-- @@ -22,6 +25,8 @@ include __DIR__ . '/vendor/autoload.php'; DDTrace\close_span(); +dd_trace_serialize_closed_spans(); + dd_trace_internal_fn("finalize_telemetry"); for ($i = 0; $i < 100; ++$i) { @@ -30,7 +35,7 @@ for ($i = 0; $i < 100; ++$i) { foreach (file(__DIR__ . '/config-telemetry.out') as $l) { if ($l) { $json = json_decode($l, true); - if ($json["request_type"] == "app-started") { + if ($json && $json["request_type"] == "app-started" && $json["application"]["service_name"] != "background_sender-php-service" && $json["application"]["service_name"] != "datadog-ipc-helper") { $cfg = $json["payload"]["configuration"]; print_r(array_values(array_filter($cfg, function($c) { return $c["origin"] == "EnvVar" && $c["name"] != "trace.sources_path" && $c["name"] != "trace.sidecar_trace_sender"; @@ -61,27 +66,20 @@ Array ) [1] => Array - ( - [name] => trace.cli_enabled - [value] => 1 - [origin] => EnvVar - ) - - [2] => Array ( [name] => instrumentation_telemetry_enabled [value] => 1 [origin] => EnvVar ) - [3] => Array + [2] => Array ( [name] => trace.generate_root_span [value] => 0 [origin] => EnvVar ) - [4] => Array + [3] => Array ( [name] => trace.git_metadata_enabled [value] => 0 diff --git a/tests/ext/telemetry/disabled.phpt b/tests/ext/telemetry/disabled.phpt index 57b9de851b..606c412925 100644 --- a/tests/ext/telemetry/disabled.phpt +++ b/tests/ext/telemetry/disabled.phpt @@ -3,6 +3,7 @@ Disabled telemetry test --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/telemetry/integration.phpt b/tests/ext/telemetry/integration.phpt index 5b5ace0727..5c1672acd3 100644 --- a/tests/ext/telemetry/integration.phpt +++ b/tests/ext/telemetry/integration.phpt @@ -3,7 +3,9 @@ Signal integration telemetry --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/telemetry/metrics_logs_created.phpt b/tests/ext/telemetry/metrics_logs_created.phpt index 042a4df218..c276408173 100644 --- a/tests/ext/telemetry/metrics_logs_created.phpt +++ b/tests/ext/telemetry/metrics_logs_created.phpt @@ -3,7 +3,9 @@ --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/telemetry/metrics_spans_created.phpt b/tests/ext/telemetry/metrics_spans_created.phpt index d8cd82f60d..cbbe30e911 100644 --- a/tests/ext/telemetry/metrics_spans_created.phpt +++ b/tests/ext/telemetry/metrics_spans_created.phpt @@ -3,7 +3,9 @@ --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 diff --git a/tests/ext/telemetry/simple.phpt b/tests/ext/telemetry/simple.phpt index 5c05d8a8bb..fbe1662849 100644 --- a/tests/ext/telemetry/simple.phpt +++ b/tests/ext/telemetry/simple.phpt @@ -3,7 +3,9 @@ Simple telemetry test --SKIPIF-- --ENV-- DD_TRACE_GENERATE_ROOT_SPAN=0 @@ -33,11 +35,15 @@ for ($i = 0; $i < 100; ++$i) { foreach (file(__DIR__ . '/simple-telemetry.out') as $l) { if ($l) { $json = json_decode($l, true); + if ($json["application"]["service_name"] == "background_sender-php-service" || $json["application"]["service_name"] == "datadog-ipc-helper") { + continue; + } array_push($batches, ...($json["request_type"] == "message-batch" ? $json["payload"] : [$json])); } } $found = array_filter($batches, function ($json) { - return $json["request_type"] == "app-started" || $json["request_type"] == "app-closing"; + return ($json["request_type"] == "app-started" && $json["application"]["service_name"] == "simple-telemetry-app") + || $json["request_type"] == "app-closing"; }); if (count($found) == 2) { foreach ($found as $json) { diff --git a/tests/ext/traced_attribute.phpt b/tests/ext/traced_attribute.phpt index 1e1d713b27..eb196337fd 100644 --- a/tests/ext/traced_attribute.phpt +++ b/tests/ext/traced_attribute.phpt @@ -3,6 +3,7 @@ Test tracing via attributes --SKIPIF-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- --ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 DD_TRACE_GENERATE_ROOT_SPAN=0 --FILE-- + Memcheck:Param sendmsg(msg.msg_control) + ... fun:sendmsg + ... fun:*send_with_fd* -} \ No newline at end of file +} diff --git a/tests/overhead/Makefile b/tests/overhead/Makefile index f8be94974d..66b5cfad7c 100644 --- a/tests/overhead/Makefile +++ b/tests/overhead/Makefile @@ -59,12 +59,12 @@ time_l57_%: request_synthetic_%: @echo "Synthetic request: $*" - @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_CLI_ENABLED=true DD_TRACE_NO_AUTOLOADER=true php /var/www/public/synthetic.php' + @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_NO_AUTOLOADER=true php /var/www/public/synthetic.php' @echo "\n------------" request_hook_%: @echo "Request init hook: $*" - @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_CLI_ENABLED=true DD_TRACE_NO_AUTOLOADER=true php -ddatadog.trace.soucres_path="" /var/www/public/dd-trace-sources' + @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_NO_AUTOLOADER=true php -ddatadog.trace.soucres_path="" /var/www/public/dd-trace-sources' @echo "\n------------" time_hook_notracer: @@ -74,5 +74,5 @@ time_hook_notracer: time_hook_%: @echo "Benchmarking time for init hook only: $*" - @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_CLI_ENABLED=true DD_TRACE_NO_AUTOLOADER=true multitime -n ${EXEC_REPETITIONS} -s 0 php -ddatadog.trace.soucres_path="" /var/www/public/dd-trace-sources' + @docker-compose exec php-fpm-$* sh -c 'DD_TRACE_NO_AUTOLOADER=true multitime -n ${EXEC_REPETITIONS} -s 0 php -ddatadog.trace.soucres_path="" /var/www/public/dd-trace-sources' @echo "\n------------" diff --git a/tests/phpbench-opcache.json b/tests/phpbench-opcache.json index c80674e3f7..675db25b29 100644 --- a/tests/phpbench-opcache.json +++ b/tests/phpbench-opcache.json @@ -1,7 +1,7 @@ { - "$schema":"vendor/phpbench/phpbench/phpbench.schema.json", - "runner.bootstrap": "./bootstrap_utils.php", - "runner.path": "Benchmarks", + "$schema":"Benchmarks/vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "./bootstrap_phpbench.php", + "runner.path": [ "Benchmarks/API", "Benchmarks/Integrations" ], "runner.file_pattern": "*Bench.php", "runner.php_env": { "DD_TRACE_OTEL_ENABLED": "true", diff --git a/tests/phpbench.json b/tests/phpbench.json index 2fc223ab07..dc39b6d356 100644 --- a/tests/phpbench.json +++ b/tests/phpbench.json @@ -1,7 +1,7 @@ { - "$schema":"vendor/phpbench/phpbench/phpbench.schema.json", - "runner.bootstrap": "./bootstrap_utils.php", - "runner.path": "Benchmarks", + "$schema":"Benchmarks/vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "./bootstrap_phpbench.php", + "runner.path": [ "Benchmarks/API", "Benchmarks/Integrations" ], "runner.file_pattern": "*Bench.php", "runner.php_env": { "DD_TRACE_OTEL_ENABLED": "true", diff --git a/tests/phpunit.xml b/tests/phpunit.xml index af8482b1e3..4e36afbfc6 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -7,7 +7,7 @@ beStrictAboutResourceUsageDuringSmallTests="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutTodoAnnotatedTests="true" - bootstrap="./bootstrap.php" + bootstrap="./bootstrap_phpunit.php" colors="true" columns="max" verbose="true" @@ -74,6 +74,7 @@ ./Integrations/Laravel/Octane + ./OpenTelemetry/Integration/Context/Fiber ./OpenTelemetry/Unit/API ./OpenTelemetry/Unit/Context ./OpenTelemetry/Unit/Propagation diff --git a/tests/randomized/README.md b/tests/randomized/README.md index dfa4247811..cd0b5017e2 100644 --- a/tests/randomized/README.md +++ b/tests/randomized/README.md @@ -145,6 +145,16 @@ Note that the generated scenarios can be run individually. In the directory `.tm ### Analyzing a core dump generated in CI +Run a script `circleci_core.sh` which will install the used extension, and launch a gdb shell with the core dump: + +``` +tests/randomized/circleci_core.sh https://app.circleci.com/pipelines/github/DataDog/dd-trace-php/17113/workflows/196b5740-f3ce-4faf-8731-e9a4b15d114a/jobs/4964970/steps +``` + +The only argument to that script is the URL of the job. + +#### Alternative: Manual steps + Run a container with the most recent version of the proper docker image for the specific version of PHP. For example, assuming PHP 8.0: ``` @@ -172,7 +182,7 @@ Then load it in `gdb`: gdb --core=/tmp/core php-fpm|httpd|php ``` -### Analyzing using ASAN +#### Manual steps using ASAN In some cases it might be tricky to find the memory corruption, but you can always use an address sanitizer to try and find problems. diff --git a/tests/randomized/analyze-results.php b/tests/randomized/analyze-results.php index b5efa5fea9..4067da43b7 100644 --- a/tests/randomized/analyze-results.php +++ b/tests/randomized/analyze-results.php @@ -10,14 +10,23 @@ function analyze_web($tmpScenariosFolder) $unexpectedCodes = []; $possibleSegfaults = []; $minimumRequestCount = []; + $prepareErrors = []; foreach (scandir($resultsFolder) as $identifier) { if (in_array($identifier, ['.', '..'])) { continue; } + + $analyzed[] = $identifier; + $scenarioResultsRoot = $resultsFolder . DIRECTORY_SEPARATOR . $identifier; + $prepareErrorFilePath = $scenarioResultsRoot . DIRECTORY_SEPARATOR . 'prepare-error'; $absFilePath = $scenarioResultsRoot . DIRECTORY_SEPARATOR . 'results.json'; - $analyzed[] = $identifier; + + if (file_exists($prepareErrorFilePath)) { + $prepareErrors[$identifier] = (int)file_get_contents($prepareErrorFilePath); + continue; + } $jsonResult = json_decode(file_get_contents($absFilePath), 1); @@ -71,6 +80,10 @@ function analyze_web($tmpScenariosFolder) echo "Minimum request not matched: " . var_export($minimumRequestCount, 1) . "\n"; $isError = true; } + if (count($prepareErrors)) { + echo "Prepare step failed with error: " . var_export($prepareErrors, 1) . "\n"; + $isError = count($prepareErrors) > count($analyzed) / 2; // In that case it's probably not transient + } if ($isError) { return false; @@ -115,7 +128,13 @@ function analyze_cli($tmpScenariosFolder) $analyzed[] = $identifier; - $absFilePath = $resultsFolder . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR . 'memory.out'; + $scenarioResultsRoot = $resultsFolder . DIRECTORY_SEPARATOR . $identifier; + $absFilePath = $scenarioResultsRoot . DIRECTORY_SEPARATOR . 'memory.out'; + $prepareErrorFilePath = $scenarioResultsRoot . DIRECTORY_SEPARATOR . 'prepare-error'; + + if (file_exists($prepareErrorFilePath)) { + continue; // We already handled it in analyze_web + } $values = array_map('intval', array_filter( file($absFilePath), diff --git a/tests/randomized/circleci_core.sh b/tests/randomized/circleci_core.sh new file mode 100755 index 0000000000..154e2348af --- /dev/null +++ b/tests/randomized/circleci_core.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# e.g. https://app.circleci.com/pipelines/github/DataDog/dd-trace-php/17113/workflows/196b5740-f3ce-4faf-8731-e9a4b15d114a/jobs/4964970/steps +URL=${1} + +if ! [[ $URL =~ workflows/([0-9a-f-]+)/jobs/([0-9]+) ]]; then + echo "Invalid URL?" + exit 1 +fi + +CIRCLE_WORKFLOW_ID=${BASH_REMATCH[1]} +CIRCLE_JOB_ID=${BASH_REMATCH[2]} + +TEST_NAME=$(curl -s -X GET "https://circleci.com/api/v2/project/gh/DataDog/dd-trace-php/job/$CIRCLE_JOB_ID" -H "Accept: application/json" | grep -Eo '"name":"randomized[^"]+') +TEST_NAME=${TEST_NAME:8} + +echo "Found test run: $TEST_NAME" + +COREFILES=($(curl -s -X GET "https://circleci.com/api/v2/project/gh/DataDog/dd-trace-php/$CIRCLE_JOB_ID/artifacts" -H "Accept: application/json" | grep -Eo 'https://[^"]+core')) + +if [[ -z $COREFILES ]]; then + echo "Found no core files..." + exit 1 +fi + +num=1 +for file in "${COREFILES[@]}"; do + echo "$((num++)): $(echo "$file" | grep -Eo 'randomized-[^/]+')" +done + +while : ; do + echo -n "Select one core file: " + read num + if [[ $num -gt ${#COREFILES[@]} || $num -le 0 ]]; then + echo "Invalid number $num" + else + break + fi +done + +COREFILE=${COREFILES[$((num - 1))]} +corefilename=$(echo "$COREFILE" | grep -Eo 'randomized-[^/]+') +container="datadog/dd-trace-ci:php-randomizedtests-$(if [[ $COREFILE == *buster* ]]; then echo buster; else echo centos7; fi)-${corefilename: -2:1}.${corefilename: -1}-2" + +if [[ $TEST_NAME == *asan* ]]; then + ARTIFACTS_JOB='package extension zts-debug-asan' +else + ARTIFACTS_JOB='package extension' +fi + +PACKAGE_ARCH=$(if [[ $TEST_NAME == *arm* ]]; then echo aarch64; else echo x86_64; fi) + +parent_job_id=$(curl -s -X GET "https://circleci.com/api/v2/workflow/$CIRCLE_WORKFLOW_ID/job" -H "Accept: application/json" | grep -Eo '\{[^}]*"'"$ARTIFACTS_JOB"'"[^}]*' | grep -Eo '"job_number":[^,]+' | tail -c +14) +ARTIFACTS_RESULT=$(curl -s -X GET "https://circleci.com/api/v2/project/github/DataDog/dd-trace-php/$parent_job_id/artifacts" -H "Accept: application/json") +artifact_url=$(echo "$ARTIFACTS_RESULT" | grep -Eo '\{[^}]*"dd-library-php-[^"]+'"${PACKAGE_ARCH}"'-linux-gnu.tar.gz"[^}]*' | grep -Eo '"url":"[^"]+' | tail -c +8) +setup_url=$(echo "$ARTIFACTS_RESULT" | grep -Eo '\{[^}]*"datadog-setup.php"[^}]*' | grep -Eo '"url":"[^"]+' | tail -c +8) + +set -x +docker run --platform=linux/$(if [[ $TEST_NAME == *arm* ]]; then echo arm64; else echo amd64; fi) --rm -ti "$container" bash -c "curl -Lo /tmp/datadog-setup.php '$setup_url'; curl -Lo /tmp/${artifact_url##*/} '$artifact_url'; curl -Lo /tmp/core '$COREFILE'; php /tmp/datadog-setup.php --php-bin all --file /tmp/${artifact_url##*/} --enable-profiling; $(if [[ $TEST_NAME == *asan* ]]; then echo "switch-php debug-zts-asan; "; fi)exec bash --rcfile <(echo 'gdb \$(file /tmp/core | grep -Po "'from\\s\\x27\\K[^\\x27:]+'") --core=/tmp/core -ix /usr/local/src/php/.gdbinit')" diff --git a/tests/randomized/docker/buster.Dockerfile b/tests/randomized/docker/buster.Dockerfile index 1d7e5ef329..41cf886f19 100644 --- a/tests/randomized/docker/buster.Dockerfile +++ b/tests/randomized/docker/buster.Dockerfile @@ -10,6 +10,9 @@ RUN for DIR in /opt/php/*; do (echo "zend_extension=opcache.so"; echo "opcache.e # don't execute an asan *binary* under qemu RUN mv /opt/php/debug/bin/php-config /opt/php/debug/bin/php-config-debug; cp /opt/php/debug-zts-asan/bin/php-config /opt/php/debug/bin/php-config +# install redis for randomized tests +RUN echo "extension=redis" >> $(php-config --ini-dir)/redis.ini + # Igbinary RUN set -eux; \ pecl install "igbinary"; \ @@ -65,6 +68,10 @@ RUN sed -i 's/apache2/httpd/' /etc/apache2/envvars ADD run.sh /scripts/run.sh ADD prepare.sh /scripts/prepare.sh +# actually bind the sidecar error output to docker out +ENV _DD_DEBUG_SIDECAR_LOG_METHOD=file:///proc/1/fd/2 +ENV DD_SPAWN_WORKER_USE_EXEC=1 + WORKDIR /var/www/html ENV COMPOSER_CACHE_DIR /composer-cache diff --git a/tests/randomized/docker/centos.Dockerfile b/tests/randomized/docker/centos.Dockerfile index 9e562dc920..c134fe5ad4 100644 --- a/tests/randomized/docker/centos.Dockerfile +++ b/tests/randomized/docker/centos.Dockerfile @@ -30,7 +30,7 @@ RUN set -eux; \ # Redis RUN set -eux; \ - printf 'yes' | pecl install "redis"; \ + printf 'yes' | pecl install "redis$(if [ ${PHP_VERSION/./} -le 71 ]; then echo -5.3.7; fi)"; \ for DIR in /opt/php/*; do echo "extension=redis.so" > $DIR/conf.d/redis.ini; done # Create coredumps folder @@ -83,6 +83,8 @@ RUN echo "CoreDumpDirectory /tmp/corefiles" >> /etc/httpd/conf/httpd.conf ADD run.sh /scripts/run.sh ADD prepare.sh /scripts/prepare.sh +ENV DD_SPAWN_WORKER_USE_EXEC=1 + WORKDIR /var/www/html ENV COMPOSER_CACHE_DIR /composer-cache diff --git a/tests/randomized/docker/prepare.sh b/tests/randomized/docker/prepare.sh index e201b12f6d..4e6c322754 100644 --- a/tests/randomized/docker/prepare.sh +++ b/tests/randomized/docker/prepare.sh @@ -25,7 +25,11 @@ php -v echo "Starting PHP-FPM" mkdir -p /var/log/php-fpm/ chmod a+w /var/log/php-fpm/ -php-fpm -D -d datadog.trace.log_file=/results/dd_php_error.log +if ldd $(which php) 2>/dev/null | grep -q libasan; then + php-fpm -D -d datadog.trace.log_file=/results/dd_php_error.log +else + nohup strace -ttfs 200 bash -c 'php-fpm -F -d datadog.trace.log_file=/results/dd_php_error.log 2>&3' 0<&- 3>&2 2>/results/php-fpm.strace >/dev/null & +fi sleep 1 # Start nginx diff --git a/tests/randomized/docker/run.sh b/tests/randomized/docker/run.sh index c49d752f2c..583b2044b2 100644 --- a/tests/randomized/docker/run.sh +++ b/tests/randomized/docker/run.sh @@ -3,12 +3,23 @@ set -e . /scripts/enable-coredump.sh -bash /scripts/prepare.sh + +ret=0 +bash /scripts/prepare.sh || ret=$? +if [[ $ret -ne 0 ]]; then + # handle transient prepare failures + echo $ret > /results/prepare-error + exit $ret +fi echo "Starting web load" vegeta -cpus=2 attack -format=http -targets=/vegeta-request-targets.txt -duration=${DURATION:-30s} -keepalive=false -max-workers=10 -workers=10 -rate=100 | tee results.bin | vegeta report --type=json --output=/results/results.json echo "Done web load" echo "Starting CLI load" -sh /cli-runner.sh +if ldd $(which php) 2>/dev/null | grep -q libasan; then + sh /cli-runner.sh +else + strace -ttfs 200 bash -c 'sh /cli-runner.sh 2>&3' 3>&2 2>/results/php-cli.strace +fi echo "Done CLI load" diff --git a/tests/snapshots/integrations.code_igniter.v3_1.no_ci_controller_test.test_scenario_health_check.json b/tests/snapshots/integrations.code_igniter.v3_1.no_ci_controller_test.test_scenario_health_check.json index 80b3573821..94f4baa203 100644 --- a/tests/snapshots/integrations.code_igniter.v3_1.no_ci_controller_test.test_scenario_health_check.json +++ b/tests/snapshots/integrations.code_igniter.v3_1.no_ci_controller_test.test_scenario_health_check.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "health_check/ping", "http.status_code": "200", - "http.url": "http://localhost:9999/health_check/ping", + "http.url": "http://localhost/health_check/ping", "runtime-id": "ca127e85-a20d-46aa-b510-07e9077a14c9", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_parameterized.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_parameterized.json index 369c5a37f1..1da3301fe8 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_parameterized.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_parameterized.json @@ -15,7 +15,7 @@ "http.method": "GET", "http.route": "parameterized/(:any)", "http.status_code": "200", - "http.url": "http://localhost:9999/parameterized/paramValue", + "http.url": "http://localhost/parameterized/paramValue", "runtime-id": "26ab24e6-051f-4e1f-9adf-3d609bef6946", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_return_string.json index 5801d38892..6f87498651 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_return_string.json @@ -15,7 +15,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "26ab24e6-051f-4e1f-9adf-3d609bef6946", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route.json index 8ab53c9245..5170c9dbe3 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "does_not_exist", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "232799b0-3a18-4d64-9c97-4f6b441bb91e", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route_cgi.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route_cgi.json index 63eeaac51b..c50a1bd1bf 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route_cgi.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_to_missing_route_cgi.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "does_not_exist", "http.status_code": "200", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "0caac976-2232-42e0-a186-9f73b4c27f50", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception.json index 20d5d5b608..93f7dfef97 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception.json @@ -13,13 +13,13 @@ "_dd.p.tid": "660bead900000000", "app.endpoint": "Error_::index", "component": "codeigniter", - "error.message": "Uncaught Exception (500): datadog in /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 {main}", + "error.message": "Uncaught Exception (500): datadog in /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "91489364-94f6-4030-b3ed-5df886a158a0", "span.kind": "server" }, @@ -38,8 +38,8 @@ "error": 1, "meta": { "component": "codeigniter", - "error.message": "Thrown Exception (500): datadog in /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 {main}", + "error.message": "Thrown Exception (500): datadog in /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 {main}", "error.type": "Exception" } }]] diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception_cgi.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception_cgi.json index 8345b56767..fc4999eca5 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception_cgi.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_exception_cgi.json @@ -13,13 +13,13 @@ "_dd.p.tid": "660beb1100000000", "app.endpoint": "Error_::index", "component": "codeigniter", - "error.message": "Uncaught Exception: datadog in /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/ddshim.php(8): require()\n#3 {main}", + "error.message": "Uncaught Exception: datadog in /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/ddshim.php(8): require()\n#3 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "0caac976-2232-42e0-a186-9f73b4c27f50", "span.kind": "server" }, @@ -38,8 +38,8 @@ "error": 1, "meta": { "component": "codeigniter", - "error.message": "Thrown Exception: datadog in /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 /home/circleci/datadog/tests/Frameworks/CodeIgniter/Version_3_1/ddshim.php(8): require()\n#3 {main}", + "error.message": "Thrown Exception: datadog in /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/application/controllers/Error_.php:5", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/system/core/CodeIgniter.php(533): Error_->index()\n#1 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/index.php(315): require_once()\n#2 /home/circleci/app/tests/Frameworks/CodeIgniter/Version_3_1/ddshim.php(8): require()\n#3 {main}", "error.type": "Exception" } }]] diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_view.json index b896206aff..f1c4002d99 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.common_scenarios_test.test_scenario_get_with_view.json @@ -15,7 +15,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "26ab24e6-051f-4e1f-9adf-3d609bef6946", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.code_igniter.v3_1.exit_test.test_scenario_exit.json b/tests/snapshots/tests.integrations.code_igniter.v3_1.exit_test.test_scenario_exit.json index 345b0d7373..fb342972d7 100644 --- a/tests/snapshots/tests.integrations.code_igniter.v3_1.exit_test.test_scenario_exit.json +++ b/tests/snapshots/tests.integrations.code_igniter.v3_1.exit_test.test_scenario_exit.json @@ -15,7 +15,7 @@ "http.method": "GET", "http.route": "exits", "http.status_code": "200", - "http.url": "http://localhost:9999/exits", + "http.url": "http://localhost/exits", "runtime-id": "de8ed04e-02e4-40ef-be2a-20aeeb1398ef", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json index c024f9c3ca..ef64c585d8 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "1b0e863a-39eb-40f0-8723-5446bc1f418b", "span.kind": "server", "symfony.route.action": "Drupal\\system\\Controller\\Http4xxController@on404" @@ -81,8 +81,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Routing/Router.php:144", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(90): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/EventListener/RouterListener.php(105): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(158): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET http://localhost:9999/does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/EventListener/RouterListener.php:127\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(158): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Routing/Router.php:144", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(90): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/EventListener/RouterListener.php(105): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(158): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET http://localhost/does_not_exist\" in /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/EventListener/RouterListener.php:127\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(158): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json index bcd55ced67..ee7ee96a88 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "drupal", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Render/Renderer.php(592): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(181): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Render/Renderer.php(592): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(181): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "9909a648-1886-44d2-b341-b1ddc0f62f34", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@error", @@ -122,8 +122,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Render/Renderer.php(592): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(181): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Render/Renderer.php(592): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(181): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json index c6c7b140b0..16afc5508e 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view", + "http.url": "http://localhost/simple_view", "runtime-id": "cecb7d5b-eb5a-49f8-8374-29dce2d6fce6", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@simpleView", diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json index 107ebf6b3e..0dcc9db5c7 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "692423cd-81b3-449e-a67e-43150df99f74", "span.kind": "server", "symfony.route.action": "Drupal\\system\\Controller\\Http4xxController@on404" @@ -81,8 +81,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Routing/Router.php:125", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(92): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/EventListener/RouterListener.php(113): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(127): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/EventListener/RouterListener.php:137\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(127): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Routing/Router.php:125", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(92): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/EventListener/RouterListener.php(113): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(127): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/EventListener/RouterListener.php:137\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(111): call_user_func()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(127): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json index 8b289f78b4..f30d57b633 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "drupal", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Render/Renderer.php(573): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Render/Renderer.php(573): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "692423cd-81b3-449e-a67e-43150df99f74", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@error", @@ -122,8 +122,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Render/Renderer.php(573): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Render/Renderer.php(573): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json index c936976841..42eaba32f3 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view", + "http.url": "http://localhost/simple_view", "runtime-id": "692423cd-81b3-449e-a67e-43150df99f74", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@simpleView", diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json index e71b4094b6..30bad45145 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "7a7b1e1f-04e8-41dc-82bd-81f24ef6e0a4", "span.kind": "server", "symfony.route.action": "Drupal\\system\\Controller\\Http4xxController@on404" @@ -81,8 +81,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Routing/Router.php:124", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(93): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/EventListener/RouterListener.php(112): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(145): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/EventListener/RouterListener.php:136\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(145): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException: No routes found for \"/does_not_exist\". in /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Routing/Router.php:124", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Routing/AccessAwareRouter.php(93): Drupal\\Core\\Routing\\Router->matchRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/EventListener/RouterListener.php(112): Drupal\\Core\\Routing\\AccessAwareRouter->matchRequest()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(145): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#13 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/EventListener/RouterListener.php:136\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(145): Drupal\\Component\\EventDispatcher\\ContainerAwareEventDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#11 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json index 7611262ecd..d6a0f8f00d 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "drupal", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(169): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(169): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "3afe4427-fcda-4ade-bdc5-572a8c5f8b12", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@error", @@ -122,8 +122,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/modules/datadog/src/Controller/DatadogController.php:19", - "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(169): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/modules/datadog/src/Controller/DatadogController.php:19", + "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\\Core\\Render\\Renderer->executeInRenderContext()\n#4 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()\n#5 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(169): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle()\n#9 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle()\n#10 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle()\n#11 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle()\n#12 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle()\n#13 /home/circleci/app/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle()\n#14 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json index 546d0db745..fbcc57a8a7 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json @@ -12,7 +12,7 @@ "component": "drupal", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view", + "http.url": "http://localhost/simple_view", "runtime-id": "7a7b1e1f-04e8-41dc-82bd-81f24ef6e0a4", "span.kind": "server", "symfony.route.action": "Drupal\\datadog\\Controller\\DatadogController@simpleView", diff --git a/tests/snapshots/tests.integrations.guzzle.v7.guzzle_integration_test.test_multi_exec.json b/tests/snapshots/tests.integrations.guzzle.v7.guzzle_integration_test.test_multi_exec.json index 480964ea38..52c0b445a8 100644 --- a/tests/snapshots/tests.integrations.guzzle.v7.guzzle_integration_test.test_multi_exec.json +++ b/tests/snapshots/tests.integrations.guzzle.v7.guzzle_integration_test.test_multi_exec.json @@ -228,7 +228,7 @@ "curl.total_time_us": "1834", "curl.upload_content_length": "-1", "error.message": "Couldn't resolve host name", - "error.stack": "#0 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(233): curl_multi_info_read()\n#1 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(174): GuzzleHttp\\Handler\\CurlMultiHandler->processMessages()\n#2 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(189): GuzzleHttp\\Handler\\CurlMultiHandler->tick()\n#3 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\\Handler\\CurlMultiHandler->execute()\n#4 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\\Promise\\Promise->invokeWaitFn()\n#5 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#6 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\\Promise\\Promise->invokeWaitList()\n#7 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#8 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Utils.php(121): GuzzleHttp\\Promise\\Promise->wait()\n#9 /home/circleci/datadog/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(77): GuzzleHttp\\Promise\\Utils::unwrap()\n#10 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(171): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->DDTrace\\Tests\\Integrations\\Guzzle\\V7\\{closure}()\n#11 /home/circleci/datadog/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(86): DDTrace\\Tests\\Integrations\\Guzzle\\V6\\GuzzleIntegrationTest->isolateTracerSnapshot()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1612): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->testMultiExec()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1218): PHPUnit\\Framework\\TestCase->runTest()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(968): PHPUnit\\Framework\\TestResult->run()\n#16 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run()\n#17 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestSuite->run()\n#18 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#19 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(144): PHPUnit\\TextUI\\TestRunner->run()\n#20 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(97): PHPUnit\\TextUI\\Command->run()\n#21 phpvfscomposer:///home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(106): PHPUnit\\TextUI\\Command::main()\n#22 /home/circleci/datadog/tests/vendor/bin/phpunit(118): include()\n#23 {main}", + "error.stack": "#0 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(233): curl_multi_info_read()\n#1 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(174): GuzzleHttp\\Handler\\CurlMultiHandler->processMessages()\n#2 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(189): GuzzleHttp\\Handler\\CurlMultiHandler->tick()\n#3 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\\Handler\\CurlMultiHandler->execute()\n#4 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\\Promise\\Promise->invokeWaitFn()\n#5 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#6 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\\Promise\\Promise->invokeWaitList()\n#7 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#8 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Utils.php(121): GuzzleHttp\\Promise\\Promise->wait()\n#9 /home/circleci/app/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(77): GuzzleHttp\\Promise\\Utils::unwrap()\n#10 /home/circleci/app/tests/Common/SnapshotTestTrait.php(171): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->DDTrace\\Tests\\Integrations\\Guzzle\\V7\\{closure}()\n#11 /home/circleci/app/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(86): DDTrace\\Tests\\Integrations\\Guzzle\\V6\\GuzzleIntegrationTest->isolateTracerSnapshot()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1612): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->testMultiExec()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1218): PHPUnit\\Framework\\TestCase->runTest()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(968): PHPUnit\\Framework\\TestResult->run()\n#16 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run()\n#17 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestSuite->run()\n#18 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#19 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(144): PHPUnit\\TextUI\\TestRunner->run()\n#20 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(97): PHPUnit\\TextUI\\Command->run()\n#21 phpvfscomposer:///home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(106): PHPUnit\\TextUI\\Command::main()\n#22 /home/circleci/app/tests/vendor/bin/phpunit(118): include()\n#23 {main}", "error.type": "curl error", "http.status_code": "0", "http.url": "https://google.wrong/", @@ -496,7 +496,7 @@ "curl.total_time_us": "1572", "curl.upload_content_length": "-1", "error.message": "Couldn't resolve host name", - "error.stack": "#0 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(233): curl_multi_info_read()\n#1 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(174): GuzzleHttp\\Handler\\CurlMultiHandler->processMessages()\n#2 /home/circleci/datadog/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(189): GuzzleHttp\\Handler\\CurlMultiHandler->tick()\n#3 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\\Handler\\CurlMultiHandler->execute()\n#4 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\\Promise\\Promise->invokeWaitFn()\n#5 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#6 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\\Promise\\Promise->invokeWaitList()\n#7 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#8 /home/circleci/datadog/tests/vendor/guzzlehttp/promises/src/Utils.php(121): GuzzleHttp\\Promise\\Promise->wait()\n#9 /home/circleci/datadog/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(77): GuzzleHttp\\Promise\\Utils::unwrap()\n#10 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(171): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->DDTrace\\Tests\\Integrations\\Guzzle\\V7\\{closure}()\n#11 /home/circleci/datadog/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(86): DDTrace\\Tests\\Integrations\\Guzzle\\V6\\GuzzleIntegrationTest->isolateTracerSnapshot()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1612): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->testMultiExec()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1218): PHPUnit\\Framework\\TestCase->runTest()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(968): PHPUnit\\Framework\\TestResult->run()\n#16 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run()\n#17 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestSuite->run()\n#18 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#19 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(144): PHPUnit\\TextUI\\TestRunner->run()\n#20 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(97): PHPUnit\\TextUI\\Command->run()\n#21 phpvfscomposer:///home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(106): PHPUnit\\TextUI\\Command::main()\n#22 /home/circleci/datadog/tests/vendor/bin/phpunit(118): include()\n#23 {main}", + "error.stack": "#0 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(233): curl_multi_info_read()\n#1 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(174): GuzzleHttp\\Handler\\CurlMultiHandler->processMessages()\n#2 /home/circleci/app/tests/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php(189): GuzzleHttp\\Handler\\CurlMultiHandler->tick()\n#3 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(251): GuzzleHttp\\Handler\\CurlMultiHandler->execute()\n#4 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(227): GuzzleHttp\\Promise\\Promise->invokeWaitFn()\n#5 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(272): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#6 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(229): GuzzleHttp\\Promise\\Promise->invokeWaitList()\n#7 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Promise.php(69): GuzzleHttp\\Promise\\Promise->waitIfPending()\n#8 /home/circleci/app/tests/vendor/guzzlehttp/promises/src/Utils.php(121): GuzzleHttp\\Promise\\Promise->wait()\n#9 /home/circleci/app/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(77): GuzzleHttp\\Promise\\Utils::unwrap()\n#10 /home/circleci/app/tests/Common/SnapshotTestTrait.php(171): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->DDTrace\\Tests\\Integrations\\Guzzle\\V7\\{closure}()\n#11 /home/circleci/app/tests/Integrations/Guzzle/V7/GuzzleIntegrationTest.php(86): DDTrace\\Tests\\Integrations\\Guzzle\\V6\\GuzzleIntegrationTest->isolateTracerSnapshot()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1612): DDTrace\\Tests\\Integrations\\Guzzle\\V7\\GuzzleIntegrationTest->testMultiExec()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1218): PHPUnit\\Framework\\TestCase->runTest()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(968): PHPUnit\\Framework\\TestResult->run()\n#16 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run()\n#17 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestSuite->run()\n#18 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#19 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(144): PHPUnit\\TextUI\\TestRunner->run()\n#20 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(97): PHPUnit\\TextUI\\Command->run()\n#21 phpvfscomposer:///home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(106): PHPUnit\\TextUI\\Command::main()\n#22 /home/circleci/app/tests/vendor/bin/phpunit(118): include()\n#23 {main}", "error.type": "curl error", "http.status_code": "0", "http.url": "https://google.still.wrong/", diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json index 3bd72d2481..62d9258fd7 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json @@ -12,7 +12,7 @@ "component": "laminas", "http.method": "POST", "http.status_code": "201", - "http.url": "http://localhost:9999/datadog-rest-service", + "http.url": "http://localhost/datadog-rest-service", "http.version": "1.1", "laminas.route.action": "DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource@create", "laminas.route.name": "datadog-api.rest.datadog-rest-service", diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json index 55e9528630..0b7f9591c2 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json @@ -12,7 +12,7 @@ "component": "laminas", "http.method": "GET", "http.status_code": "405", - "http.url": "http://localhost:9999/datadog-rest-service/1", + "http.url": "http://localhost/datadog-rest-service/1", "http.version": "1.1", "laminas.route.action": "DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource@fetch", "laminas.route.name": "datadog-api.rest.datadog-rest-service", @@ -383,7 +383,7 @@ "meta": { "component": "laminas", "error.message": "The GET method has not been defined for individual resources", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(548): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(548): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n", "error.type": "ApiProblem" } }, diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json index 7b7e1dbf6a..f6a8684ce5 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "laminas", - "error.message": "Uncaught Error (500): Attempt to assign property \"b\" on null in /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", + "error.message": "Uncaught Error (500): Attempt to assign property \"b\" on null in /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", "error.type": "Error", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/datadog-rest-service/42", + "http.url": "http://localhost/datadog-rest-service/42", "http.version": "1.1", "laminas.route.action": "DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource@fetch", "laminas.route.name": "datadog-api.rest.datadog-rest-service", @@ -374,8 +374,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Error (500): Attempt to assign property \"b\" on null in /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", + "error.message": "Thrown Error (500): Attempt to assign property \"b\" on null in /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", "error.type": "Error" } }, @@ -390,8 +390,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Error (500): Attempt to assign property \"b\" on null in /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", + "error.message": "Thrown Error (500): Attempt to assign property \"b\" on null in /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/module/DatadogApi/src/V1/Rest/DatadogRestService/DatadogRestServiceResource.php:55", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch()\n#1 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(544): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent()\n#5 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get()\n#7 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch()\n#8 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\ApiTools\\Rest\\RestController->onDispatch()\n#9 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#10 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#11 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#12 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#14 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(177): Laminas\\EventManager\\EventManager->triggerListeners()\n#15 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#16 /home/circleci/app/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", "error.type": "Error" } }, diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json index f6018e1251..bf9b87bbc1 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json @@ -13,7 +13,7 @@ "http.method": "GET", "laminas.route.name": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@simple", "span.kind": "server" diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json index 1fe603ebae..9a3d7fbc43 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "laminas", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "http.version": "1.1", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json index dd89efef43..3daf1afba2 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,13 +11,13 @@ "meta": { "_dd.p.dm": "-0", "component": "laminas", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception", "http.method": "GET", "laminas.route.name": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@error", "span.kind": "server" @@ -205,8 +205,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, @@ -221,8 +221,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, @@ -237,8 +237,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json index b42283934e..aaace5641a 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json @@ -13,7 +13,7 @@ "http.method": "GET", "laminas.route.name": "simpleView", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@view", "span.kind": "server" diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json index 02fe659dc1..bc4864a27d 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json @@ -13,7 +13,7 @@ "http.method": "GET", "laminas.route.name": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@simple", "span.kind": "server" diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json index 81bd2112f1..38776b7911 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "laminas", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "http.version": "1.1", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json index 66ad41ea34..5b5d0ced68 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,13 +11,13 @@ "meta": { "_dd.p.dm": "-0", "component": "laminas", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception", "http.method": "GET", "laminas.route.name": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@error", "span.kind": "server" @@ -205,8 +205,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, @@ -221,8 +221,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, @@ -237,8 +237,8 @@ "error": 1, "meta": { "component": "laminas", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/module/Application/src/Controller/CommonSpecsController.php:33", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch()\n#2 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#3 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#4 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch()\n#5 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch()\n#6 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners()\n#7 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil()\n#8 /home/circleci/app/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json index 17dc87e777..447e982da3 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json @@ -13,7 +13,7 @@ "http.method": "GET", "laminas.route.name": "simpleView", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "http.version": "1.1", "laminas.route.action": "Application\\Controller\\CommonSpecsController@view", "span.kind": "server" diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_dynamic_route.json index d4e3db996a..9a8a1a25ed 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "runtime-id": "b32e6f79-c5ef-4d17-bae1-33f912b0a03d" }, "metrics": { @@ -239,7 +239,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 18, diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json index f4f849cb49..4f0313c6f4 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "d74a50e5-32ec-4668-ad83-9a267ef93418", diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json index c7f5bf628e..c8c608c023 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "d74a50e5-32ec-4668-ad83-9a267ef93418" }, "metrics": { @@ -239,7 +239,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 18, diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json index 7f306ef712..c77278a12b 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "662b88ba00000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(805): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(99): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(805): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(99): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "d74a50e5-32ec-4668-ad83-9a267ef93418", @@ -231,8 +231,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(805): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(99): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(805): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(99): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_10_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json index da1642411a..3521ab143b 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "d74a50e5-32ec-4668-ad83-9a267ef93418", @@ -279,7 +279,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_10_x/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 27, "parent_id": 20, diff --git a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_dynamic_route.json index d28120f47a..69b2d5ac11 100644 --- a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "runtime-id": "3ddc27d7-3081-4e28-a35f-c8c6e7f4ec38" }, "metrics": { @@ -203,7 +203,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 19, "parent_id": 16, diff --git a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_return_string.json index e519386218..8305fe3dd9 100644 --- a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "4f76a87a-df89-41fb-909e-e269a4616334", diff --git a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_to_missing_route.json index 7f8f7b5052..3d1e616fbd 100644 --- a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "4f76a87a-df89-41fb-909e-e269a4616334" }, "metrics": { @@ -203,7 +203,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 19, "parent_id": 16, diff --git a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_exception.json index c12c4556c5..ffceb2d891 100644 --- a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "662b8a2800000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(206): Illuminate\\Routing\\Route->runController()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\\Routing\\Route->run()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\\Routing\\Router->runRouteWithinStack()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\\Routing\\Router->runRoute()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\\Routing\\Router->dispatchToRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\\Routing\\Router->dispatch()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\ValidatePostSize->handle()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Pipeline\\Pipeline->then()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1188): Illuminate\\Foundation\\Http\\Kernel->handle()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/public/index.php(17): Illuminate\\Foundation\\Application->handleRequest()\n#44 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(206): Illuminate\\Routing\\Route->runController()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\\Routing\\Route->run()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\\Routing\\Router->runRouteWithinStack()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\\Routing\\Router->runRoute()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\\Routing\\Router->dispatchToRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\\Routing\\Router->dispatch()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\ValidatePostSize->handle()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Pipeline\\Pipeline->then()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1188): Illuminate\\Foundation\\Http\\Kernel->handle()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/public/index.php(17): Illuminate\\Foundation\\Application->handleRequest()\n#44 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "4f76a87a-df89-41fb-909e-e269a4616334", @@ -219,8 +219,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(206): Illuminate\\Routing\\Route->runController()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\\Routing\\Route->run()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\\Routing\\Router->runRouteWithinStack()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\\Routing\\Router->runRoute()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\\Routing\\Router->dispatchToRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\\Routing\\Router->dispatch()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\ValidatePostSize->handle()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Pipeline\\Pipeline->then()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1188): Illuminate\\Foundation\\Http\\Kernel->handle()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/public/index.php(17): Illuminate\\Foundation\\Application->handleRequest()\n#44 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(46): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(206): Illuminate\\Routing\\Route->runController()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(808): Illuminate\\Routing\\Route->run()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(88): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Session\\Middleware\\StartSession->handle()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(786): Illuminate\\Routing\\Router->runRouteWithinStack()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(750): Illuminate\\Routing\\Router->runRoute()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(739): Illuminate\\Routing\\Router->dispatchToRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(201): Illuminate\\Routing\\Router->dispatch()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(51): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\ValidatePostSize->handle()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(110): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Pipeline\\Pipeline->then()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(145): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(1188): Illuminate\\Foundation\\Http\\Kernel->handle()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_11_x/public/index.php(17): Illuminate\\Foundation\\Application->handleRequest()\n#44 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_view.json index 071b0f5b27..6403732db9 100644 --- a/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v11_x.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "4f76a87a-df89-41fb-909e-e269a4616334", @@ -243,7 +243,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_11_x/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_11_x/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 25, "parent_id": 19, diff --git a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_dynamic_route.json index fdf0d5f5c6..6f8bd98d19 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "dynamic_route/{param01}/static/{param02?}", "http.status_code": "200", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@dynamicRoute", "laravel.route.name": "unnamed_route", "runtime-id": "4878b25b-9211-4ac8-8906-d7721b2fa3e5", diff --git a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_return_string.json index 83b601cdf9..70b03dbba7 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "4878b25b-9211-4ac8-8906-d7721b2fa3e5", diff --git a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_to_missing_route.json index 10e73cc490..d12233d5d3 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "4878b25b-9211-4ac8-8906-d7721b2fa3e5" }, "metrics": { @@ -218,7 +218,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 19, "parent_id": 16, diff --git a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_exception.json index 94138284a4..d9713635a8 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "664c86b600000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/app/Http/Controllers/CommonSpecsController.php:22", - "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(684): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(659): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(614): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/app/Http/Controllers/CommonSpecsController.php:22", + "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(684): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(659): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(614): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "4878b25b-9211-4ac8-8906-d7721b2fa3e5", @@ -210,8 +210,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/app/Http/Controllers/CommonSpecsController.php:22", - "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(684): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(659): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(614): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/app/Http/Controllers/CommonSpecsController.php:22", + "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(684): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(659): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Router.php(614): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_5_7/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_view.json index 5fdd4f20b7..0f745e4712 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v5_7.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "4878b25b-9211-4ac8-8906-d7721b2fa3e5", @@ -246,7 +246,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_5_7/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_5_7/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 17, diff --git a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_dynamic_route.json index ee11b0f6dc..a61c2451a4 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "dynamic_route/{param01}/static/{param02?}", "http.status_code": "200", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@dynamicRoute", "laravel.route.name": "unnamed_route", "runtime-id": "5db5654d-5a9e-4cf0-908b-a51f7ced2ff2", diff --git a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_return_string.json index f7e465454e..f54e4fac1d 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "5db5654d-5a9e-4cf0-908b-a51f7ced2ff2", diff --git a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_to_missing_route.json index 83f1ce2ea1..3b220f829e 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "5db5654d-5a9e-4cf0-908b-a51f7ced2ff2" }, "metrics": { @@ -218,7 +218,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 19, "parent_id": 16, diff --git a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_exception.json index c33afc309b..a02ff15ede 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "664c88ca00000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/app/Http/Controllers/CommonSpecsController.php:22", - "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(680): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(657): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(623): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(612): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/app/Http/Controllers/CommonSpecsController.php:22", + "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(680): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(657): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(623): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(612): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "5db5654d-5a9e-4cf0-908b-a51f7ced2ff2", @@ -210,8 +210,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/app/Http/Controllers/CommonSpecsController.php:22", - "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(680): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(657): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(623): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(612): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/app/Http/Controllers/CommonSpecsController.php:22", + "error.stack": "#0 [internal function]: App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(219): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Route.php(176): Illuminate\\Routing\\Route->runController()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(680): Illuminate\\Routing\\Route->run()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(682): Illuminate\\Pipeline\\Pipeline->then()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(657): Illuminate\\Routing\\Router->runRouteWithinStack()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(623): Illuminate\\Routing\\Router->runRoute()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Router.php(612): Illuminate\\Routing\\Router->dispatchToRoute()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(176): Illuminate\\Routing\\Router->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(30): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(104): Illuminate\\Routing\\Pipeline->Illuminate\\Routing\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\\Pipeline\\Pipeline->then()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_5_8/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->handle()\n#18 {main}", "error.type": "Exception" } }, @@ -266,7 +266,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/500.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_5_8/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/500.blade.php", "trace_id": 0, "span_id": 22, "parent_id": 19, diff --git a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_view.json index 066d91bbc5..2347d4af8d 100644 --- a/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v5_8.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "5db5654d-5a9e-4cf0-908b-a51f7ced2ff2", @@ -246,7 +246,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_5_8/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_5_8/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 17, diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_dynamic_route.json index e2337941b9..888f07cfc1 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "dynamic_route/{param01}/static/{param02?}", "http.status_code": "200", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@dynamicRoute", "laravel.route.name": "unnamed_route", "runtime-id": "3bd45f16-c853-4884-a7ca-1017a443b1fe", diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_return_string.json index 5b7dceca26..5bd41327ec 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "3bd45f16-c853-4884-a7ca-1017a443b1fe", diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_to_missing_route.json index 2e4560c2f2..a83c10872f 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "3bd45f16-c853-4884-a7ca-1017a443b1fe" }, "metrics": { @@ -215,7 +215,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 19, "parent_id": 16, diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_exception.json index d6e7846b63..1c2888ba55 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "664c898e00000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/CommonSpecsController.php:21", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(721): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(723): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(698): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(651): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/CommonSpecsController.php:21", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(721): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(723): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(698): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(651): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "3bd45f16-c853-4884-a7ca-1017a443b1fe", @@ -207,8 +207,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/CommonSpecsController.php:21", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(721): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(723): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(698): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(651): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/CommonSpecsController.php:21", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(262): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(721): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(723): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(698): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(651): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_8_x/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_view.json index 7f6142e012..132a2cbfe8 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "3bd45f16-c853-4884-a7ca-1017a443b1fe", @@ -243,7 +243,7 @@ { "name": "laravel.view", "service": "laravel_test_app", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_8_x/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_8_x/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 17, diff --git a/tests/snapshots/tests.integrations.laravel.v8_x.queue_test.test_broadcast.json b/tests/snapshots/tests.integrations.laravel.v8_x.queue_test.test_broadcast.json index fd3f39e1b8..c18eeccd87 100644 --- a/tests/snapshots/tests.integrations.laravel.v8_x.queue_test.test_broadcast.json +++ b/tests/snapshots/tests.integrations.laravel.v8_x.queue_test.test_broadcast.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "queue/broadcast", "http.status_code": "200", - "http.url": "http://localhost:9999/queue/broadcast", + "http.url": "http://localhost/queue/broadcast", "laravel.route.action": "App\\Http\\Controllers\\QueueTestController@broadcast", "laravel.route.name": "broadcast", "runtime-id": "2b8747b7-5a3e-44cb-8545-9c808e7b4e93", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_dynamic_route.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_dynamic_route.json index 7cb9c0b4ab..78b1ba2cac 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_dynamic_route.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_dynamic_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/dynamic_route/dynamic01/static/dynamic02", + "http.url": "http://localhost/dynamic_route/dynamic01/static/dynamic02", "runtime-id": "5c9f7cdc-4833-4e56-a8af-852b6607f988" }, "metrics": { @@ -239,7 +239,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 18, diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json index dadf0a490b..01e28d6b43 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple", "laravel.route.name": "simple_route", "runtime-id": "614cba3e-708f-4797-8da5-40fa71e9c272", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json index a5e8b4e4eb..78d03746d8 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "laravel", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "614cba3e-708f-4797-8da5-40fa71e9c272" }, "metrics": { @@ -239,7 +239,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/404.blade.php", "trace_id": 0, "span_id": 21, "parent_id": 18, diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json index 50bb3ff268..ece8e66718 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "662b874300000000", "component": "laravel", - "error.message": "Uncaught Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(797): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(740): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(729): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(190): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(134): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Uncaught Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(797): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(740): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(729): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(190): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(134): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "error", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@error", "laravel.route.name": "unnamed_route", "runtime-id": "614cba3e-708f-4797-8da5-40fa71e9c272", @@ -231,8 +231,8 @@ "error": 1, "meta": { "component": "laravel", - "error.message": "Thrown Exception (500): Controller error in /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/app/Http/Controllers/CommonSpecsController.php:19", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(797): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(740): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(729): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(190): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(134): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", + "error.message": "Thrown Exception (500): Controller error in /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/app/Http/Controllers/CommonSpecsController.php:19", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()\n#2 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()\n#6 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#7 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()\n#8 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#9 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()\n#10 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()\n#12 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()\n#14 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle()\n#15 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#16 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()\n#17 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#18 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()\n#19 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#20 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(797): Illuminate\\Pipeline\\Pipeline->then()\n#21 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Illuminate\\Routing\\Router->runRouteWithinStack()\n#22 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(740): Illuminate\\Routing\\Router->runRoute()\n#23 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(729): Illuminate\\Routing\\Router->dispatchToRoute()\n#24 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(190): Illuminate\\Routing\\Router->dispatch()\n#25 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()\n#26 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#27 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#28 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()\n#29 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#30 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()\n#31 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()\n#32 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#33 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()\n#34 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#35 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()\n#36 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#37 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle()\n#38 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#39 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle()\n#40 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()\n#41 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\\Pipeline\\Pipeline->then()\n#42 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(134): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()\n#43 /home/circleci/app/tests/Frameworks/Laravel/Version_9_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle()\n#44 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json index 3e5dd32003..8a07e2caaa 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "simple_view", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "laravel.route.action": "App\\Http\\Controllers\\CommonSpecsController@simple_view", "laravel.route.name": "unnamed_route", "runtime-id": "614cba3e-708f-4797-8da5-40fa71e9c272", @@ -267,7 +267,7 @@ { "name": "laravel.view", "service": "my_service", - "resource": "/home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/resources/views/simple_view.blade.php", + "resource": "/home/circleci/app/tests/Frameworks/Laravel/Version_9_x/resources/views/simple_view.blade.php", "trace_id": 0, "span_id": 23, "parent_id": 19, diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json index d1935c5815..2997d6ac17 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/datadog/simple/index?key=value&", + "http.url": "http://localhost/datadog/simple/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json index 1db2098449..19ee5e01bd 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "does_not_exist", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json index 7c86977209..6fab5fd34d 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "magento", - "error.message": "Caught Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Caught Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/datadog/error/index?key=value&", + "http.url": "http://localhost/datadog/error/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", @@ -64,8 +64,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception" } }, @@ -94,8 +94,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception" } }, @@ -141,8 +141,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception" } }, @@ -157,8 +157,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception", "magento.action": "index", "magento.controller": "error", @@ -282,8 +282,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json index 957464d6ec..61fc7ee450 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/datadog/simpleview/index?key=value&", + "http.url": "http://localhost/datadog/simpleview/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json index 50812fac22..72c71d68a0 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/datadog/simple/index?key=value&", + "http.url": "http://localhost/datadog/simple/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json index 4d692939d4..b953f93fb2 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "does_not_exist", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json index da7242f8d5..ae3c2463f1 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json @@ -11,12 +11,12 @@ "meta": { "_dd.p.dm": "-0", "component": "magento", - "error.message": "Caught Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Caught Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/datadog/error/index?key=value&", + "http.url": "http://localhost/datadog/error/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", @@ -64,8 +64,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception" } }, @@ -94,8 +94,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception" } }, @@ -141,8 +141,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception" } }, @@ -261,8 +261,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception", "magento.action": "index", "magento.controller": "error", @@ -282,8 +282,8 @@ "error": 1, "meta": { "component": "magento", - "error.message": "Thrown Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", + "error.message": "Thrown Exception (500): This is an exception in /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/app/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json index 5d239d7b68..eb115310c0 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json @@ -12,7 +12,7 @@ "component": "magento", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/datadog/simpleview/index?key=value&", + "http.url": "http://localhost/datadog/simpleview/index?key=value&", "magento.area": "frontend", "magento.cached": "false", "magento.frontname": "datadog", diff --git a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_chat_completion_stream_with_error.json b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_chat_completion_stream_with_error.json index 9a64fff5b0..948c7b2782 100644 --- a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_chat_completion_stream_with_error.json +++ b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_chat_completion_stream_with_error.json @@ -12,8 +12,8 @@ "_dd.p.dm": "0", "_dd.p.tid": "6659d8a000000000", "env": "test", - "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: The server had an error while processing your request. Sorry about that! in /home/circleci/datadog/tests/vendor/openai-php/client/src/Responses/StreamResponse.php:60", - "error.stack": "#0 [internal function]: OpenAI\\Responses\\StreamResponse->getIterator()\n#1 /home/circleci/datadog/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(1079): Generator->valid()\n#2 /home/circleci/datadog/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(1033): DDTrace\\Integrations\\OpenAI\\OpenAIIntegration::readAndStoreStreamedResponse()\n#3 /home/circleci/datadog/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(204): DDTrace\\Integrations\\OpenAI\\OpenAIIntegration::handleStreamedResponse()\n#4 /home/circleci/datadog/tests/vendor/openai-php/client/src/Resources/Chat.php(54): OpenAI\\Resources\\Chat->DDTrace\\Integrations\\OpenAI\\{closure}()\n#5 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(100): OpenAI\\Resources\\Chat->createStreamed()\n#6 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#7 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(93): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#8 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(455): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->callStreamed()\n#9 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testCreateChatCompletionStreamWithError()\n#10 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#11 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#16 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#17 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#18 /home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#19 /home/circleci/datadog/tests/vendor/bin/phpunit(122): include()\n#20 {main}", + "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: The server had an error while processing your request. Sorry about that! in /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Responses/StreamResponse.php:60", + "error.stack": "#0 [internal function]: OpenAI\\Responses\\StreamResponse->getIterator()\n#1 /home/circleci/app/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(1079): Generator->valid()\n#2 /home/circleci/app/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(1033): DDTrace\\Integrations\\OpenAI\\OpenAIIntegration::readAndStoreStreamedResponse()\n#3 /home/circleci/app/src/DDTrace/Integrations/OpenAI/OpenAIIntegration.php(204): DDTrace\\Integrations\\OpenAI\\OpenAIIntegration::handleStreamedResponse()\n#4 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Resources/Chat.php(54): OpenAI\\Resources\\Chat->DDTrace\\Integrations\\OpenAI\\{closure}()\n#5 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(100): OpenAI\\Resources\\Chat->createStreamed()\n#6 /home/circleci/app/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#7 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(93): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#8 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(455): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->callStreamed()\n#9 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testCreateChatCompletionStreamWithError()\n#10 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#11 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#16 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#17 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#18 /home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#19 /home/circleci/app/tests/vendor/bin/phpunit(122): include()\n#20 {main}", "error.type": "OpenAI\\Exceptions\\ErrorException", "openai.api_base": "https://api.openai.com/v1/", "openai.request.endpoint": "/v1/chat/completions", diff --git a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_completions_with_multiple_error_messages.json b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_completions_with_multiple_error_messages.json index de1e1d04c0..e5f86487cd 100644 --- a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_completions_with_multiple_error_messages.json +++ b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_create_completions_with_multiple_error_messages.json @@ -12,8 +12,8 @@ "_dd.p.dm": "0", "_dd.p.tid": "6659d74a00000000", "env": "test", - "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: Invalid schema for function 'get_current_weather':\nIn context=('properties', 'location'), array schema missing items in /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", - "error.stack": "#0 /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/datadog/tests/vendor/openai-php/client/src/Resources/Completions.php(33): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(81): OpenAI\\Resources\\Completions->create()\n#3 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(468): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testCreateCompletionsWithMultipleErrorMessages()\n#7 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/datadog/tests/vendor/bin/phpunit(122): include()\n#17 {main}", + "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: Invalid schema for function 'get_current_weather':\nIn context=('properties', 'location'), array schema missing items in /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", + "error.stack": "#0 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Resources/Completions.php(33): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(81): OpenAI\\Resources\\Completions->create()\n#3 /home/circleci/app/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(468): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testCreateCompletionsWithMultipleErrorMessages()\n#7 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/app/tests/vendor/bin/phpunit(122): include()\n#17 {main}", "error.type": "OpenAI\\Exceptions\\ErrorException", "openai.api_base": "https://api.openai.com/v1/", "openai.request.endpoint": "/v1/completions", diff --git a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_error.json b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_error.json index 628958802f..c76b32e0a0 100644 --- a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_error.json +++ b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_error.json @@ -12,8 +12,8 @@ "_dd.p.dm": "0", "_dd.p.tid": "6659d8a500000000", "env": "test", - "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: Incorrect API key provided: foo. You can find your API key at https://platform.openai.com. in /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", - "error.stack": "#0 /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/datadog/tests/vendor/openai-php/client/src/Resources/Models.php(28): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(83): OpenAI\\Resources\\Models->list()\n#3 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(463): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testListModelsWithError()\n#7 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/datadog/tests/vendor/bin/phpunit(122): include()\n#17 {main}", + "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: Incorrect API key provided: foo. You can find your API key at https://platform.openai.com. in /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", + "error.stack": "#0 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Resources/Models.php(28): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(83): OpenAI\\Resources\\Models->list()\n#3 /home/circleci/app/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(463): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testListModelsWithError()\n#7 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/app/tests/vendor/bin/phpunit(122): include()\n#17 {main}", "error.type": "OpenAI\\Exceptions\\ErrorException", "openai.api_base": "https://api.openai.com/v1/", "openai.request.endpoint": "/v1/models", diff --git a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_null_error_type.json b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_null_error_type.json index 79f0b7633b..891c75a521 100644 --- a/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_null_error_type.json +++ b/tests/snapshots/tests.integrations.open_ai.open_ai_test.test_list_models_with_null_error_type.json @@ -12,8 +12,8 @@ "_dd.p.dm": "0", "_dd.p.tid": "6659d8b000000000", "env": "test", - "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: You exceeded your current quota, please check in /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", - "error.stack": "#0 /home/circleci/datadog/tests/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/datadog/tests/vendor/openai-php/client/src/Resources/Models.php(28): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(83): OpenAI\\Resources\\Models->list()\n#3 /home/circleci/datadog/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/datadog/tests/Integrations/OpenAI/OpenAITest.php(473): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testListModelsWithNullErrorType()\n#7 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/datadog/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/datadog/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/datadog/tests/vendor/bin/phpunit(122): include()\n#17 {main}", + "error.message": "Uncaught OpenAI\\Exceptions\\ErrorException: You exceeded your current quota, please check in /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php:131", + "error.stack": "#0 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Transporters/HttpTransporter.php(57): OpenAI\\Transporters\\HttpTransporter->throwIfJsonError()\n#1 /home/circleci/app/tests/Integrations/OpenAI/vendor/openai-php/client/src/Resources/Models.php(28): OpenAI\\Transporters\\HttpTransporter->requestObject()\n#2 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(83): OpenAI\\Resources\\Models->list()\n#3 /home/circleci/app/tests/Common/SnapshotTestTrait.php(347): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->DDTrace\\Tests\\Integrations\\OpenAI\\{closure}()\n#4 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(76): DDTrace\\Tests\\Common\\IntegrationTestCase->isolateTracerSnapshot()\n#5 /home/circleci/app/tests/Integrations/OpenAI/OpenAITest.php(473): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->call()\n#6 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1617): DDTrace\\Tests\\Integrations\\OpenAI\\OpenAITest->testListModelsWithNullErrorType()\n#7 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(1223): PHPUnit\\Framework\\TestCase->runTest()\n#8 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestResult.php(729): PHPUnit\\Framework\\TestCase->runBare()\n#9 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestCase.php(973): PHPUnit\\Framework\\TestResult->run()\n#10 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestCase->run()\n#11 /home/circleci/app/tests/vendor/phpunit/phpunit/src/Framework/TestSuite.php(685): PHPUnit\\Framework\\TestSuite->run()\n#12 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(651): PHPUnit\\Framework\\TestSuite->run()\n#13 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(146): PHPUnit\\TextUI\\TestRunner->run()\n#14 /home/circleci/app/tests/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit\\TextUI\\Command->run()\n#15 /home/circleci/app/tests/vendor/phpunit/phpunit/phpunit(107): PHPUnit\\TextUI\\Command::main()\n#16 /home/circleci/app/tests/vendor/bin/phpunit(122): include()\n#17 {main}", "error.type": "OpenAI\\Exceptions\\ErrorException", "openai.api_base": "https://api.openai.com/v1/", "openai.request.endpoint": "/v1/models", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string.json index bac36e6323..82348173bb 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php/simple?key=value&", + "http.url": "http://localhost/app.php/simple?key=value&", "runtime-id": "93535b2f-0ff1-4add-8907-53304703f545", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string_apache.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string_apache.json index 7ccbb60f63..7d67fefaba 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_return_string_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "ac93bd2f-42bb-4c03-888a-da12a9f25915", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json index 982048534c..c61b412da0 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/app.php/does_not_exist?key=value&", + "http.url": "http://localhost/app.php/does_not_exist?key=value&", "runtime-id": "93535b2f-0ff1-4add-8907-53304703f545", "span.kind": "server", "symfony.route.action": "Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController@showAction" @@ -70,8 +70,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/appProdUrlMatcher.php:55", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1227): appProdUrlMatcher->match()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1886): Symfony\\Component\\Routing\\Router->match()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php:1896\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#8 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/appProdUrlMatcher.php:55", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1227): appProdUrlMatcher->match()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1886): Symfony\\Component\\Routing\\Router->match()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php:1896\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#8 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route_apache.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route_apache.json index eb1fed3cd7..e1e5acac6c 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_to_missing_route_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "1e42fc1f-1d82-4b6d-924f-b260e67bd97d", "span.kind": "server", "symfony.route.action": "Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController@showAction" @@ -70,8 +70,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/appProdUrlMatcher.php:55", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1227): appProdUrlMatcher->match()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1886): Symfony\\Component\\Routing\\Router->match()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php:1896\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#8 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/appProdUrlMatcher.php:55", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1227): appProdUrlMatcher->match()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1886): Symfony\\Component\\Routing\\Router->match()\n#2 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php:1896\nStack trace:\n#0 [internal function]: Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1691): call_user_func()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/cache/prod/classes.php(1617): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2967): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#8 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception.json index fd5e74862d..e76f67bda8 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,12 +12,12 @@ "_dd.p.dm": "-0", "_dd.p.tid": "6661b35000000000", "component": "symfony", - "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", + "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/app.php/error?key=value&", + "http.url": "http://localhost/app.php/error?key=value&", "runtime-id": "93535b2f-0ff1-4add-8907-53304703f545", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@errorAction", @@ -99,8 +99,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", + "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception_apache.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception_apache.json index 7d1b3dfd46..1ce2e63f95 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_exception_apache.json @@ -12,12 +12,12 @@ "_dd.p.dm": "-0", "_dd.p.tid": "6661bb9f00000000", "component": "symfony", - "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", + "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "264f8207-13c8-42ee-bd15-24d728192870", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@errorAction", @@ -99,8 +99,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", + "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 [internal function]: AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2978): call_user_func_array()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2952): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(3081): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/app/bootstrap.php.cache(2351): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_3/web/app.php(14): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#6 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view.json index e51b4db73b..02531a7ac6 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php/simple_view?key=value&", + "http.url": "http://localhost/app.php/simple_view?key=value&", "runtime-id": "05d2d546-7cb6-4e46-bc10-0ff5602cdf9d", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleViewAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view_apache.json b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view_apache.json index 26618af5f2..955ae18522 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.common_scenarios_test.test_scenario_get_with_view_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "daab05ca-d6fc-450e-b1bf-e4a6478521aa", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleViewAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping.json b/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping.json index eaa17a7c23..420610342c 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php?key=value&", + "http.url": "http://localhost/app.php?key=value&", "runtime-id": "7ddbbd63-c083-4257-a7f5-44101e551ef1", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\DefaultController@testingRouteNameAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping_apache.json b/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping_apache.json index 131ad7010f..684028567f 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_3.route_name_test.test_resource_to_uri_mapping_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/?key=value&", + "http.url": "http://localhost/?key=value&", "runtime-id": "d29fcd90-cbec-4796-bdc8-5e3fb022b605", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\DefaultController@testingRouteNameAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string.json index 25abc6024d..a0a9c47464 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php/simple?key=value&", + "http.url": "http://localhost/app.php/simple?key=value&", "runtime-id": "4374c14f-def1-4b62-97c0-598f46322850", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string_apache.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string_apache.json index 30c67847c1..a024fdc05c 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_return_string_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "ff0fb76b-fe5f-4bb4-8306-ba78e1172e49", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route.json index fe07dbc60b..f6fea7bb66 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/app.php/does_not_exist?key=value&", + "http.url": "http://localhost/app.php/does_not_exist?key=value&", "runtime-id": "4374c14f-def1-4b62-97c0-598f46322850", "span.kind": "server", "symfony.route.action": "Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController@showAction" @@ -70,8 +70,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/appProdProjectContainerUrlMatcher.php:57", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1455): appProdProjectContainerUrlMatcher->match()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1318): Symfony\\Component\\Routing\\Matcher\\UrlMatcher->matchRequest()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(2119): Symfony\\Component\\Routing\\Router->matchRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3264): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php:2135\nStack trace:\n#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3264): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#7 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/appProdProjectContainerUrlMatcher.php:57", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1455): appProdProjectContainerUrlMatcher->match()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1318): Symfony\\Component\\Routing\\Matcher\\UrlMatcher->matchRequest()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(2119): Symfony\\Component\\Routing\\Router->matchRequest()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3264): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php:2135\nStack trace:\n#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3264): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#7 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route_apache.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route_apache.json index a03f1921a1..d757b10341 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_to_missing_route_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "0371654f-098d-496a-ad8e-f1ea99fc59c6", "span.kind": "server", "symfony.route.action": "Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController@showAction" @@ -70,8 +70,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/appProdProjectContainerUrlMatcher.php:57", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1455): appProdProjectContainerUrlMatcher->match()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1318): Symfony\\Component\\Routing\\Matcher\\UrlMatcher->matchRequest()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(2119): Symfony\\Component\\Routing\\Router->matchRequest()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3265): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php:2135\nStack trace:\n#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3265): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#7 {main}", + "error.message": "Thrown Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/appProdProjectContainerUrlMatcher.php:57", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1455): appProdProjectContainerUrlMatcher->match()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1318): Symfony\\Component\\Routing\\Matcher\\UrlMatcher->matchRequest()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(2119): Symfony\\Component\\Routing\\Router->matchRequest()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3265): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#10 {main}\n\nNext Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException: No route found for \"GET /does_not_exist\" in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php:2135\nStack trace:\n#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1886): Symfony\\Component\\HttpKernel\\EventListener\\RouterListener->onKernelRequest()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/cache/prod/classes.php(1801): Symfony\\Component\\EventDispatcher\\EventDispatcher->doDispatch()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3265): Symfony\\Component\\EventDispatcher\\EventDispatcher->dispatch()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#7 {main}", "error.type": "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception.json index 5785921142..d5292f1bbf 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,12 +12,12 @@ "_dd.p.dm": "-0", "_dd.p.tid": "6660700e00000000", "component": "symfony", - "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3275): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", + "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3275): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/app.php/error?key=value&", + "http.url": "http://localhost/app.php/error?key=value&", "runtime-id": "4374c14f-def1-4b62-97c0-598f46322850", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@errorAction", @@ -111,8 +111,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3275): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", + "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3275): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3388): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception_apache.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception_apache.json index 80f9337a9e..d44ebe924d 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_exception_apache.json @@ -12,12 +12,12 @@ "_dd.p.dm": "-0", "_dd.p.tid": "6663129e00000000", "component": "symfony", - "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3277): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", + "error.message": "Uncaught Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3277): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", "error.type": "Exception", "http.method": "GET", "http.status_code": "500", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "686fd8bf-e7d3-47f9-bbae-eadd9fdb7bfd", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@errorAction", @@ -111,8 +111,8 @@ "error": 1, "meta": { "component": "symfony", - "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3277): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", + "error.message": "Thrown Exception (500): An exception occurred in /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/src/AppBundle/Controller/CommonScenariosController.php:40", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3277): AppBundle\\Controller\\CommonScenariosController->errorAction()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3234): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(3392): Symfony\\Component\\HttpKernel\\HttpKernel->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/app/bootstrap.php.cache(2594): Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_2_8/web/app.php(15): Symfony\\Component\\HttpKernel\\Kernel->handle()\n#5 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view.json index bc9c3810ab..37bea1d81a 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php/simple_view?key=value&", + "http.url": "http://localhost/app.php/simple_view?key=value&", "runtime-id": "a908b910-f481-44e7-bf7b-8d48954a4639", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleViewAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view_apache.json b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view_apache.json index 9723d0599f..d2b15a256e 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.common_scenarios_test.test_scenario_get_with_view_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "86589533-e0a0-485f-bc9a-135cbd0cf009", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\CommonScenariosController@simpleViewAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping.json b/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping.json index a9f8a874f9..f6586cf06a 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/app.php?key=value&", + "http.url": "http://localhost/app.php?key=value&", "runtime-id": "4b578cb2-1f1d-441e-aacb-a4aaff240713", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\DefaultController@testingRouteNameAction", diff --git a/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping_apache.json b/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping_apache.json index 9e79731fe6..ea4b9f9c97 100644 --- a/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping_apache.json +++ b/tests/snapshots/tests.integrations.symfony.v2_8.route_name_test.test_resource_to_uri_mapping_apache.json @@ -13,7 +13,7 @@ "component": "symfony", "http.method": "GET", "http.status_code": "200", - "http.url": "http://localhost:9999/?key=value&", + "http.url": "http://localhost/?key=value&", "runtime-id": "a57c3bd6-89e0-4682-b232-7fceb6a5d409", "span.kind": "server", "symfony.route.action": "AppBundle\\Controller\\DefaultController@testingRouteNameAction", diff --git a/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure.json b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure.json new file mode 100644 index 0000000000..3acc322d5a --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure.json @@ -0,0 +1,382 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_fail", + "trace_id": 0, + "span_id": 1, + "parent_id": 7459318832667968177, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "669786ad00000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/fail", + "runtime-id": "f776fad2-f39f-4b4f-81e6-9faafebfaaee", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@fail", + "symfony.route.name": "lucky_fail" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::fail", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 20, + "parent_id": 19, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 21, + "parent_id": 19, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 22, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 24, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.security.authentication.success", + "service": "symfony_messenger_test", + "resource": "symfony.security.authentication.success", + "trace_id": 0, + "span_id": 13, + "parent_id": 10, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure_consumer.json b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure_consumer.json new file mode 100644 index 0000000000..f5d5703f3f --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_failure_consumer.json @@ -0,0 +1,312 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 3836038585860194322, + "type": "queue", + "error": 1, + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "669786ad00000000", + "component": "symfonymessenger", + "error.message": "Uncaught Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(65): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#20 {main}\n\nNext Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82\nStack trace:\n#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "898812cf-eb15-4cac-a04e-fcd31464a1bc", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineReceivedStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:82", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(65): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(79): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(67): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(42): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/MessageBus.php(80): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/TraceableMessageBus.php(41): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\TraceableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(119): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Worker.php(82): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(211): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Command/Command.php(255): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(1039): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(97): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(275): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/framework-bundle/Console/Application.php(83): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/vendor/symfony/console/Application.php(149): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_4_4/bin/console(42): Symfony\\Component\\Console\\Application->run()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success.json b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success.json new file mode 100644 index 0000000000..00c70e16f6 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success.json @@ -0,0 +1,382 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_number", + "trace_id": 0, + "span_id": 1, + "parent_id": 5696153891440035761, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6697868300000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/number", + "runtime-id": "f776fad2-f39f-4b4f-81e6-9faafebfaaee", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@number", + "symfony.route.name": "lucky_number" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::number", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 20, + "parent_id": 19, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 21, + "parent_id": 19, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 22, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 24, + "parent_id": 21, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.security.authentication.success", + "service": "symfony_messenger_test", + "resource": "symfony.security.authentication.success", + "trace_id": 0, + "span_id": 13, + "parent_id": 10, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success_consumer.json b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success_consumer.json new file mode 100644 index 0000000000..3477157027 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v4_4.messenger_test.test_async_success_consumer.json @@ -0,0 +1,294 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 9153271162721253303, + "type": "queue", + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "6697868300000000", + "component": "symfonymessenger", + "messaging.destination": "Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineTransport", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "8242009e-441e-484d-b32b-0ababcffc2ae", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\HandledStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Transport\\Doctrine\\DoctrineReceivedStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\StackMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony44", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure.json b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure.json new file mode 100644 index 0000000000..0b776583b6 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure.json @@ -0,0 +1,370 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_fail", + "trace_id": 0, + "span_id": 1, + "parent_id": 17193270540770397716, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6696288c00000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/fail", + "runtime-id": "1705ac20-fbbf-4e9f-b538-5524ce6a6530", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@fail", + "symfony.route.name": "lucky_fail" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::fail", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure_consumer.json b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure_consumer.json new file mode 100644 index 0000000000..be3ba5ffcc --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_failure_consumer.json @@ -0,0 +1,390 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 11493428454241304998, + "type": "queue", + "error": 1, + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66966bf000000000", + "component": "symfonymessenger", + "error.message": "Uncaught Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(63): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#19 {main}\n\nNext Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80\nStack trace:\n#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "75c5d263-021f-463a-a643-473dd0d1066c", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 7, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:80", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(63): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(74): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(48): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(43): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/MessageBus.php(77): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(114): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Worker.php(77): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(197): Symfony\\Component\\Messenger\\Worker->run()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Command/Command.php(256): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(989): Symfony\\Component\\Console\\Command\\Command->run()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(96): Symfony\\Component\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(290): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/framework-bundle/Console/Application.php(82): Symfony\\Component\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/vendor/symfony/console/Application.php(166): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_5_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#19 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 8, + "parent_id": 4, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "3", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.redelivered_at": "2024-07-16T12:47:55+00:00", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\DelayStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\RedeliveryStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentToFailureTransportStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 2 + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 10, + "parent_id": 8, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 11, + "parent_id": 8, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "DELETE FROM messenger_messages WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "DELETE FROM messenger_messages WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success.json b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success.json new file mode 100644 index 0000000000..cb40b1ceb0 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success.json @@ -0,0 +1,370 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_number", + "trace_id": 0, + "span_id": 1, + "parent_id": 10088477474525139052, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "6696287200000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/number", + "runtime-id": "1705ac20-fbbf-4e9f-b538-5524ce6a6530", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@number", + "symfony.route.name": "lucky_number" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::number", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success_consumer.json b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success_consumer.json new file mode 100644 index 0000000000..d72b62ce4d --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v5_2.messenger_test.test_async_success_consumer.json @@ -0,0 +1,294 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 4317723020732676646, + "type": "queue", + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66966bd500000000", + "component": "symfonymessenger", + "messaging.destination": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "4a7cf583-c1ad-4d0d-84a0-61badf8e3764", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\HandledStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\StackMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "DELETE FROM messenger_messages WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "DELETE FROM messenger_messages WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony52", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure.json b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure.json new file mode 100644 index 0000000000..6d321a309c --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure.json @@ -0,0 +1,370 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_fail", + "trace_id": 0, + "span_id": 1, + "parent_id": 4742951813910861142, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "66c6f8e000000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/fail", + "runtime-id": "d07cb84d-0e78-4f60-bbe6-25954de69a8a", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@fail", + "symfony.route.name": "lucky_fail" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::fail", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure_consumer.json b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure_consumer.json new file mode 100644 index 0000000000..c157937452 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_failure_consumer.json @@ -0,0 +1,313 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 17504473594943785929, + "type": "queue", + "error": 1, + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66c6f8e000000000", + "component": "symfonymessenger", + "error.message": "Uncaught Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/src/MessageHandler/LuckyNumberNotificationHandler.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(157): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(96): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->callHandler()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#20 {main}\n\nNext Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129\nStack trace:\n#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "7421040e-f494-41ed-bd29-c4e60aa1d4e3", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\AckStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:129", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#18 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException: Number is out of bounds in /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/src/MessageHandler/LuckyNumberNotificationHandler.php:15", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(157): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(96): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->callHandler()\n#2 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(77): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#3 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#4 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#5 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#6 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(37): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#7 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(40): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#8 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#9 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#10 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(161): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Worker.php(110): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(229): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Command/Command.php(312): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(88): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(314): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/framework-bundle/Console/Application.php(77): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/vendor/symfony/console/Application.php(168): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/app/tests/Frameworks/Symfony/Version_6_2/bin/console(43): Symfony\\Component\\Console\\Application->run()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\UnrecoverableMessageHandlingException", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success.json b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success.json new file mode 100644 index 0000000000..8756997fe9 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success.json @@ -0,0 +1,370 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_number", + "trace_id": 0, + "span_id": 1, + "parent_id": 1821185153506234818, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "66c6f8cf00000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/number", + "runtime-id": "d07cb84d-0e78-4f60-bbe6-25954de69a8a", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@number", + "symfony.route.name": "lucky_number" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::number", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success_consumer.json b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success_consumer.json new file mode 100644 index 0000000000..c87d201249 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v6_2.messenger_test.test_async_success_consumer.json @@ -0,0 +1,295 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 2618824753639377523, + "type": "queue", + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66c6f8cf00000000", + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "3f23a876-79a6-4ac2-b1e9-5e25bed8870f", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\AckStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\HandledStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\StackMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony62", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure.json b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure.json new file mode 100644 index 0000000000..cb4b9bcd71 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure.json @@ -0,0 +1,391 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_fail", + "trace_id": 0, + "span_id": 1, + "parent_id": 8370649148870068342, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "66962c1d00000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/fail", + "runtime-id": "b0f51f16-e8af-4b82-9b0b-9c9ee45b6023", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@fail", + "symfony.route.name": "lucky_fail" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::fail", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "PDO.commit", + "service": "pdo", + "resource": "PDO.commit", + "trace_id": 0, + "span_id": 24, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure_consumer.json b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure_consumer.json new file mode 100644 index 0000000000..d619b31782 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_failure_consumer.json @@ -0,0 +1,413 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 17593395603432814175, + "type": "queue", + "error": 1, + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66962c1d00000000", + "component": "symfonymessenger", + "error.message": "Uncaught OutOfBoundsException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(152): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(91): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->callHandler()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#20 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#21 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#22 {main}\n\nNext Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124\nStack trace:\n#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "OutOfBoundsException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "69f83ee5-2540-4254-8e9e-06ccaa49330e", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\AckStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 7, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 9, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown Symfony\\Component\\Messenger\\Exception\\HandlerFailedException: Handling \"App\\Message\\LuckyNumberNotification\" failed: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php:124", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#20 {main}", + "error.type": "Symfony\\Component\\Messenger\\Exception\\HandlerFailedException", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "2", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "error": 1, + "meta": { + "component": "symfonymessenger", + "error.message": "Thrown OutOfBoundsException: Number is out of bounds in /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/src/MessageHandler/LuckyNumberNotificationHandler.php:14", + "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(152): App\\MessageHandler\\LuckyNumberNotificationHandler->__invoke()\n#1 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/HandleMessageMiddleware.php(91): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->callHandler()\n#2 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/SendMessageMiddleware.php(71): Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware->handle()\n#3 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/FailedMessageProcessingMiddleware.php(34): Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware->handle()\n#4 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/DispatchAfterCurrentBusMiddleware.php(68): Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware->handle()\n#5 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/RejectRedeliveredMessageMiddleware.php(41): Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware->handle()\n#6 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/AddBusNameStampMiddleware.php(35): Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware->handle()\n#7 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Middleware/TraceableMiddleware.php(36): Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware->handle()\n#8 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/MessageBus.php(70): Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware->handle()\n#9 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/RoutableMessageBus.php(54): Symfony\\Component\\Messenger\\MessageBus->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(162): Symfony\\Component\\Messenger\\RoutableMessageBus->dispatch()\n#11 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Worker.php(109): Symfony\\Component\\Messenger\\Worker->handleMessage()\n#12 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php(244): Symfony\\Component\\Messenger\\Worker->run()\n#13 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Command/Command.php(279): Symfony\\Component\\Messenger\\Command\\ConsumeMessagesCommand->execute()\n#14 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(1049): Symfony\\Component\\Console\\Command\\Command->run()\n#15 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(125): Symfony\\Component\\Console\\Application->doRunCommand()\n#16 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(318): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRunCommand()\n#17 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/framework-bundle/Console/Application.php(79): Symfony\\Component\\Console\\Application->doRun()\n#18 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/console/Application.php(169): Symfony\\Bundle\\FrameworkBundle\\Console\\Application->doRun()\n#19 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php(49): Symfony\\Component\\Console\\Application->run()\n#20 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/vendor/autoload_runtime.php(29): Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner->run()\n#21 /home/circleci/datadog/tests/Frameworks/Symfony/Version_7_0/bin/console(11): require_once()\n#22 {main}", + "error.type": "OutOfBoundsException", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageFailedEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 8, + "parent_id": 4, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "3", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.redelivered_at": "2024-07-16T08:15:31+00:00", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\AckStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\DelayStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\RedeliveryStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentToFailureTransportStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 2 + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 10, + "parent_id": 8, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 11, + "parent_id": 8, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }, + { + "name": "PDO.commit", + "service": "pdo", + "resource": "PDO.commit", + "trace_id": 0, + "span_id": 12, + "parent_id": 8, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success.json b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success.json new file mode 100644 index 0000000000..9831de4280 --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success.json @@ -0,0 +1,391 @@ +[[ + { + "name": "symfony.request", + "service": "symfony_messenger_test", + "resource": "lucky_number", + "trace_id": 0, + "span_id": 1, + "parent_id": 1531364475549898567, + "type": "web", + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "66962c0800000000", + "component": "symfony", + "http.method": "GET", + "http.status_code": "200", + "http.url": "http://localhost/lucky/number", + "runtime-id": "b0f51f16-e8af-4b82-9b0b-9c9ee45b6023", + "span.kind": "server", + "symfony.route.action": "App\\Controller\\LuckyController@number", + "symfony.route.name": "lucky_number" + }, + "metrics": { + "_sampling_priority_v1": 1.0 + } + }, + { + "name": "symfony.httpkernel.kernel.handle", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony", + "span.kind": "server" + } + }, + { + "name": "symfony.httpkernel.kernel.boot", + "service": "symfony_messenger_test", + "resource": "App\\Kernel", + "trace_id": 0, + "span_id": 4, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.handle", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.handle", + "trace_id": 0, + "span_id": 5, + "parent_id": 2, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.request", + "trace_id": 0, + "span_id": 6, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller", + "trace_id": 0, + "span_id": 7, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.controller_arguments", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.controller_arguments", + "trace_id": 0, + "span_id": 8, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.controller", + "service": "symfony_messenger_test", + "resource": "App\\Controller\\LuckyController::number", + "trace_id": 0, + "span_id": 9, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 12, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 14, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 16, + "parent_id": 15, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 17, + "parent_id": 16, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 18, + "parent_id": 17, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\SendMessageToTransportsEvent", + "trace_id": 0, + "span_id": 19, + "parent_id": 18, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.send", + "service": "symfony_messenger_test", + "resource": "App\\Message\\LuckyNumberNotification -> async", + "trace_id": 0, + "span_id": 20, + "parent_id": 18, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "send", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.symfony.sender": "Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineTransport", + "messaging.system": "symfony", + "span.kind": "producer" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\SentStamp": 1.0, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1.0 + } + }, + { + "name": "PDO.__construct", + "service": "pdo", + "resource": "PDO.__construct", + "trace_id": 0, + "span_id": 21, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 22, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "INSERT INTO messenger_messages (body, headers, queue_name, created_at, available_at) VALUES(?, ?, ?, ?, ?)", + "trace_id": 0, + "span_id": 23, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1.0 + } + }, + { + "name": "PDO.commit", + "service": "pdo", + "resource": "PDO.commit", + "trace_id": 0, + "span_id": 24, + "parent_id": 20, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "symfony.kernel.response", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.response", + "trace_id": 0, + "span_id": 10, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.finish_request", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.finish_request", + "trace_id": 0, + "span_id": 11, + "parent_id": 5, + "type": "web", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.kernel.terminate", + "service": "symfony_messenger_test", + "resource": "symfony.kernel.terminate", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "web", + "meta": { + "component": "symfony" + } + }]] diff --git a/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success_consumer.json b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success_consumer.json new file mode 100644 index 0000000000..5a3d171b3b --- /dev/null +++ b/tests/snapshots/tests.integrations.symfony.v7_0.messenger_test.test_async_success_consumer.json @@ -0,0 +1,295 @@ +[[ + { + "name": "symfony.messenger.consume", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 1, + "parent_id": 15105530756925232482, + "type": "queue", + "meta": { + "_dd.p.dm": "0", + "_dd.p.tid": "66962c0800000000", + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "receive", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony", + "runtime-id": "183d43c7-590d-426c-bb3d-719483290c51", + "span.kind": "consumer" + }, + "metrics": { + "_sampling_priority_v1": 1 + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageReceivedEvent", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "symfony.messenger.dispatch", + "service": "symfony_messenger_test", + "resource": "async -> App\\Message\\LuckyNumberNotification", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + }, + "metrics": { + "messaging.symfony.stamps.DDTrace\\Integrations\\SymfonyMessenger\\DDTraceStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Bridge\\Doctrine\\Transport\\DoctrineReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\AckStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\BusNameStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ConsumedByWorkerStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\HandledStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp": 1, + "messaging.symfony.stamps.Symfony\\Component\\Messenger\\Stamp\\TransportMessageIdStamp": 1 + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\TraceableMiddleware", + "trace_id": 0, + "span_id": 7, + "parent_id": 3, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\AddBusNameStampMiddleware", + "trace_id": 0, + "span_id": 8, + "parent_id": 7, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\RejectRedeliveredMessageMiddleware", + "trace_id": 0, + "span_id": 9, + "parent_id": 8, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\DispatchAfterCurrentBusMiddleware", + "trace_id": 0, + "span_id": 10, + "parent_id": 9, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\FailedMessageProcessingMiddleware", + "trace_id": 0, + "span_id": 11, + "parent_id": 10, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware", + "trace_id": 0, + "span_id": 12, + "parent_id": 11, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware", + "trace_id": 0, + "span_id": 13, + "parent_id": 12, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.handle", + "service": "symfony_messenger_test", + "resource": "App\\MessageHandler\\LuckyNumberNotificationHandler", + "trace_id": 0, + "span_id": 14, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination_kind": "queue", + "messaging.operation": "process", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.messenger.middleware", + "service": "symfony_messenger_test", + "resource": "Symfony\\Component\\Messenger\\Middleware\\StackMiddleware", + "trace_id": 0, + "span_id": 15, + "parent_id": 13, + "type": "queue", + "meta": { + "component": "symfonymessenger", + "messaging.destination": "async", + "messaging.destination_kind": "queue", + "messaging.message_id": "1", + "messaging.operation": "process", + "messaging.symfony.bus": "messenger.bus.default", + "messaging.symfony.handler": "App\\MessageHandler\\LuckyNumberNotificationHandler::__invoke", + "messaging.symfony.message": "App\\Message\\LuckyNumberNotification", + "messaging.system": "symfony" + } + }, + { + "name": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "service": "symfony_messenger_test", + "resource": "symfony.Symfony\\Component\\Messenger\\Event\\WorkerMessageHandledEvent", + "trace_id": 0, + "span_id": 4, + "parent_id": 1, + "type": "queue", + "meta": { + "component": "symfony" + } + }, + { + "name": "PDO.prepare", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 5, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + } + }, + { + "name": "PDOStatement.execute", + "service": "pdo", + "resource": "UPDATE messenger_messages SET delivered_at = ? WHERE id = ?", + "trace_id": 0, + "span_id": 6, + "parent_id": 1, + "type": "sql", + "meta": { + "_dd.base_service": "symfony_messenger_test", + "component": "pdo", + "db.charset": "utf8mb4", + "db.engine": "mysql", + "db.name": "symfony70", + "db.system": "mysql", + "db.user": "test", + "out.host": "mysql_integration", + "out.port": "3306", + "span.kind": "client" + }, + "metrics": { + "db.row_count": 1 + } + }]] diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 56149bed8d..a94467e5ad 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "b4ee1995-4afb-4457-9e9d-b361460bfa16", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 27245bd3bb..2adc90fcde 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e7019100000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "b4ee1995-4afb-4457-9e9d-b361460bfa16", "span.kind": "server" }, @@ -400,8 +400,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -428,8 +428,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 0294b312db..4cea0bce0d 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "(.?.+?)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "b4ee1995-4afb-4457-9e9d-b361460bfa16", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json index 123707ce63..34e87afcbd 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "8fdcf6ef-7cd9-4910-b426-c7c9809f3dd4", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json index a804c59ea9..9879dbab07 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e701c900000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "8fdcf6ef-7cd9-4910-b426-c7c9809f3dd4", "span.kind": "server" }, @@ -248,8 +248,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -276,8 +276,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json index afea354d76..3da297ba27 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "(.?.+?)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "8fdcf6ef-7cd9-4910-b426-c7c9809f3dd4", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 68f0766b86..5fe007192c 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "4ad4333f-2e0b-4278-a6f7-2182e7771b34", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json index 3b8d8a4ee4..2f1cd43bf7 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "wordpress", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "4ad4333f-2e0b-4278-a6f7-2182e7771b34", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 9fe8f0edfc..c99f52c4c2 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e7011700000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "4ad4333f-2e0b-4278-a6f7-2182e7771b34", "span.kind": "server" }, @@ -680,8 +680,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -708,8 +708,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 300851b9bc..f74b08cfaf 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "4ad4333f-2e0b-4278-a6f7-2182e7771b34", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json index 839b9656bb..b2cc305f4f 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "f188c752-a672-4955-97f7-e41a31d13fe7", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json index 788a941f34..80cf347caa 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "wordpress", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "f188c752-a672-4955-97f7-e41a31d13fe7", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json index b884cb598d..f3e3533156 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e7016000000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "f188c752-a672-4955-97f7-e41a31d13fe7", "span.kind": "server" }, @@ -248,8 +248,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -276,8 +276,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json index 492176538a..ca8d5fe134 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "f188c752-a672-4955-97f7-e41a31d13fe7", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 216092a205..ebfef74a26 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "896f86bc-7139-44f3-a99f-ed35e643f726", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json index 9f7a269e17..07c0e83a94 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "wordpress", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "896f86bc-7139-44f3-a99f-ed35e643f726", "span.kind": "server" }, @@ -2065,7 +2065,7 @@ "parent_id": 124, "type": "web", "meta": { - "closure.declaration": "/home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/block-supports/duotone.php:464", + "closure.declaration": "/home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/block-supports/duotone.php:464", "component": "wordpress", "wordpress.callback": "Closure", "wordpress.hook": "wp_footer" diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 87568082bb..1e4f677a7f 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e7000f00000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "896f86bc-7139-44f3-a99f-ed35e643f726", "span.kind": "server" }, @@ -1226,8 +1226,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -1254,8 +1254,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 719184349d..26b4263431 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "896f86bc-7139-44f3-a99f-ed35e643f726", "span.kind": "server" }, @@ -2143,7 +2143,7 @@ "parent_id": 130, "type": "web", "meta": { - "closure.declaration": "/home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/block-supports/duotone.php:464", + "closure.declaration": "/home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/block-supports/duotone.php:464", "component": "wordpress", "wordpress.callback": "Closure", "wordpress.hook": "wp_footer" diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json index 945e9fe194..a2f281d70d 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "4c46007f-c934-41aa-bcbe-c48ecee2d4cc", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json index 1cd65d6f3b..29e5c09037 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -13,7 +13,7 @@ "component": "wordpress", "http.method": "GET", "http.status_code": "404", - "http.url": "http://localhost:9999/does_not_exist?key=value&", + "http.url": "http://localhost/does_not_exist?key=value&", "runtime-id": "4c46007f-c934-41aa-bcbe-c48ecee2d4cc", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json index 083ca48221..2efffe0509 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e7005c00000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "4c46007f-c934-41aa-bcbe-c48ecee2d4cc", "span.kind": "server" }, @@ -248,8 +248,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -276,8 +276,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json index d281b5f8ec..671eb3f7d1 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "4c46007f-c934-41aa-bcbe-c48ecee2d4cc", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 1bbbc80ab1..e6d2db3849 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "333590aa-cf9b-4804-9dde-1ac7b59c09ab", "span.kind": "server" }, @@ -1459,7 +1459,7 @@ "parent_id": 10, "type": "web", "meta": { - "closure.declaration": "/home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", + "closure.declaration": "/home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", "component": "wordpress", "wordpress.callback": "Closure", "wordpress.hook": "wp_loaded" diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 1584ac8565..031cdd1f31 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e6feb000000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "333590aa-cf9b-4804-9dde-1ac7b59c09ab", "span.kind": "server" }, @@ -1463,7 +1463,7 @@ "parent_id": 10, "type": "web", "meta": { - "closure.declaration": "/home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", + "closure.declaration": "/home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", "component": "wordpress", "wordpress.callback": "Closure", "wordpress.hook": "wp_loaded" @@ -1494,8 +1494,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -1522,8 +1522,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json index c93632d63a..19747ed578 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "333590aa-cf9b-4804-9dde-1ac7b59c09ab", "span.kind": "server" }, @@ -1459,7 +1459,7 @@ "parent_id": 10, "type": "web", "meta": { - "closure.declaration": "/home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", + "closure.declaration": "/home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/script-loader.php:3398", "component": "wordpress", "wordpress.callback": "Closure", "wordpress.hook": "wp_loaded" diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json index b8a960127b..cfbeb419d9 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple?key=value&", + "http.url": "http://localhost/simple?key=value&", "runtime-id": "df54db4d-0cc0-4b1c-9fce-8004a54aa78b", "span.kind": "server" }, diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json index b689214e37..b71f520f3c 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json @@ -12,13 +12,13 @@ "_dd.p.dm": "-0", "_dd.p.tid": "65e6ff0700000000", "component": "wordpress", - "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Uncaught Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception", "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/error?key=value&", + "http.url": "http://localhost/error?key=value&", "runtime-id": "df54db4d-0cc0-4b1c-9fce-8004a54aa78b", "span.kind": "server" }, @@ -235,8 +235,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, @@ -263,8 +263,8 @@ "error": 1, "meta": { "component": "wordpress", - "error.message": "Thrown Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", - "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", + "error.message": "Thrown Exception: Oops! in /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", + "error.stack": "#0 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/app/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", "error.type": "Exception" } }, diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json index 50c224d266..4b9c200068 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json @@ -14,7 +14,7 @@ "http.method": "GET", "http.route": "([^/]+)(?:/([0-9]+))?/?$", "http.status_code": "200", - "http.url": "http://localhost:9999/simple_view?key=value&", + "http.url": "http://localhost/simple_view?key=value&", "runtime-id": "df54db4d-0cc0-4b1c-9fce-8004a54aa78b", "span.kind": "server" }, diff --git a/tests/xdebug/2.7.2/self_disable_php_7.0.phpt b/tests/xdebug/2.7.2/self_disable_php_7.0.phpt index ca2cae91ad..d8a32bbac1 100644 --- a/tests/xdebug/2.7.2/self_disable_php_7.0.phpt +++ b/tests/xdebug/2.7.2/self_disable_php_7.0.phpt @@ -11,5 +11,6 @@ if (!extension_loaded('Xdebug')) die('skip: Xdebug required'); echo 'Done.' . PHP_EOL; ?> --EXPECTF-- -[ddtrace] [error] Found incompatible Xdebug version %s; disabling conflicting functionality +[ddtrace] [error] Found incompatible Xdebug version %s +[ddtrace] [error] Found incompatible extension(s); disabling conflicting functionality Done. diff --git a/tests/xdebug/2.9.2/force_inject.phpt b/tests/xdebug/2.9.2/force_inject.phpt new file mode 100644 index 0000000000..b735f5fe22 --- /dev/null +++ b/tests/xdebug/2.9.2/force_inject.phpt @@ -0,0 +1,18 @@ +--TEST-- +The tracer will ignore incompatible extensions +--SKIPIF-- + +--INI-- +xdebug.remote_enable=1 +datadog.inject_force=1 +datadog.trace.log_level=warn +--FILE-- += 0) die('Xdebug < 2.9.5 required'); + +echo 'Done.' . PHP_EOL; +?> +--EXPECTF-- +[ddtrace] [warning] Found incompatible Xdebug version %s; ddtrace requires Xdebug 2.9.5 or greater +[ddtrace] [warning] Found incompatible extension(s); ignoring since 'datadog.inject_force' is enabled +Done. diff --git a/tests/xdebug/2.9.2/self_disable.phpt b/tests/xdebug/2.9.2/self_disable.phpt index b40eaaf1ad..a6fe0fae32 100644 --- a/tests/xdebug/2.9.2/self_disable.phpt +++ b/tests/xdebug/2.9.2/self_disable.phpt @@ -11,5 +11,6 @@ if (!extension_loaded('Xdebug') || version_compare(phpversion('Xdebug'), '2.9.5' echo 'Done.' . PHP_EOL; ?> --EXPECTF-- -[ddtrace] [error] Found incompatible Xdebug version %s; ddtrace requires Xdebug 2.9.5 or greater; disabling conflicting functionality +[ddtrace] [error] Found incompatible Xdebug version %s; ddtrace requires Xdebug 2.9.5 or greater +[ddtrace] [error] Found incompatible extension(s); disabling conflicting functionality Done. diff --git a/tests/xdebug/2.9.2/startup_logging_diagnostics.phpt b/tests/xdebug/2.9.2/startup_logging_diagnostics.phpt index 27599a5ff3..f639637a3e 100644 --- a/tests/xdebug/2.9.2/startup_logging_diagnostics.phpt +++ b/tests/xdebug/2.9.2/startup_logging_diagnostics.phpt @@ -18,4 +18,4 @@ if (!isset($logs['incompatible module xdebug'])) { echo 'Log: ' . $logs['incompatible module xdebug'] . PHP_EOL; ?> --EXPECT-- -Log: Found incompatible Xdebug version 2.9.2; ddtrace requires Xdebug 2.9.5 or greater; disabling conflicting functionality +Log: Found incompatible Xdebug version 2.9.2; ddtrace requires Xdebug 2.9.5 or greater diff --git a/tests/xdebug/3.0.0/line-breakpoint.inc b/tests/xdebug/3.0.0/line-breakpoint.inc new file mode 100644 index 0000000000..505c5f3f11 --- /dev/null +++ b/tests/xdebug/3.0.0/line-breakpoint.inc @@ -0,0 +1,17 @@ +log(); +echo "post-hook\n"; diff --git a/tests/xdebug/3.0.0/line-breakpoint.phpt b/tests/xdebug/3.0.0/line-breakpoint.phpt new file mode 100644 index 0000000000..e1d61b2872 --- /dev/null +++ b/tests/xdebug/3.0.0/line-breakpoint.phpt @@ -0,0 +1,55 @@ +--TEST-- +Line breakpoint on interface-hook +--FILE-- + "xdebug-" . phpversion('xdebug'), "datadog.trace.sources_path" => __DIR__ . "/..", "datadog.logs_injection" => 0], ["show-stdout" => true]); + +?> +--EXPECTF-- + + + +-> feature_set -i 1 -n resolved_breakpoint -v 1 + + + +-> step_into -i 2 + + + +-> breakpoint_set -i 3 -t line -f %s/line-breakpoint.inc -n 15 + + + +-> breakpoint_set -i 4 -t line -f %s/line-breakpoint.inc -n 17 + + + +-> run -i 5 + +%S + +-> run -i 6 + +%S + +-> detach -i 7 + + + +pre-hook +hey +post-hook diff --git a/tests/xdebug/DDTrace/Integrations/Logs/LogsIntegration.php b/tests/xdebug/DDTrace/Integrations/Logs/LogsIntegration.php new file mode 100644 index 0000000000..25dd6fef32 --- /dev/null +++ b/tests/xdebug/DDTrace/Integrations/Logs/LogsIntegration.php @@ -0,0 +1,10 @@ +port; + } + + public function setPort($port) + { + $this->port = $port; + } + + protected function getIPAddress() + { + return "127.0.0.1"; + } + + protected function getAddress() + { + return "tcp://" . $this->getIPAddress() . ":" . $this->getPort(); + } + + public function __construct() + { + $this->tmpDir = getTmpDir(); + } + + private function open( &$errno, &$errstr ) + { + $socket = @stream_socket_server( $this->getAddress(), $errno, $errstr ); + if ( $socket ) + { + $name = stream_socket_get_name( $socket, false ); + $name = explode( ":", $name ); + $this->port = array_pop( $name ); + } + return $socket; + } + + private function launchPhp( &$pipes, $filename, array $ini_options = [], array $extra_options = [] ) + { + @unlink( $this->tmpDir . 'error-output.txt' ); + @unlink( $this->tmpDir . 'remote_log.txt' ); + + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'pipe', 'w' ), + 2 => array( 'file', $this->tmpDir . 'error-output.txt', 'a' ) + ); + + $default_options = array( + "xdebug.mode" => "debug", + "xdebug.start_with_request" => "'yes'", + "xdebug.client_host" => $this->getIPAddress(), + "xdebug.client_port" => $this->getPort(), + 'xdebug.control_socket' => "'no'", + ); + + $env_vars = array_key_exists( 'env', $extra_options ) ? $extra_options['env'] : []; + $env_vars += $_ENV; + + $options = (getenv('TEST_PHP_ARGS') ?: ''); + $ini_options = array_merge( $default_options, $ini_options ); + foreach ( $ini_options as $key => $value ) + { + $options .= " -d{$key}=$value"; + } + + if ( array_key_exists( 'auto_prepend', $extra_options ) ) + { + $prependFile = "{$this->tmpDir}auto-prepend.inc"; + file_put_contents( $prependFile, $extra_options['auto_prepend'] ); + $options .= " -dauto_prepend_file={$prependFile}"; + } + + $php = getenv( 'TEST_PHP_EXECUTABLE' ); + $cmd = "{$php} $options {$filename} >{$this->tmpDir}php-stdout.txt 2>{$this->tmpDir}php-stderr.txt"; + if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + $cmd = "exec {$cmd}"; + } + $cwd = dirname( __FILE__ ); + + $process = proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env_vars ); + return $process; + } + + function fixFilePath( $m ) + { + preg_match( '@.*/(.*\.inc)@', $m[2], $fm ); + if ( !isset( $fm[1] ) ) + { + $fm[1] = ''; + } + return " {$m[1]}=\"file://{$fm[1]}\""; + } + + function doRead( $conn, ?string $transaction_id = null ) + { + stream_set_timeout( $conn, 3 ); + do { + $trans_id = null; + $length = 0; + while ( "\0" !== ( $char = fgetc($conn) ) ) { + if ( $char === false ) { + echo "read a false for $transaction_id" . PHP_EOL; + return null; + } + if ( !is_numeric($char) ) { + echo "read a non-number for $transaction_id" . PHP_EOL; + return null; + } + $length = $length * 10 + (int)$char; + } + + $read = ''; + while ( 0 < $length ) { + $data = fread( $conn, $length ); + if ( $data === false ) + { + echo "read a false for $transaction_id" . PHP_EOL; + return null; + } + $length -= strlen( $data ); + $read .= $data; + } + $char = fgetc( $conn ); + if ( $char !== "\0" ) + { + echo 'must end with \0' . PHP_EOL; + } + + // sanitize + $read = preg_replace( '@(?\d+)"@', $read, $matches ) ) + { + $trans_id = $matches['transaction_id'] ?? null; + } + } while ( $trans_id !== $transaction_id ); + } + + function start( $filename, array $ini_options = [], array $options = []) + { + $filename = realpath( $filename ); + + $this->socket = $this->open( $errno, $errstr ); + if ( $this->socket === false ) + { + echo "Could not create socket server - already in use?\n"; + echo "Error: {$errstr}, errno: {$errno}\n"; + echo "Address: {$this->getAddress()}\n"; + return false; + } + $this->php = $this->launchPhp( $this->ppipes, $filename, $ini_options, $options ); + $conn = @stream_socket_accept( $this->socket, isset( $options['timeout'] ) ? $options['timeout'] : 5 ); + + if ( $conn === false ) + { + echo @file_get_contents( $this->tmpDir . 'php-stdout.txt' ), "\n"; + echo @file_get_contents( $this->tmpDir . 'php-stderr.txt' ), "\n"; + echo @file_get_contents( $this->tmpDir . 'error-output.txt' ), "\n"; + echo @file_get_contents( $this->tmpDir . 'remote_log.txt' ), "\n"; + proc_close( $this->php ); + return false; + } + return $conn; + } + + function stop( $conn, array $options = [] ) + { + fclose( $conn ); + fclose( $this->ppipes[0] ); + fclose( $this->ppipes[1] ); + fclose( $this->socket ); + proc_close( $this->php ); + + if ( array_key_exists( 'show-stdout', $options ) && $options['show-stdout'] ) + { + echo @file_get_contents( $this->tmpDir . 'php-stdout.txt' ), "\n"; + } + // echo @file_get_contents( $this->tmpDir . 'php-stderr.txt' ), "\n"; + // echo @file_get_contents( $this->tmpDir . 'error-output.txt' ), "\n"; + } + + function sendCommand( $conn, $command, $transaction_id ) + { + // inject identifier + $parts = explode( ' ', $command, 2 ); + if ( count($parts) == 1 ) + { + $command = $parts[0] . " -i $transaction_id"; + } + else + { + $command = $parts[0] . " -i $transaction_id " . $parts[1]; + } + + /* Replace PID macro */ + $command = str_replace( "{{PID}}", $this->pid & 0x1ffff, $command ); + + $sanitised = $command; + $sanitised = preg_replace( '@\sfile://.*[/\\\\](.*\.inc)\s@', ' file://\\1 ', $sanitised ); + + echo "-> ", $sanitised, "\n"; + fwrite( $conn, $command . "\0" ); + } + + function runTest( $filename, array $commands, array $ini_options = [], array $options = [] ) + { + $conn = $this->start( $filename, $ini_options, $options ); + if ( $conn === false ) + { + return; + } + $i = 1; + $procInfo = proc_get_status( $this->php ); + $this->pid = $procInfo['pid']; + + // read header + $this->doRead( $conn ); + foreach ( $commands as $command ) + { + $this->sendCommand( $conn, $command, $i ); + $this->doRead( $conn, (string)$i ); + + $i++; + } + $this->stop( $conn, $options ); + } +} + +class DebugClientIPv6 extends DebugClient +{ + protected function getIPAddress() + { + return "::1"; + } + + protected function getAddress() + { + return "tcp://[" . $this->getIPAddress() . "]:" . $this->getPort(); + } + + public static function isSupported( &$errno, &$errstr ) + { + $socket = @stream_socket_server( "tcp://[::1]:0", $errno, $errstr ); + + if ( $socket === false ) + { + return false; + } + + fclose( $socket ); + return true; + } +} + +function dbgpRunFile( $data, $commands, array $ini_options = [], array $options = [] ) +{ + if ( isset( $options['ipv'] ) && $options['ipv'] == 6 ) + { + $t = new DebugClientIPv6(); + } + else + { + $t = new DebugClient(); + } + + $t->runTest( $data, $commands, $ini_options, $options ); +} +?> \ No newline at end of file diff --git a/tooling/bin/generate-final-artifact.sh b/tooling/bin/generate-final-artifact.sh index d2ea5f2525..7170826cc9 100755 --- a/tooling/bin/generate-final-artifact.sh +++ b/tooling/bin/generate-final-artifact.sh @@ -133,14 +133,14 @@ for architecture in "${architectures[@]}"; do done # Helper - mkdir -p "${tmp_folder_final_gnu_appsec}/bin" "${tmp_folder_final_musl_appsec}/bin" + mkdir -p "${tmp_folder_final_gnu_appsec}/lib" "${tmp_folder_final_musl_appsec}/lib" cp \ - "./appsec_${architecture}/ddappsec-helper" \ - "${tmp_folder_final_gnu_appsec}/bin/ddappsec-helper" + "./appsec_${architecture}/libddappsec-helper.so" \ + "${tmp_folder_final_gnu_appsec}/lib/libddappsec-helper.so" cp \ - "./appsec_${architecture}/ddappsec-helper" \ - "${tmp_folder_final_musl_appsec}/bin/ddappsec-helper" + "./appsec_${architecture}/libddappsec-helper.so" \ + "${tmp_folder_final_musl_appsec}/lib/libddappsec-helper.so" # Recommended rules mkdir -p "${tmp_folder_final_gnu_appsec}/etc" "${tmp_folder_final_musl_appsec}/etc" diff --git a/tooling/bin/generate-ssi-package.sh b/tooling/bin/generate-ssi-package.sh index 3406e6596a..6d60d84f2c 100755 --- a/tooling/bin/generate-ssi-package.sh +++ b/tooling/bin/generate-ssi-package.sh @@ -62,6 +62,7 @@ for architecture in "${architectures[@]}"; do cp -r ./src ${trace}/ echo "$release_version_sanitized" > ${root}/version + ln ./loader/packaging/requirements.json ${root}/requirements.json ######################## # Final archives diff --git a/zend_abstract_interface/CMakeLists.txt b/zend_abstract_interface/CMakeLists.txt index e5ae6a1ded..f100b0285f 100644 --- a/zend_abstract_interface/CMakeLists.txt +++ b/zend_abstract_interface/CMakeLists.txt @@ -66,6 +66,9 @@ add_subdirectory(env) add_subdirectory(exceptions) add_subdirectory(config) add_subdirectory(json) +if(PhpConfig_VERNUM GREATER_EQUAL "80000") + add_subdirectory(jit_utils) +endif() add_subdirectory(symbols) add_subdirectory(hook) add_subdirectory(interceptor) diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index 196d577b34..6f57b0ed96 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -232,8 +232,9 @@ void zai_config_rinit(void) { void zai_config_rshutdown(void) { zai_config_runtime_config_dtor(); } -bool zai_config_system_ini_change(zval *old_value, zval *new_value) { +bool zai_config_system_ini_change(zval *old_value, zval *new_value, zend_string *new_str) { (void)old_value; (void)new_value; + (void)new_str; return false; } diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index e1fbeee065..7b2f48d750 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -216,7 +216,7 @@ static ZEND_INI_MH(ZaiConfigOnUpdateIni) { return SUCCESS; } - if (memoized->ini_change && !memoized->ini_change(zai_config_get_value(id), &new_zv)) { + if (memoized->ini_change && !memoized->ini_change(zai_config_get_value(id), &new_zv, new_value)) { zval_dtor(&new_zv); return FAILURE; } diff --git a/zend_abstract_interface/config/config_ini.h b/zend_abstract_interface/config/config_ini.h index dcea28a1af..fb253295a9 100644 --- a/zend_abstract_interface/config/config_ini.h +++ b/zend_abstract_interface/config/config_ini.h @@ -34,9 +34,9 @@ int16_t zai_config_initialize_ini_value(zend_ini_entry **entries, zai_str default_value, zai_config_id entry_id); -typedef bool (*zai_config_apply_ini_change)(zval *old_value, zval *new_value); +typedef bool (*zai_config_apply_ini_change)(zval *old_value, zval *new_value, zend_string *new_str); typedef bool (*zai_env_config_fallback)(zai_env_buffer buf, bool pre_rinit); -bool zai_config_system_ini_change(zval *old_value, zval *new_value); +bool zai_config_system_ini_change(zval *old_value, zval *new_value, zend_string *new_str); bool zai_config_is_modified(zai_config_id entry_id); diff --git a/zend_abstract_interface/exceptions/exceptions.c b/zend_abstract_interface/exceptions/exceptions.c index 166829e678..107ab0a317 100644 --- a/zend_abstract_interface/exceptions/exceptions.c +++ b/zend_abstract_interface/exceptions/exceptions.c @@ -18,6 +18,7 @@ #define ZEND_STR_TYPE "type" #define ZEND_STR_FUNCTION "function" #define ZEND_STR_TRACE "trace" +#define ZEND_STR_MESSAGE "message" #define zend_hash_find(ht, name) zend_hash_str_find(ht, ZEND_STRL(name)) #define ZSTR_KNOWN(id) id diff --git a/zend_abstract_interface/exceptions/exceptions.h b/zend_abstract_interface/exceptions/exceptions.h index 6122d214ed..aa06dde378 100644 --- a/zend_abstract_interface/exceptions/exceptions.h +++ b/zend_abstract_interface/exceptions/exceptions.h @@ -12,10 +12,7 @@ static inline zend_class_entry *zai_get_exception_base(zend_object *object) { return instanceof_function(object->ce, zend_ce_exception) ? zend_ce_exception : zend_ce_error; } -#if PHP_VERSION_ID < 70100 -#define ZEND_STR_MESSAGE "message" -#define ZEND_STR_CODE "code" -static inline zval *zai_exception_read_property(zend_object *object, const char *pn, size_t pnl) { +static inline zval *zai_exception_read_property_str(zend_object *object, const char *pn, size_t pnl) { zval zv; ZVAL_OBJ(&zv, object); @@ -28,8 +25,7 @@ static inline zval *zai_exception_read_property(zend_object *object, const char return property; } -#define ZAI_EXCEPTION_PROPERTY(object, id) zai_exception_read_property(object, ZEND_STRL(id)) -#else + static inline zval *zai_exception_read_property(zend_object *object, zend_string *name) { zval zv; @@ -43,12 +39,14 @@ static inline zval *zai_exception_read_property(zend_object *object, zend_string return property; } -#if PHP_VERSION_ID < 70200 + +#if PHP_VERSION_ID < 70100 +#define ZAI_EXCEPTION_PROPERTY(object, id) zai_exception_read_property_str(object, ZEND_STRL(id)) +#elif PHP_VERSION_ID < 70200 #define ZAI_EXCEPTION_PROPERTY(object, id) zai_exception_read_property(object, CG(known_strings)[id]) #else #define ZAI_EXCEPTION_PROPERTY(object, id) zai_exception_read_property(object, ZSTR_KNOWN(id)) #endif -#endif zend_string *zai_exception_message(zend_object *ex); // fallback string if message invalid zend_string *zai_get_trace_without_args(zend_array *trace); diff --git a/zend_abstract_interface/hook/CMakeLists.txt b/zend_abstract_interface/hook/CMakeLists.txt index e8f4b2ca8c..c96ca3932a 100644 --- a/zend_abstract_interface/hook/CMakeLists.txt +++ b/zend_abstract_interface/hook/CMakeLists.txt @@ -7,6 +7,9 @@ target_include_directories( target_compile_features(zai_hook PUBLIC c_std_99) target_link_libraries(zai_hook PUBLIC Tea::Php Zai::Symbols) +if(PhpConfig_VERNUM GREATER_EQUAL "80000") + target_link_libraries(zai_hook PUBLIC Zai::JitUtils) +endif() set_target_properties(zai_hook PROPERTIES EXPORT_NAME Hook VERSION ${PROJECT_VERSION}) diff --git a/zend_abstract_interface/hook/hook.c b/zend_abstract_interface/hook/hook.c index 0cbb8f3731..01f9e60aaa 100644 --- a/zend_abstract_interface/hook/hook.c +++ b/zend_abstract_interface/hook/hook.c @@ -1,6 +1,7 @@ #include "../tsrmls_cache.h" #include #include +#include /* {{{ */ @@ -273,7 +274,7 @@ static inline zend_function *zai_hook_lookup_function(zai_str scope, zai_str fun } function = zai_symbol_lookup_function(ZAI_SYMBOL_SCOPE_CLASS, *ce, &func); } else { - ce = NULL; + *ce = NULL; function = zai_symbol_lookup_function(ZAI_SYMBOL_SCOPE_GLOBAL, NULL, &func); } return function; @@ -399,6 +400,13 @@ static void zai_hook_resolve_hooks_entry(zai_hooks_entry *hooks, zend_function * #endif } hooks->is_generator = (resolved->common.fn_flags & ZEND_ACC_GENERATOR) != 0; + if (hooks->is_generator) { +#if ZAI_JIT_BLACKLIST_ACTIVE + // Generator observers may replace the EX(opline) by a custom op. The tracing JIT will not like this. Blacklist them. + // Note that we cannot defer the blacklisting until the observer hook is invoked as that one will not be called before ZEND_GENERATOR_CREATE. + zai_jit_blacklist_function_inlining(&resolved->op_array); +#endif + } #endif if ((resolved->common.fn_flags & ZEND_ACC_CLOSURE) == 0) { @@ -516,6 +524,10 @@ static zend_long zai_hook_resolved_install(zai_hook_t *hook, zend_function *reso zai_hooks_entry *hooks = zai_hook_resolved_ensure_hooks_entry(resolved, ce); zend_long index = zai_hook_add_entry(hooks, hook); + if (hook->aux.resolved) { + hook->aux.resolved(hook->aux.data, index >= 0); + } + if (hook->is_abstract) { zai_hook_resolved_install_abstract_recursive(hook, (zend_ulong)index, resolved->common.scope); } else if (!ZEND_USER_CODE(resolved->type) && resolved->common.scope) { @@ -540,6 +552,13 @@ static zend_long zai_hook_request_install(zai_hook_t *hook) { hook->resolved_scope = ce; hook->is_abstract = (function->common.fn_flags & ZEND_ACC_ABSTRACT) != 0; return zai_hook_resolved_install(hook, function, ce); + } else if (ce) { // class exists, but function does not; report it as error + if (!hook->is_global) { + zend_string_release(hook->scope); + zend_string_release(hook->function); + } + efree(hook); + return -1; } HashTable *funcs; @@ -755,6 +774,10 @@ static inline void zai_hook_resolve(HashTable *base_ht, zend_class_entry *ce, ze } else if (!ZEND_USER_CODE(function->type) && function->common.scope) { zai_hook_resolved_install_inherited_internal_function_recursive(hook, (zend_ulong)index, function->common.scope, function->internal_function.handler); } + + if (hook->aux.resolved) { + hook->aux.resolved(hook->aux.data, true); + } } ZEND_HASH_FOREACH_END(); // we remove the whole zai_hooks_entry, excluding the individual zai_hook_t which we moved @@ -783,6 +806,10 @@ static inline void zai_hook_resolve(HashTable *base_ht, zend_class_entry *ce, ze } else if (!ZEND_USER_CODE(function->type) && function->common.scope) { zai_hook_resolved_install_inherited_internal_function_recursive(hook, (zend_ulong)index, function->common.scope, function->internal_function.handler); } + + if (hook->aux.resolved) { + hook->aux.resolved(hook->aux.data, true); + } } ZEND_HASH_FOREACH_END(); } } @@ -829,6 +856,17 @@ void zai_hook_resolve_class(zend_class_entry *ce, zend_string *lcname) { if (zend_hash_num_elements(method_table) == 0) { // note: no pDestructor handling needed: zai_hook_resolve empties the table for us zend_hash_del(&zai_hook_tls->request_classes, lcname); + } else { + // Notify about missing methods + zai_hooks_entry *hooks; + ZEND_HASH_FOREACH_PTR(method_table, hooks) { + zai_hook_t *hook; + ZEND_HASH_FOREACH_PTR(&hooks->hooks, hook) { + if (hook->aux.resolved) { + hook->aux.resolved(hook->aux.data, false); + } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); } } @@ -1075,13 +1113,22 @@ void zai_hook_finish(zend_execute_data *ex, zval *rv, zai_hook_memory_t *memory) zend_ulong address = zai_hook_frame_address(ex); zai_hook_table_find(&zai_hook_resolved, address, (void**)&hooks); zval *hook_zv; - if ((hook_zv = zend_hash_index_find(&hooks->hooks, (zend_ulong) -hook->id))) { + ZEND_ASSERT(CG(unclean_shutdown) || hooks != NULL); + if (hooks && (hook_zv = zend_hash_index_find(&hooks->hooks, (zend_ulong) -hook->id))) { if (Z_TYPE_INFO_P(hook_zv) == ZAI_IS_SHARED_HOOK_PTR) { // lookup primary by name zend_class_entry *ce = NULL; zend_function *origin_func = zai_hook_lookup_function((zai_str)ZAI_STR_FROM_ZSTR(hook->scope), (zai_str)ZAI_STR_FROM_ZSTR(hook->function), &ce); zai_hook_table_find(&zai_hook_resolved, zai_hook_install_address(origin_func), (void**)&hooks); +#if PHP_VERSION_ID >= 80000 + zai_hook_last_observer = NULL; zai_hook_remove_abstract_recursive(hooks, ce, hook->function, (zend_ulong)-hook->id); + if (zai_hook_last_observer) { + zai_hook_last_observer(ex, rv); + } +#else + zai_hook_remove_abstract_recursive(hooks, ce, hook->function, (zend_ulong)-hook->id); +#endif address = zai_hook_install_address(hooks->resolved); } zend_hash_index_del(&hooks->hooks, (zend_ulong) -hook->id); @@ -1209,7 +1256,10 @@ void zai_hook_rshutdown(void) { void zai_hook_gshutdown(void) { free(zai_hook_tls); } -void zai_hook_mshutdown(void) { zend_hash_destroy(&zai_hook_static); } /* }}} */ +void zai_hook_mshutdown(void) { + zend_hash_destroy(&zai_hook_static); + zend_hash_destroy(&zai_hook_static_inheritors); +} /* }}} */ /* {{{ */ zend_long zai_hook_install_resolved_generator(zend_function *function, @@ -1260,7 +1310,7 @@ static zend_string *zai_zend_string_init_lower(const char *ptr, size_t len, bool zend_long zai_hook_install_generator(zai_str scope, zai_str function, zai_hook_begin begin, zai_hook_generator_resume resumption, zai_hook_generator_yield yield, zai_hook_end end, zai_hook_aux aux, size_t dynamic) { - bool persistent = !PG(modules_activated); + bool persistent = !PG(modules_activated) && !PG(during_request_startup); zai_hook_t *hook = pemalloc(sizeof(*hook), persistent); *hook = (zai_hook_t){ @@ -1284,7 +1334,11 @@ zend_long zai_hook_install_generator(zai_str scope, zai_str function, zend_hash_next_index_insert_ptr(&zai_hook_static, hook); return hook->id = zai_hook_static.nNextFreeElement - 1; } else { - return hook->id = zai_hook_request_install(hook); + zend_long id = zai_hook_request_install(hook); + if (id >= 0) { + hook->id = id; + } + return id; } } diff --git a/zend_abstract_interface/hook/hook.h b/zend_abstract_interface/hook/hook.h index 445968c26e..c57a6402a4 100644 --- a/zend_abstract_interface/hook/hook.h +++ b/zend_abstract_interface/hook/hook.h @@ -28,10 +28,12 @@ typedef void (*zai_hook_generator_yield)(zend_ulong invocation, zend_execute_dat typedef struct { void *data; void (*dtor)(void *data); + void (*resolved)(void *data, bool found); } zai_hook_aux; /* }}} */ /* {{{ zai_hook_aux ZAI_HOOK_AUX(void *pointer, void (*destructor)(void *pointer)) */ -#define ZAI_HOOK_AUX(pointer, destructor) (zai_hook_aux){ .data = (pointer), .dtor = (destructor) } +#define ZAI_HOOK_AUX_RESOLVED(pointer, destructor, resolvedfn) (zai_hook_aux){ .data = (pointer), .dtor = (destructor), .resolved = (resolvedfn) } +#define ZAI_HOOK_AUX(pointer, destructor) ZAI_HOOK_AUX_RESOLVED(pointer, destructor, NULL) #define ZAI_HOOK_AUX_UNUSED ZAI_HOOK_AUX(NULL, NULL) /* }}} */ diff --git a/zend_abstract_interface/hook/tests/CMakeLists.txt b/zend_abstract_interface/hook/tests/CMakeLists.txt index 69a9eb045d..d7abe945c3 100644 --- a/zend_abstract_interface/hook/tests/CMakeLists.txt +++ b/zend_abstract_interface/hook/tests/CMakeLists.txt @@ -4,6 +4,9 @@ add_executable(hooks ) target_link_libraries(hooks PUBLIC catch2_main Tea::Tea Zai::Symbols Zai::Hook) +if(PhpConfig_VERNUM GREATER_EQUAL "80000") + target_link_libraries(hooks PUBLIC Zai::JitUtils) +endif() file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/stubs DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/zend_abstract_interface/interceptor/php7/interceptor.c b/zend_abstract_interface/interceptor/php7/interceptor.c index 7ff2a9ba97..fe52e4a8f2 100644 --- a/zend_abstract_interface/interceptor/php7/interceptor.c +++ b/zend_abstract_interface/interceptor/php7/interceptor.c @@ -44,6 +44,10 @@ static const zend_internal_function zend_pass_function = { NULL, /* module */ {0} /* reserved */ }; + +void (*zai_interrupt_function)(zend_execute_data *execute_data) = NULL; +static bool _zai_default_vm_interrupt = false; +TSRM_TLS bool *zai_vm_interrupt = &_zai_default_vm_interrupt; #endif typedef struct { @@ -167,6 +171,11 @@ uint32_t zai_interceptor_find_temporary(zend_op_array *op_array) { static user_opcode_handler_t prev_ext_nop_handler; static inline int zai_interceptor_ext_nop_handler_no_prev(zend_execute_data *execute_data) { +#if PHP_VERSION_ID < 70100 + if (UNEXPECTED(*zai_vm_interrupt) && zai_interrupt_function) { + zai_interrupt_function(execute_data); + } +#endif zend_op_array *op_array = &execute_data->func->op_array; if (UNEXPECTED(zai_hook_installed_user(op_array))) { zai_interceptor_frame_memory frame_memory, *tmp; @@ -473,6 +482,11 @@ static int zai_interceptor_bailout_get_closure(zval *obj, zend_class_entry **ce_ static void (*prev_execute_internal)(zend_execute_data *execute_data, zval *return_value); static inline void zai_interceptor_execute_internal_impl(zend_execute_data *execute_data, zval *return_value, bool prev) { +#if PHP_VERSION_ID < 70100 + if (UNEXPECTED(*zai_vm_interrupt) && zai_interrupt_function) { + zai_interrupt_function(execute_data); + } +#endif zend_function *func = execute_data->func; if (UNEXPECTED(zai_hook_installed_internal(&func->internal_function))) { zai_interceptor_frame_memory frame_memory; diff --git a/zend_abstract_interface/interceptor/php7/interceptor.h b/zend_abstract_interface/interceptor/php7/interceptor.h index 679ff8070e..4e4ea72019 100644 --- a/zend_abstract_interface/interceptor/php7/interceptor.h +++ b/zend_abstract_interface/interceptor/php7/interceptor.h @@ -2,6 +2,7 @@ #define ZAI_INTERCEPTOR_H #include +#include void zai_interceptor_op_array_ctor(zend_op_array *op_array); void zai_interceptor_op_array_pass_two(zend_op_array *op_array); @@ -14,4 +15,9 @@ void zai_interceptor_shutdown(void); void zai_interceptor_terminate_all_pending_observers(void); +#if PHP_VERSION_ID < 70100 +extern void (*zai_interrupt_function)(zend_execute_data *execute_data); +extern TSRM_TLS bool *zai_vm_interrupt; +#endif + #endif // ZAI_INTERCEPTOR_H diff --git a/zend_abstract_interface/interceptor/php8/interceptor.c b/zend_abstract_interface/interceptor/php8/interceptor.c index 298b52e4d0..c6ec86a7ac 100644 --- a/zend_abstract_interface/interceptor/php8/interceptor.c +++ b/zend_abstract_interface/interceptor/php8/interceptor.c @@ -741,6 +741,7 @@ static void *ZEND_FASTCALL zai_interceptor_handle_created_generator_goto(void) { if (zai_interceptor_avoid_compile_opt) { uintptr_t tmp = (uintptr_t)&&zai_interceptor_handle_created_generator_goto_LABEL2; zai_interceptor_dummy_label_use = tmp; + zai_interceptor_avoid_compile_opt = false; // tell the compiler that the other branch is not unreachable // We need to return zai_interceptor_handle_created_generator_goto_LABEL; zai_interceptor_handle_created_generator_goto cannot be jumped to directly as it will contain prologue updating the stack pointer. tmp = (uintptr_t)&&zai_interceptor_handle_created_generator_goto_LABEL; return (void *)tmp; // extra var to prevent 'function returns address of label [-Werror=return-local-addr]' @@ -907,6 +908,10 @@ zend_result zai_interceptor_post_startup(void) { zai_registered_observers = (zend_op_array_extension_handles - zend_observer_fcall_op_array_extension) / 2; #endif +#ifdef __GNUC__ + zai_interceptor_avoid_compile_opt = true; // Reset it in case MINIT gets re-executed +#endif + return result; } diff --git a/zend_abstract_interface/jit_utils/CMakeLists.txt b/zend_abstract_interface/jit_utils/CMakeLists.txt new file mode 100644 index 0000000000..18d5d50caf --- /dev/null +++ b/zend_abstract_interface/jit_utils/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(zai_jit_utils jit_blacklist.c) + +target_include_directories( + zai_jit_utils PUBLIC $ + $) + +target_compile_features(zai_jit_utils PUBLIC c_std_99) + +target_link_libraries(zai_jit_utils PUBLIC Tea::Php dl) + +set_target_properties(zai_jit_utils PROPERTIES EXPORT_NAME JitUtils + VERSION ${PROJECT_VERSION}) + +add_library(Zai::JitUtils ALIAS zai_jit_utils) + +install( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/jit_blacklist.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jit_utils/) + +target_link_libraries(zai_zend_abstract_interface INTERFACE zai_jit_utils) + +install(TARGETS zai_jit_utils EXPORT ZendAbstractInterfaceTargets) diff --git a/zend_abstract_interface/symbols/call.c b/zend_abstract_interface/symbols/call.c index 90c87d5d38..4f70c0749f 100644 --- a/zend_abstract_interface/symbols/call.c +++ b/zend_abstract_interface/symbols/call.c @@ -208,7 +208,7 @@ bool zai_symbol_call_impl( bool zai_symbol_call_bailed = false; bool rebound_closure = false; zval new_closure; - zend_op_array *op_array; + zend_op_array *op_array = NULL; if (function_type == ZAI_SYMBOL_FUNCTION_CLOSURE && fcc.called_scope) { zend_class_entry *closure_called_scope; diff --git a/zend_abstract_interface/symbols/lookup.c b/zend_abstract_interface/symbols/lookup.c index a80123fb76..e373eda73f 100644 --- a/zend_abstract_interface/symbols/lookup.c +++ b/zend_abstract_interface/symbols/lookup.c @@ -38,6 +38,7 @@ static inline void *zai_symbol_lookup_table(HashTable *table, zai_str key, bool zval resultzv; zend_function *func; if (table == EG(function_table) && (func = zend_fetch_function_str(key.ptr, key.len))) { + ZEND_ASSERT(pointer == true); result = &resultzv; ZVAL_PTR(result, func); } else @@ -55,6 +56,7 @@ static inline void *zai_symbol_lookup_table(HashTable *table, zai_str key, bool #if PHP_VERSION_ID >= 70300 if (table == EG(function_table) && (func = zend_fetch_function_str(key.ptr, key.len))) { + ZEND_ASSERT(pointer == true); result = &resultzv; ZVAL_PTR(result, func); } else