diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bbb2ff8e8..a99a9a29d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,7 +17,7 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v4 - name: Dependency Review - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v4.3.3 with: license-check: true vulnerability-check: false diff --git a/composer.json b/composer.json index 3c82564be..54bf56e24 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,10 @@ }, "extra": { "aws/aws-sdk-php": [ - "Polly" + "Personalize", + "PersonalizeEvents", + "PersonalizeRuntime", + "Polly" ] } } diff --git a/composer.lock b/composer.lock index 6e8d01ca0..5e0af2384 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "03778b3283f192d1c1736b533930b225", + "content-hash": "6d73ae5bd256ff0cf7c49db3367d393b", "packages": [ { "name": "aws/aws-crt-php", - "version": "v1.2.4", + "version": "v1.2.6", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2" + "reference": "a63485b65b6b3367039306496d49737cf1995408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2", - "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", + "reference": "a63485b65b6b3367039306496d49737cf1995408", "shasum": "" }, "require": { @@ -56,22 +56,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" }, - "time": "2023-11-08T00:42:13+00:00" + "time": "2024-06-13T17:21:28+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.300.13", + "version": "3.317.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b1eb7307d30ebcfa4e156971f658c2d177434db3" + "reference": "dc1e3031c2721a25beb2e8fbb175b576e3d60ab9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b1eb7307d30ebcfa4e156971f658c2d177434db3", - "reference": "b1eb7307d30ebcfa4e156971f658c2d177434db3", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/dc1e3031c2721a25beb2e8fbb175b576e3d60ab9", + "reference": "dc1e3031c2721a25beb2e8fbb175b576e3d60ab9", "shasum": "" }, "require": { @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.300.13" + "source": "https://github.com/aws/aws-sdk-php/tree/3.317.1" }, - "time": "2024-03-07T19:14:04+00:00" + "time": "2024-08-02T18:09:42+00:00" }, { "name": "composer/ca-bundle", @@ -161,24 +161,24 @@ "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/063d9aa8696582f5a41dffbbaf3c81024f0a604a", + "reference": "063d9aa8696582f5a41dffbbaf3c81024f0a604a", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", + "phpstan/phpstan": "^1.10", + "psr/log": "^1.0 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "default-branch": true, "type": "library", @@ -214,7 +214,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.1" }, "funding": [ { @@ -230,7 +230,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-07-08T15:28:20+00:00" }, { "name": "guzzlehttp/guzzle", @@ -238,18 +238,18 @@ "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -260,9 +260,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -270,6 +270,7 @@ "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, + "default-branch": true, "type": "library", "extra": { "bamarni-bin": { @@ -340,7 +341,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -356,7 +357,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", @@ -364,12 +365,12 @@ "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", "shasum": "" }, "require": { @@ -377,7 +378,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "default-branch": true, "type": "library", @@ -424,7 +425,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.3" }, "funding": [ { @@ -440,20 +441,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-07-18T10:29:17+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.x-dev", + "version": "2.7.x-dev", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -468,8 +469,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -541,7 +542,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -557,7 +558,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "mtdowling/jmespath.php", @@ -681,23 +682,22 @@ }, { "name": "psr/http-factory", - "version": "dev-master", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/7037f4b0950474e9d1350e8df89b15f1842085f6", - "reference": "7037f4b0950474e9d1350e8df89b15f1842085f6", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -733,7 +733,7 @@ "support": { "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-09-22T11:16:44+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -839,12 +839,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d" + "reference": "d36279a5a4bc7f3ca2c412839f10d7c0aa2c1a02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/80d075412b557d41002320b96a096ca65aa2c98d", - "reference": "80d075412b557d41002320b96a096ca65aa2c98d", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/d36279a5a4bc7f3ca2c412839f10d7c0aa2c1a02", + "reference": "d36279a5a4bc7f3ca2c412839f10d7c0aa2c1a02", "shasum": "" }, "require": { @@ -898,7 +898,7 @@ "type": "tidelift" } ], - "time": "2023-01-24T14:02:46+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -906,12 +906,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "8740a072b86292957feb42703edde77fcfca84fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8740a072b86292957feb42703edde77fcfca84fb", + "reference": "8740a072b86292957feb42703edde77fcfca84fb", "shasum": "" }, "require": { @@ -963,7 +963,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" }, "funding": [ { @@ -979,7 +979,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-20T08:18:00+00:00" }, { "name": "ua-parser/uap-php", @@ -1182,32 +1182,32 @@ }, { "name": "automattic/vipwpcs", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/VIP-Coding-Standards.git", - "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd" + "reference": "2b1d206d81b74ed999023cffd924f862ff2753c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/1b8960ebff9ea3eb482258a906ece4d1ee1e25fd", - "reference": "1b8960ebff9ea3eb482258a906ece4d1ee1e25fd", + "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/2b1d206d81b74ed999023cffd924f862ff2753c8", + "reference": "2b1d206d81b74ed999023cffd924f862ff2753c8", "shasum": "" }, "require": { "php": ">=5.4", - "phpcsstandards/phpcsextra": "^1.1.0", - "phpcsstandards/phpcsutils": "^1.0.8", - "sirbrillig/phpcs-variable-analysis": "^2.11.17", - "squizlabs/php_codesniffer": "^3.7.2", - "wp-coding-standards/wpcs": "^3.0" + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.11", + "sirbrillig/phpcs-variable-analysis": "^2.11.18", + "squizlabs/php_codesniffer": "^3.9.2", + "wp-coding-standards/wpcs": "^3.1.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9", "phpcsstandards/phpcsdevtools": "^1.0", - "phpunit/phpunit": "^4 || ^5 || ^6 || ^7" + "phpunit/phpunit": "^4 || ^5 || ^6 || ^7 || ^8 || ^9" }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", @@ -1232,7 +1232,7 @@ "source": "https://github.com/Automattic/VIP-Coding-Standards", "wiki": "https://github.com/Automattic/VIP-Coding-Standards/wiki" }, - "time": "2023-09-05T11:01:05+00:00" + "time": "2024-05-10T20:31:09+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -1388,12 +1388,12 @@ "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "2f5294676c802a62b0549f6bc8983f14294ce369" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/2f5294676c802a62b0549f6bc8983f14294ce369", - "reference": "2f5294676c802a62b0549f6bc8983f14294ce369", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -1433,7 +1433,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.x" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -1441,20 +1441,20 @@ "type": "tidelift" } ], - "time": "2024-02-10T11:10:03+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nikic/php-parser", - "version": "dev-master", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -1465,9 +1465,8 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, - "default-branch": true, "bin": [ "bin/php-parse" ], @@ -1498,9 +1497,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -1685,28 +1684,28 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -1736,22 +1735,37 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2022-10-25T01:46:02+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:30:46+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/01c1ff2704a58e46f0cb1ca9d06aee07b3589082", + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082", "shasum": "" }, "require": { @@ -1759,10 +1773,10 @@ "phpcompatibility/phpcompatibility-paragonie": "^1.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -1791,9 +1805,24 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" }, - "time": "2022-10-24T09:00:36+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:37:59+00:00" }, { "name": "phpcsstandards/phpcsextra", @@ -1801,12 +1830,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "7384703f57a65879dc6fb5a0fc0dbe60fe2c1d8d" + "reference": "de3789a59ee046291bb196c03862c871a692dc75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/7384703f57a65879dc6fb5a0fc0dbe60fe2c1d8d", - "reference": "7384703f57a65879dc6fb5a0fc0dbe60fe2c1d8d", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/de3789a59ee046291bb196c03862c871a692dc75", + "reference": "de3789a59ee046291bb196c03862c871a692dc75", "shasum": "" }, "require": { @@ -1872,7 +1901,7 @@ "type": "open_collective" } ], - "time": "2024-03-04T02:11:33+00:00" + "time": "2024-07-23T06:25:32+00:00" }, { "name": "phpcsstandards/phpcsutils", @@ -1880,18 +1909,18 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "7883bd854d3a7594a1504fca79aacd3595dacd2d" + "reference": "2616376fd8ae9a065a73808235f010d6c3f114b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/7883bd854d3a7594a1504fca79aacd3595dacd2d", - "reference": "7883bd854d3a7594a1504fca79aacd3595dacd2d", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/2616376fd8ae9a065a73808235f010d6c3f114b4", + "reference": "2616376fd8ae9a065a73808235f010d6c3f114b4", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.9.0 || 4.0.x-dev@dev" + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", @@ -1961,7 +1990,7 @@ "type": "open_collective" } ], - "time": "2024-03-04T08:00:16+00:00" + "time": "2024-07-27T08:14:47+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1969,12 +1998,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "328a747f499cca790acff5634a4e55b957f40634" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/328a747f499cca790acff5634a4e55b957f40634", + "reference": "328a747f499cca790acff5634a4e55b957f40634", "shasum": "" }, "require": { @@ -1993,7 +2022,7 @@ "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.6" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -2002,7 +2031,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "9.2.x-dev" } }, "autoload": { @@ -2031,7 +2060,7 @@ "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.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2" }, "funding": [ { @@ -2039,7 +2068,7 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-07-17T05:19:21+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2288,41 +2317,41 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5b92d2809fe6c9dbf892e8016df656f16ef157e1" + "reference": "d83130070127e5bc064b96b815ba03bb7e0200e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5b92d2809fe6c9dbf892e8016df656f16ef157e1", - "reference": "5b92d2809fe6c9dbf892e8016df656f16ef157e1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d83130070127e5bc064b96b815ba03bb7e0200e1", + "reference": "d83130070127e5bc064b96b815ba03bb7e0200e1", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^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", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "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.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "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/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -2383,7 +2412,7 @@ "type": "tidelift" } ], - "time": "2024-03-06T06:47:12+00:00" + "time": "2024-08-02T05:57:17+00:00" }, { "name": "sebastian/cli-parser", @@ -3191,12 +3220,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "20bdda85c7c585ab265c0c37ec052a019bae29c4" + "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/20bdda85c7c585ab265c0c37ec052a019bae29c4", - "reference": "20bdda85c7c585ab265c0c37ec052a019bae29c4", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25", + "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25", "shasum": "" }, "require": { @@ -3238,7 +3267,7 @@ "type": "github" } ], - "time": "2023-03-25T08:11:39+00:00" + "time": "2024-03-14T18:47:08+00:00" }, { "name": "sebastian/type", @@ -3355,12 +3384,12 @@ "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "b52d51ca3f224c4459a6ae686a0104e80fbfb7df" + "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/b52d51ca3f224c4459a6ae686a0104e80fbfb7df", - "reference": "b52d51ca3f224c4459a6ae686a0104e80fbfb7df", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", + "reference": "bc8d7e30e2005bce5c59018b7cdb08e9fb45c0d1", "shasum": "" }, "require": { @@ -3406,7 +3435,7 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2024-03-04T15:42:00+00:00" + "time": "2024-06-26T20:08:34+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -3414,12 +3443,12 @@ "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "e72c99b4785937d05f9790a95e41259dd8e9777c" + "reference": "d49ea71f84594a9b5098321beb3ea9af94596578" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/e72c99b4785937d05f9790a95e41259dd8e9777c", - "reference": "e72c99b4785937d05f9790a95e41259dd8e9777c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d49ea71f84594a9b5098321beb3ea9af94596578", + "reference": "d49ea71f84594a9b5098321beb3ea9af94596578", "shasum": "" }, "require": { @@ -3487,7 +3516,7 @@ "type": "open_collective" } ], - "time": "2024-03-07T21:48:16+00:00" + "time": "2024-08-01T21:08:31+00:00" }, { "name": "theseer/tokenizer", @@ -3541,16 +3570,16 @@ }, { "name": "wp-coding-standards/wpcs", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", - "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", "shasum": "" }, "require": { @@ -3559,16 +3588,16 @@ "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=5.4", - "phpcsstandards/phpcsextra": "^1.1.0", - "phpcsstandards/phpcsutils": "^1.0.8", - "squizlabs/php_codesniffer": "^3.7.2" + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.0", "phpcsstandards/phpcsdevtools": "^1.2.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "suggest": { "ext-iconv": "For improved results", @@ -3599,11 +3628,11 @@ }, "funding": [ { - "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "url": "https://opencollective.com/php_codesniffer", "type": "custom" } ], - "time": "2023-09-14T07:06:09+00:00" + "time": "2024-03-25T16:39:00+00:00" }, { "name": "yoast/phpunit-polyfills", @@ -3611,12 +3640,12 @@ "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "e3a5bad2c69c147fbde4b8ad83f422bac3b36153" + "reference": "05495b934d2ae9a3b1c54b766ed479343a29f6b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/e3a5bad2c69c147fbde4b8ad83f422bac3b36153", - "reference": "e3a5bad2c69c147fbde4b8ad83f422bac3b36153", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/05495b934d2ae9a3b1c54b766ed479343a29f6b1", + "reference": "05495b934d2ae9a3b1c54b766ed479343a29f6b1", "shasum": "" }, "require": { @@ -3625,8 +3654,8 @@ }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", - "php-parallel-lint/php-parallel-lint": "^1.3.2", - "yoast/yoastcs": "^3.0.0" + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "yoast/yoastcs": "^3.1.0" }, "type": "library", "extra": { @@ -3666,7 +3695,7 @@ "security": "https://github.com/Yoast/PHPUnit-Polyfills/security/policy", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2024-03-04T17:15:33+00:00" + "time": "2024-07-15T17:38:35+00:00" } ], "aliases": [], diff --git a/includes/Classifai/Blocks/recommended-content-block/block.json b/includes/Classifai/Blocks/recommended-content-block/block.json index 415710040..d115dfde2 100644 --- a/includes/Classifai/Blocks/recommended-content-block/block.json +++ b/includes/Classifai/Blocks/recommended-content-block/block.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2, "title": "Recommended Content", - "description": "Display content recommended by Azure AI Personalizer", + "description": "Display personalized content recommended by AI", "textdomain": "classifai", "name": "classifai/recommended-content-block", "category": "classifai-blocks", diff --git a/includes/Classifai/Blocks/recommended-content-block/index.js b/includes/Classifai/Blocks/recommended-content-block/index.js index 0f67973af..4e9f57290 100644 --- a/includes/Classifai/Blocks/recommended-content-block/index.js +++ b/includes/Classifai/Blocks/recommended-content-block/index.js @@ -26,7 +26,7 @@ import { ReactComponent as icon } from '../../../../assets/img/block-icon.svg'; registerBlockType( block.name, { title: __( 'Recommended Content', 'classifai' ), description: __( - 'Display content recommended by Azure AI Personalizer', + 'Display personalized content recommended by AI', 'classifai' ), edit, diff --git a/includes/Classifai/Blocks/recommended-content-block/register.php b/includes/Classifai/Blocks/recommended-content-block/register.php index e78210762..9555a2532 100644 --- a/includes/Classifai/Blocks/recommended-content-block/register.php +++ b/includes/Classifai/Blocks/recommended-content-block/register.php @@ -8,7 +8,6 @@ namespace Classifai\Blocks\RecommendedContentBlock; use Classifai\Features\RecommendedContent; -use Classifai\Providers\Azure\Personalizer; use function Classifai\get_asset_info; /** @@ -54,8 +53,8 @@ function register() { function render_block_callback( array $attributes ): string { // Render block in Gutenberg Editor. if ( defined( 'REST_REQUEST' ) && \REST_REQUEST ) { - $personalizer = new Personalizer( false ); - return $personalizer->render_recommended_content( $attributes ); + $provider_instance = ( new RecommendedContent() )->get_feature_provider_instance(); + return $provider_instance->render_recommended_content( $attributes ); } // Render block in Front-end. diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index cd1e6a636..b0e33e931 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -507,8 +507,9 @@ public function render_input( array $args ) { switch ( $type ) { case 'text': case 'password': - $attrs = ' value="' . esc_attr( $value ) . '"'; - $class = 'regular-text'; + $placeholder = $args['placeholder'] ?? ''; + $attrs = ' value="' . esc_attr( $value ) . '" placeholder="' . esc_attr( $placeholder ) . '"'; + $class = 'regular-text'; break; case 'number': $attrs = ' value="' . esc_attr( $value ) . '"'; diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index f582ba650..455c22a2f 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -4,6 +4,11 @@ use Classifai\Services\Personalizer as PersonalizerService; use Classifai\Providers\Azure\Personalizer as PersonalizerProvider; +use Classifai\Providers\AWS\AmazonPersonalize as PersonalizeProvider; +use Classifai\Blocks; +use WP_REST_Server; +use WP_REST_Request; +use WP_Error; /** * Class RecommendedContent @@ -27,10 +32,186 @@ public function __construct() { // Contains just the providers this feature supports. $this->supported_providers = [ + PersonalizeProvider::ID => __( 'Amazon AWS Personalize', 'classifai' ), PersonalizerProvider::ID => __( 'Microsoft Azure AI Personalizer', 'classifai' ), ]; } + /** + * Set up necessary hooks. + * + * We utilize this so we can register the REST route. + */ + public function setup() { + parent::setup(); + add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); + } + + /** + * Set up necessary hooks. + * + * This only runs if is_feature_enabled() returns true. + */ + public function feature_setup() { + // Register the block. + Blocks\setup(); + + // AJAX callback for rendering recommended content. + add_action( 'wp_ajax_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); + add_action( 'wp_ajax_nopriv_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); + + add_action( 'save_post', [ $this, 'maybe_clear_transient' ] ); + } + + /** + * Register any needed endpoints. + */ + public function register_endpoints() { + register_rest_route( + 'classifai/v1', + 'personalizer/reward/(?P\d+)', + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ $this, 'rest_endpoint_callback' ], + 'args' => [ + 'itemId' => [ + 'required' => true, + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'description' => esc_html__( 'Item ID to track', 'classifai' ), + ], + 'event' => [ + 'required' => false, + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'string', + ], + 'type' => [ + 'type' => 'string', + ], + ], + 'sanitize_callback' => 'sanitize_text_field', + 'description' => esc_html__( 'Event details to track', 'classifai' ), + ], + 'rewarded' => [ + 'required' => false, + 'type' => 'string', + 'enum' => [ + '0', + '1', + ], + 'default' => '0', + 'sanitize_callback' => 'sanitize_text_field', + 'description' => esc_html__( 'Reward value we want to send', 'classifai' ), + ], + ], + 'permission_callback' => [ $this, 'permissions_check' ], + ] + ); + } + + /** + * Check if a given request has access to send reward. + * + * This check ensures that we are properly authenticated. + * TODO: add additional checks here, maybe a nonce check or rate limiting? + * + * @return WP_Error|bool + */ + public function permissions_check() { + // Check if valid authentication is in place. + if ( ! $this->is_enabled() ) { + return new WP_Error( 'not_enabled', esc_html__( 'Recommended Content not currently enabled.', 'classifai' ) ); + } + + return true; + } + + /** + * Generic request handler for all our custom routes. + * + * @param WP_REST_Request $request The full request object. + * @return \WP_REST_Response + */ + public function rest_endpoint_callback( WP_REST_Request $request ) { + $route = $request->get_route(); + + if ( strpos( $route, '/classifai/v1/personalizer/reward' ) === 0 ) { + return rest_ensure_response( + $this->run( + $request->get_param( 'itemId' ), + 'reward', + [ + 'event' => $request->get_param( 'event' ), + 'reward' => $request->get_param( 'rewarded' ), + ] + ) + ); + } + + return parent::rest_endpoint_callback( $request ); + } + + /** + * Render recommended content over AJAX. + */ + public function ajax_render_recommended_content() { + check_ajax_referer( 'classifai-recommended-block', 'security' ); + + if ( ! isset( $_POST['contentPostType'] ) || empty( $_POST['contentPostType'] ) ) { + esc_html_e( 'No results found.', 'classifai' ); + exit(); + } + + $attributes = [ + 'displayLayout' => isset( $_POST['displayLayout'] ) ? sanitize_text_field( wp_unslash( $_POST['displayLayout'] ) ) : 'grid', + 'contentPostType' => sanitize_text_field( wp_unslash( $_POST['contentPostType'] ) ), + 'excludeId' => isset( $_POST['excludeId'] ) ? absint( $_POST['excludeId'] ) : 0, + 'displayPostExcerpt' => isset( $_POST['displayPostExcerpt'] ) ? filter_var( wp_unslash( $_POST['displayPostExcerpt'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayAuthor' => isset( $_POST['displayAuthor'] ) ? filter_var( wp_unslash( $_POST['displayAuthor'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayPostDate' => isset( $_POST['displayPostDate'] ) ? filter_var( wp_unslash( $_POST['displayPostDate'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'displayFeaturedImage' => isset( $_POST['displayFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['displayFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : true, + 'addLinkToFeaturedImage' => isset( $_POST['addLinkToFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['addLinkToFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, + 'columns' => isset( $_POST['columns'] ) ? absint( $_POST['columns'] ) : 3, + 'numberOfItems' => isset( $_POST['numberOfItems'] ) ? absint( $_POST['numberOfItems'] ) : 3, + ]; + + if ( isset( $_POST['taxQuery'] ) && ! empty( $_POST['taxQuery'] ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + foreach ( $_POST['taxQuery'] as $key => $value ) { + $attributes['taxQuery'][ $key ] = array_map( 'absint', $value ); + } + } + + $provider_instance = $this->get_feature_provider_instance(); + + echo $provider_instance->render_recommended_content( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + exit(); + } + + /** + * Maybe clear transients for recent actions. + * + * @param int $post_id Post Id. + */ + public function maybe_clear_transient( int $post_id ) { + global $wpdb; + + $post_type = get_post_type( $post_id ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $transients = $wpdb->get_col( $wpdb->prepare( "SELECT `option_name` FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_classifai_actions_' . $post_type . '%' ) ); + + // Delete all transients + if ( ! empty( $transients ) ) { + foreach ( $transients as $transient ) { + delete_transient( str_replace( '_transient_', '', $transient ) ); + } + } + } + /** * Get the description for the enable field. * @@ -51,27 +232,6 @@ public function get_feature_default_settings(): array { ]; } - /** - * Runs the feature. - * - * @param mixed ...$args Arguments required by the feature depending on the provider selected. - * @return mixed - */ - public function run( ...$args ) { - $settings = $this->get_settings(); - $provider_id = $settings['provider'] ?? PersonalizerProvider::ID; - $provider_instance = $this->get_feature_provider_instance( $provider_id ); - $result = ''; - - if ( PersonalizerProvider::ID === $provider_instance::ID ) { - /** @var PersonalizerProvider $provider_instance */ - $result = call_user_func_array( - [ $provider_instance, 'personalizer_send_reward' ], - [ ...$args ] - ); - } - } - /** * Generates feature setting data required for migration from * ClassifAI < 3.0.0 to 3.0.0 diff --git a/includes/Classifai/Providers/AWS/AmazonPersonalize.php b/includes/Classifai/Providers/AWS/AmazonPersonalize.php new file mode 100644 index 000000000..2d0689baa --- /dev/null +++ b/includes/Classifai/Providers/AWS/AmazonPersonalize.php @@ -0,0 +1,746 @@ +feature_instance = $feature_instance; + + do_action( 'classifai_' . static::ID . '_init', $this ); + } + + /** + * Render the provider fields. + */ + public function render_provider_fields() { + $settings = $this->feature_instance->get_settings( static::ID ); + + add_settings_field( + 'access_key_id', + esc_html__( 'Access key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'access_key_id', + 'input_type' => 'text', + 'default_value' => $settings['access_key_id'], + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, + 'description' => $this->feature_instance->is_configured_with_provider( static::ID ) ? + '' : + sprintf( + wp_kses( + /* translators: %1$s is replaced with the AWS documentation URL */ + __( 'Enter the AWS access key. Please follow the steps given here to generate AWS credentials.', 'classifai' ), + [ + 'a' => [ + 'href' => [], + 'title' => [], + ], + ] + ), + esc_url( 'https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey' ) + ), + ] + ); + + add_settings_field( + 'secret_access_key', + esc_html__( 'Secret access key', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'secret_access_key', + 'input_type' => 'password', + 'default_value' => $settings['secret_access_key'], + 'class' => 'classifai-provider-field hidden provider-scope-' . static::ID, + 'description' => $this->feature_instance->is_configured_with_provider( static::ID ) ? + '' : + esc_html__( 'Enter the AWS secret access key.', 'classifai' ), + ] + ); + + add_settings_field( + 'aws_region', + esc_html__( 'Region', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'aws_region', + 'input_type' => 'text', + 'default_value' => $settings['aws_region'], + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, + 'description' => $this->feature_instance->is_configured_with_provider( static::ID ) ? + '' : + wp_kses( + __( 'Enter the AWS Region. eg: us-east-1', 'classifai' ), + [ + 'code' => [], + ] + ), + ] + ); + + add_settings_field( + 'event_tracker_id', + esc_html__( 'Event Tracker ID', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'event_tracker_id', + 'input_type' => 'text', + 'default_value' => $settings['event_tracker_id'], // TODO: could make an API request to get all event trackers and populate a dropdown. Or make an API request to get the dataset and automatically get the tracker ID from the chosen dataset. + 'placeholder' => '4282ac5f-1681-53fg-8g35-1acf2gae4125', + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, + 'description' => esc_html__( 'Enter the event tracker ID associated with your dataset', 'classifai' ), + ] + ); + + add_settings_field( + 'campaign_arn', + esc_html__( 'Campaign ARN', 'classifai' ), + [ $this->feature_instance, 'render_input' ], + $this->feature_instance->get_option_name(), + $this->feature_instance->get_option_name() . '_section', + [ + 'option_index' => static::ID, + 'label_for' => 'campaign_arn', + 'input_type' => 'text', + 'default_value' => $settings['campaign_arn'], + 'placeholder' => 'arn:aws:personalize:us-east-1:12345:campaign/name', + 'class' => 'large-text classifai-provider-field hidden provider-scope-' . static::ID, + 'description' => esc_html__( 'Enter the Amazon Resource Name (ARN) of the campaign to use for generating the personalized ranking', 'classifai' ), + ] + ); + + do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); + } + + /** + * Returns the default settings for this provider. + * + * @return array + */ + public function get_default_provider_settings(): array { + $common_settings = [ + 'access_key_id' => '', + 'secret_access_key' => '', + 'aws_region' => '', + 'event_tracker_id' => '', + 'campaign_arn' => '', + 'authenticated' => false, + ]; + + switch ( $this->feature_instance::ID ) { + case RecommendedContent::ID: + return $common_settings; + } + + return []; + } + + /** + * Sanitization callback for settings. + * + * @param array $new_settings The settings being saved. + * @return array + */ + public function sanitize_settings( array $new_settings ): array { + $settings = $this->feature_instance->get_settings(); + $is_credentials_changed = false; + + $new_settings[ static::ID ]['authenticated'] = $settings[ static::ID ]['authenticated']; + + if ( + ! empty( $new_settings[ static::ID ]['access_key_id'] ) && + ! empty( $new_settings[ static::ID ]['secret_access_key'] ) && + ! empty( $new_settings[ static::ID ]['aws_region'] ) + ) { + $new_access_key_id = sanitize_text_field( $new_settings[ static::ID ]['access_key_id'] ); + $new_secret_access_key = sanitize_text_field( $new_settings[ static::ID ]['secret_access_key'] ); + $new_aws_region = sanitize_text_field( $new_settings[ static::ID ]['aws_region'] ); + + if ( + $new_access_key_id !== $settings[ static::ID ]['access_key_id'] || + $new_secret_access_key !== $settings[ static::ID ]['secret_access_key'] || + $new_aws_region !== $settings[ static::ID ]['aws_region'] + ) { + $is_credentials_changed = true; + } + + if ( $is_credentials_changed || ! $new_settings[ static::ID ]['authenticated'] ) { + $new_settings[ static::ID ]['access_key_id'] = $new_access_key_id; + $new_settings[ static::ID ]['secret_access_key'] = $new_secret_access_key; + $new_settings[ static::ID ]['aws_region'] = $new_aws_region; + + $connected = $this->check_connection( + [ + 'access_key_id' => $new_access_key_id, + 'secret_access_key' => $new_secret_access_key, + 'aws_region' => $new_aws_region, + ] + ); + + if ( $connected ) { + $new_settings[ static::ID ]['authenticated'] = true; + } else { + $new_settings[ static::ID ]['authenticated'] = false; + } + } + } else { + $new_settings[ static::ID ]['access_key_id'] = $settings[ static::ID ]['access_key_id']; + $new_settings[ static::ID ]['secret_access_key'] = $settings[ static::ID ]['secret_access_key']; + $new_settings[ static::ID ]['aws_region'] = $settings[ static::ID ]['aws_region']; + + add_settings_error( + $this->feature_instance->get_option_name(), + 'classifai-aws-personalize-auth-empty', + esc_html__( 'One or more credentials required to connect to the Amazon Personalize service is empty.', 'classifai' ), + 'error' + ); + } + + $new_settings[ static::ID ]['event_tracker_id'] = sanitize_text_field( $new_settings[ static::ID ]['event_tracker_id'] ?? $settings[ static::ID ]['event_tracker_id'] ); + + $new_settings[ static::ID ]['campaign_arn'] = sanitize_text_field( $new_settings[ static::ID ]['campaign_arn'] ?? $settings[ static::ID ]['campaign_arn'] ); + + return $new_settings; + } + + /** + * Check the connection to the Amazon Personalize service. + * + * @param array $args Overridable args. + * @return bool + */ + public function check_connection( array $args = [] ): bool { + $settings = $this->feature_instance->get_settings( static::ID ); + + $default = [ + 'access_key_id' => $settings[ static::ID ]['access_key_id'] ?? '', + 'secret_access_key' => $settings[ static::ID ]['secret_access_key'] ?? '', + 'aws_region' => $settings[ static::ID ]['aws_region'] ?? 'us-east-1', + ]; + + $default = wp_parse_args( $args, $default ); + + // Return if credentials don't exist. + if ( empty( $default['access_key_id'] ) || empty( $default['secret_access_key'] ) ) { + return false; + } + + try { + /** + * Filters the return value of the check connection function. + * + * Returning a non-false value from the filter will short-circuit the request + * and return early with that value. + * + * This filter is useful for E2E tests. + * + * @since x.x.x + * @hook classifai_aws_personalize_pre_check_connection + * + * @param {bool} $pre The value of pre connect to service. Default false. Non-false value will short-circuit the request. + * + * @return {bool} The filtered value of connect to service. + */ + $pre = apply_filters( 'classifai_' . self::ID . '_pre_check_connection', false ); + + if ( false !== $pre ) { + return (bool) $pre; + } + + $client = $this->get_client( 'personalize', $args ); + $schemas = $client->listSchemas( [ 'maxResults' => 1 ] ); + + return $schemas && isset( $schemas['schemas'] ); + } catch ( \Exception $e ) { + add_settings_error( + $this->feature_instance->get_option_name(), + 'aws-personalize-auth-failed', + sprintf( + /* translators: %s is replaced with the error message */ + esc_html__( 'Connection to Amazon Personalize failed. Error: %s', 'classifai' ), + $e->getMessage() + ), + 'error' + ); + + return false; + } + } + + /** + * Returns proper AWS client. + * + * @param string $client_type Client type. + * @param array $aws_config AWS configuration array. + * @return \Aws\AwsClient|null + */ + public function get_client( string $client_type = '', array $aws_config = [] ) { + $settings = $this->feature_instance->get_settings( static::ID ); + + $default = [ + 'access_key_id' => $settings['access_key_id'] ?? '', + 'secret_access_key' => $settings['secret_access_key'] ?? '', + 'aws_region' => $settings['aws_region'] ?? 'us-east-1', + ]; + + $default = wp_parse_args( $aws_config, $default ); + + // Return if credentials don't exist. + if ( empty( $default['access_key_id'] ) || empty( $default['secret_access_key'] ) ) { + return null; + } + + // Set the AWS SDK configuration. + $config = [ + 'region' => $default['aws_region'] ?? 'us-east-1', + 'version' => 'latest', + 'ua_append' => [ 'request-source/classifai' ], + 'credentials' => [ + 'key' => $default['access_key_id'], + 'secret' => $default['secret_access_key'], + ], + ]; + + $sdk = new Sdk( $config ); + + switch ( $client_type ) { + case 'personalize': + return $sdk->createPersonalize(); + case 'personalize-events': + return $sdk->createPersonalizeEvents(); + case 'personalize-runtime': + return $sdk->createPersonalizeRuntime(); + } + + return null; + } + + /** + * Renders the markup for the Recommended Content block. + * + * @param array $attributes The block attributes. + * @return string. + */ + public function render_recommended_content( array $attributes ): string { + /** + * Filter the recommended content block attributes + * + * @since 2.0.0 + * @hook classifai_recommended_block_attributes + * + * @param {array} $attributes Attributes of blocks. + * + * @return {string} The filtered attributes. + */ + $attributes = apply_filters( 'classifai_recommended_block_attributes', $attributes ); + + $recommended_ids = $this->get_recommended_items( $attributes ); + + if ( empty( $recommended_ids ) ) { + return __( 'No results found.', 'classifai' ); + } + + $number_of_posts = isset( $attributes['numberOfItems'] ) ? absint( $attributes['numberOfItems'] ) : 3; + $recommended_ids = array_slice( $recommended_ids, 0, $number_of_posts ); + + $markup = ''; + $args = [ + 'post__in' => $recommended_ids, + 'post_type' => $attributes['contentPostType'], + 'posts_per_page' => $number_of_posts, + 'orderby' => 'post__in', + 'no_found_rows' => true, + 'update_post_term_cache' => false, + ]; + + $recommended_posts = get_posts( $args ); + + foreach ( $recommended_posts as $post ) { + $post_link = get_permalink( $post ); + $title = get_the_title( $post ); + + if ( ! $title ) { + $title = __( '(no title)', 'classifai' ); + } + + $markup .= '
  • '; + + if ( $attributes['displayFeaturedImage'] && has_post_thumbnail( $post ) ) { + $image_classes = 'wp-block-classifai-recommended-content__featured-image'; + + $featured_image = get_the_post_thumbnail( $post ); + + if ( $attributes['addLinkToFeaturedImage'] ) { + $featured_image = sprintf( + '%4$s', + esc_url( $post_link ), + esc_attr( $title ), + esc_attr( $post->ID ), + $featured_image + ); + } + + $markup .= sprintf( + '
    %2$s
    ', + esc_attr( $image_classes ), + $featured_image + ); + } + + $markup .= sprintf( + '%3$s', + esc_url( $post_link ), + esc_attr( $post->ID ), + esc_html( $title ) + ); + + if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) { + $author_display_name = get_the_author_meta( 'display_name', $post->post_author ); + + /* translators: byline. %s: current author. */ + $byline = sprintf( __( 'by %s', 'classifai' ), $author_display_name ); + + if ( ! empty( $author_display_name ) ) { + $markup .= sprintf( + '', + esc_html( $byline ) + ); + } + } + + if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { + $markup .= sprintf( + '', + esc_attr( get_the_date( 'c', $post ) ), + esc_html( get_the_date( '', $post ) ) + ); + } + + if ( isset( $attributes['displayPostExcerpt'] ) && $attributes['displayPostExcerpt'] ) { + $trimmed_excerpt = get_the_excerpt( $post ); + + if ( post_password_required( $post ) ) { + $trimmed_excerpt = __( 'This content is password protected.', 'classifai' ); + } + + $markup .= sprintf( + '
    %1$s
    ', + esc_html( $trimmed_excerpt ) + ); + } + + $markup .= "
  • \n"; + } + + $class = 'wp-block-classifai-recommended-content wp-block-classifai-recommended-content__list'; + + if ( 'grid' === $attributes['displayLayout'] ) { + $class .= ' is-grid'; + + if ( isset( $attributes['columns'] ) && $attributes['columns'] ) { + $class .= ' columns-' . $attributes['columns']; + } + } + + if ( isset( $attributes['displayPostDate'] ) && $attributes['displayPostDate'] ) { + $class .= ' has-dates'; + } + + if ( isset( $attributes['displayAuthor'] ) && $attributes['displayAuthor'] ) { + $class .= ' has-author'; + } + + $final_markup = sprintf( + '', + esc_attr( $class ), + $markup + ); + + /** + * Filter the recommended content block markup + * + * @since 1.8.0 + * @hook classifai_recommended_block_markup + * + * @param {string} $final_markup HTML Markup of recommended content block. + * @param {array} $attributes The block attributes. + * @param {array} $recommended_ids Recommended post IDs. + * + * @return {string} The filtered markup. + */ + return apply_filters( 'classifai_recommended_block_markup', $final_markup, $attributes, $recommended_ids ); + } + + /** + * Get recommended items from Amazon Personalize. + * + * @param array $attributes The block attributes. + * @return array + */ + public function get_recommended_items( array $attributes ): array { + $items = $this->get_recent_items( $attributes ); + + if ( empty( $items ) ) { + return $items; + } + + // If items are less than or equal to number we want to display, avoid API call. + $number_of_posts = isset( $attributes['numberOfItems'] ) ? absint( $attributes['numberOfItems'] ) : 3; + if ( count( $items ) <= $number_of_posts ) { + return $items; + } + + $settings = $this->feature_instance->get_settings( static::ID ); + + // We need a campaign ARN to proceed. + if ( empty( $settings['campaign_arn'] ) ) { + return $items; + } + + // Get our AWS client. + $client = $this->get_client( 'personalize-runtime' ); + + if ( ! $client ) { + return $items; + } + + // Convert the post IDs to strings as the API expects that. + $items = array_map( + function ( $item ) { + return (string) $item; + }, + $items + ); + + try { + $result = $client->getPersonalizedRanking( + [ + 'campaignArn' => $settings['campaign_arn'] ?? '', + 'inputList' => $items, + 'userId' => '1', // TODO: We need a user ID that can be tracked across page views. + ] + ); + + // Pull the post IDs out of the personalized ranking response. + if ( ! empty( $result['personalizedRanking'] ) ) { + $items = array_map( + function ( $item ) { + return (int) $item['itemId']; + }, + $result['personalizedRanking'] + ); + } + } catch ( \Exception $e ) { + return $items; + } + + return $items; + } + + /** + * Get recent items based on given arguments. + * + * These will then be re-ranked by Amazon Personalize. + * + * @param array $attributes The block attributes. + * @return array + */ + public function get_recent_items( array $attributes ): array { + $post_type = $attributes['contentPostType']; + $key_attributes = [ + 'terms' => $attributes['taxQuery'] ?? [], + 'excluded' => $attributes['excludeId'] ?? 0, + ]; + $transient_key = 'classifai_actions_' . $post_type . md5( maybe_serialize( $key_attributes ) ); + $items = get_transient( $transient_key ); + + if ( false !== $items && ! empty( $items ) ) { + return $items; + } + + $query_args = [ + 'posts_per_page' => 100, + 'post_status' => 'publish', + 'no_found_rows' => true, + 'ignore_sticky_posts' => true, + 'post_type' => $post_type, + ]; + + // Exclude item on which we are displaying the block. + if ( ! empty( $attributes['excludeId'] ) ) { + $query_args['post__not_in'] = [ absint( $attributes['excludeId'] ) ]; + } + + // Handle taxonomy filters. + if ( isset( $attributes['taxQuery'] ) && ! empty( $attributes['taxQuery'] ) ) { + foreach ( $attributes['taxQuery'] as $taxonomy => $terms ) { + if ( ! empty( $terms ) ) { + $query_args['tax_query'][] = [ + 'taxonomy' => $taxonomy, + 'field' => 'term_id', + 'terms' => $terms, + ]; + } + } + + if ( isset( $query_args['tax_query'] ) && count( $query_args['tax_query'] ) > 1 ) { + $query_args['tax_query']['relation'] = 'AND'; + } + } + + /** + * Filters recommended content post arguments. + * + * @since 1.8.0 + * @hook classifai_recommended_content_post_args + * + * @param {array} $query_args Array of query args to get posts + * @param {array} $attributes The block attributes. + * + * @return {array} Array of query args to get posts + */ + $query_args = apply_filters( + 'classifai_recommended_content_post_args', + $query_args, + $attributes + ); + + $items = []; + $query = new \WP_Query( $query_args ); + + if ( $query->have_posts() ) { + while ( $query->have_posts() ) { + $query->the_post(); + + $items[] = get_the_ID(); + } + } + + wp_reset_postdata(); + + if ( ! empty( $content ) ) { + set_transient( $transient_key, $items, 6 * \HOUR_IN_SECONDS ); + } + + return $items; + } + + /** + * Send a tracking event to Amazon Personalize. + * + * @param int $post_id The post ID to track. + * @param array $args Additional event arguments. + * @return bool|WP_Error + */ + public function track_event( $post_id, array $args = [] ) { + $settings = $this->feature_instance->get_settings( static::ID ); + $client = $this->get_client( 'personalize-events' ); + + if ( ! $client ) { + return new WP_Error( 'client_not_found', esc_html__( 'Client not found.', 'classifai' ) ); + } + + $id = uniqid(); // TODO: Is this the best way to generate a session ID? + $params = [ + 'eventList' => [ + [ + 'eventType' => $args['event']['type'] ?? 'click', + 'itemId' => (string) $post_id, + 'sentAt' => time(), // TODO: what other things should we / can we track? + ], + ], + 'sessionId' => $id, + 'trackingId' => $settings['event_tracker_id'] ?? '', + 'userId' => $id, // TODO: We need a user ID that can be tracked across page views. + ]; + + if ( isset( $args['event']['id'] ) ) { + $params['eventList'][0]['eventId'] = (string) $args['event']['id']; + } + + try { + $client->putEvents( $params ); + } catch ( \Exception $e ) { + return new WP_Error( 'event_failed', esc_html__( 'Event tracking failed.', 'classifai' ) ); + } + + return true; + } + + /** + * Common entry point for all REST endpoints for this provider. + * + * @param int $post_id The post ID we're processing. + * @param string $route_to_call The name of the route we're going to be processing. + * @param array $args Optional arguments to pass to the route. + * @return array|string|WP_Error + */ + public function rest_endpoint_callback( $post_id, string $route_to_call = '', array $args = [] ) { + if ( ! $post_id || ! get_post( $post_id ) ) { + return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required.', 'classifai' ) ); + } + + $route_to_call = strtolower( $route_to_call ); + $return = ''; + + // Handle all of our routes. + switch ( $route_to_call ) { + case 'reward': + $return = $this->track_event( $post_id, $args ); + break; + } + + return $return; + } + + /** + * Returns the debug information for the provider settings. + * + * @return array + */ + public function get_debug_information(): array { + $settings = $this->feature_instance->get_settings(); + $provider_settings = $settings[ static::ID ]; + $debug_info = []; + + if ( $this->feature_instance instanceof RecommendedContent ) { + $debug_info[ __( 'Authenticated', 'classifai' ) ] = $provider_settings['authenticated']; + } + + return apply_filters( + 'classifai_' . self::ID . '_debug_information', + $debug_info, + $settings, + $this->feature_instance + ); + } +} diff --git a/includes/Classifai/Providers/Azure/Personalizer.php b/includes/Classifai/Providers/Azure/Personalizer.php index 99a00ecd3..632d0572c 100644 --- a/includes/Classifai/Providers/Azure/Personalizer.php +++ b/includes/Classifai/Providers/Azure/Personalizer.php @@ -39,7 +39,6 @@ class Personalizer extends Provider { public function __construct( $feature_instance = null ) { $this->feature_instance = $feature_instance; - add_action( 'rest_api_init', [ $this, 'register_endpoints' ] ); do_action( 'classifai_' . static::ID . '_init', $this ); } @@ -48,13 +47,6 @@ public function __construct( $feature_instance = null ) { */ public function register() { add_action( 'classifai_before_feature_nav', [ $this, 'show_deprecation_message' ] ); - - if ( ( new RecommendedContent() )->is_feature_enabled() ) { - add_action( 'wp_ajax_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); - add_action( 'wp_ajax_nopriv_classifai_render_recommended_content', [ $this, 'ajax_render_recommended_content' ] ); - add_action( 'save_post', [ $this, 'maybe_clear_transient' ] ); - Blocks\setup(); - } } /** @@ -63,7 +55,7 @@ public function register() { * @param string $active_feature Feature currently shown. */ public function show_deprecation_message( string $active_feature ) { - if ( 'feature_recommended_content' !== $active_feature ) { + if ( 'feature_recommended_content' !== $active_feature || ( new RecommendedContent() )->get_settings( 'provider' ) !== static::ID ) { return; } ?> @@ -744,117 +736,29 @@ protected function authenticate_credentials( string $url, string $api_key ) { } /** - * Register the REST API endpoints. - */ - public function register_endpoints() { - register_rest_route( - 'classifai/v1', - 'personalizer/reward/(?P[a-zA-Z0-9-]+)', - [ - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => [ $this, 'reward_endpoint_callback' ], - 'args' => [ - 'eventId' => [ - 'required' => true, - 'type' => 'string', - 'sanitize_callback' => 'sanitize_text_field', - 'description' => esc_html__( 'Event ID to track', 'classifai' ), - ], - 'rewarded' => [ - 'required' => false, - 'type' => 'string', - 'enum' => [ - '0', - '1', - ], - 'default' => '0', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Reward we want to send', 'classifai' ), - ], - 'route' => [ - 'required' => false, - 'type' => 'string', - 'default' => 'reward', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', - 'description' => esc_html__( 'Route we want to call', 'classifai' ), - ], - ], - 'permission_callback' => [ $this, 'reward_permissions_check' ], - ] - ); - } - - /** - * Check if a given request has access to send reward. + * Common entry point for all REST endpoints for this provider. * - * This check ensures that we are properly authenticated. - * TODO: add additional checks here, maybe a nonce check or rate limiting? - * - * @return WP_Error|bool + * @param int $post_id The post ID we're processing. + * @param string $route_to_call The name of the route we're going to be processing. + * @param array $args Optional arguments to pass to the route. + * @return array|string|WP_Error */ - public function reward_permissions_check() { - // Check if valid authentication is in place. - if ( ( new RecommendedContent() )->is_feature_enabled() ) { - return new WP_Error( 'auth', esc_html__( 'Please set up valid authentication with Azure.', 'classifai' ) ); + public function rest_endpoint_callback( $post_id, string $route_to_call = '', array $args = [] ) { + if ( ! $post_id || ! get_post( $post_id ) ) { + return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required.', 'classifai' ) ); } - return true; - } - - /** - * Render Recommended Content over AJAX. - */ - public function ajax_render_recommended_content() { - check_ajax_referer( 'classifai-recommended-block', 'security' ); + $route_to_call = strtolower( $route_to_call ); + $return = ''; - if ( ! isset( $_POST['contentPostType'] ) || empty( $_POST['contentPostType'] ) ) { - esc_html_e( 'No results found.', 'classifai' ); - exit(); + // Handle all of our routes. + switch ( $route_to_call ) { + case 'reward': + $return = $this->personalizer_send_reward( $post_id, $args['rewarded'] ); + break; } - $attributes = array( - 'displayLayout' => isset( $_POST['displayLayout'] ) ? sanitize_text_field( wp_unslash( $_POST['displayLayout'] ) ) : 'grid', - 'contentPostType' => sanitize_text_field( wp_unslash( $_POST['contentPostType'] ) ), - 'excludeId' => isset( $_POST['excludeId'] ) ? absint( $_POST['excludeId'] ) : 0, - 'displayPostExcerpt' => isset( $_POST['displayPostExcerpt'] ) ? filter_var( wp_unslash( $_POST['displayPostExcerpt'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayAuthor' => isset( $_POST['displayAuthor'] ) ? filter_var( wp_unslash( $_POST['displayAuthor'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayPostDate' => isset( $_POST['displayPostDate'] ) ? filter_var( wp_unslash( $_POST['displayPostDate'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'displayFeaturedImage' => isset( $_POST['displayFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['displayFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : true, - 'addLinkToFeaturedImage' => isset( $_POST['addLinkToFeaturedImage'] ) ? filter_var( wp_unslash( $_POST['addLinkToFeaturedImage'] ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ) : false, - 'columns' => isset( $_POST['columns'] ) ? absint( $_POST['columns'] ) : 3, - 'numberOfItems' => isset( $_POST['numberOfItems'] ) ? absint( $_POST['numberOfItems'] ) : 3, - ); - - if ( isset( $_POST['taxQuery'] ) && ! empty( $_POST['taxQuery'] ) ) { - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash - foreach ( $_POST['taxQuery'] as $key => $value ) { - $attributes['taxQuery'][ $key ] = array_map( 'absint', $value ); - } - } - - echo $this->render_recommended_content( $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - - exit(); - } - - /** - * Maybe clear transients for recent actions. - * - * @param int $post_id Post Id. - */ - public function maybe_clear_transient( int $post_id ) { - global $wpdb; - $post_type = get_post_type( $post_id ); - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching - $transients = $wpdb->get_col( $wpdb->prepare( "SELECT `option_name` FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_classifai_actions_' . $post_type . '%' ) ); - // Delete all transients - if ( ! empty( $transients ) ) { - foreach ( $transients as $transient ) { - delete_transient( str_replace( '_transient_', '', $transient ) ); - } - } + return $return; } /** diff --git a/includes/Classifai/Services/Personalizer.php b/includes/Classifai/Services/Personalizer.php index 6b71ac307..875f08848 100644 --- a/includes/Classifai/Services/Personalizer.php +++ b/includes/Classifai/Services/Personalizer.php @@ -37,6 +37,7 @@ public static function get_service_providers(): array { return apply_filters( 'classifai_recommendation_service_providers', [ + 'Classifai\Providers\AWS\AmazonPersonalize', 'Classifai\Providers\Azure\Personalizer', ] );