diff --git a/composer.lock b/composer.lock index 8f2e27e7..4a1db18c 100644 --- a/composer.lock +++ b/composer.lock @@ -1645,7 +1645,7 @@ }, { "name": "illuminate/collections", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", @@ -1700,7 +1700,7 @@ }, { "name": "illuminate/conditionable", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -1746,7 +1746,7 @@ }, { "name": "illuminate/contracts", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", @@ -1794,7 +1794,7 @@ }, { "name": "illuminate/macroable", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -1840,16 +1840,16 @@ }, { "name": "illuminate/support", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "0a8526d55756955fcec6be7c2c6cd14d915c8c0f" + "reference": "38d3e064b7b9420d2173f23a31a435bde221b56d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/0a8526d55756955fcec6be7c2c6cd14d915c8c0f", - "reference": "0a8526d55756955fcec6be7c2c6cd14d915c8c0f", + "url": "https://api.github.com/repos/illuminate/support/zipball/38d3e064b7b9420d2173f23a31a435bde221b56d", + "reference": "38d3e064b7b9420d2173f23a31a435bde221b56d", "shasum": "" }, "require": { @@ -1907,7 +1907,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-08-14T21:56:59+00:00" + "time": "2023-08-21T13:45:59+00:00" }, { "name": "ivome/graphql-relay-php", @@ -2951,16 +2951,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.29", + "version": "1.10.30", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ee5d8f2d3977fb09e55603eee6fb53bdd76ee9c1" + "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ee5d8f2d3977fb09e55603eee6fb53bdd76ee9c1", - "reference": "ee5d8f2d3977fb09e55603eee6fb53bdd76ee9c1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2910afdd3fe33e5afd71c09f3fb0d0845b48c410", + "reference": "2910afdd3fe33e5afd71c09f3fb0d0845b48c410", "shasum": "" }, "require": { @@ -3009,7 +3009,7 @@ "type": "tidelift" } ], - "time": "2023-08-14T13:24:11+00:00" + "time": "2023-08-22T13:48:25+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/src/Admin/Editor.php b/src/Admin/Editor.php index 3df834ff..e305fb5f 100644 --- a/src/Admin/Editor.php +++ b/src/Admin/Editor.php @@ -10,6 +10,7 @@ use WPGraphQL\SmartCache\AdminErrors; use WPGraphQL\SmartCache\Document; use WPGraphQL\SmartCache\Document\Grant; +use WPGraphQL\SmartCache\Document\Group; use WPGraphQL\SmartCache\Document\MaxAge; use GraphQL\Error\SyntaxError; use GraphQL\Server\RequestError; @@ -34,6 +35,7 @@ public function admin_init() { add_filter( sprintf( 'manage_edit-%s_sortable_columns', Document::TYPE_NAME ), [ $this, 'make_excerpt_column_sortable_in_admin_cb' ], 10, 1 ); add_filter( 'wp_editor_settings', [ $this, 'wp_editor_settings' ], 10, 2 ); + add_action( 'post_submitbox_misc_actions', [ $this, 'draw_save_as_new_checkbox_cb' ] ); } /** @@ -71,7 +73,8 @@ public function validate_and_pre_save_cb( $data, $post ) { return $data; } - $document = new Document(); + $document = new Document(); + $existing_post = get_post( $post['ID'], ARRAY_A ); try { // Check for empty post_content when publishing the query and throw @@ -81,6 +84,36 @@ public function validate_and_pre_save_cb( $data, $post ) { $data['post_content'] = $document->valid_or_throw( $post['post_content'], $post['ID'] ); + // If post is already published and saving as published, and graphql query string is different on save + if ( 'publish' === $existing_post['post_status'] && 'publish' === $post['post_status'] ) { + // If selected to save as new + // phpcs:ignore + if ( isset( $_POST['graphql_query_save_new'] ) && 'save_as_new' === $_POST['graphql_query_save_new'] ) { + // phpcs:ignore + unset( $_POST['graphql_query_save_new'] ); + + // Reset some data in the post before save as new. $data doesn't have a post_id. + $data['post_status'] = 'draft'; + $data['post_date_gmt'] = '0000-00-00 00:00:00'; + $data['post_date'] = ''; + if ( $data['post_title'] === $existing_post['post_title'] ) { + $data['post_title'] = $existing_post['post_title'] . ' (copy)'; + } + + $new_post_id = wp_insert_post( $data ); + + $group = new Group(); + $group->save( $new_post_id, $group->get( $existing_post['ID'] ) ); + + // Redirect to the new post edit page after save + wp_safe_redirect( admin_url( sprintf( '/post.php?post=%d&action=edit', $new_post_id ) ) ); + exit; + } + + if ( $data['post_content'] !== $existing_post['post_content'] ) { + throw new RequestError( __( 'Changing query for published query is not allowed. Select the save as new and publish again.', 'wp-graphql-smart-cache' ) ); + } + } } catch ( RequestError $e ) { AdminErrors::add_message( $e->getMessage() ); @@ -88,7 +121,6 @@ public function validate_and_pre_save_cb( $data, $post ) { if ( 'publish' === $post['post_status'] ) { // If has an existing published post and trying to publish with errors, bail before save_post - $existing_post = get_post( $post['ID'], ARRAY_A ); if ( $existing_post && 'publish' === $existing_post['post_status'] ) { wp_safe_redirect( admin_url( sprintf( '/post.php?post=%d&action=edit', $post['ID'] ) ) ); @@ -363,4 +395,44 @@ public function wp_editor_settings( $settings, $editor_id ) { return $settings; } + + /** + * @param \WP_Post $post + * @return void + */ + public function draw_save_as_new_checkbox_cb( $post ) { + $post_id = get_the_ID(); + + if ( Document::TYPE_NAME !== $post->post_type ) { + return; + } + + if ( 'publish' !== $post->post_status ) { + return; + } + + $html = '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + + /** @var array[] */ + $allowed_html = [ + 'div' => [ + 'class' => true, + ], + 'input' => [ + 'type' => true, + 'id' => true, + 'name' => true, + 'value' => true, + 'checked' => true, + ], + 'br' => true, + ]; + echo wp_kses( + $html, + $allowed_html + ); + } } diff --git a/src/Document/Group.php b/src/Document/Group.php index 6bc3012b..eab04802 100644 --- a/src/Document/Group.php +++ b/src/Document/Group.php @@ -51,4 +51,16 @@ public static function get( $post_id ) { $item = get_the_terms( $post_id, self::TAXONOMY_NAME ); return ! is_wp_error( $item ) && isset( $item[0] ) && property_exists( $item[0], 'name' ) ? $item[0]->name : ''; } + + /** + * Save the data + * + * @param int $post_id + * @param string $value + * @return array|false|\WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure. + */ + public function save( $post_id, $value ) { + return wp_set_post_terms( $post_id, $value, self::TAXONOMY_NAME ); + } + } diff --git a/tests/functional/AdminEditorDocumentCest.php b/tests/functional/AdminEditorDocumentCest.php index eec41963..616b8ec6 100644 --- a/tests/functional/AdminEditorDocumentCest.php +++ b/tests/functional/AdminEditorDocumentCest.php @@ -487,4 +487,92 @@ public function createNewQueryWithInvalidContentThenTrashItTest( FunctionalTeste $I->dontSeeElement('//*[@id="plugin-message"]'); $I->dontSee('Invalid graphql query string "{ __typename broken"', '//*[@id="plugin-message"]'); } + + public function havePublishedQueryWhenChangeQueryStringThePublishShowsErrorTest( FunctionalTester $I ) { + $post_title = 'test-post'; + $original_query = '{ __typename }'; + $normalized_query_string = "{\n __typename\n}\n"; + $new_query = '{ __typename __typename }'; + $the_new_normal = "{\n __typename\n __typename\n}\n"; + + // Create a new query in the admin editor + $I->loginAsAdmin(); + $I->amOnPage( '/wp-admin/post-new.php?post_type=graphql_document'); + + // Add title should trigger auto-draft, but not save document + $I->fillField( "//input[@name='post_title']", $post_title); + $I->fillField( 'content', $original_query); + $I->fillField( 'graphql_query_maxage', '200'); + + // Publish post + $I->click('#publish'); + + $post_id_1 = $I->grabValueFrom(['name' => 'post_ID']); + + $I->seePostInDatabase( [ + 'post_title' => $post_title, + 'post_status' => 'publish', + 'post_content' => $normalized_query_string, + ]); + + // new query + $I->fillField( 'content', $new_query ); + + // Shows save-as-new check box + $I->seeElement('//*[@id="graphql_query_save_new"]'); + $I->see('Save As Draft'); + $I->dontSeeCheckboxIsChecked("//input[@type='checkbox' and @name='graphql_query_save_new']"); + + // // Publish post button + $I->click('#publish'); + + $post_id_2 = $I->grabValueFrom(['name' => 'post_ID']); + $I->assertEquals( $post_id_1, $post_id_2 ); + $I->seeInCurrentUrl( "/wp-admin/post.php?post=$post_id_1&action=edit" ); + + // Should not see success of the publish. + $I->dontSeeElement('//*[@id="message"]'); + $I->dontSee('Post published.'); + $I->dontSee('Post updated.'); + $I->dontSee('Post saved.'); + $I->dontSee('Publish immediately'); // has date because already published + + // Shows error message + $I->seeElement('//*[@id="plugin-message"]'); + $I->see('Changing query for published query is not allowed. Select the save as new and publish again.', '//*[@id="plugin-message"]'); + + // Shows save-as-new check box + $I->seeElement('//*[@id="graphql_query_save_new"]'); + $I->see('Save As Draft'); + $I->dontSeeCheckboxIsChecked("//input[@type='checkbox' and @name='graphql_query_save_new']"); + + // new query + $I->fillField( 'content', $new_query ); + + // Select the save as new + $I->checkOption("//input[@type='checkbox' and @name='graphql_query_save_new']"); + + // // Publish post button + $I->click('#publish'); + + $post_id_3 = $I->grabValueFrom(['name' => 'post_ID']); + $I->assertNotEquals( $post_id_1, $post_id_3 ); + $I->seeInCurrentUrl( "/wp-admin/post.php?post=$post_id_3&action=edit" ); + + // should not see our admin error + $I->dontSeeElement('//*[@id="plugin-message"]'); + + // Saves the different query as draft with new title + $I->seePostInDatabase( [ + 'post_title' => "$post_title (copy)", + 'post_status' => 'draft', + 'post_content' => $the_new_normal, + ]); + + $post_id = $I->grabValueFrom(['name' => 'post_ID']); + + // Should also save the other data for the new post + $I->seeInField(['name' => 'graphql_query_maxage'], '200'); + } + }