diff --git a/.hooks/pre-push b/.hooks/pre-push new file mode 100755 index 00000000..8972970e --- /dev/null +++ b/.hooks/pre-push @@ -0,0 +1,8 @@ +#!/bin/bash + +branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') + +if [[ "$branch" = "master" ]]; then + echo "Error: pushing directly to the master branch is prohibited" + exit 1 +fi diff --git a/composer.json b/composer.json index a5650e16..9182412a 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,6 @@ "phpcompatibility/phpcompatibility-wp": "^2.1", "composer/installers": "^2.0", "brainmaestro/composer-git-hooks": "^2.8", - "xwp/wp-dev-lib": "^1.3", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "yoast/phpunit-polyfills": "^1.0", "phpunit/phpunit": "^7.0 || ^9.5", @@ -26,8 +25,9 @@ "extra": { "hooks": { "pre-commit": [ - "./vendor/xwp/wp-dev-lib/scripts/pre-commit && ./node_modules/.bin/lint-staged" + "./node_modules/.bin/lint-staged" ], + "pre-push": "./.hooks/pre-push", "commit-msg": [ "cat $1 | ./node_modules/.bin/commitlint" ] diff --git a/composer.lock b/composer.lock index ccb6a0ed..78cf396c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0a2c2fcfdde6c09cf804cbe54ed72069", + "content-hash": "8849e876e5bc1cf8f489df71d21ccd24", "packages": [ { "name": "drewm/mailchimp-api", @@ -806,16 +806,16 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "ddabec839cc003651f2ce695c938686d1086cf43" + "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43", - "reference": "ddabec839cc003651f2ce695c938686d1086cf43", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", "shasum": "" }, "require": { @@ -852,13 +852,14 @@ "paragonie", "phpcs", "polyfill", - "standards" + "standards", + "static analysis" ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2021-02-15T10:24:51+00:00" + "time": "2022-10-25T01:46:02+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", @@ -1235,16 +1236,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.6", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -1318,7 +1319,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -1334,7 +1335,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:43:46+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "psr/container", @@ -2350,16 +2351,16 @@ }, { "name": "sirbrillig/phpcs-variable-analysis", - "version": "v2.11.8", + "version": "v2.11.16", "source": { "type": "git", "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git", - "reference": "aaf902277f2889fddbdb37046ae02b9965e2cf0f" + "reference": "dc5582dc5a93a235557af73e523c389aac9a8e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/aaf902277f2889fddbdb37046ae02b9965e2cf0f", - "reference": "aaf902277f2889fddbdb37046ae02b9965e2cf0f", + "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/dc5582dc5a93a235557af73e523c389aac9a8e88", + "reference": "dc5582dc5a93a235557af73e523c389aac9a8e88", "shasum": "" }, "require": { @@ -2367,7 +2368,7 @@ "squizlabs/php_codesniffer": "^3.5.6" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "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", @@ -2404,20 +2405,20 @@ "source": "https://github.com/sirbrillig/phpcs-variable-analysis", "wiki": "https://github.com/sirbrillig/phpcs-variable-analysis/wiki" }, - "time": "2022-09-08T18:35:53+00:00" + "time": "2023-03-31T16:46:32+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -2453,27 +2454,28 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "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": "2022-06-18T07:21:10+00:00" + "time": "2023-02-22T23:07:41+00:00" }, { "name": "symfony/console", - "version": "v5.4.12", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", - "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8", + "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8", "shasum": "" }, "require": { @@ -2538,12 +2540,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.12" + "source": "https://github.com/symfony/console/tree/v5.4.22" }, "funding": [ { @@ -2559,7 +2561,7 @@ "type": "tidelift" } ], - "time": "2022-08-17T13:18:05+00:00" + "time": "2023-03-25T09:27:28+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2712,16 +2714,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", "shasum": "" }, "require": { @@ -2733,7 +2735,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2773,7 +2775,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" }, "funding": [ { @@ -2789,20 +2791,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "shasum": "" }, "require": { @@ -2814,7 +2816,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2857,7 +2859,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" }, "funding": [ { @@ -2873,20 +2875,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -2901,7 +2903,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2940,7 +2942,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -2956,20 +2958,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", "shasum": "" }, "require": { @@ -2978,7 +2980,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3019,7 +3021,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" }, "funding": [ { @@ -3035,20 +3037,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -3057,7 +3059,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3102,7 +3104,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -3118,7 +3120,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/service-contracts", @@ -3205,16 +3207,16 @@ }, { "name": "symfony/string", - "version": "v5.4.12", + "version": "v5.4.22", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058" + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058", - "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058", + "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", "shasum": "" }, "require": { @@ -3271,7 +3273,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.12" + "source": "https://github.com/symfony/string/tree/v5.4.22" }, "funding": [ { @@ -3287,7 +3289,7 @@ "type": "tidelift" } ], - "time": "2022-08-12T17:03:11+00:00" + "time": "2023-03-14T06:11:53+00:00" }, { "name": "theseer/tokenizer", @@ -3390,59 +3392,18 @@ }, "time": "2020-05-13T23:57:56+00:00" }, - { - "name": "xwp/wp-dev-lib", - "version": "1.6.5", - "source": { - "type": "git", - "url": "https://github.com/xwp/wp-dev-lib.git", - "reference": "a62ccca94f9995f294e12d500c45856adff81e34" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/xwp/wp-dev-lib/zipball/a62ccca94f9995f294e12d500c45856adff81e34", - "reference": "a62ccca94f9995f294e12d500c45856adff81e34", - "shasum": "" - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "XWP", - "email": "technology@xwp.co", - "homepage": "https://xwp.co" - } - ], - "description": "Common code used during development of WordPress plugins and themes", - "homepage": "https://github.com/xwp/wp-dev-lib", - "keywords": [ - "development", - "plugins", - "themes", - "tools", - "wordpress" - ], - "support": { - "issues": "https://github.com/xwp/wp-dev-lib/issues", - "source": "https://github.com/xwp/wp-dev-lib" - }, - "time": "2020-10-16T04:03:40+00:00" - }, { "name": "yoast/phpunit-polyfills", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/Yoast/PHPUnit-Polyfills.git", - "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c" + "reference": "3b59adeef77fb1c03ff5381dbb9d68b0aaff3171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", - "reference": "3c621ff5429d2b1ff96dc5808ad6cde99d31ea4c", + "url": "https://api.github.com/repos/Yoast/PHPUnit-Polyfills/zipball/3b59adeef77fb1c03ff5381dbb9d68b0aaff3171", + "reference": "3b59adeef77fb1c03ff5381dbb9d68b0aaff3171", "shasum": "" }, "require": { @@ -3450,13 +3411,12 @@ "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "require-dev": { - "yoast/yoastcs": "^2.2.1" + "yoast/yoastcs": "^2.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev", - "dev-develop": "1.x-dev" + "dev-main": "2.x-dev" } }, "autoload": { @@ -3490,7 +3450,7 @@ "issues": "https://github.com/Yoast/PHPUnit-Polyfills/issues", "source": "https://github.com/Yoast/PHPUnit-Polyfills" }, - "time": "2022-11-16T09:07:52+00:00" + "time": "2023-03-30T23:39:05+00:00" } ], "aliases": [], diff --git a/includes/class-newspack-popups-api.php b/includes/class-newspack-popups-api.php index 86adb04f..4af050ee 100644 --- a/includes/class-newspack-popups-api.php +++ b/includes/class-newspack-popups-api.php @@ -100,6 +100,26 @@ public function register_api_endpoints() { ], ] ); + + // API endpoints for RAS presets. + register_rest_route( + 'newspack-popups/v1', + '/reader-activation/campaign', + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'api_get_reader_activation_campaign_settings' ], + 'permission_callback' => [ $this, 'permission_callback' ], + ] + ); + register_rest_route( + 'newspack-popups/v1', + '/reader-activation/campaign', + [ + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'api_update_reader_activation_campaign_settings' ], + 'permission_callback' => [ $this, 'permission_callback' ], + ] + ); } /** @@ -282,5 +302,41 @@ public function api_duplicate_popup( $request ) { $response = Newspack_Popups::duplicate_popup( $request['id'], $request['title'] ); return rest_ensure_response( $response ); } + + /** + * Get reader activation campaign settings. + * + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response + */ + public function api_get_reader_activation_campaign_settings( $request ) { + $response = Newspack_Popups_Presets::get_ras_presets(); + + if ( \is_wp_error( $response ) ) { + return new \WP_REST_Response( [ 'message' => $response->get_error_message() ], 400 ); + } + + return rest_ensure_response( $response['prompts'] ); + } + + /** + * Update reader activation campaign settings. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function api_update_reader_activation_campaign_settings( $request ) { + $slug = $request['slug']; + $data = $request['data']; + + $response = Newspack_Popups_Presets::update_preset_prompt( $slug, $data ); + + if ( \is_wp_error( $response ) ) { + return new \WP_REST_Response( [ 'message' => $response->get_error_message() ], 400 ); + } + + return rest_ensure_response( $response['prompts'] ); + } } $newspack_popups_api = new Newspack_Popups_API(); diff --git a/includes/class-newspack-popups-exporter.php b/includes/class-newspack-popups-exporter.php index effef9c8..79a5e8ef 100644 --- a/includes/class-newspack-popups-exporter.php +++ b/includes/class-newspack-popups-exporter.php @@ -194,6 +194,14 @@ private function prepare_prompt_for_export( $prompt ) { unset( $prompt['options']['utm_suppression'] ); } + // We do not export custom taxonomies added to the popup. + $custom_taxonomies = Newspack_Popups_Model::get_custom_taxonomies(); + foreach ( $custom_taxonomies as $custom_tax ) { + if ( isset( $prompt[ $custom_tax ] ) ) { + unset( $prompt[ $custom_tax ] ); + } + } + return $prompt; } diff --git a/includes/class-newspack-popups-importer.php b/includes/class-newspack-popups-importer.php index 6123feaa..d642ec70 100644 --- a/includes/class-newspack-popups-importer.php +++ b/includes/class-newspack-popups-importer.php @@ -228,9 +228,17 @@ private function process_prompts() { $prompts = $this->pre_process_prompts_terms( $prompts ); foreach ( $prompts as $prompt ) { + $prompt_slug = isset( $prompt['slug'] ) ? $prompt['slug'] : null; + $prompt_content = $prompt['content']; + $user_input_fields = isset( $prompt['user_input_fields'] ) ? $prompt['user_input_fields'] : []; + + foreach ( $user_input_fields as $field ) { + $prompt_content = Newspack_Popups_Presets::process_user_inputs( $prompt_content, $field ); + } + $post_data = [ 'post_title' => $prompt['title'], - 'post_content' => $prompt['content'], + 'post_content' => $prompt_content, 'post_status' => $prompt['status'], 'post_type' => Newspack_Popups::NEWSPACK_POPUPS_CPT, ]; @@ -253,6 +261,11 @@ private function process_prompts() { if ( ! empty( $prompt['tags'] ) ) { Newspack_Popups_Model::set_popup_terms( $new_post, $prompt['tags'], 'post_tag' ); } + + // If there's a featured image. + if ( ! empty( $prompt['featured_image_id'] ) && false !== wp_get_attachment_url( (int) $prompt['featured_image_id'] ) ) { + set_post_thumbnail( $new_post, (int) $prompt['featured_image_id'] ); + } } } diff --git a/includes/class-newspack-popups-inserter.php b/includes/class-newspack-popups-inserter.php index 23bc7448..a4058200 100755 --- a/includes/class-newspack-popups-inserter.php +++ b/includes/class-newspack-popups-inserter.php @@ -48,6 +48,10 @@ public static function popups_for_post() { $preview_popup = Newspack_Popups_Model::retrieve_preview_popup( Newspack_Popups::previewed_popup_id() ); return [ $preview_popup ]; } + if ( Newspack_Popups::preset_popup_id() ) { + $preset_popup = Newspack_Popups_Presets::retrieve_preset_popup( Newspack_Popups::preset_popup_id() ); + return [ $preset_popup ]; + } // Popups disabled for this page. if ( self::assess_has_disabled_popups() ) { @@ -659,7 +663,9 @@ public static function enqueue_scripts() { * @return HTML */ public static function popup_shortcode( $atts = array() ) { - if ( isset( $atts['id'] ) ) { + if ( Newspack_Popups::preset_popup_id() ) { + $found_popup = Newspack_Popups_Presets::retrieve_preset_popup( Newspack_Popups::preset_popup_id() ); + } elseif ( isset( $atts['id'] ) ) { $include_unpublished = Newspack_Popups::is_preview_request(); $found_popup = Newspack_Popups_Model::retrieve_popup_by_id( $atts['id'], $include_unpublished ); } @@ -787,6 +793,11 @@ function( $popup_a, $popup_b ) { 'noPingback' => true, ]; + // If previewing a preset prompt, no need to include config for any prompts. + if ( Newspack_Popups::preset_popup_id() ) { + return; + } + // If previewing a specific prompt, no need to include config for all prompts. $previewed_popup_id = Newspack_Popups::previewed_popup_id(); if ( $previewed_popup_id ) { @@ -1057,7 +1068,17 @@ public static function should_display( $popup, $check_if_is_post = false ) { } $is_post_context_matching = $is_taxonomy_matching && in_array( $post_type, $supported_post_types ); - return $is_post_context_matching; + /** + * Filters the result of the should_display check for each prompt. + * + * If $check_result is false, it means it failed the previous checks. Changing this to true will make the prompt appear. + * Use it with caution as this might result in unexpected behavior. + * + * @param bool $check_result Whether the popup should be displayed. + * @param object $popup The popup to assess. + * @param bool $check_if_is_post Should the post type of post be taken into account. + */ + return apply_filters( 'newspack_popups_should_display_prompt', $is_post_context_matching, $popup, $check_if_is_post ); } /** diff --git a/includes/class-newspack-popups-model.php b/includes/class-newspack-popups-model.php index 0c82264f..9f77ee65 100644 --- a/includes/class-newspack-popups-model.php +++ b/includes/class-newspack-popups-model.php @@ -223,6 +223,44 @@ public static function retrieve_eligible_popups( $include_unpublished = false, $ return self::retrieve_popups_with_query( new WP_Query( $args ) ); } + /** + * Get an array of options from abbreviated query parameters. + * + * @return array Array of options. + */ + public static function get_preview_query_options() { + $options_filters = [ + 'background_color' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'display_title' => FILTER_VALIDATE_BOOLEAN, + 'hide_border' => FILTER_VALIDATE_BOOLEAN, + 'large_border' => FILTER_VALIDATE_BOOLEAN, + 'frequency' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'frequency_max' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'frequency_start' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'frequency_between' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'frequency_reset' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'overlay_color' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'overlay_opacity' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'overlay_size' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'no_overlay_background' => FILTER_VALIDATE_BOOLEAN, + 'placement' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'trigger_type' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'trigger_delay' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'trigger_scroll_progress' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'trigger_blocks_count' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'archive_insertion_posts_count' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + 'archive_insertion_is_repeating' => FILTER_VALIDATE_BOOLEAN, + 'utm_suppression' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, + ]; + + $options = []; + foreach ( $options_filters as $option => $filter ) { + $options[ $option ] = filter_input( INPUT_GET, Newspack_Popups::PREVIEW_QUERY_KEYS[ $option ], $filter ); + } + + return $options; + } + /** * Retrieve popup preview CPT post. * @@ -236,33 +274,7 @@ public static function retrieve_preview_popup( $post_id ) { // Setting proper id for correct API calls. $post_object->ID = $post_id; - return self::create_popup_object( - $post_object, - false, - [ - 'background_color' => filter_input( INPUT_GET, 'n_bc', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'display_title' => filter_input( INPUT_GET, 'n_ti', FILTER_VALIDATE_BOOLEAN ), - 'hide_border' => filter_input( INPUT_GET, 'n_hb', FILTER_VALIDATE_BOOLEAN ), - 'large_border' => filter_input( INPUT_GET, 'n_lb', FILTER_VALIDATE_BOOLEAN ), - 'frequency' => filter_input( INPUT_GET, 'n_fr', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'frequency_max' => filter_input( INPUT_GET, 'n_fm', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'frequency_start' => filter_input( INPUT_GET, 'n_fs', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'frequency_between' => filter_input( INPUT_GET, 'n_fb', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'frequency_reset' => filter_input( INPUT_GET, 'n_ft', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'overlay_color' => filter_input( INPUT_GET, 'n_oc', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'overlay_opacity' => filter_input( INPUT_GET, 'n_oo', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'overlay_size' => filter_input( INPUT_GET, 'n_os', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'no_overlay_background' => filter_input( INPUT_GET, 'n_bg', FILTER_VALIDATE_BOOLEAN ), - 'placement' => filter_input( INPUT_GET, 'n_pl', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'trigger_type' => filter_input( INPUT_GET, 'n_tt', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'trigger_delay' => filter_input( INPUT_GET, 'n_td', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'trigger_scroll_progress' => filter_input( INPUT_GET, 'n_ts', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'trigger_blocks_count' => filter_input( INPUT_GET, 'n_tb', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'archive_insertion_posts_count' => filter_input( INPUT_GET, 'n_ac', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - 'archive_insertion_is_repeating' => filter_input( INPUT_GET, 'n_ar', FILTER_VALIDATE_BOOLEAN ), - 'utm_suppression' => filter_input( INPUT_GET, 'n_ut', FILTER_SANITIZE_FULL_SPECIAL_CHARS ), - ] - ); + return self::create_popup_object( $post_object, false, self::get_preview_query_options() ); } /** @@ -613,6 +625,12 @@ public static function create_popup_object( $campaign_post, $include_taxonomies $popup['categories'] = get_the_category( $id ); $popup['tags'] = get_the_tags( $id ); $popup['campaign_groups'] = get_the_terms( $id, Newspack_Popups::NEWSPACK_POPUPS_TAXONOMY ); + + $all_taxonomies = self::get_custom_taxonomies(); + + foreach ( $all_taxonomies as $custom_taxonomy ) { + $popup[ $custom_taxonomy ] = get_the_terms( $id, $custom_taxonomy ); + } } $duplicate_of = get_post_meta( $id, 'duplicate_of', true ); @@ -654,6 +672,16 @@ public static function create_popup_object( $campaign_post, $include_taxonomies return $popup; } + /** + * Gets custom taxonomies that are assigned to the popup post type. + * + * @return array + */ + public static function get_custom_taxonomies() { + $all_taxonomies = get_object_taxonomies( Newspack_Popups::NEWSPACK_POPUPS_CPT ); + return array_diff( $all_taxonomies, [ Newspack_Popups::NEWSPACK_POPUPS_TAXONOMY, 'category', 'post_tag' ] ); + } + /** * Get the popup delay in milliseconds. * @@ -1055,7 +1083,7 @@ public static function get_access_attrs( $popup ) { if ( Newspack_Popups_Settings::is_non_interactive() ) { return ''; } - if ( Newspack_Popups::previewed_popup_id() ) { + if ( Newspack_Popups::previewed_popup_id() || Newspack_Popups::preset_popup_id() ) { return ''; } // The amp-access endpoint is queried only once (on page load), but after changing block settings, @@ -1196,6 +1224,9 @@ public static function generate_popup( $popup ) { if ( Newspack_Popups::previewed_popup_id() ) { $popup = self::retrieve_preview_popup( Newspack_Popups::previewed_popup_id() ); } + if ( Newspack_Popups::preset_popup_id() ) { + $popup = Newspack_Popups_Presets::retrieve_preset_popup( Newspack_Popups::preset_popup_id() ); + } if ( self::is_overlay( $popup ) && self::is_overlay( $popup ) && has_block( 'newspack-blocks/homepage-articles', $popup['content'] ) ) { add_filter( 'newspack_blocks_homepage_enable_duplication', '__return_true' ); @@ -1227,7 +1258,7 @@ public static function generate_popup( $popup ) { $no_overlay_background = $popup['options']['no_overlay_background']; $hidden_fields = self::get_hidden_fields( $popup ); $is_newsletter_prompt = self::has_newsletter_prompt( $popup ); - $has_featured_image = has_post_thumbnail( $popup['id'] ); + $has_featured_image = has_post_thumbnail( $popup['id'] ) || ! empty( $popup['options']['featured_image_id'] ); $classes = array( 'newspack-lightbox', 'newspack-popup', 'newspack-lightbox-placement-' . $popup['options']['placement'], 'newspack-lightbox-size-' . $overlay_size ); $classes[] = ( ! empty( $popup['title'] ) && $display_title ) ? 'newspack-lightbox-has-title' : null; $classes[] = $hide_border ? 'newspack-lightbox-no-border' : null; @@ -1263,7 +1294,7 @@ class=""
As an independent publication, we rely on donations to fund our journalism<\/p>\n\n\n", + "slug": "ras_registration_overlay", + "title": "Registration Overlay", + "content": "\n
{{success_message}}
<\/div>\n", "options": { "background_color": "#FFFFFF", "display_title": false, "hide_border": false, "large_border": false, - "frequency": "always", + "frequency": "custom", "frequency_max": 0, "frequency_start": 0, - "frequency_between": 0, - "frequency_reset": "month", + "frequency_between": "3", + "frequency_reset": "day", "overlay_color": "#000000", "overlay_opacity": "30", "overlay_size": "medium", "no_overlay_background": false, - "placement": "inline", - "trigger_type": "scroll", - "trigger_delay": "3", - "trigger_scroll_progress": "100", - "trigger_blocks_count": 0, + "placement": "center", + "trigger_type": "time", + "trigger_delay": "2", + "trigger_scroll_progress": 0, + "trigger_blocks_count": "3", "archive_insertion_posts_count": 1, "archive_insertion_is_repeating": false, - "selected_segment_id": "", + "selected_segment_id": "639b337fdbf33", "post_types": [ - "post" + "post", + "page", + "product", + "newspack_nl_cpt" ], "archive_page_types": [ "category", @@ -48,12 +52,77 @@ "id": 30, "name": "Reader Activation Defaults" } - ] + ], + "user_input_fields": [ + { + "name": "heading", + "type": "string", + "label": "Heading", + "description": "A heading to describe the prompt.", + "required": true, + "default": "Join our community for free. Sign up now.", + "max_length": 40 + }, + { + "name": "body", + "type": "string", + "label": "Body", + "description": "The primary call to action for the prompt.", + "required": true, + "default": "Register today to stay up-to-date with the latest news.", + "max_length": 300 + }, + { + "name": "button_label", + "type": "string", + "label": "Button Label", + "description": "The label for the primary CTA button.", + "required": true, + "default": "Sign up", + "max_length": 20 + }, + { + "name": "success_message", + "type": "string", + "label": "Success Message", + "description": "The message shown to readers after completing the signup.", + "required": true, + "default": "Thank you for registering!", + "max_length": 300 + }, + { + "name": "featured_image_id", + "type": "int", + "label": "Header Image (optional)", + "description": "Banner image to show at the top of the prompt.", + "default": 0 + }, + { + "name": "lists", + "type": "array", + "label": "Select Newsletters", + "description": "Newsletter lists the reader can subscribe to.", + "required": true, + "default": [] + } + ], + "help_info": { + "screenshot": "reg-overlay.png", + "description": "The Registration Overlay is shown to readers who have not yet registered nor signed up for a newsletter. It’s triggered on the first pageview, and then again every four pageviews.", + "recommendations": [ + "Focus on the benefits of creating an account.", + "Highlight the value that the reader will get for free.", + "Use action-oriented verbs like “join,” “register,” and “sign up.”", + "Create a sense of urgency/Encourage readers to take immediate action by using words like “now” and “today.”" + ], + "url": "https://help.newspack.com/engagement/reader-activation-system" + } }, { "status": "draft", - "title": "Registration Overlay", - "content": "\n{{body}}<\/p>\n\n\n", "options": { "background_color": "#FFFFFF", "display_title": false, @@ -75,7 +144,7 @@ "trigger_blocks_count": "3", "archive_insertion_posts_count": 1, "archive_insertion_is_repeating": false, - "selected_segment_id": "639b337fdbf33", + "selected_segment_id": "639b333b41025", "post_types": [ "post", "page", @@ -101,36 +170,104 @@ "id": 30, "name": "Reader Activation Defaults" } - ] + ], + "user_input_fields": [ + { + "name": "heading", + "type": "string", + "label": "Heading", + "description": "A heading to describe the prompt.", + "required": true, + "default": "Stay in the know", + "max_length": 40 + }, + { + "name": "body", + "type": "string", + "label": "Body", + "description": "The primary call to action for the prompt.", + "required": true, + "default": "Sign up to our free newsletter to get the latest news delivered straight to your inbox.", + "max_length": 300 + }, + { + "name": "button_label", + "type": "string", + "label": "Button Label", + "description": "The label for the primary CTA button.", + "required": true, + "default": "Sign up", + "max_length": 20 + }, + { + "name": "success_message", + "type": "string", + "label": "Success Message", + "description": "The message shown to readers after completing the signup.", + "required": true, + "default": "Thank you for signing up!", + "max_length": 300 + }, + { + "name": "featured_image_id", + "type": "int", + "label": "Header Image (optional)", + "description": "Banner image to show at the top of the prompt.", + "default": 0 + }, + { + "name": "lists", + "type": "array", + "label": "Select Newsletters", + "description": "Newsletter lists the reader can subscribe to.", + "required": true, + "default": [] + } + ], + "help_info": { + "screenshot": "newsletter-overlay.png", + "description": "The Newsletter Signup Overlay is shown to readers who have registered but not yet signed up for a newsletter. It’s triggered on the first pageview, and then again every four pageviews.", + "recommendations": [ + "Highlight the value the reader will get from the newsletter(s).", + "Emphasize that signing up is free.", + "Use action-oriented verbs like “join,” “register,” and “sign up.”", + "Create a sense of urgency. Encourage readers to take immediate action by using words like “now” and “today.”" + ], + "url": "https://help.newspack.com/engagement/reader-activation-system" + } }, { "status": "draft", - "title": "Inline Newsletter Signup (secondary)", - "content": "\n
Sign up for our free newsletter to receive the latest news
\n\n\n", + "slug": "ras_donation_overlay", + "title": "Donation Overlay", + "content": "\n{{body}}<\/p>\n\n\n", "options": { "background_color": "#FFFFFF", "display_title": false, "hide_border": false, "large_border": false, - "frequency": "always", + "frequency": "custom", "frequency_max": 0, "frequency_start": 0, - "frequency_between": 0, - "frequency_reset": "month", + "frequency_between": "3", + "frequency_reset": "day", "overlay_color": "#000000", "overlay_opacity": "30", "overlay_size": "medium", "no_overlay_background": false, - "placement": "inline", - "trigger_type": "blocks_count", - "trigger_delay": "3", + "placement": "center", + "trigger_type": "time", + "trigger_delay": "2", "trigger_scroll_progress": 0, - "trigger_blocks_count": 2, + "trigger_blocks_count": "3", "archive_insertion_posts_count": 1, "archive_insertion_is_repeating": false, - "selected_segment_id": "639b333b41025,639b337fdbf33", + "selected_segment_id": "639b3315239f0", "post_types": [ - "post" + "post", + "page", + "product", + "newspack_nl_cpt" ], "archive_page_types": [ "category", @@ -151,39 +288,93 @@ "id": 30, "name": "Reader Activation Defaults" } - ] + ], + "user_input_fields": [ + { + "name": "heading", + "type": "string", + "label": "Heading", + "description": "A heading to describe the prompt.", + "required": true, + "default": "Support our work", + "max_length": 40 + }, + { + "name": "body", + "type": "string", + "label": "Body", + "description": "The primary call to action for the prompt.", + "required": true, + "default": "As an independent publication, we rely on contributions from readers like you to fund our journalism.", + "max_length": 300 + }, + { + "name": "thanks_message", + "type": "string", + "label": "Thanks Message", + "description": "A message to thank the reader for their contribution.", + "required": true, + "default": "Thanks for your contribution!", + "max_length": 300 + }, + { + "name": "button_label", + "type": "string", + "label": "Button Label", + "description": "The label for the primary CTA button.", + "required": true, + "default": "Contribute Now", + "max_length": 20 + }, + { + "name": "featured_image_id", + "type": "int", + "label": "Header Image (optional)", + "description": "Banner image to show at the top of the prompt.", + "default": 0 + } + ], + "help_info": { + "screenshot": "donation-overlay.png", + "description": "The Donation Overlay is shown to readers who have registered and are signed up for at least one newsletter, but haven’t contributed yet. It’s triggered on the first pageview, and then again every four pageviews.", + "recommendations": [ + "Highlight the importance of supporting your publication.", + "Focus on the value readers add by contributing financially.", + "Use action-oriented verbs like “donate,” “give,” and “contribute.”", + "Create a sense of urgency. Encourage readers to take immediate action by using words like “now” and “today.”" + ], + "url": "https://help.newspack.com/engagement/reader-activation-system" + } }, { "status": "draft", - "title": "Newsletter Sign-up Overlay", - "content": "\n
Sign up for our free newsletters to receive the latest news directly in your inbox.<\/p>\n\n\n", + "slug": "ras_newsletter_inline", + "title": "Inline Newsletter Signup (secondary)", + "content": "\n
{{body}}
\n\n\n", "options": { "background_color": "#FFFFFF", "display_title": false, "hide_border": false, "large_border": false, - "frequency": "custom", + "frequency": "always", "frequency_max": 0, "frequency_start": 0, - "frequency_between": "3", - "frequency_reset": "day", + "frequency_between": 0, + "frequency_reset": "month", "overlay_color": "#000000", "overlay_opacity": "30", "overlay_size": "medium", "no_overlay_background": false, - "placement": "center", - "trigger_type": "time", - "trigger_delay": "2", + "placement": "inline", + "trigger_type": "blocks_count", + "trigger_delay": "3", "trigger_scroll_progress": 0, - "trigger_blocks_count": "3", + "trigger_blocks_count": 2, "archive_insertion_posts_count": 1, "archive_insertion_is_repeating": false, - "selected_segment_id": "639b333b41025", + "selected_segment_id": "639b333b41025,639b337fdbf33", "post_types": [ - "post", - "page", - "product", - "newspack_nl_cpt" + "post" ], "archive_page_types": [ "category", @@ -204,39 +395,85 @@ "id": 30, "name": "Reader Activation Defaults" } - ] + ], + "user_input_fields": [ + { + "name": "body", + "type": "string", + "label": "Body", + "description": "The primary call to action for the prompt.", + "required": true, + "default": "Sign up for our free newsletter.", + "max_length": 300 + }, + { + "name": "button_label", + "type": "string", + "label": "Button Label", + "description": "The label for the primary CTA button.", + "required": true, + "default": "Sign up", + "max_length": 20 + }, + { + "name": "success_message", + "type": "string", + "label": "Success Message", + "description": "The message shown to readers after completing the signup.", + "required": true, + "default": "Thank you for signing up!", + "max_length": 300 + }, + { + "name": "lists", + "type": "array", + "label": "Select Newsletters", + "description": "Newsletter lists the reader can subscribe to.", + "required": true, + "default": [] + } + ], + "help_info": { + "screenshot": "newsletter-inline.png", + "description": "The Inline Newsletter Signup is embedded within articles and shown to readers who have not yet signed up for a newsletter whether they are registered or not. It’s triggered on the first pageview, and then again every four pageviews and is inserted after the second paragraph block in every article.", + "recommendations": [ + "Highlight the value the reader will get from the newsletter(s).", + "Emphasize that signing up is free.", + "Use action-oriented verbs like “join,” “register,” and “sign up.”", + "Consider including the sending frequency to set expectations." + ], + "url": "https://help.newspack.com/engagement/reader-activation-system" + } }, { "status": "draft", - "title": "Donation Overlay", - "content": "\nAs an independent publication, we rely on donations to fund our journalism<\/p>\n\n\n", + "slug": "ras_donation_inline", + "title": "Inline Donation (secondary)", + "content": "\n
{{body}}<\/p>\n\n\n", "options": { "background_color": "#FFFFFF", "display_title": false, - "hide_border": false, + "hide_border": true, "large_border": false, - "frequency": "custom", + "frequency": "always", "frequency_max": 0, "frequency_start": 0, - "frequency_between": "3", - "frequency_reset": "day", + "frequency_between": 0, + "frequency_reset": "month", "overlay_color": "#000000", "overlay_opacity": "30", "overlay_size": "medium", "no_overlay_background": false, - "placement": "center", - "trigger_type": "time", - "trigger_delay": "2", - "trigger_scroll_progress": 0, - "trigger_blocks_count": "3", + "placement": "inline", + "trigger_type": "scroll", + "trigger_delay": "3", + "trigger_scroll_progress": "100", + "trigger_blocks_count": 0, "archive_insertion_posts_count": 1, "archive_insertion_is_repeating": false, - "selected_segment_id": "639b3315239f0", + "selected_segment_id": "", "post_types": [ - "post", - "page", - "product", - "newspack_nl_cpt" + "post" ], "archive_page_types": [ "category", @@ -257,7 +494,56 @@ "id": 30, "name": "Reader Activation Defaults" } - ] + ], + "user_input_fields": [ + { + "name": "heading", + "type": "string", + "label": "Heading", + "description": "A heading to describe the prompt.", + "required": true, + "default": "Support our work", + "max_length": 40 + }, + { + "name": "body", + "type": "string", + "label": "Body", + "description": "The primary call to action for the prompt.", + "required": true, + "default": "As an independent publication, we rely on contributions from readers like you to fund our journalism.", + "max_length": 300 + }, + { + "name": "thanks_message", + "type": "string", + "label": "Thanks Message", + "description": "A message to thank the reader for their contribution.", + "required": true, + "default": "Thanks for your contribution!", + "max_length": 300 + }, + { + "name": "button_label", + "type": "string", + "label": "Button Label", + "description": "The label for the primary CTA button.", + "required": true, + "default": "Contribute Now", + "max_length": 20 + } + ], + "help_info": { + "screenshot": "donation-inline.png", + "description": "The Inline Donation prompt is shown to all readers. It’s placed on every page below the content.", + "recommendations": [ + "Focus on the value readers add by contributing financially.", + "Highlight the importance of supporting your publication.", + "Use action-oriented verbs like “donate,” “give,” and “contribute.”", + "Create a sense of urgency. Encourage readers to take immediate action by using words like “now” and “today.”" + ], + "url": "https://help.newspack.com/engagement/reader-activation-system" + } } ], "segments": [ diff --git a/src/assets/donation-inline.png b/src/assets/donation-inline.png new file mode 100644 index 00000000..5cc6895f Binary files /dev/null and b/src/assets/donation-inline.png differ diff --git a/src/assets/donation-overlay.png b/src/assets/donation-overlay.png new file mode 100644 index 00000000..1f4d8849 Binary files /dev/null and b/src/assets/donation-overlay.png differ diff --git a/src/assets/newsletter-inline.png b/src/assets/newsletter-inline.png new file mode 100644 index 00000000..9c21caf1 Binary files /dev/null and b/src/assets/newsletter-inline.png differ diff --git a/src/assets/newsletter-overlay.png b/src/assets/newsletter-overlay.png new file mode 100644 index 00000000..0e6bdf7c Binary files /dev/null and b/src/assets/newsletter-overlay.png differ diff --git a/src/assets/reg-overlay.png b/src/assets/reg-overlay.png new file mode 100644 index 00000000..23c71bed Binary files /dev/null and b/src/assets/reg-overlay.png differ diff --git a/src/view/style.scss b/src/view/style.scss index ee86e4c9..c10fba4e 100644 --- a/src/view/style.scss +++ b/src/view/style.scss @@ -485,7 +485,8 @@ $width__tablet: 782px; display: none; } -.newspack-modal-checkout-open { +.newspack-modal-checkout-open, +.newspack-signin { .newspack-lightbox { display: none !important; } diff --git a/tests/test-presets.php b/tests/test-presets.php new file mode 100644 index 00000000..af9c815e --- /dev/null +++ b/tests/test-presets.php @@ -0,0 +1,151 @@ +assertTrue( isset( $presets['prompts'] ) && isset( $presets['segments'] ) && isset( $presets['campaigns'] ), 'The fetched presets match the JSON configuration.' ); + $this->assertEquals( 5, count( $presets['prompts'] ), 'The fetched presets match the JSON configuration.' ); + $this->assertEquals( 3, count( $presets['segments'] ), 'The fetched presets match the JSON configuration.' ); + $this->assertEquals( 1, count( $presets['campaigns'] ), 'The fetched presets match the JSON configuration.' ); + } + + /** + * Test fetching presets data with user inputs. + */ + public function test_preset_user_input() { + $user_inputs = [ + 'heading' => 'Test Heading copy', + 'body' => 'Test Body copy', + 'button_label' => 'Test Button Label', + 'success_message' => 'Test Success Message', + 'featured_image_id' => 123, + 'lists' => [ 1, 2, 3 ], + 'invalid_field' => 'invalid', + ]; + + // Test with invalid preset slug. + $this->assertTrue( \is_wp_error( Newspack_Popups_Presets::update_preset_prompt( 'invalid_slug', $user_inputs ) ), 'Invalid preset slug returns an error.' ); + + // Test with invalid field in user inputs. + $this->assertTrue( \is_wp_error( Newspack_Popups_Presets::update_preset_prompt( 'ras_registration_overlay', $user_inputs ) ), 'Invalid field name for a preset returns an error.' ); + + // Remove invalid field. + unset( $user_inputs['invalid_field'] ); + + // Test that data is updated with user inputs. + $presets = Newspack_Popups_Presets::update_preset_prompt( 'ras_registration_overlay', $user_inputs ); + $index = 0; + foreach ( $user_inputs as $field_name => $value ) { + $this->assertEquals( $value, $presets['prompts'][0]['user_input_fields'][ $index ]['value'], 'Preset data is returned with user inputs attached to each field.' ); + $index ++; + } + } + + /** + * Test activation of presets. Existing prompts and segments should be deactivated. + */ + public function test_preset_activation() { + $post_data = [ + 'post_title' => 'Preexisting Prompt', + 'post_content' => 'Preexisitng prompt body', + 'post_status' => 'publish', + 'post_type' => Newspack_Popups::NEWSPACK_POPUPS_CPT, + ]; + $preexisting_prompt = \wp_insert_post( $post_data ); + $preexisting_segment = [ + 'name' => 'Preexisting Segment', + 'configuration' => [ + 'is_subscribed' => true, + ], + ]; + Newspack_Popups_Segmentation::create_segment( $preexisting_segment ); + + // Activate presets. + $user_inputs = [ + 'heading' => 'Test Heading copy', + 'body' => 'Test Body copy', + 'button_label' => 'Test Button Label', + 'success_message' => 'Test Success Message', + 'featured_image_id' => 123, + 'lists' => [ 1, 2, 3 ], + ]; + $presets = Newspack_Popups_Presets::update_preset_prompt( 'ras_registration_overlay', $user_inputs ); + $activated = Newspack_Popups_Presets::activate_ras_presets(); + $all_prompts = Newspack_Popups_Model::retrieve_popups(); + + $preexisting_prompt_object = Newspack_Popups_Model::retrieve_popup_by_id( $preexisting_prompt, false, true ); + $this->assertEquals( + $preexisting_prompt_object['title'], + $post_data['post_title'] + ); + $this->assertEquals( + $preexisting_prompt_object['status'], + 'draft', + 'Preexisting prompt was deactivated' + ); + + $preset_titles = array_map( + function( $preset ) { + return $preset['title']; + }, + $presets['prompts'] + ); + $active_prompt_titles = array_map( + function( $prompt ) { + return $prompt['title']; + }, + $all_prompts + ); + $this->assertEmpty( array_diff( $preset_titles, $active_prompt_titles ) ); + $this->assertEquals( count( $presets['prompts'] ), count( $all_prompts ), 'Presets are the only published prompts' ); + + $all_segments = Newspack_Popups_Segmentation::get_segments(); + $this->assertEquals( $all_segments[0]['name'], $preexisting_segment['name'] ); + $this->assertTrue( $all_segments[0]['configuration']['is_disabled'], 'Preexisting segment is deactivated' ); + + $preset_segment_names = array_map( + function( $segment ) { + return $segment['name']; + }, + $presets['segments'] + ); + $activated_segment_names = array_map( + function( $segment ) { + return $segment['name']; + }, + array_filter( + $all_segments, + function( $segment ) { + return empty( $segment['options']['is_disabled'] ); + } + ) + ); + + $this->assertEmpty( array_diff( $preset_segment_names, $activated_segment_names ), 'Presets are the only active segments' ); + } +} diff --git a/tests/test-schemas.php b/tests/test-schemas.php index bf6940ce..9cc82f13 100644 --- a/tests/test-schemas.php +++ b/tests/test-schemas.php @@ -19,10 +19,11 @@ public function prompts_data() { return [ 'complete and valid' => [ [ - 'title' => 'Test Campaign', - 'content' => 'Test content', - 'status' => 'publish', - 'categories' => [ + 'slug' => 'test_campaign', + 'title' => 'Test Campaign', + 'content' => 'Test content', + 'status' => 'publish', + 'categories' => [ [ 'id' => 1, 'name' => 'Category 1', @@ -32,7 +33,7 @@ public function prompts_data() { 'name' => 'Category 2', ], ], - 'options' => [ + 'options' => [ 'background_color' => '#FFFFFF', 'display_title' => false, 'hide_border' => false, @@ -63,6 +64,44 @@ public function prompts_data() { 'duplicate_of' => 0, 'newspack_popups_has_disabled_popups' => false, ], + 'user_input_fields' => [ + [ + 'name' => 'heading', + 'type' => 'string', + 'label' => 'Heading', + 'description' => 'A heading to describe the prompt.', + 'required' => true, + 'default' => 'Sign Up', + 'max_length' => 40, + ], + [ + 'name' => 'body', + 'type' => 'string', + 'label' => 'Body', + 'description' => 'The primary call to action for the prompt.', + 'required' => true, + 'default' => 'Sign up for our free newsletter to receive the latest news.', + 'max_length' => 300, + ], + [ + 'name' => 'button_label', + 'type' => 'string', + 'label' => 'Button Label', + 'description' => 'The label for the primary CTA button.', + 'required' => true, + 'default' => 'Sign up', + 'max_length' => 20, + ], + [ + 'name' => 'success_message', + 'type' => 'string', + 'label' => 'Success Message', + 'description' => 'The message shown to readers after completing the signup.', + 'required' => true, + 'default' => 'Thank you for registering!', + 'max_length' => 300, + ], + ], ], true, ], diff --git a/tests/test-segments.php b/tests/test-segments.php new file mode 100644 index 00000000..e4997d9f --- /dev/null +++ b/tests/test-segments.php @@ -0,0 +1,533 @@ + 'Complete and valid', + 'priority' => 10, + 'configuration' => [ + 'max_posts' => 1, + 'min_posts' => 1, + 'min_session_posts' => 1, + 'max_session_posts' => 1, + 'is_subscribed' => true, + 'is_not_subscribed' => true, + 'is_donor' => true, + 'is_not_donor' => true, + 'is_former_donor' => true, + 'is_logged_in' => true, + 'is_not_logged_in' => true, + 'favorite_categories' => [], + 'referrers' => '', + 'referrers_not' => '', + 'is_disabled' => false, + ], + ]; + + /** + * A valid segment, without all the properties. + * + * @var array + */ + public $valid = [ + 'name' => 'Valid', + 'priority' => 20, + 'configuration' => [ + 'max_posts' => 1, + ], + ]; + + /** + * A segment missing required properties. + * + * @var array + */ + public $missing_required = [ + 'priority' => 30, + 'configuration' => [ + 'max_posts' => 1, + ], + ]; + + /** + * A segment with additional unknown properties. + * + * @var array + */ + public $additional_properties = [ + 'name' => 'Additional properties', + 'priority' => 40, + 'configuration' => [ + 'max_posts' => 1, + 'unknown' => 'invalid', + ], + ]; + + /** + * A segment with a string in a property that expects an integer. + * + * @var array + */ + public $invalid_int = [ + 'name' => 'Invalid Int', + 'priority' => 10, + 'configuration' => [ + 'max_posts' => 'string', + ], + ]; + + /** + * Make sure we have a clear environment + */ + public static function set_up_before_class() { + $segments = Newspack_Popups_Segmentation::get_segments(); + foreach ( $segments as $segment ) { + Newspack_Popups_Segmentation::delete_segment( $segment['id'] ); + } + } + + /** + * Data provider for test_create_segment + * + * @return array + */ + public function create_segment_data() { + return [ + 'complete_and_valid' => [ $this->complete_and_valid ], + 'valid' => [ $this->valid ], + 'missing_required' => [ $this->missing_required ], + 'additional_properties' => [ $this->additional_properties ], + 'invalid_int' => [ $this->invalid_int ], + ]; + } + + /** + * Test create_segment + * + * @param array $segment The segment. + * @dataProvider create_segment_data + */ + public function test_create_segment( $segment ) { + $result = Newspack_Popups_Segmentation::create_segment( $segment ); + $this->assertSame( 1, count( $result ) ); + + // Assert everything passed in was stored. + // As of today, any arbitrary properties are allowed. + foreach ( $segment as $key => $value ) { + $this->assertSame( $value, $result[0][ $key ] ); + } + + $created_properties = [ 'id', 'created_at', 'updated_at' ]; + foreach ( $created_properties as $property ) { + $this->assertArrayHasKey( $property, $result[0] ); + } + } + + /** + * Test create_segment throws an error when passed a string. + */ + public function test_create_segment_throws() { + $this->expectException( TypeError::class ); + Newspack_Popups_Segmentation::create_segment( 'string' ); + } + + /** + * Test get_segments + */ + public function test_get_segments() { + $this->assertSame( [], Newspack_Popups_Segmentation::get_segments() ); + Newspack_Popups_Segmentation::create_segment( $this->complete_and_valid ); + Newspack_Popups_Segmentation::create_segment( $this->valid ); + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $this->assertSame( 2, count( $segments ) ); + $this->assertSame( $this->complete_and_valid['name'], $segments[0]['name'] ); + $this->assertSame( $this->valid['name'], $segments[1]['name'] ); + } + + /** + * Test get_segments fill in empty priorities. + */ + public function test_get_segments_reindex_priorities() { + $modified = $this->complete_and_valid; + unset( $modified['priority'] ); + Newspack_Popups_Segmentation::create_segment( $this->valid ); + Newspack_Popups_Segmentation::create_segment( $modified ); + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $this->assertSame( 2, count( $segments ) ); + $this->assertSame( $this->valid['name'], $segments[0]['name'] ); + $this->assertSame( $this->complete_and_valid['name'], $segments[1]['name'] ); + $this->assertSame( 0, $segments[0]['priority'] ); + $this->assertSame( 1, $segments[1]['priority'] ); + } + + /** + * Test get_segments fill in empty priorities. + */ + public function test_get_segments_rremove_non_existent_categories() { + $cat_1 = $this->factory()->category->create_and_get( [ 'name' => 'Category 1' ] ); + $cat_2 = $this->factory()->category->create_and_get( [ 'name' => 'Category 2' ] ); + + + $modified = $this->complete_and_valid; + $modified['configuration']['favorite_categories'] = [ $cat_1->term_id, $cat_2->term_id, 9999 ]; + Newspack_Popups_Segmentation::create_segment( $modified ); + + $modified = $this->valid; + $modified['configuration']['favorite_categories'] = [ 8888 ]; + Newspack_Popups_Segmentation::create_segment( $modified ); + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $this->assertSame( 2, count( $segments ) ); + $this->assertSame( [ $cat_1->term_id, $cat_2->term_id ], $segments[0]['configuration']['favorite_categories'] ); + $this->assertSame( [], $segments[1]['configuration']['favorite_categories'] ); + } + + /** + * Test get_segment_ids + */ + public function test_get_segment_ids() { + $this->assertSame( [], Newspack_Popups_Segmentation::get_segment_ids() ); + Newspack_Popups_Segmentation::create_segment( $this->complete_and_valid ); + Newspack_Popups_Segmentation::create_segment( $this->valid ); + + $segments = Newspack_Popups_Segmentation::get_segments(); + $segment_ids = Newspack_Popups_Segmentation::get_segment_ids(); + + $this->assertSame( 2, count( $segment_ids ) ); + $this->assertSame( $segments[0]['id'], $segment_ids[0] ); + $this->assertSame( $segments[1]['id'], $segment_ids[1] ); + } + + /** + * Test get_segment + */ + public function test_get_segment() { + $segments_to_create = [ + $this->complete_and_valid, + $this->valid, + $this->missing_required, + $this->additional_properties, + $this->invalid_int, + ]; + + foreach ( $segments_to_create as $segment ) { + Newspack_Popups_Segmentation::create_segment( $segment ); + } + + $segments = Newspack_Popups_Segmentation::get_segments(); + + foreach ( $segments as $segment ) { + $segment_id = $segment['id']; + $segment_from_db = Newspack_Popups_Segmentation::get_segment( $segment_id ); + $this->assertSame( $segment, $segment_from_db ); + } + + } + + /** + * Test delete_segment + */ + public function test_delete_segment() { + $segments_to_create = [ + $this->complete_and_valid, + $this->valid, + $this->missing_required, + $this->additional_properties, + $this->invalid_int, + ]; + + foreach ( $segments_to_create as $segment ) { + Newspack_Popups_Segmentation::create_segment( $segment ); + } + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $delete_result = Newspack_Popups_Segmentation::delete_segment( $segments[0]['id'] ); + $this->assertSame( 4, count( $delete_result ) ); + $this->assertSame( $segments[1]['id'], $delete_result[0]['id'] ); + + $delete_result = Newspack_Popups_Segmentation::delete_segment( $segments[3]['id'] ); + $this->assertSame( 3, count( $delete_result ) ); + $this->assertSame( $segments[4]['id'], $delete_result[2]['id'] ); + + $delete_result2 = Newspack_Popups_Segmentation::delete_segment( 'non-existent' ); + $this->assertSame( $delete_result, $delete_result2 ); + + } + + /** + * Test update_segment + */ + public function test_update_segment() { + $segments_to_create = [ + $this->complete_and_valid, + $this->valid, + ]; + + foreach ( $segments_to_create as $segment ) { + Newspack_Popups_Segmentation::create_segment( $segment ); + } + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $complete = $segments[0]; + + $complete['name'] = 'Edited'; + $complete['priority'] = 99; + $complete['configuration']['min_posts'] = 30; + $complete['other_properties'] = true; + + $result = Newspack_Popups_Segmentation::update_segment( $complete ); + + $this->assertSame( 'Edited', $result[0]['name'] ); + $this->assertSame( 30, $result[0]['configuration']['min_posts'] ); + $this->assertSame( 10, $result[0]['priority'], 'Priority should not be updated' ); + $this->assertNotContains( 'other_properties', $result[0], 'additional properties should not be included' ); + + $this->assertSame( $this->valid['name'], $result[1]['name'] ); + + } + + /** + * Test update_segment throws an error when passed a string. + */ + public function test_update_segment_throws() { + $this->expectException( TypeError::class ); + Newspack_Popups_Segmentation::update_segment( 'string' ); + } + + /** + * Test reindex_segments + */ + public function test_reindex_segments() { + $segments_to_create_in_different_order = [ + $this->complete_and_valid, + $this->additional_properties, + $this->invalid_int, + $this->valid, + ]; + + // Remove priority from the first segment. This will be populated with priority 0 and will be persisted when the second segment is created. + unset( $segments_to_create_in_different_order[0]['priority'] ); + + foreach ( $segments_to_create_in_different_order as $segment ) { + Newspack_Popups_Segmentation::create_segment( $segment ); + } + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $reindexed = Newspack_Popups_Segmentation::reindex_segments( $segments ); + $index = 0; + foreach ( $reindexed as $segment ) { + $this->assertSame( $index, $segment['priority'] ); + $this->assertSame( $segments_to_create_in_different_order[ $index ]['name'], $segment['name'] ); + $index++; + } + + // Assert that the reindexed segments were not persisted. + $segments = Newspack_Popups_Segmentation::get_segments(); + $index = 0; + foreach ( $segments as $segment ) { + $expected_priority = $segments_to_create_in_different_order[ $index ]['priority'] ?? 0; + $this->assertSame( $segments_to_create_in_different_order[ $index ]['name'], $segment['name'] ); + $this->assertSame( $expected_priority, $segment['priority'] ); + $index++; + } + + } + + /** + * Test reindex_segments throws an error when passed a string. + */ + public function test_reindex_segments_throws() { + $this->expectException( TypeError::class ); + Newspack_Popups_Segmentation::reindex_segments( 'string' ); + } + + /** + * Test sort_segments + */ + public function test_sort_segments() { + $segments_to_create_in_different_order = [ + $this->complete_and_valid, + $this->additional_properties, + $this->invalid_int, + $this->valid, + ]; + + foreach ( $segments_to_create_in_different_order as $segment ) { + Newspack_Popups_Segmentation::create_segment( $segment ); + } + + $segments = Newspack_Popups_Segmentation::get_segments(); + + $new_order = [ + $segments[3]['id'], + $segments[0]['id'], + $segments[1]['id'], + $segments[2]['id'], + ]; + + $sorted = Newspack_Popups_Segmentation::sort_segments( $new_order ); + + $this->assertSame( $segments[3]['name'], $sorted[0]['name'] ); + $this->assertSame( $segments[0]['name'], $sorted[1]['name'] ); + $this->assertSame( $segments[1]['name'], $sorted[2]['name'] ); + $this->assertSame( $segments[2]['name'], $sorted[3]['name'] ); + + // Assert that the sorted segments were persisted. + $this->assertSame( $sorted, Newspack_Popups_Segmentation::get_segments() ); + + $this->assertTrue( + is_wp_error( + Newspack_Popups_Segmentation::sort_segments( array_merge( $new_order, [ 'asdasd' ] ) ) + ), + 'Should return wp error if an invalid id is part of the array' + ); + + } + + /** + * Test sort_segments throws an error when passed a string. + */ + public function test_sort_segments_throws() { + $this->expectException( TypeError::class ); + Newspack_Popups_Segmentation::sort_segments( 'string' ); + } + + /** + * Data provider for test_validate_segment_ids + * + * @return array + */ + public function data_validate_segment_ids() { + return [ + [ + [ 1, 2, 3 ], + [ + [ + 'id' => 1, + ], + [ + 'id' => 2, + ], + [ + 'id' => 3, + ], + ], + true, + ], + [ + [ 1, 2, 3 ], + [ + [ + 'id' => 1, + ], + [ + 'id' => 2, + ], + ], + false, + ], + [ + [ 1, 2, 3 ], + [ + [ + 'id' => 1, + ], + [ + 'id' => 2, + ], + [ + 'id' => 4, + ], + ], + false, + ], + [ + [ 1, 2, 3 ], + [ + [ + 'id' => 1, + ], + [ + 'id' => 2, + ], + [ + 'id' => 3, + ], + [ + 'id' => 4, + ], + ], + false, + ], + [ + [ 1, 2, 3, 4 ], + [ + [ + 'id' => 1, + ], + [ + 'id' => 2, + ], + [ + 'id' => 4, + ], + ], + false, + ], + [ + [ 1, 2 ], + 'string', + false, + true, + ], + [ + 'string', + [ + [ + 'id' => 1, + ], + ], + false, + true, + ], + ]; + } + + /** + * Test validate_segment_ids + * + * @param array $segment_ids Array of segment IDs to validate. + * @param array $segments Array of existing segments to validate against. + * @param boolean $expected Whether $segment_ids is valid. + * @param boolean $throw Whether it will throw Type_Error. + * @dataProvider data_validate_segment_ids + */ + public function test_validate_segment_ids( $segment_ids, $segments, $expected, $throw = false ) { + if ( $throw ) { + $this->expectException( TypeError::class ); + } + $this->assertSame( $expected, Newspack_Popups_Segmentation::validate_segment_ids( $segment_ids, $segments ) ); + } + +}