From 1a488e536d5633a5d79bcc4a6c59068117ff6e0b Mon Sep 17 00:00:00 2001 From: Caroline Moore Date: Wed, 21 Jun 2023 09:59:38 -0400 Subject: [PATCH 01/29] Launchpad: Add backend tracks event for task completion (#31444) * Update tracks completion event * UPdate naming, add to other mark task complete function. * Add extra props arg * Add changelog * Add tracks event to API completions * Only fire event if value is true * Fix linting * Add action for marking domain tasks as complete * Only track task completion if it succeeds * Need to break down the value into separate task keys * Update endpoint logic to avoid changing the output --- .../changelog/update-add-tracks-function | 4 ++ .../launchpad/launchpad-task-definitions.php | 69 ++++++++++++++++--- ...s-wpcom-rest-api-v2-endpoint-launchpad.php | 7 ++ 3 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/update-add-tracks-function diff --git a/projects/packages/jetpack-mu-wpcom/changelog/update-add-tracks-function b/projects/packages/jetpack-mu-wpcom/changelog/update-add-tracks-function new file mode 100644 index 0000000000000..f06da6f352fd4 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/update-add-tracks-function @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add a function to fire off a Tracks event when a task is completed and update existing mark task complete functions to use it. diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php b/projects/packages/jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php index db74668f0eb20..94bffa850945a 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/launchpad/launchpad-task-definitions.php @@ -229,6 +229,31 @@ function wpcom_launchpad_get_task_definitions() { return array_merge( $extended_task_definitions, $task_definitions ); } +/** + * Record completion event in Tracks if we're running on WP.com. + * + * @param string $task_id The task ID. + * @param array $extra_props Optional extra arguments to pass to the Tracks event. + * + * @return void + */ +function wpcom_launchpad_track_completed_task( $task_id, $extra_props = array() ) { + if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) { + return; + } + + require_lib( 'tracks/client' ); + + tracks_record_event( + wp_get_current_user(), + 'wpcom_launchpad_mark_task_complete', + array_merge( + array( 'task_id' => $task_id ), + $extra_props + ) + ); +} + /** * Mark a task as complete. * @@ -253,16 +278,8 @@ function wpcom_mark_launchpad_task_complete( $task_id ) { $statuses[ $key ] = true; $result = update_option( 'launchpad_checklist_tasks_statuses', $statuses ); - // Record the completion event in Tracks if we're running on WP.com. - if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { - require_lib( 'tracks/client' ); - - tracks_record_event( - wp_get_current_user(), - 'launchpad_mark_task_completed', - array( 'task_id' => $key ) - ); - } + // Record the completion event in Tracks. + wpcom_launchpad_track_completed_task( $key ); return $result; } @@ -330,7 +347,12 @@ function wpcom_launchpad_init_task_definitions() { * @return bool True if successful, false if not. */ function wpcom_mark_launchpad_task_complete_if_active( $task_id ) { - return wpcom_launchpad_checklists()->mark_task_complete_if_active( $task_id ); + if ( wpcom_launchpad_checklists()->mark_task_complete_if_active( $task_id ) ) { + wpcom_launchpad_track_completed_task( $task_id ); + return true; + } + + return false; } /** @@ -633,3 +655,28 @@ function ( $site_purchase ) { return ! empty( $domain_purchases ); } + +/** + * Mark `domain_claim`, `domain_upsell`, and `domain_upsell_deferred` tasks complete + * when a domain product is activated. + * + * @param int $blog_id The blog ID. + * @param int $user_id The user ID. + * @param string $product_id The product ID. + * + * @return void + */ +function wpcom_mark_domain_tasks_complete( $blog_id, $user_id, $product_id ) { + if ( ! class_exists( 'domains' ) ) { + return; + } + + if ( ! domains::is_domain_product( $product_id ) ) { + return; + } + + wpcom_mark_launchpad_task_complete( 'domain_claim' ); + wpcom_mark_launchpad_task_complete( 'domain_upsell' ); + wpcom_mark_launchpad_task_complete( 'domain_upsell_deferred' ); +} +add_action( 'activate_product', 'wpcom_mark_domain_tasks_complete', 10, 6 ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php index a989340865728..53377db922334 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php @@ -142,6 +142,13 @@ public function update_site_options( $request ) { $launchpad_checklist_tasks_statuses_option = (array) get_option( 'launchpad_checklist_tasks_statuses', array() ); $launchpad_checklist_tasks_statuses_option = array_merge( $launchpad_checklist_tasks_statuses_option, $value ); + foreach ( $value as $task => $task_value ) { + if ( $task_value ) { + // If we're marking a task as complete, the value should be truthy, so fire the Tracks event. + wpcom_launchpad_track_completed_task( $task ); + } + } + if ( update_option( 'launchpad_checklist_tasks_statuses', $launchpad_checklist_tasks_statuses_option ) ) { $updated[ $key ] = $value; } From 12880c2bccaae61143394ccd861f851688615662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Jun 2023 11:25:43 -0300 Subject: [PATCH 02/29] AI Extension: do not extend block sidebar (#31476) * [not verified] do not extend the block sidebar for now * [not verified] changelog --- .../update-ai-extension-do-not-extend-sidebar-for-now | 4 ++++ .../extensions/ai-assistant/with-ai-assistant.tsx | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-do-not-extend-sidebar-for-now diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-do-not-extend-sidebar-for-now b/projects/plugins/jetpack/changelog/update-ai-extension-do-not-extend-sidebar-for-now new file mode 100644 index 0000000000000..08d0f1302ae63 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-do-not-extend-sidebar-for-now @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AU Extension: do not extend block sidebar diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index 71d7f64f10b33..963e74f0684c0 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { InspectorControls, BlockControls } from '@wordpress/block-editor'; +import { BlockControls } from '@wordpress/block-editor'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; @@ -13,7 +13,6 @@ import React from 'react'; import AiAssistantDropdown, { AiAssistantDropdownOnChangeOptionsArgProps, } from '../../components/ai-assistant-controls'; -import AiAssistantPanel from '../../components/ai-assistant-panel'; import useSuggestionsFromAI from '../../hooks/use-suggestions-from-ai'; import { PromptItemProps, PromptTypeProp, getPrompt } from '../../lib/prompt'; @@ -72,10 +71,6 @@ export const withAIAssistant = createHigherOrderComponent( <> - - - - From 195f239e10a3ce96abeb40b55017c2307879d81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Jun 2023 12:31:24 -0300 Subject: [PATCH 03/29] AI Extension: populate the prompt with previous messages (#31470) * [not verified] change the prompt local state structure * [not verified] move debug() to hook * [not verified] populate prompt with the previous messages * [not verified] add prevMessages to getPrompt() function * [not verified] improve doc * [not verified] auto requst onle when autoRequest * [not verified] propagate prev messages to the getPrompt() * [not verified] populate prompt with assistant message * [not verified] change request API of the useSuggestionsFromAI * [not verified] limit the messages to six items per prompt * changelog --- ...ion-preserve-prompt-content-among-requests | 4 + .../ai-assistant-controls/index.tsx | 6 +- .../ai-assistant/with-ai-assistant.tsx | 75 ++++++++++++++++--- .../hooks/use-suggestions-from-ai/index.ts | 54 +++++++------ .../blocks/ai-assistant/lib/prompt/index.ts | 56 ++++++++++---- 5 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-preserve-prompt-content-among-requests diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-preserve-prompt-content-among-requests b/projects/plugins/jetpack/changelog/update-ai-extension-preserve-prompt-content-among-requests new file mode 100644 index 0000000000000..15b9ca3d0d728 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-preserve-prompt-content-among-requests @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: populate the prompt with previous messages diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx index 9e8c5607be354..0d7aff288da17 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/components/ai-assistant-controls/index.tsx @@ -125,7 +125,7 @@ export default function AiAssistantDropdown( { iconPosition="left" key={ `key-${ quickAction.key }` } onClick={ () => { - onChange( quickAction.aiSuggestion, { contentType: 'generated' } ); + onChange( quickAction.aiSuggestion ); closeDropdown(); } } isSelected={ key === quickAction.key } @@ -136,14 +136,14 @@ export default function AiAssistantDropdown( { { - onChange( PROMPT_TYPE_CHANGE_TONE, { tone, contentType: 'generated' } ); + onChange( PROMPT_TYPE_CHANGE_TONE, { tone } ); closeDropdown(); } } /> { - onChange( PROMPT_TYPE_CHANGE_LANGUAGE, { language, contentType: 'generated' } ); + onChange( PROMPT_TYPE_CHANGE_LANGUAGE, { language } ); closeDropdown(); } } /> diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index 963e74f0684c0..841c233a72830 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -14,7 +14,15 @@ import AiAssistantDropdown, { AiAssistantDropdownOnChangeOptionsArgProps, } from '../../components/ai-assistant-controls'; import useSuggestionsFromAI from '../../hooks/use-suggestions-from-ai'; -import { PromptItemProps, PromptTypeProp, getPrompt } from '../../lib/prompt'; +import { getPrompt } from '../../lib/prompt'; +/* + * Types + */ +import type { PromptItemProps, PromptTypeProp } from '../../lib/prompt'; + +type StoredPromptProps = { + messages: Array< PromptItemProps >; +}; /* * Extend the withAIAssistant function of the block @@ -23,7 +31,9 @@ import { PromptItemProps, PromptTypeProp, getPrompt } from '../../lib/prompt'; export const withAIAssistant = createHigherOrderComponent( BlockEdit => props => { const { clientId } = props; - const [ storedPrompt, setStoredPrompt ] = useState< Array< PromptItemProps > >( [] ); + const [ storedPrompt, setStoredPrompt ] = useState< StoredPromptProps >( { + messages: [], + } ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); @@ -53,18 +63,63 @@ export const withAIAssistant = createHigherOrderComponent( [ clientId, updateBlockAttributes ] ); - useSuggestionsFromAI( { prompt: storedPrompt, onSuggestion: setContent } ); + const addAssistantMessage = useCallback( + ( assistantContent: string ) => { + setStoredPrompt( prevPrompt => { + /* + * Add the assistant messages to the prompt. + * - Preserve the first item of the array (`system` role ) + * - Keep the last 4 messages. + */ + + // Pick the first item of the array. + const firstItem = prevPrompt.messages.shift(); + + const messages: Array< PromptItemProps > = [ + firstItem, // first item (`system` by default) + ...prevPrompt.messages.splice( -3 ), // last 3 items + { + role: 'assistant', + content: assistantContent, // + 1 `assistant` role item + }, + ]; + + return { + ...prevPrompt, + messages, + }; + } ); + }, + [ setStoredPrompt ] + ); + + const { request } = useSuggestionsFromAI( { + prompt: storedPrompt.messages, + onSuggestion: setContent, + onDone: addAssistantMessage, + autoRequest: false, + } ); const requestSuggestion = useCallback( ( promptType: PromptTypeProp, options: AiAssistantDropdownOnChangeOptionsArgProps ) => { - setStoredPrompt( - getPrompt( promptType, { - ...options, - content, - } ) - ); + setStoredPrompt( prevPrompt => { + const freshPrompt = { + ...prevPrompt, + messages: getPrompt( promptType, { + ...options, + content, + prevMessages: prevPrompt.messages, + } ), + }; + + // Request the suggestion from the AI. + request( freshPrompt.messages ); + + // Update the stored prompt. + return freshPrompt; + } ); }, - [ content ] + [ content, request ] ); return ( diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts index 1cd4eadb4d340..951dd2f608a91 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts @@ -4,12 +4,15 @@ import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { useCallback, useEffect, useRef } from '@wordpress/element'; +import debugFactory from 'debug'; /** * Types */ import { SuggestionsEventSource, askQuestion } from '../../lib/suggestions'; import type { PromptItemProps } from '../../lib/prompt'; +const debug = debugFactory( 'jetpack-ai-assistant:prompt' ); + type UseSuggestionsFromAIOptions = { /* * Request prompt. @@ -51,7 +54,7 @@ type useSuggestionsFromAIProps = { /* * The request handler. */ - request: () => Promise< void >; + request: ( prompt: Array< PromptItemProps > ) => Promise< void >; }; /** @@ -102,26 +105,33 @@ export default function useSuggestionsFromAI( { * * @returns {Promise} The promise. */ - const request = useCallback( async () => { - try { - source.current = await askQuestion( prompt, { - postId, - requireUpgrade: false, // It shouldn't be part of the askQuestion API. - fromCache: false, - } ); - - if ( onSuggestion ) { - source?.current?.addEventListener( 'suggestion', handleSuggestion ); - } - - if ( onDone ) { - source?.current?.addEventListener( 'done', handleDone ); + const request = useCallback( + async ( promptArg: Array< PromptItemProps > ) => { + promptArg.forEach( ( { role, content: promptContent }, i ) => + debug( '(%s/%s) %o\n%s', i + 1, promptArg.length, `[${ role }]`, promptContent ) + ); + + try { + source.current = await askQuestion( promptArg, { + postId, + requireUpgrade: false, // It shouldn't be part of the askQuestion API. + fromCache: false, + } ); + + if ( onSuggestion ) { + source?.current?.addEventListener( 'suggestion', handleSuggestion ); + } + + if ( onDone ) { + source?.current?.addEventListener( 'done', handleDone ); + } + } catch ( e ) { + // eslint-disable-next-line no-console + console.error( e ); } - } catch ( e ) { - // eslint-disable-next-line no-console - console.error( e ); - } - }, [ prompt, postId, onSuggestion, onDone, handleSuggestion, handleDone ] ); + }, + [ postId, onSuggestion, onDone, handleSuggestion, handleDone ] + ); // Request suggestions automatically when ready. useEffect( () => { @@ -131,7 +141,9 @@ export default function useSuggestionsFromAI( { } // Trigger the request. - request(); + if ( autoRequest ) { + request( prompt ); + } // Close the connection when unmounting. return () => { diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index 3eb5fe9e7c41e..60cf195f24bb8 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -78,10 +78,30 @@ ${ extraRules }- Format your responses in Markdown syntax, ready to be published } type PromptOptionsProps = { + /* + * The content to add to the prompt. + */ content: string; + + /* + * The language to translate to. Optional. + */ language?: string; + + /* + * The tone to use. Optional. + */ tone?: ToneProp; + + /* + * The role of the prompt. Optional. + */ role?: PromptItemProps[ 'role' ]; + + /* + * The previous messages of the same prompt. Optional. + */ + prevMessages?: Array< PromptItemProps >; }; function getCorrectSpellingPrompt( { @@ -376,13 +396,22 @@ export function getPrompt( type: PromptTypeProp, options: PromptOptionsProps ): Array< PromptItemProps > { + debug( 'Addressing prompt type: %o %o', type, options ); + const { prevMessages } = options; + const context = 'You are an excellent polyglot ghostwriter. ' + 'Your task is to help the user to create and modify content based on their requests.'; - const initialSystemPrompt: PromptItemProps = { - role: 'system', - content: `${ context } + /* + * Create the initial prompt only if there are no previous messages. + * Otherwise, let's use the previous messages as the initial prompt. + */ + let prompt: Array< PromptItemProps > = ! prevMessages?.length + ? [ + { + role: 'system', + content: `${ context } Writing rules: - When it isn't clarified, the content should be written in the same language as the original content. - Format your responses in Markdown syntax. @@ -390,38 +419,35 @@ Writing rules: - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. - If you cannot generate a meaningful response to a user’s request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. `, - }; + }, + ] + : prevMessages; - const prompt: Array< PromptItemProps > = [ initialSystemPrompt ]; switch ( type ) { case PROMPT_TYPE_CORRECT_SPELLING: - prompt.push( ...getCorrectSpellingPrompt( options ) ); + prompt = [ ...prompt, ...getCorrectSpellingPrompt( options ) ]; break; case PROMPT_TYPE_SIMPLIFY: - prompt.push( ...getSimplifyPrompt( options ) ); + prompt = [ ...prompt, ...getSimplifyPrompt( options ) ]; break; case PROMPT_TYPE_SUMMARIZE: - prompt.push( ...getSummarizePrompt( options ) ); + prompt = [ ...prompt, ...getSummarizePrompt( options ) ]; break; case PROMPT_TYPE_MAKE_LONGER: - prompt.push( ...getExpandPrompt( options ) ); + prompt = [ ...prompt, ...getExpandPrompt( options ) ]; break; case PROMPT_TYPE_CHANGE_LANGUAGE: - prompt.push( ...getTranslatePrompt( options ) ); + prompt = [ ...prompt, ...getTranslatePrompt( options ) ]; break; case PROMPT_TYPE_CHANGE_TONE: - prompt.push( ...getTonePrompt( options ) ); + prompt = [ ...prompt, ...getTonePrompt( options ) ]; break; } - prompt.forEach( ( { role, content: promptContent }, i ) => - debug( '(%s/%s) %o\n%s', i + 1, prompt.length, `[${ role }]`, promptContent ) - ); - return prompt; } From 4b064ab703abe4dbec86b110db14768b93ecffb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Jun 2023 12:31:57 -0300 Subject: [PATCH 04/29] AI Extension: improve block transform process (#31481) * improve the transform block process * changelog * Update projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx Co-authored-by: Douglas Henri * Update projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx --------- Co-authored-by: Douglas Henri --- ...extension-fix-transform-from-heading-block | 4 ++ .../blocks/ai-assistant/transforms/index.tsx | 51 ++++++++++++------- 2 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-fix-transform-from-heading-block diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-fix-transform-from-heading-block b/projects/plugins/jetpack/changelog/update-ai-extension-fix-transform-from-heading-block new file mode 100644 index 0000000000000..444f3ad69db06 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-fix-transform-from-heading-block @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: improve block transform process diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx index 7edeeebc21ea2..b3df4907de2b4 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/transforms/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { createBlock } from '@wordpress/blocks'; +import { createBlock, getBlockContent } from '@wordpress/blocks'; import TurndownService from 'turndown'; /** * Internal dependencies @@ -13,24 +13,41 @@ import { EXTENDED_BLOCKS, isPossibleToExtendBlock } from '../extensions/ai-assis */ import { PromptItemProps } from '../lib/prompt'; -const turndownService = new TurndownService(); +const turndownService = new TurndownService( { emDelimiter: '_' } ); -const transformFromCore = { - type: 'block', - blocks: EXTENDED_BLOCKS, - isMatch: () => isPossibleToExtendBlock(), - transform: ( { content } ) => { - const messages: Array< PromptItemProps > = [ - { - role: 'assistant', - content, - }, - ]; +const from = []; - return createBlock( blockName, { content: turndownService.turndown( content ), messages } ); - }, -}; +/* + * Create individual transform handler for each block type. + */ +for ( const blockType of EXTENDED_BLOCKS ) { + from.push( { + type: 'block', + blocks: [ blockType ], + isMatch: () => isPossibleToExtendBlock(), + transform: ( { content } ) => { + // Create a temporary block to get the HTML content. + const temporaryBlock = createBlock( blockType, { content } ); + const htmlContent = getBlockContent( temporaryBlock ); + + // Convert the content to markdown. + content = turndownService.turndown( htmlContent ); + + // Create a pair of user/assistant messages. + const messages: Array< PromptItemProps > = [ + { + role: 'user', + content: 'Tell me some content for this block, please.', + }, + { + role: 'assistant', + content, + }, + ]; -const from = [ transformFromCore ]; + return createBlock( blockName, { content, messages } ); + }, + } ); +} export default { from }; From ef6b463796be3ce6a583cfbd4cfa8b096756513e Mon Sep 17 00:00:00 2001 From: gogdzl <37049295+gogdzl@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:32:14 -0300 Subject: [PATCH 05/29] CRM: Bump Jetpack CRM's major version to 6 (#31489) * Update major version * changelog * Update changelog (entry may only be empty when Significance is "patch") * Run tools/fixup-project-versions.sh --- projects/plugins/crm/ZeroBSCRM.php | 2 +- .../crm/changelog/update-crm-3156-bump-major-version-to-6 | 5 +++++ projects/plugins/crm/composer.json | 2 +- projects/plugins/crm/includes/ZeroBSCRM.Core.php | 2 +- projects/plugins/crm/package.json | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 diff --git a/projects/plugins/crm/ZeroBSCRM.php b/projects/plugins/crm/ZeroBSCRM.php index 75e14bff15868..b5a132db04709 100644 --- a/projects/plugins/crm/ZeroBSCRM.php +++ b/projects/plugins/crm/ZeroBSCRM.php @@ -3,7 +3,7 @@ * Plugin Name: Jetpack CRM * Plugin URI: https://jetpackcrm.com * Description: Jetpack CRM is the simplest CRM for WordPress. Self host your own Customer Relationship Manager using WP. - * Version: 5.9.0-alpha + * Version: 6.0.0-alpha * Author: Automattic - Jetpack CRM team * Author URI: https://jetpackcrm.com * Text Domain: zero-bs-crm diff --git a/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 b/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 new file mode 100644 index 0000000000000..13e2f599b71b3 --- /dev/null +++ b/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 @@ -0,0 +1,5 @@ +Significance: major +Type: changed +Comment: Bump major version to 6.0.0. + +CRM: Change version to 6.0.0 diff --git a/projects/plugins/crm/composer.json b/projects/plugins/crm/composer.json index 0205033f0cb48..79a158dc92437 100644 --- a/projects/plugins/crm/composer.json +++ b/projects/plugins/crm/composer.json @@ -47,7 +47,7 @@ "platform": { "php": "7.2" }, - "autoloader-suffix": "06c775433a83ed276f0a1d8ac25f93ba_crmⓥ5_9_0_alpha", + "autoloader-suffix": "06c775433a83ed276f0a1d8ac25f93ba_crmⓥ6_0_0_alpha", "allow-plugins": { "automattic/jetpack-autoloader": true, "automattic/jetpack-composer-plugin": true, diff --git a/projects/plugins/crm/includes/ZeroBSCRM.Core.php b/projects/plugins/crm/includes/ZeroBSCRM.Core.php index 2d8b49581d234..37a12edbb4163 100644 --- a/projects/plugins/crm/includes/ZeroBSCRM.Core.php +++ b/projects/plugins/crm/includes/ZeroBSCRM.Core.php @@ -24,7 +24,7 @@ final class ZeroBSCRM { * * @var string */ - public $version = '5.8.0'; + public $version = '6.0.0'; /** * WordPress version tested with. diff --git a/projects/plugins/crm/package.json b/projects/plugins/crm/package.json index e891223b70368..db909cc05b95c 100644 --- a/projects/plugins/crm/package.json +++ b/projects/plugins/crm/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-crm", - "version": "5.9.0-alpha", + "version": "6.0.0-alpha", "description": "The CRM for WordPress", "author": "Automattic", "license": "GPL-2.0", From 540064cf9bf3ac2618147e26b71c2c61f83bb3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Gomes?= Date: Wed, 21 Jun 2023 17:04:18 +0100 Subject: [PATCH 06/29] Jetpack plugin: fix warnings in related posts (#31480) * Jetpack plugin: fix warnings in related posts * Add one more check --- ...related-posts-undefined-array-key-warnings | 5 +++ .../related-posts/jetpack-related-posts.php | 34 +++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/fix-related-posts-undefined-array-key-warnings diff --git a/projects/plugins/jetpack/changelog/fix-related-posts-undefined-array-key-warnings b/projects/plugins/jetpack/changelog/fix-related-posts-undefined-array-key-warnings new file mode 100644 index 0000000000000..b954ff46311f9 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-related-posts-undefined-array-key-warnings @@ -0,0 +1,5 @@ +Significance: patch +Type: bugfix +Comment: Small fix to a recent enhancement. + + diff --git a/projects/plugins/jetpack/modules/related-posts/jetpack-related-posts.php b/projects/plugins/jetpack/modules/related-posts/jetpack-related-posts.php index 0a541552f2325..8cbe65a4df069 100644 --- a/projects/plugins/jetpack/modules/related-posts/jetpack-related-posts.php +++ b/projects/plugins/jetpack/modules/related-posts/jetpack-related-posts.php @@ -1501,8 +1501,10 @@ protected function generate_related_post_image_params( $post_id ) { ); if ( is_array( $post_image ) ) { - $img_url = $post_image['src']; - $src_width = $post_image['src_width']; + $img_url = $post_image['src']; + if ( ! empty( $post_image['src_width'] ) ) { + $src_width = $post_image['src_width']; + } } elseif ( class_exists( 'Jetpack_Media_Summary' ) ) { $media = Jetpack_Media_Summary::get( $post_id ); @@ -1517,12 +1519,24 @@ protected function generate_related_post_image_params( $post_id ) { } else { $image_params['alt_text'] = ''; } - $image_params['width'] = $thumbnail_size['width']; - $image_params['height'] = $thumbnail_size['height']; - $image_params['src'] = Jetpack_PostImages::fit_image_url( + + $thumbnail_width = 0; + $thumbnail_height = 0; + + if ( ! empty( $thumbnail_size['width'] ) ) { + $thumbnail_width = $thumbnail_size['width']; + $image_params['width'] = $thumbnail_width; + } + + if ( ! empty( $thumbnail_size['height'] ) ) { + $thumbnail_height = $thumbnail_size['height']; + $image_params['height'] = $thumbnail_height; + } + + $image_params['src'] = Jetpack_PostImages::fit_image_url( $img_url, - $thumbnail_size['width'], - $thumbnail_size['height'] + $thumbnail_width, + $thumbnail_height ); // Add a srcset to handle zoomed views and high-density screens. @@ -1530,9 +1544,9 @@ protected function generate_related_post_image_params( $post_id ) { $srcset_values = array(); foreach ( $multipliers as $multiplier ) { // Forcefully cast to int, in case we ever add decimal multipliers. - $srcset_width = (int) $thumbnail_size['width'] * $multiplier; - $srcset_height = (int) $thumbnail_size['height'] * $multiplier; - if ( ! isset( $src_width ) || $srcset_width > $src_width ) { + $srcset_width = (int) ( $thumbnail_width * $multiplier ); + $srcset_height = (int) ( $thumbnail_height * $multiplier ); + if ( empty( $src_width ) || $srcset_width < 1 || $srcset_width > $src_width ) { break; } From 2c12dc3f5e500ca43c520b1866fa3fb243f2bbe8 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 21 Jun 2023 12:54:37 -0400 Subject: [PATCH 07/29] Update mirror repo info in docs/monorepo.md (#31467) Also copy in info about merging in other repos. --- docs/monorepo.md | 69 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/docs/monorepo.md b/docs/monorepo.md index 5af3fb3a5ed01..300f305737be6 100644 --- a/docs/monorepo.md +++ b/docs/monorepo.md @@ -254,12 +254,13 @@ Most projects in the monorepo should have a mirror repository holding a built ve 1. Create the mirror repo on GitHub. It will most likely be named like "https://github.com/Automattic/jetpack-_something_". 1. The repo's description should begin with `[READ ONLY]` and end with `This repository is a mirror, for issue tracking and development head to: https://github.com/automattic/jetpack`. 2. The default branch should be `trunk`, matching the monorepo. + * Note that you can't set the default branch until at least one branch is created in the repo. 3. In the repo's settings, turn off wikis, issues, projects, and so on. 4. Make sure that [matticbot](https://github.com/matticbot) can push to the repo. You would do this here: `https://github.com/Automattic/example-reposiroty-name/settings/branches` - creating a new branch protection rule where only Matticbot (and whoever needs access to push, for example Ground Control) can push to that repository. 5. Make sure that Actions are enabled. The build process copies workflows from `.github/files/mirror-.github` into the mirror to do useful things like automatically close PRs with a reference back to the monorepo. 6. Create any secrets needed (e.g. for Autotagger or Npmjs-Autopublisher). See PCYsg-xsv-p2#mirror-repo-secrets for details. -2. For a package you also need to go to packagist.org and create the package there. This requires pushing a first commit with a valid `composer.json` to the repository. That can be done by copying the new package's `composer.json` from the PR that introduced it. - 1. Once the package is added to Packagist, it is best to add some maintainers. Currently these will be `automattic`, `kraft` and `dereksmart`. +2. For a PHP package (or a plugin listed in Packagist) you also need to go to packagist.org and create the package there. This requires pushing a first commit with a valid `composer.json` to the repository. That can be done by copying the new package's `composer.json` from the PR that introduced it. + 1. Be sure that `automattic` is added as a maintainer. 3. If your project requires building, configure `.scripts.build-production` in your project's `composer.json` to run the necessary commands. 4. If there are any files included in the monorepo that should not be included in the mirror, use `.gitattributes` to tag them with "production-exclude". 5. If there are any built files in `.gitignore` that should be included in the mirror, use `.gitattributes` to tag them with "production-include". @@ -378,28 +379,56 @@ Within a single project, changlogger’s `version next` command can tell you the ## New Projects -### Creating a new Composer Package - -To add a Composer package: -* For Automatticians, drop us a line in #jetpack-crew to discuss your needs, just to be sure we don't have something already. -* Use the `jetpack generate package` command to create a skeleton project. -* Create your package and submit a PR as usual. +To begin, +* For Automatticians, drop us a line in #jetpack-crew to discuss your needs, just to be sure we don't have something already. For others, it would probably be best to open an issue to discuss it. +* Use the `jetpack generate` command to create a skeleton project. +* Create your project based on the skeleton and submit a PR as usual. -Once reviewed and approved, the Crew team does the following: -* Creates a GitHub repo in the Automattic repo to be the mirror repo for this project, if not done already. The new repo follows the [mirror repo guidelines](#mirror-repositories). -* Adds a `composer.json` file to the repo, with some basic information about the package. This file is used by Packagist to generate the package page. -* Creates a new Packagist package on packagist.org under the Automattic org. @jeherve, @dsmart, and @kraftbj are added as maintainers of all Jetpack monorepo packages. - -### Creating a new plugin +Once we're sure that the project will be created and what its name will be, someone (you or the Crew team) does the following: +* Create a GitHub repo in the Automattic repo to be the mirror repo for this project. The new repo follows the [mirror repo guidelines](#mirror-repositories). -If you're thinking about developing a new plugin in the monorepo, come chat with us in #jetpack-crew. We'll help you get started. +### Creating a new Composer Package -Once you are ready to start working on a first version of your plugin in the monorepo, use the `jetpack generate plugin` command to create the first files for your plugin. Then, open a new PR with that skeleton. +In addition to the above, after creating the mirror repo, +* Add a `composer.json` file to the repo, with some basic information about the package. This file is used by Packagist to generate the package page. +* Create a new Packagist package on packagist.org under the Automattic org. Add `automattic` as a maintainer. -Before you can merge your PR, the Crew team will do the following: +### Creating a new plugin -* Create the mirror repo for the plugin following the [mirror repo guidelines](#mirror-repositories). +In addition to the above, after creating the mirror repo, * Add a first version of a `composer.json` file to the mirror repo. -* Add the plugin to Packagist, for folks who may be consuming it through Composer. -* Add maintainers to the Packagist entry, just like for Composer packages above. +* Add the plugin to Packagist, just like for Composer packages above, for folks who want to consume it through Composer. * Add an entry for the new plugin in the Beta server settings. Find extra details on this process in the Jetpack Beta Builder repository. More information: PCYsg-gDE-p2 + +### Importing an existing repo + +To move development of an existing (public) repo into the Jetpack monorepo, you might do something like this. + +Preparation in the original repo: +* Set up PHP_CodeSniffer with [our ruleset](https://packagist.org/packages/Automattic/jetpack-codesniffer) and fix any lints identified. +* Merge any PRs that are ready to merge. + +In a checkout of the monorepo: +* Use `git remote add` to add a new remote for the existing repo, e.g. `git remote add existing-source-repo git@github.com:Automattic/existing-source-repo` +* `git fetch existing-source-repo` +* Create a new (temporary) branch based on the existing source repo: `git checkout -b existing-repo/prepare-source existing-source-repo/trunk` +* Move the files to where they should live in the monorepo, e.g. `git mv -k * .* projects/plugins/new-plugin` + * You may need to do something like `mkdir --parents ./projects/plugins/new-plugin` for the move to work. + * TODO: Consider whether `git filter-repo` might be better. See p9dueE-2on-p2#comment-5761 +* Commit `git add --all && git commit -m "Prepare XXX for monorepo"` +* Create the branch for the actual import: `git fetch origin && git checkout -b add/import-from-existing-repo origin/trunk` +* `git merge --allow-unrelated-histories existing-repo/prepare-source`. This will merge in the source plugin into the monorepo while maintaining all previous commits. +* Create additional commits to clean up the new project: adjust tooling to use what the monorepo provides, remove unneeded tooling, set monorepo configuration in `composer.json`, etc. +* Run linting and such. Commit anything necessary. +* `git push origin HEAD` and create your PR. Add the "DO NOT MERGE" tag. +* When it's time to merge the PR, go to the [GitHub settings page](https://href.li/?https://github.com/Automattic/jetpack/settings) and enable "Allow merge commits". Then go to the PR. There should be a caret dropdown next to "Squash and Merge" which you can use to select "Create a merge commit" instead. +* Clean up: + * Go back to the settings and turn "Allow merge commits" back off. + * `git branch -D existing-repo/prepare-source` to delete the temporary branch. + * If you want to move any open PRs from the old repo, check out the branches, `git merge origin/trunk` (and resolve any conflicts), push to origin, and recreate. + * `git remote remove existing-source-repo` to remove the remote. +* If you're going to reuse the old repo as the mirror, reconfigure it to match the [mirror repo guidelines](#mirror-repositories). + +See p9dueE-2on-p2 for past uses of this process. + +While a private repo could be imported similarly, you'd have a lot of auditing to do to make sure no old commit exposes any private information. From fa1a1fc882e4ca2056557a0fda39d500403cb17f Mon Sep 17 00:00:00 2001 From: gogdzl <37049295+gogdzl@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:09:32 -0300 Subject: [PATCH 08/29] Release Jetpack CRM (#31490) --- projects/js-packages/api/CHANGELOG.md | 4 ++ .../api/changelog/renovate-wordpress-monorepo | 4 -- projects/js-packages/api/package.json | 2 +- projects/js-packages/base-styles/CHANGELOG.md | 5 ++ .../changelog/renovate-wordpress-monorepo | 4 -- projects/js-packages/base-styles/package.json | 2 +- projects/js-packages/components/CHANGELOG.md | 4 ++ .../changelog/renovate-wordpress-monorepo | 4 -- projects/js-packages/components/package.json | 2 +- projects/js-packages/connection/CHANGELOG.md | 4 ++ .../changelog/renovate-wordpress-monorepo | 4 -- projects/js-packages/connection/package.json | 2 +- .../i18n-loader-webpack-plugin/CHANGELOG.md | 5 ++ .../changelog/renovate-wordpress-monorepo | 4 -- .../i18n-loader-webpack-plugin/package.json | 2 +- .../shared-extension-utils/CHANGELOG.md | 5 ++ .../changelog/renovate-wordpress-monorepo | 4 -- .../shared-extension-utils/package.json | 2 +- .../js-packages/webpack-config/CHANGELOG.md | 4 ++ .../changelog/renovate-wordpress-monorepo | 4 -- .../js-packages/webpack-config/package.json | 2 +- projects/packages/assets/CHANGELOG.md | 5 ++ .../changelog/renovate-wordpress-monorepo | 4 -- projects/plugins/crm/CHANGELOG.md | 24 ++++++++++ projects/plugins/crm/ZeroBSCRM.php | 2 +- .../crm/changelog/add-composer-dev-keywords | 5 -- .../add-crm-phpcompatibility-rules-automation | 4 -- .../crm/changelog/api-include-contact-tags | 4 -- .../changelog/fix-crm-1907-welcome_tour_fixes | 4 -- ...985-email_manager_placeholder_position_fix | 5 -- ...lient-portal-fatal-error-loading-endpoints | 4 -- .../fix-crm-3091-clean_up_user_roles | 4 -- .../changelog/fix-crm-3104-emerald_listviews | 4 -- ...rm-3105-redesign-colors-buttons-to-emerald | 4 -- .../changelog/fix-crm-3106-emerald_learn_menu | 4 -- .../changelog/fix-crm-3107-emerald_top_menu | 4 -- .../fix-crm-3114-screenopt_save_issues | 5 -- .../changelog/fix-crm-3117-emerald_dashboard | 4 -- .../crm/changelog/fix-crm-3118-remove_inbox | 3 -- .../fix-crm-3119-handle_wp_update_pages | 5 -- ...crm-3120-change-CRM-page-layout-to-emerald | 4 -- ...ix-crm-3121-allow_unsubscribe_flag_removal | 4 -- .../fix-crm-3122-php_notices_in_listview | 5 -- ...-crm-3124-change-crm-edit-pages-to-emerald | 4 -- .../crm/changelog/fix-crm-3129-php_errors | 4 -- ...30-centralise_browser_autocomplete_disable | 5 -- .../fix-crm-3131-emerald_totals_table | 4 -- .../changelog/fix-crm-3132-crm-emerald-polish | 3 -- .../fix-crm-3133-quote_sort_by_status | 4 -- .../fix-crm-3134-show_invoice_assignee_link | 4 -- .../fix-crm-3137-whitelabel-error-not-allowed | 4 -- ...rm-3138-hide_resources_page_on_white_label | 4 -- .../fix-crm-3143-additional_emerald_polish | 5 -- .../fix-crm-3145-company_fields_typo | 5 -- .../changelog/fix-crm-quote-template-new-line | 4 -- ...x-crm-temporarily-disabling-tests-in-trunk | 3 -- .../crm/changelog/fix-forms-hash-generation | 5 -- .../crm/changelog/fix-forms-hash-generation#2 | 5 -- .../plugins/crm/changelog/init-release-cycle | 5 -- .../remove-crm-jetpack-forms-dependency | 5 -- .../plugins/crm/changelog/remove-twitter-api | 4 -- .../crm/changelog/renovate-wordpress-monorepo | 4 -- .../changelog/renovate-wordpress-monorepo#2 | 4 -- .../update-crm-3156-bump-major-version-to-6 | 5 -- .../update-crm-change-wording-to-jetpack-team | 5 -- .../crm/changelog/update-crm-gravatar-hash | 4 -- projects/plugins/crm/composer.json | 2 +- projects/plugins/crm/package.json | 2 +- projects/plugins/crm/readme.txt | 47 +++++++------------ 69 files changed, 87 insertions(+), 247 deletions(-) delete mode 100644 projects/js-packages/api/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/components/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/connection/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/shared-extension-utils/changelog/renovate-wordpress-monorepo delete mode 100644 projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo delete mode 100644 projects/packages/assets/changelog/renovate-wordpress-monorepo delete mode 100644 projects/plugins/crm/changelog/add-composer-dev-keywords delete mode 100644 projects/plugins/crm/changelog/add-crm-phpcompatibility-rules-automation delete mode 100644 projects/plugins/crm/changelog/api-include-contact-tags delete mode 100644 projects/plugins/crm/changelog/fix-crm-1907-welcome_tour_fixes delete mode 100644 projects/plugins/crm/changelog/fix-crm-2985-email_manager_placeholder_position_fix delete mode 100644 projects/plugins/crm/changelog/fix-crm-3003-client-portal-fatal-error-loading-endpoints delete mode 100644 projects/plugins/crm/changelog/fix-crm-3091-clean_up_user_roles delete mode 100644 projects/plugins/crm/changelog/fix-crm-3104-emerald_listviews delete mode 100644 projects/plugins/crm/changelog/fix-crm-3105-redesign-colors-buttons-to-emerald delete mode 100644 projects/plugins/crm/changelog/fix-crm-3106-emerald_learn_menu delete mode 100644 projects/plugins/crm/changelog/fix-crm-3107-emerald_top_menu delete mode 100644 projects/plugins/crm/changelog/fix-crm-3114-screenopt_save_issues delete mode 100644 projects/plugins/crm/changelog/fix-crm-3117-emerald_dashboard delete mode 100644 projects/plugins/crm/changelog/fix-crm-3118-remove_inbox delete mode 100644 projects/plugins/crm/changelog/fix-crm-3119-handle_wp_update_pages delete mode 100644 projects/plugins/crm/changelog/fix-crm-3120-change-CRM-page-layout-to-emerald delete mode 100644 projects/plugins/crm/changelog/fix-crm-3121-allow_unsubscribe_flag_removal delete mode 100644 projects/plugins/crm/changelog/fix-crm-3122-php_notices_in_listview delete mode 100644 projects/plugins/crm/changelog/fix-crm-3124-change-crm-edit-pages-to-emerald delete mode 100644 projects/plugins/crm/changelog/fix-crm-3129-php_errors delete mode 100644 projects/plugins/crm/changelog/fix-crm-3130-centralise_browser_autocomplete_disable delete mode 100644 projects/plugins/crm/changelog/fix-crm-3131-emerald_totals_table delete mode 100644 projects/plugins/crm/changelog/fix-crm-3132-crm-emerald-polish delete mode 100644 projects/plugins/crm/changelog/fix-crm-3133-quote_sort_by_status delete mode 100644 projects/plugins/crm/changelog/fix-crm-3134-show_invoice_assignee_link delete mode 100644 projects/plugins/crm/changelog/fix-crm-3137-whitelabel-error-not-allowed delete mode 100644 projects/plugins/crm/changelog/fix-crm-3138-hide_resources_page_on_white_label delete mode 100644 projects/plugins/crm/changelog/fix-crm-3143-additional_emerald_polish delete mode 100644 projects/plugins/crm/changelog/fix-crm-3145-company_fields_typo delete mode 100644 projects/plugins/crm/changelog/fix-crm-quote-template-new-line delete mode 100644 projects/plugins/crm/changelog/fix-crm-temporarily-disabling-tests-in-trunk delete mode 100644 projects/plugins/crm/changelog/fix-forms-hash-generation delete mode 100644 projects/plugins/crm/changelog/fix-forms-hash-generation#2 delete mode 100644 projects/plugins/crm/changelog/init-release-cycle delete mode 100644 projects/plugins/crm/changelog/remove-crm-jetpack-forms-dependency delete mode 100644 projects/plugins/crm/changelog/remove-twitter-api delete mode 100644 projects/plugins/crm/changelog/renovate-wordpress-monorepo delete mode 100644 projects/plugins/crm/changelog/renovate-wordpress-monorepo#2 delete mode 100644 projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 delete mode 100644 projects/plugins/crm/changelog/update-crm-change-wording-to-jetpack-team delete mode 100644 projects/plugins/crm/changelog/update-crm-gravatar-hash diff --git a/projects/js-packages/api/CHANGELOG.md b/projects/js-packages/api/CHANGELOG.md index 21a27cec1f8bf..a83aba96350ab 100644 --- a/projects/js-packages/api/CHANGELOG.md +++ b/projects/js-packages/api/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## 0.15.7 - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## 0.15.6 - 2023-06-06 ### Changed - Updated package dependencies. [#31129] diff --git a/projects/js-packages/api/changelog/renovate-wordpress-monorepo b/projects/js-packages/api/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/api/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/api/package.json b/projects/js-packages/api/package.json index 602bccaf409fb..b9c0cf98063fd 100644 --- a/projects/js-packages/api/package.json +++ b/projects/js-packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-api", - "version": "0.15.7-alpha", + "version": "0.15.7", "description": "Jetpack Api Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/base-styles/CHANGELOG.md b/projects/js-packages/base-styles/CHANGELOG.md index 1a415f13af7d3..95137aa612b44 100644 --- a/projects/js-packages/base-styles/CHANGELOG.md +++ b/projects/js-packages/base-styles/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1] - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## [0.6.0] - 2023-06-06 ### Changed - Update connection module to have an RNA option that updates the design [#31201] @@ -185,6 +189,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Use Node 16.7.0 in tooling. This shouldn't change the behavior of the code itself. +[0.6.1]: https://github.com/Automattic/jetpack-base-styles/compare/0.6.0...0.6.1 [0.6.0]: https://github.com/Automattic/jetpack-base-styles/compare/0.5.1...0.6.0 [0.5.1]: https://github.com/Automattic/jetpack-base-styles/compare/0.5.0...0.5.1 [0.5.0]: https://github.com/Automattic/jetpack-base-styles/compare/0.4.4...0.5.0 diff --git a/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo b/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/base-styles/package.json b/projects/js-packages/base-styles/package.json index a177c61160fab..1e317bfa61682 100644 --- a/projects/js-packages/base-styles/package.json +++ b/projects/js-packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-base-styles", - "version": "0.6.1-alpha", + "version": "0.6.1", "description": "Jetpack components base styles", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/base-styles/#readme", "bugs": { diff --git a/projects/js-packages/components/CHANGELOG.md b/projects/js-packages/components/CHANGELOG.md index 7416429ec77fa..b32fff28df984 100644 --- a/projects/js-packages/components/CHANGELOG.md +++ b/projects/js-packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## 0.38.1 - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## 0.38.0 - 2023-06-15 ### Added - Add testimonial component and use it on the backup connect screen [#31221] diff --git a/projects/js-packages/components/changelog/renovate-wordpress-monorepo b/projects/js-packages/components/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/components/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 5c7e5aa2224f6..71bb04c40aadd 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-components", - "version": "0.38.1-alpha", + "version": "0.38.1", "description": "Jetpack Components Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/connection/CHANGELOG.md b/projects/js-packages/connection/CHANGELOG.md index f3df644bbe651..320cfe57cf3fa 100644 --- a/projects/js-packages/connection/CHANGELOG.md +++ b/projects/js-packages/connection/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Connection Component releases. +## 0.29.1 - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## 0.29.0 - 2023-06-15 ### Changed - Connection: always display connection button on connection screen. [#31196] diff --git a/projects/js-packages/connection/changelog/renovate-wordpress-monorepo b/projects/js-packages/connection/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/connection/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/connection/package.json b/projects/js-packages/connection/package.json index d0a958c30e069..f20f51cfe1083 100644 --- a/projects/js-packages/connection/package.json +++ b/projects/js-packages/connection/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-connection", - "version": "0.29.1-alpha", + "version": "0.29.1", "description": "Jetpack Connection Component", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md b/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md index c42eeae3e3006..92958d21aadca 100644 --- a/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md +++ b/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.33] - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## [2.0.32] - 2023-06-06 ### Changed - Updated package dependencies. [#31129] @@ -156,6 +160,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release. +[2.0.33]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.32...v2.0.33 [2.0.32]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.31...v2.0.32 [2.0.31]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.30...v2.0.31 [2.0.30]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.29...v2.0.30 diff --git a/projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo b/projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/i18n-loader-webpack-plugin/package.json b/projects/js-packages/i18n-loader-webpack-plugin/package.json index cc75cbc3c3b6e..591539a8f5edf 100644 --- a/projects/js-packages/i18n-loader-webpack-plugin/package.json +++ b/projects/js-packages/i18n-loader-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/i18n-loader-webpack-plugin", - "version": "2.0.33-alpha", + "version": "2.0.33", "description": "A Webpack plugin to load WordPress i18n when Webpack lazy-loads a bundle.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/i18n-loader-webpack-plugin/#readme", "bugs": { diff --git a/projects/js-packages/shared-extension-utils/CHANGELOG.md b/projects/js-packages/shared-extension-utils/CHANGELOG.md index 9f79f5dd1aef3..e046dea243933 100644 --- a/projects/js-packages/shared-extension-utils/CHANGELOG.md +++ b/projects/js-packages/shared-extension-utils/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.6] - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## [0.10.5] - 2023-06-06 ### Changed - Updated package dependencies. [#31129] @@ -205,6 +209,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Core: prepare utility for release +[0.10.6]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.10.5...0.10.6 [0.10.5]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.10.4...0.10.5 [0.10.4]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.10.3...0.10.4 [0.10.3]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.10.2...0.10.3 diff --git a/projects/js-packages/shared-extension-utils/changelog/renovate-wordpress-monorepo b/projects/js-packages/shared-extension-utils/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/shared-extension-utils/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/shared-extension-utils/package.json b/projects/js-packages/shared-extension-utils/package.json index 9d50c2e8f6856..eddddfb3c5240 100644 --- a/projects/js-packages/shared-extension-utils/package.json +++ b/projects/js-packages/shared-extension-utils/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-shared-extension-utils", - "version": "0.10.6-alpha", + "version": "0.10.6", "description": "Utility functions used by the block editor extensions", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/shared-extension-utils/#readme", "bugs": { diff --git a/projects/js-packages/webpack-config/CHANGELOG.md b/projects/js-packages/webpack-config/CHANGELOG.md index e32c756fd2350..7d7a35cc920e2 100644 --- a/projects/js-packages/webpack-config/CHANGELOG.md +++ b/projects/js-packages/webpack-config/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.5.2 - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## 1.5.1 - 2023-06-06 ### Changed - Updated package dependencies. [#31129] diff --git a/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo b/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/webpack-config/package.json b/projects/js-packages/webpack-config/package.json index 15fdad3175226..076cb2af9e687 100644 --- a/projects/js-packages/webpack-config/package.json +++ b/projects/js-packages/webpack-config/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-webpack-config", - "version": "1.5.2-alpha", + "version": "1.5.2", "description": "Library of pieces for webpack config in Jetpack projects.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/webpack-config/#readme", "bugs": { diff --git a/projects/packages/assets/CHANGELOG.md b/projects/packages/assets/CHANGELOG.md index 6e314a66836ef..ea5a5f4e2948d 100644 --- a/projects/packages/assets/CHANGELOG.md +++ b/projects/packages/assets/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.18.5] - 2023-06-21 +### Changed +- Updated package dependencies. [#31468] + ## [1.18.4] - 2023-06-06 ### Changed - Updated package dependencies. [#31129] @@ -333,6 +337,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Statically access asset tools +[1.18.5]: https://github.com/Automattic/jetpack-assets/compare/v1.18.4...v1.18.5 [1.18.4]: https://github.com/Automattic/jetpack-assets/compare/v1.18.3...v1.18.4 [1.18.3]: https://github.com/Automattic/jetpack-assets/compare/v1.18.2...v1.18.3 [1.18.2]: https://github.com/Automattic/jetpack-assets/compare/v1.18.1...v1.18.2 diff --git a/projects/packages/assets/changelog/renovate-wordpress-monorepo b/projects/packages/assets/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/assets/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/crm/CHANGELOG.md b/projects/plugins/crm/CHANGELOG.md index e0b2d7cfcbc24..10ecd96337f5e 100644 --- a/projects/plugins/crm/CHANGELOG.md +++ b/projects/plugins/crm/CHANGELOG.md @@ -5,6 +5,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [6.0.0] - 2023-06-21 +### Added +- CRM: Revamped CRM User Interface - Merge the sleek aesthetics of Jetpack’s style, bringing a new level of sophistication and seamless navigation to your CRM experience [#30916] +- API: Now it retrieves contacts with tags [#31418] +- Contacts: Allow unsubscribe flag to be removed [#31029] + +### Changed +- User roles: Further restricted capabilities on some roles [#31174] +- Contacts: Use sha256 instead of md5 for gravatar images [#31288] + +### Fixed +- Client Portal: Fix a fatal error initializing endpoints and shortcodes [#30678] +- CRM: Fix new lines display in quote templates [#30974] +- CRM: Fix whitelabel bug with full menu layout [#31126] +- CRM: Page layout now has a max width of 1551px [#30961] +- CRM: Welcome tour now goes through all steps [#31178] +- Extensions: Catch PHP notice if offline [#31032] +- Invoices: Show assigned contact/company link [#31153] +- Listview: Per-page settings no longer reset +- Listview: PHP notice no longer shows when saving settings [#31154] +- Quotes: Fix sort by status [#31087] +- White label: JPCRM support and resources pages no longer show [#31155] + ## [5.8.0] - 2023-05-18 ### Added - Composer: Added jetpack-forms as a required dependency to fix a Jetpack form compat issue [#30749] @@ -165,6 +188,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved: Added a migration to remove outdated AKA lines [5.5.4-a.1]: https://github.com/Automattic/jetpack-crm/compare/v5.5.3...v5.5.4-a.1 +[6.0.0]: https://github.com/Automattic/jetpack-crm/compare/5.8.0...6.0.0 [5.8.0]: https://github.com/Automattic/jetpack-crm/compare/5.7.0...5.8.0 [5.7.0]: https://github.com/Automattic/jetpack-crm/compare/v5.6.0...v5.7.0 [5.6.0]: https://github.com/Automattic/jetpack-crm/compare/v5.5.4-a.1...v5.6.0 diff --git a/projects/plugins/crm/ZeroBSCRM.php b/projects/plugins/crm/ZeroBSCRM.php index b5a132db04709..1f5d39235230b 100644 --- a/projects/plugins/crm/ZeroBSCRM.php +++ b/projects/plugins/crm/ZeroBSCRM.php @@ -3,7 +3,7 @@ * Plugin Name: Jetpack CRM * Plugin URI: https://jetpackcrm.com * Description: Jetpack CRM is the simplest CRM for WordPress. Self host your own Customer Relationship Manager using WP. - * Version: 6.0.0-alpha + * Version: 6.0.0 * Author: Automattic - Jetpack CRM team * Author URI: https://jetpackcrm.com * Text Domain: zero-bs-crm diff --git a/projects/plugins/crm/changelog/add-composer-dev-keywords b/projects/plugins/crm/changelog/add-composer-dev-keywords deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/crm/changelog/add-composer-dev-keywords +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/crm/changelog/add-crm-phpcompatibility-rules-automation b/projects/plugins/crm/changelog/add-crm-phpcompatibility-rules-automation deleted file mode 100644 index a830ebbef317c..0000000000000 --- a/projects/plugins/crm/changelog/add-crm-phpcompatibility-rules-automation +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -PHPCompatibility: Adding additional rules based on new PHP added in the Automations project. diff --git a/projects/plugins/crm/changelog/api-include-contact-tags b/projects/plugins/crm/changelog/api-include-contact-tags deleted file mode 100644 index a9cf32627e04c..0000000000000 --- a/projects/plugins/crm/changelog/api-include-contact-tags +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -API contact endpoints: Now it retrieves contacts with tags diff --git a/projects/plugins/crm/changelog/fix-crm-1907-welcome_tour_fixes b/projects/plugins/crm/changelog/fix-crm-1907-welcome_tour_fixes deleted file mode 100644 index b337396223ceb..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-1907-welcome_tour_fixes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Welcome tour now goes through all steps diff --git a/projects/plugins/crm/changelog/fix-crm-2985-email_manager_placeholder_position_fix b/projects/plugins/crm/changelog/fix-crm-2985-email_manager_placeholder_position_fix deleted file mode 100644 index 347ffa36f4ef7..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-2985-email_manager_placeholder_position_fix +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: Move the email manager placeholders upward - - diff --git a/projects/plugins/crm/changelog/fix-crm-3003-client-portal-fatal-error-loading-endpoints b/projects/plugins/crm/changelog/fix-crm-3003-client-portal-fatal-error-loading-endpoints deleted file mode 100644 index 3683e0c71855e..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3003-client-portal-fatal-error-loading-endpoints +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Client Portal: Fix a fatal error initializing endpoints and shortcodes. diff --git a/projects/plugins/crm/changelog/fix-crm-3091-clean_up_user_roles b/projects/plugins/crm/changelog/fix-crm-3091-clean_up_user_roles deleted file mode 100644 index abad5b76e13ca..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3091-clean_up_user_roles +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -User roles: further restricted capabilities on some roles diff --git a/projects/plugins/crm/changelog/fix-crm-3104-emerald_listviews b/projects/plugins/crm/changelog/fix-crm-3104-emerald_listviews deleted file mode 100644 index dbd02e01d6154..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3104-emerald_listviews +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Listviews: implement Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3105-redesign-colors-buttons-to-emerald b/projects/plugins/crm/changelog/fix-crm-3105-redesign-colors-buttons-to-emerald deleted file mode 100644 index 4d0318980f41a..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3105-redesign-colors-buttons-to-emerald +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -CRM: buttons and colors to Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3106-emerald_learn_menu b/projects/plugins/crm/changelog/fix-crm-3106-emerald_learn_menu deleted file mode 100644 index 8807a8c535e9a..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3106-emerald_learn_menu +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Learn menu: implement Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3107-emerald_top_menu b/projects/plugins/crm/changelog/fix-crm-3107-emerald_top_menu deleted file mode 100644 index aa90ddae360a5..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3107-emerald_top_menu +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Top menu: implement Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3114-screenopt_save_issues b/projects/plugins/crm/changelog/fix-crm-3114-screenopt_save_issues deleted file mode 100644 index dbdca7c09d741..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3114-screenopt_save_issues +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed - -Listview: per-page settings no longer reset -Listview: PHP notice no longer shows when saving settings diff --git a/projects/plugins/crm/changelog/fix-crm-3117-emerald_dashboard b/projects/plugins/crm/changelog/fix-crm-3117-emerald_dashboard deleted file mode 100644 index 35ac1e230d07e..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3117-emerald_dashboard +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Dashboard: implement Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3118-remove_inbox b/projects/plugins/crm/changelog/fix-crm-3118-remove_inbox deleted file mode 100644 index f8cc5b8e77955..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3118-remove_inbox +++ /dev/null @@ -1,3 +0,0 @@ -Significance: patch -Type: removed -Comment: removed non-implemented CRM Inbox references \ No newline at end of file diff --git a/projects/plugins/crm/changelog/fix-crm-3119-handle_wp_update_pages b/projects/plugins/crm/changelog/fix-crm-3119-handle_wp_update_pages deleted file mode 100644 index 7a41264f2ab79..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3119-handle_wp_update_pages +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: Update tests to handle WP database updates - - diff --git a/projects/plugins/crm/changelog/fix-crm-3120-change-CRM-page-layout-to-emerald b/projects/plugins/crm/changelog/fix-crm-3120-change-CRM-page-layout-to-emerald deleted file mode 100644 index 45f4172ec292a..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3120-change-CRM-page-layout-to-emerald +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fixed - -CRM: page layout now has a max width of 1551px diff --git a/projects/plugins/crm/changelog/fix-crm-3121-allow_unsubscribe_flag_removal b/projects/plugins/crm/changelog/fix-crm-3121-allow_unsubscribe_flag_removal deleted file mode 100644 index 2295c0702bac3..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3121-allow_unsubscribe_flag_removal +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Contacts: allow unsubscribe flag to be removed diff --git a/projects/plugins/crm/changelog/fix-crm-3122-php_notices_in_listview b/projects/plugins/crm/changelog/fix-crm-3122-php_notices_in_listview deleted file mode 100644 index f896faee258f2..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3122-php_notices_in_listview +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: Catch PHP notices when using an invalid filter name. - - diff --git a/projects/plugins/crm/changelog/fix-crm-3124-change-crm-edit-pages-to-emerald b/projects/plugins/crm/changelog/fix-crm-3124-change-crm-edit-pages-to-emerald deleted file mode 100644 index 904b1a60103c0..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3124-change-crm-edit-pages-to-emerald +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fixed - -CRM: edit pages changed to match Jetpack Emerald style diff --git a/projects/plugins/crm/changelog/fix-crm-3129-php_errors b/projects/plugins/crm/changelog/fix-crm-3129-php_errors deleted file mode 100644 index 9f45a5e597e1d..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3129-php_errors +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Extensions: catch PHP notice if offline diff --git a/projects/plugins/crm/changelog/fix-crm-3130-centralise_browser_autocomplete_disable b/projects/plugins/crm/changelog/fix-crm-3130-centralise_browser_autocomplete_disable deleted file mode 100644 index ee377562a6c21..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3130-centralise_browser_autocomplete_disable +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Use jpcrm_disable_browser_autocomplete() throughout - - diff --git a/projects/plugins/crm/changelog/fix-crm-3131-emerald_totals_table b/projects/plugins/crm/changelog/fix-crm-3131-emerald_totals_table deleted file mode 100644 index 4518c78339fcc..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3131-emerald_totals_table +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Listview: use Emerald style on totals table diff --git a/projects/plugins/crm/changelog/fix-crm-3132-crm-emerald-polish b/projects/plugins/crm/changelog/fix-crm-3132-crm-emerald-polish deleted file mode 100644 index 4476201923408..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3132-crm-emerald-polish +++ /dev/null @@ -1,3 +0,0 @@ -Significance: patch -Type: changed -Comment: Polish the CRM after changes from project Emerald diff --git a/projects/plugins/crm/changelog/fix-crm-3133-quote_sort_by_status b/projects/plugins/crm/changelog/fix-crm-3133-quote_sort_by_status deleted file mode 100644 index d5b1bcc40e0ab..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3133-quote_sort_by_status +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Quotes: sort by status now works diff --git a/projects/plugins/crm/changelog/fix-crm-3134-show_invoice_assignee_link b/projects/plugins/crm/changelog/fix-crm-3134-show_invoice_assignee_link deleted file mode 100644 index a23ef7078271d..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3134-show_invoice_assignee_link +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Invoices: show assigned contact/company link diff --git a/projects/plugins/crm/changelog/fix-crm-3137-whitelabel-error-not-allowed b/projects/plugins/crm/changelog/fix-crm-3137-whitelabel-error-not-allowed deleted file mode 100644 index 5e8fad3b0b8fc..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3137-whitelabel-error-not-allowed +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -CRM: fix whitelabel bug with full menu layout diff --git a/projects/plugins/crm/changelog/fix-crm-3138-hide_resources_page_on_white_label b/projects/plugins/crm/changelog/fix-crm-3138-hide_resources_page_on_white_label deleted file mode 100644 index b29d7d264abc5..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3138-hide_resources_page_on_white_label +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -White label: JPCRM support and resources pages no longer show diff --git a/projects/plugins/crm/changelog/fix-crm-3143-additional_emerald_polish b/projects/plugins/crm/changelog/fix-crm-3143-additional_emerald_polish deleted file mode 100644 index b60cc38c5e8b7..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3143-additional_emerald_polish +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: Additional Emerald polish - - diff --git a/projects/plugins/crm/changelog/fix-crm-3145-company_fields_typo b/projects/plugins/crm/changelog/fix-crm-3145-company_fields_typo deleted file mode 100644 index 12d415eb8d500..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-3145-company_fields_typo +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: Fixed typo introduced in #31030 - - diff --git a/projects/plugins/crm/changelog/fix-crm-quote-template-new-line b/projects/plugins/crm/changelog/fix-crm-quote-template-new-line deleted file mode 100644 index 37f27d2f1ac84..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-quote-template-new-line +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -CRM: Fix new lines display in quote templates diff --git a/projects/plugins/crm/changelog/fix-crm-temporarily-disabling-tests-in-trunk b/projects/plugins/crm/changelog/fix-crm-temporarily-disabling-tests-in-trunk deleted file mode 100644 index f3a387a54cdd5..0000000000000 --- a/projects/plugins/crm/changelog/fix-crm-temporarily-disabling-tests-in-trunk +++ /dev/null @@ -1,3 +0,0 @@ -Significance: patch -Type: changed -Comment: Temporarily disable tests in trunk diff --git a/projects/plugins/crm/changelog/fix-forms-hash-generation b/projects/plugins/crm/changelog/fix-forms-hash-generation deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/crm/changelog/fix-forms-hash-generation +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/crm/changelog/fix-forms-hash-generation#2 b/projects/plugins/crm/changelog/fix-forms-hash-generation#2 deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/crm/changelog/fix-forms-hash-generation#2 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/crm/changelog/init-release-cycle b/projects/plugins/crm/changelog/init-release-cycle deleted file mode 100644 index c4db98c2d1e7b..0000000000000 --- a/projects/plugins/crm/changelog/init-release-cycle +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Init 5.8.1-alpha - - diff --git a/projects/plugins/crm/changelog/remove-crm-jetpack-forms-dependency b/projects/plugins/crm/changelog/remove-crm-jetpack-forms-dependency deleted file mode 100644 index 23fbc59df57d8..0000000000000 --- a/projects/plugins/crm/changelog/remove-crm-jetpack-forms-dependency +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Composer: Removed jetpack-forms as a required dependency as the Jetpack form compat issue is now fixed - - diff --git a/projects/plugins/crm/changelog/remove-twitter-api b/projects/plugins/crm/changelog/remove-twitter-api deleted file mode 100644 index 4cbe57878b9e4..0000000000000 --- a/projects/plugins/crm/changelog/remove-twitter-api +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: deprecated - -Minor changes around upcoming functionality change in Twitter. diff --git a/projects/plugins/crm/changelog/renovate-wordpress-monorepo b/projects/plugins/crm/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/crm/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/crm/changelog/renovate-wordpress-monorepo#2 b/projects/plugins/crm/changelog/renovate-wordpress-monorepo#2 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/crm/changelog/renovate-wordpress-monorepo#2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 b/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 deleted file mode 100644 index 13e2f599b71b3..0000000000000 --- a/projects/plugins/crm/changelog/update-crm-3156-bump-major-version-to-6 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: major -Type: changed -Comment: Bump major version to 6.0.0. - -CRM: Change version to 6.0.0 diff --git a/projects/plugins/crm/changelog/update-crm-change-wording-to-jetpack-team b/projects/plugins/crm/changelog/update-crm-change-wording-to-jetpack-team deleted file mode 100644 index c375c5005a991..0000000000000 --- a/projects/plugins/crm/changelog/update-crm-change-wording-to-jetpack-team +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Simply renaming refences from Woody/Mike to Jetpack which doesn't change anything for end users - - diff --git a/projects/plugins/crm/changelog/update-crm-gravatar-hash b/projects/plugins/crm/changelog/update-crm-gravatar-hash deleted file mode 100644 index 49cfdada240fa..0000000000000 --- a/projects/plugins/crm/changelog/update-crm-gravatar-hash +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Use sha256 instead of md5 for gravatar images diff --git a/projects/plugins/crm/composer.json b/projects/plugins/crm/composer.json index 79a158dc92437..cfe451149f116 100644 --- a/projects/plugins/crm/composer.json +++ b/projects/plugins/crm/composer.json @@ -47,7 +47,7 @@ "platform": { "php": "7.2" }, - "autoloader-suffix": "06c775433a83ed276f0a1d8ac25f93ba_crmⓥ6_0_0_alpha", + "autoloader-suffix": "06c775433a83ed276f0a1d8ac25f93ba_crmⓥ6_0_0", "allow-plugins": { "automattic/jetpack-autoloader": true, "automattic/jetpack-composer-plugin": true, diff --git a/projects/plugins/crm/package.json b/projects/plugins/crm/package.json index db909cc05b95c..79d62e99f6566 100644 --- a/projects/plugins/crm/package.json +++ b/projects/plugins/crm/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-crm", - "version": "6.0.0-alpha", + "version": "6.0.0", "description": "The CRM for WordPress", "author": "Automattic", "license": "GPL-2.0", diff --git a/projects/plugins/crm/readme.txt b/projects/plugins/crm/readme.txt index 58af4b421b1f5..21ca93b221243 100644 --- a/projects/plugins/crm/readme.txt +++ b/projects/plugins/crm/readme.txt @@ -389,39 +389,26 @@ We offer a full, no-hassle refund within 14 days. You can read more about that, == Changelog == -### 5.8.0 - 2023-05-18 +### 6.0.0 - 2023-06-21 #### Added -- Composer: Added jetpack-forms as a required dependency to fix a Jetpack form compat issue -- Segments: Adding a doesnotcontain condition for email segments, for better compatibility with Advanced Segments +- CRM: Revamped CRM User Interface - Merge the sleek aesthetics of Jetpack’s style, bringing a new level of sophistication and seamless navigation to your CRM experience +- API: Now it retrieves contacts with tags +- Contacts: Allow unsubscribe flag to be removed #### Changed -- Code cleanup: Cleaning up WP Editor helper functions and wp_editor usage -- General: Update link references to releases in changelog -- Navigation: Changed Learn More button and Learn More link to be consistent with Jetpack styles -- PDF generator: Objects in filenames are translated -- WooSync: Improved status mapping logic +- User roles: Further restricted capabilities on some roles +- Contacts: Use sha256 instead of md5 for gravatar images #### Fixed -- Companies: Fix company name prefill so add links - transaction, invoice and tasks - prefill company name -- Contact / Company: Fix date styling for transactions, invoices and quotes -- Contact / Company: Profile summary total value and invoice count now removes deleted invoices -- Custom fields: Use native date pickers -- Quotes: Use native date pickers -- Export: Contact segments now export company info -- Logs: Facebook, Twitter, Feedback, and Other Contact log types now update last contacted timestamp -- Settings: Eliminate orphaned references to custom fields within field sorting settings when removing custom fields -- Segments: Make sure total count is updated on tag changes -- Tasks: Start and end times now show correctly throughout the CRM -- Tasks: New migration to remove timezone offset from database -- Tasks: Removed reliance on strftime for better PHP 8.1 compatibility -- Tasks: Switch to native browser date and time inputs -- Tasks: Catch moment.js notice due to using fallback date format -- Tasks: Fix ##TASK-BODY## placeholder -- Tooling: Allowing minification of JS files in development -- Transactions: Always show current status in editor -- WooSync: Fix the fee amount from a WooCommerce order is not added to the invoice -- WooSync: Fix shipping tax and discount amounts from Woocommerce orders are not calculated in invoices -- WooSync: Fix the subtotal amount from WooCommerce orders is not calculated in invoices -- WooSync: Fix PHP Warning -- Invoices: On invoice update the shipping tax selected is removed resulting on incorrect total amount +- Client Portal: Fix a fatal error initializing endpoints and shortcodes +- CRM: Fix new lines display in quote templates +- CRM: Fix whitelabel bug with full menu layout +- CRM: Page layout now has a max width of 1551px +- CRM: Welcome tour now goes through all steps +- Extensions: Catch PHP notice if offline +- Invoices: Show assigned contact/company link +- Listview: Per-page settings no longer reset +- Listview: PHP notice no longer shows when saving settings +- Quotes: Fix sort by status +- White label: JPCRM support and resources pages no longer show From 27067b762875d711516191ffbc0b2f4267491219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Jun 2023 14:45:16 -0300 Subject: [PATCH 09/29] AI Extension: iterate over prompt to try to keep the lang of the content (#31482) * [not verified] update prompt to try to preserve the conten lang * [not verified] changelog * [not verified] we don't want to expect response in markdown * [not verified] do not add lang rule for the system role. * [not verified] rewording prompt language rule --------- Co-authored-by: Douglas --- ...erate-over-prompt-to-try-to-preserve-the-lang | 4 ++++ .../blocks/ai-assistant/lib/prompt/index.ts | 16 +++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-iterate-over-prompt-to-try-to-preserve-the-lang diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-iterate-over-prompt-to-try-to-preserve-the-lang b/projects/plugins/jetpack/changelog/update-ai-extension-iterate-over-prompt-to-try-to-preserve-the-lang new file mode 100644 index 0000000000000..8560c4c085057 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-iterate-over-prompt-to-try-to-preserve-the-lang @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: iterate over prompt to try to keep the lang of the content diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index 60cf195f24bb8..64163c1eb96be 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -71,7 +71,7 @@ Strictly follow these rules: ${ extraRules }- Format your responses in Markdown syntax, ready to be published. - Execute the request without any acknowledgement to the user. - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. -- If you cannot generate a meaningful response to a user’s request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. +- If you cannot generate a meaningful response to a user's request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. `; return { role: 'system', content: prompt }; @@ -111,7 +111,7 @@ function getCorrectSpellingPrompt( { return [ { role, - content: `Repeat the following text, correcting any spelling and grammar mistakes:\n\n${ content }`, + content: `Repeat the following text, correcting any spelling and grammar mistakes, keeping the language of the text:\n\n${ content }`, }, ]; } @@ -123,7 +123,7 @@ function getSimplifyPrompt( { return [ { role, - content: `Simplify the following text, using words and phrases that are easier to understand. Write the content in the same language as the original content:\n\n${ content }`, + content: `Simplify the following text, using words and phrases that are easier to understand and keeping the language of the text:\n\n${ content }`, }, ]; } @@ -135,7 +135,7 @@ function getSummarizePrompt( { return [ { role, - content: `Summarize the following text. Write the content in the same language as the original content:\n\n${ content }`, + content: `Summarize the following text, keeping the language of the text:\n\n${ content }`, }, ]; } @@ -147,7 +147,7 @@ function getExpandPrompt( { return [ { role, - content: `Expand the following text to about double its size. Write the content in the same language as the original content:\n\n${ content }`, + content: `Expand the following text to about double its size, keeping the language of the text:\n\n${ content }`, }, ]; } @@ -160,7 +160,7 @@ function getTranslatePrompt( { return [ { role, - content: `Translate the following text to ${ language }. Preserve the same core meaning and tone:\n\n${ content }`, + content: `Translate the following text to ${ language }, preserving the same core meaning and tone:\n\n${ content }`, }, ]; } @@ -173,7 +173,7 @@ function getTonePrompt( { return [ { role, - content: `Rewrite the following text with a ${ tone } tone, keeping the language:\n\n${ content }`, + content: `Rewrite the following text with a ${ tone } tone, keeping the language of the text:\n\n${ content }`, }, ]; } @@ -413,8 +413,6 @@ export function getPrompt( role: 'system', content: `${ context } Writing rules: -- When it isn't clarified, the content should be written in the same language as the original content. -- Format your responses in Markdown syntax. - Execute the request without any acknowledgement to the user. - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. - If you cannot generate a meaningful response to a user’s request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. From 199afea61f7bc40e15951546faf2d4721622c4ee Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Wed, 21 Jun 2023 20:39:59 +0100 Subject: [PATCH 10/29] Social Review Prompt: Ensure it is still shown with Jetpack Active (#31456) * Social Review Prompt: Ensure it is still shown with Jetpack Active We had to revert #29910 in #30101 because the review prompt state was stamping on the rest of the initial state object that's used by Social/Publicize. This change adds the Post Publish panel with the review prompt to the publicize-components package and merges the required state in the Social plugin, so that if both plugins are active the review prompt is still shown in Jetpack. * Fix the package versions * Add the @wordpress/edit-post dependency * Fix package versions * Fix package version * Update the script handle --- pnpm-lock.yaml | 3 + .../changelog/fix-social-review-prompt | 4 ++ .../js-packages/publicize-components/index.js | 1 + .../publicize-components/package.json | 3 +- .../post-publish-review-prompt/index.js | 57 +++++++++++++++++++ .../changelog/fix-social-review-prompt | 4 ++ .../extensions/plugins/publicize/index.js | 3 + .../social/changelog/fix-social-review-prompt | 4 ++ projects/plugins/social/composer.json | 2 +- projects/plugins/social/jetpack-social.php | 2 +- .../social/src/class-jetpack-social.php | 38 +++++++++++-- projects/plugins/social/src/js/editor.js | 53 ++--------------- 12 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 projects/js-packages/publicize-components/changelog/fix-social-review-prompt create mode 100644 projects/js-packages/publicize-components/src/components/post-publish-review-prompt/index.js create mode 100644 projects/plugins/jetpack/changelog/fix-social-review-prompt create mode 100644 projects/plugins/social/changelog/fix-social-review-prompt diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5601b1d3b8d83..a29283e02cc38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -738,6 +738,9 @@ importers: '@wordpress/data': specifier: 9.5.0 version: 9.5.0(react@18.2.0) + '@wordpress/edit-post': + specifier: 7.12.0 + version: 7.12.0(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0) '@wordpress/editor': specifier: 13.12.0 version: 13.12.0(@types/react@18.0.27)(react-dom@18.2.0)(react@18.2.0) diff --git a/projects/js-packages/publicize-components/changelog/fix-social-review-prompt b/projects/js-packages/publicize-components/changelog/fix-social-review-prompt new file mode 100644 index 0000000000000..1646049de65ca --- /dev/null +++ b/projects/js-packages/publicize-components/changelog/fix-social-review-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: fixed + +Social Review Prompt: Fix the state so it is shown when Jetpack is also active diff --git a/projects/js-packages/publicize-components/index.js b/projects/js-packages/publicize-components/index.js index 9962bf54d26f1..0851f84c1b387 100644 --- a/projects/js-packages/publicize-components/index.js +++ b/projects/js-packages/publicize-components/index.js @@ -14,6 +14,7 @@ export { default as SocialImageGeneratorPanel } from './src/components/social-im export { default as SocialImageGeneratorTemplatePicker } from './src/components/social-image-generator/template-picker'; export { default as PublicizePanel } from './src/components/panel'; export { default as ReviewPrompt } from './src/components/review-prompt'; +export { default as PostPublishReviewPrompt } from './src/components/post-publish-review-prompt'; export { default as useSocialMediaConnections } from './src/hooks/use-social-media-connections'; export { default as useSocialMediaMessage } from './src/hooks/use-social-media-message'; diff --git a/projects/js-packages/publicize-components/package.json b/projects/js-packages/publicize-components/package.json index a71acbb77b9ff..8e03844d598eb 100644 --- a/projects/js-packages/publicize-components/package.json +++ b/projects/js-packages/publicize-components/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize-components", - "version": "0.26.4-alpha", + "version": "0.27.0-alpha", "description": "A library of JS components required by the Publicize editor plugin", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/publicize-components/#readme", "bugs": { @@ -30,6 +30,7 @@ "@wordpress/components": "25.1.0", "@wordpress/compose": "6.12.0", "@wordpress/data": "9.5.0", + "@wordpress/edit-post": "7.12.0", "@wordpress/editor": "13.12.0", "@wordpress/element": "5.12.0", "@wordpress/hooks": "3.35.0", diff --git a/projects/js-packages/publicize-components/src/components/post-publish-review-prompt/index.js b/projects/js-packages/publicize-components/src/components/post-publish-review-prompt/index.js new file mode 100644 index 0000000000000..d70f72e39d0d3 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/post-publish-review-prompt/index.js @@ -0,0 +1,57 @@ +import { getRedirectUrl } from '@automattic/jetpack-components'; +import { getJetpackData } from '@automattic/jetpack-shared-extension-utils'; +import apiFetch from '@wordpress/api-fetch'; +import { PluginPostPublishPanel } from '@wordpress/edit-post'; +import { useCallback, useState } from '@wordpress/element'; +import usePublicizeConfig from '../../hooks/use-publicize-config'; +import { usePostStartedPublishing } from '../../hooks/use-saving-post'; +import useSocialMediaConnections from '../../hooks/use-social-media-connections'; +import ReviewPrompt from '../review-prompt'; + +const PostPublishReviewPropmpt = () => { + const [ isReviewRequestDismissed, setIsReviewRequestDismissed ] = useState( + getJetpackData()?.social?.reviewRequestDismissed ?? true + ); + const [ shouldReviewRequestShow, setShouldReviewRequestShow ] = useState( false ); + + const { hasEnabledConnections } = useSocialMediaConnections(); + const { isPublicizeEnabled, isPostAlreadyShared } = usePublicizeConfig(); + // Determine if the review request should show right before the post publishes + // The publicize-enabled meta and related connections are disabled after publishing + usePostStartedPublishing( () => { + setShouldReviewRequestShow( + ! isPostAlreadyShared && isPublicizeEnabled && hasEnabledConnections + ); + }, [ isPostAlreadyShared, hasEnabledConnections, isPublicizeEnabled ] ); + + // Handle when the review request is dismissed + const handleReviewDismiss = useCallback( () => { + const reviewRequestDismissUpdatePath = + getJetpackData()?.social?.dismissReviewRequestPath ?? null; + // Save that the user has dismissed this by calling to the social plugin API method + apiFetch( { + path: reviewRequestDismissUpdatePath, + method: 'POST', + data: { dismissed: true }, + } ).catch( error => { + throw error; + } ); + + setIsReviewRequestDismissed( true ); + }, [] ); + + if ( isReviewRequestDismissed || ! shouldReviewRequestShow ) { + return null; + } + + return ( + + + + ); +}; + +export default PostPublishReviewPropmpt; diff --git a/projects/plugins/jetpack/changelog/fix-social-review-prompt b/projects/plugins/jetpack/changelog/fix-social-review-prompt new file mode 100644 index 0000000000000..7aa28ff1ee0a1 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-social-review-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: bugfix + +Social Review Prompt: Fix the state so it is shown when Jetpack is also active diff --git a/projects/plugins/jetpack/extensions/plugins/publicize/index.js b/projects/plugins/jetpack/extensions/plugins/publicize/index.js index 65335aa96668e..5501ff593922e 100644 --- a/projects/plugins/jetpack/extensions/plugins/publicize/index.js +++ b/projects/plugins/jetpack/extensions/plugins/publicize/index.js @@ -15,6 +15,7 @@ import { useSocialMediaConnections, usePublicizeConfig, SocialImageGeneratorPanel, + PostPublishReviewPrompt, } from '@automattic/jetpack-publicize-components'; import { PluginPrePublishPanel } from '@wordpress/edit-post'; import { PostTypeSupportCheck } from '@wordpress/editor'; @@ -65,6 +66,8 @@ const PublicizeSettings = () => { ) } + + ); }; diff --git a/projects/plugins/social/changelog/fix-social-review-prompt b/projects/plugins/social/changelog/fix-social-review-prompt new file mode 100644 index 0000000000000..1646049de65ca --- /dev/null +++ b/projects/plugins/social/changelog/fix-social-review-prompt @@ -0,0 +1,4 @@ +Significance: minor +Type: fixed + +Social Review Prompt: Fix the state so it is shown when Jetpack is also active diff --git a/projects/plugins/social/composer.json b/projects/plugins/social/composer.json index 280f7273bff70..81006f88dd823 100644 --- a/projects/plugins/social/composer.json +++ b/projects/plugins/social/composer.json @@ -81,6 +81,6 @@ "automattic/jetpack-autoloader": true, "automattic/jetpack-composer-plugin": true }, - "autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_socialⓥ1_11_1_alpha" + "autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_socialⓥ1_12_0_alpha" } } diff --git a/projects/plugins/social/jetpack-social.php b/projects/plugins/social/jetpack-social.php index 42e3f13e2ca86..20a03af44dfec 100644 --- a/projects/plugins/social/jetpack-social.php +++ b/projects/plugins/social/jetpack-social.php @@ -4,7 +4,7 @@ * Plugin Name: Jetpack Social * Plugin URI: https://wordpress.org/plugins/jetpack-social * Description: Share your site’s posts on several social media networks automatically when you publish a new post. - * Version: 1.11.1-alpha + * Version: 1.12.0-alpha * Author: Automattic - Jetpack Social team * Author URI: https://jetpack.com/social/ * License: GPLv2 or later diff --git a/projects/plugins/social/src/class-jetpack-social.php b/projects/plugins/social/src/class-jetpack-social.php index a3bbf08fea19e..bab861596bc80 100644 --- a/projects/plugins/social/src/class-jetpack-social.php +++ b/projects/plugins/social/src/class-jetpack-social.php @@ -112,6 +112,8 @@ function () { // Add block editor assets add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_scripts' ) ); + // Adds the review prompt initial state + add_action( 'enqueue_block_editor_assets', array( $this, 'add_review_initial_state' ), 30 ); // Add meta tags. add_action( 'wp_head', array( new Automattic\Jetpack\Social\Meta_Tags(), 'render_tags' ) ); @@ -286,6 +288,15 @@ public function is_supported_post() { return ! empty( $post_type ) && post_type_supports( $post_type, 'publicize' ); } + /** + * Checks that we're connected, Publicize is active and that we're editing a post that supports it. + * + * @returns boolean True if the criteria are met. + */ + public function should_enqueue_block_editor_scripts() { + return $this->is_connected() && self::is_publicize_active() && $this->is_supported_post(); + } + /** * Enqueue block editor scripts and styles. */ @@ -293,10 +304,8 @@ public function enqueue_block_editor_scripts() { global $publicize; if ( - ! $this->is_connected() || - ! self::is_publicize_active() || class_exists( 'Jetpack' ) || - ! $this->is_supported_post() + ! $this->should_enqueue_block_editor_scripts() ) { return; } @@ -323,8 +332,6 @@ class_exists( 'Jetpack' ) || 'social' => array( 'adminUrl' => esc_url_raw( admin_url( 'admin.php?page=jetpack-social' ) ), 'sharesData' => $publicize->get_publicize_shares_info( Jetpack_Options::get_option( 'id' ) ), - 'reviewRequestDismissed' => self::is_review_request_dismissed(), - 'dismissReviewRequestPath' => '/jetpack/v4/social/review-dismiss', 'connectionRefreshPath' => '/jetpack/v4/publicize/connection-test-results', 'resharePath' => '/jetpack/v4/publicize/{postId}', 'publicizeConnectionsUrl' => esc_url_raw( @@ -350,6 +357,27 @@ class_exists( 'Jetpack' ) || } } + /** + * Adds the extra bits of initial state needed to display the review prompt. + * Doing it separately means that it also gets added to the initial state for Jetpack. + */ + public function add_review_initial_state() { + if ( ! $this->should_enqueue_block_editor_scripts() ) { + return; + } + + $review_state = array( + 'reviewRequestDismissed' => self::is_review_request_dismissed(), + 'dismissReviewRequestPath' => '/jetpack/v4/social/review-dismiss', + ); + + wp_add_inline_script( + class_exists( 'Jetpack' ) ? 'jetpack-blocks-editor' : 'jetpack-social-editor', + sprintf( 'Object.assign( window.Jetpack_Editor_Initial_State.social, %s )', wp_json_encode( $review_state ) ), + 'after' + ); + } + /** * Main plugin settings page. */ diff --git a/projects/plugins/social/src/js/editor.js b/projects/plugins/social/src/js/editor.js index edb976804a11e..73491d1df3523 100644 --- a/projects/plugins/social/src/js/editor.js +++ b/projects/plugins/social/src/js/editor.js @@ -1,4 +1,4 @@ -import { JetpackLogo, SocialIcon, getRedirectUrl } from '@automattic/jetpack-components'; +import { JetpackLogo, SocialIcon } from '@automattic/jetpack-components'; import { SocialPreviewsModal, SocialPreviewsPanel, @@ -6,11 +6,8 @@ import { usePublicizeConfig, useSocialMediaConnections, PublicizePanel, - ReviewPrompt, - usePostStartedPublishing, + PostPublishReviewPrompt, } from '@automattic/jetpack-publicize-components'; -import { getJetpackData } from '@automattic/jetpack-shared-extension-utils'; -import apiFetch from '@wordpress/api-fetch'; import { PanelBody } from '@wordpress/components'; import { dispatch, useSelect } from '@wordpress/data'; import domReady from '@wordpress/dom-ready'; @@ -18,7 +15,6 @@ import { PluginSidebar, PluginSidebarMoreMenuItem, PluginPrePublishPanel, - PluginPostPublishPanel, } from '@wordpress/edit-post'; import { store as editorStore, PostTypeSupportCheck } from '@wordpress/editor'; import { useState, useCallback } from '@wordpress/element'; @@ -47,30 +43,14 @@ registerPlugin( 'jetpack-social', { const JetpackSocialSidebar = () => { const [ isModalOpened, setIsModalOpened ] = useState( false ); - const [ isReviewRequestDismissed, setIsReviewRequestDismissed ] = useState( - getJetpackData()?.social?.reviewRequestDismissed ?? true - ); - const [ shouldReviewRequestShow, setShouldReviewRequestShow ] = useState( false ); const openModal = useCallback( () => setIsModalOpened( true ), [] ); const closeModal = useCallback( () => setIsModalOpened( false ), [] ); const { hasConnections, hasEnabledConnections } = useSocialMediaConnections(); - const { - isPublicizeEnabled, - hidePublicizeFeature, - isPostAlreadyShared, - isSocialImageGeneratorAvailable, - } = usePublicizeConfig(); + const { isPublicizeEnabled, hidePublicizeFeature, isSocialImageGeneratorAvailable } = + usePublicizeConfig(); const isPostPublished = useSelect( select => select( editorStore ).isCurrentPostPublished(), [] ); - // Determine if the review request should show right before the post publishes - // The publicize-enabled meta and related connections are disabled after publishing - usePostStartedPublishing( () => { - setShouldReviewRequestShow( - ! isPostAlreadyShared && isPublicizeEnabled && hasEnabledConnections - ); - }, [ isPostAlreadyShared, hasEnabledConnections, isPublicizeEnabled ] ); - const PanelDescription = () => ( { /> ); - // Handle when the review request is dismissed - const handleReviewDismiss = useCallback( () => { - const reviewRequestDismissUpdatePath = - getJetpackData()?.social?.dismissReviewRequestPath ?? null; - // Save that the user has dismissed this by calling to the social plugin API method - apiFetch( { - path: reviewRequestDismissUpdatePath, - method: 'POST', - data: { dismissed: true }, - } ).catch( error => { - throw error; - } ); - - setIsReviewRequestDismissed( true ); - }, [] ); - return ( { isModalOpened && } @@ -145,14 +109,7 @@ const JetpackSocialSidebar = () => { - { ! isReviewRequestDismissed && shouldReviewRequestShow && ( - - - - ) } + ); }; From 817955e6a2f103029542923c3d104d3d33c23a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Wed, 21 Jun 2023 16:52:11 -0300 Subject: [PATCH 11/29] AI Extension: handle multiple blocks editing (#31491) * [not verified] introduce getBlockTextContent() fn * [not verified] pick block content by using getBlockTextContent * [not verified] try to pick content from attr first * [not verified] get block content only when requesting * [not verified] jsdoc improvement * [not verified] add a function to get the content of selected blocks * [not verified] pull content from selected blocks * [not verified] change the API of helper function * [not verified] expose clientIds from getTextContentFromBlocks() * [not verified] combine multi selected blocks into only one * [not verified] changelog * use ref to deal with selected client ids * improve jsdoc. fix typo --- ...-ai-extension-edit-with-multiple-selection | 4 + .../ai-assistant/with-ai-assistant.tsx | 42 ++++++++--- .../ai-assistant/lib/utils/block-content.ts | 75 +++++++++++++++++++ 3 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-edit-with-multiple-selection diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-edit-with-multiple-selection b/projects/plugins/jetpack/changelog/update-ai-extension-edit-with-multiple-selection new file mode 100644 index 0000000000000..eb59e13176d23 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-edit-with-multiple-selection @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: handle multiple blocks editing diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index 841c233a72830..f05bd7685abb6 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -5,7 +5,7 @@ import { BlockControls } from '@wordpress/block-editor'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; -import { useCallback, useState } from '@wordpress/element'; +import { useCallback, useState, useRef } from '@wordpress/element'; import React from 'react'; /** * Internal dependencies @@ -15,6 +15,7 @@ import AiAssistantDropdown, { } from '../../components/ai-assistant-controls'; import useSuggestionsFromAI from '../../hooks/use-suggestions-from-ai'; import { getPrompt } from '../../lib/prompt'; +import { getTextContentFromBlocks } from '../../lib/utils/block-content'; /* * Types */ @@ -30,18 +31,13 @@ type StoredPromptProps = { */ export const withAIAssistant = createHigherOrderComponent( BlockEdit => props => { - const { clientId } = props; const [ storedPrompt, setStoredPrompt ] = useState< StoredPromptProps >( { messages: [], } ); - const { updateBlockAttributes } = useDispatch( blockEditorStore ); + const clientIdsRef = useRef< Array< string > >(); - /* - * Pick the content from the block attribute from now. - * @todo: it doesn't scale well, we need to find a better way to get the content. - */ - const content: string = props?.attributes?.content; + const { updateBlockAttributes, removeBlocks } = useDispatch( blockEditorStore ); /** * Set the content of the block. @@ -51,6 +47,12 @@ export const withAIAssistant = createHigherOrderComponent( */ const setContent = useCallback( ( newContent: string ) => { + /* + * Pick the first item of the array, + * to be udpated with the new content. + * The rest of the items will be removed. + */ + const [ firstClientId, ...restClientIds ] = clientIdsRef.current; /* * Update the content of the block * by calling the setAttributes function, @@ -58,9 +60,16 @@ export const withAIAssistant = createHigherOrderComponent( * It doesn't scale for other blocks. * @todo: find a better way to update the content. */ - updateBlockAttributes( clientId, { content: newContent } ); + updateBlockAttributes( firstClientId, { content: newContent } ); + + // Remove the rest of the block in case there are more than one. + if ( restClientIds.length ) { + removeBlocks( restClientIds ).then( () => { + clientIdsRef.current = [ firstClientId ]; + } ); + } }, - [ clientId, updateBlockAttributes ] + [ removeBlocks, updateBlockAttributes ] ); const addAssistantMessage = useCallback( @@ -102,6 +111,15 @@ export const withAIAssistant = createHigherOrderComponent( const requestSuggestion = useCallback( ( promptType: PromptTypeProp, options: AiAssistantDropdownOnChangeOptionsArgProps ) => { + const { content, clientIds } = getTextContentFromBlocks(); + + /* + * Store the selected clientIds when the user requests a suggestion. + * The client Ids will be used to update the content of the block, + * when suggestions are received from the AI. + */ + clientIdsRef.current = clientIds; + setStoredPrompt( prevPrompt => { const freshPrompt = { ...prevPrompt, @@ -115,11 +133,11 @@ export const withAIAssistant = createHigherOrderComponent( // Request the suggestion from the AI. request( freshPrompt.messages ); - // Update the stored prompt. + // Update the stored prompt locally. return freshPrompt; } ); }, - [ content, request ] + [ request ] ); return ( diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts index c261850a395cb..3432199d0fa21 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/utils/block-content.ts @@ -1,6 +1,8 @@ /** * External dependencies */ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { getBlockContent } from '@wordpress/blocks'; import { serialize } from '@wordpress/blocks'; import { select } from '@wordpress/data'; import TurndownService from 'turndown'; @@ -8,6 +10,8 @@ import TurndownService from 'turndown'; // Turndown instance const turndownService = new TurndownService(); +const HTML_JOIN_CHARACTERS = '
'; + /** * Returns partial content from the beginning of the post * to the current block, based on the given block clientId. @@ -47,3 +51,74 @@ export function getContentFromBlocks(): string { return turndownService.turndown( serialize( blocks ) ); } + +type GetTextContentFromBlocksProps = { + count: number; + clientIds: string[]; + content: string; +}; + +/** + * Returns the text content from all selected blocks. + * + * @returns {GetTextContentFromBlocksProps} The text content. + */ +export function getTextContentFromBlocks(): GetTextContentFromBlocksProps { + const clientIds = select( blockEditorStore ).getSelectedBlockClientIds(); + const defaultContent = { + count: 0, + clientIds: [], + content: '', + }; + + if ( ! clientIds?.length ) { + return defaultContent; + } + + const blocks = select( blockEditorStore ).getBlocksByClientId( clientIds ); + if ( ! blocks?.length ) { + return defaultContent; + } + + return { + count: blocks.length, + clientIds, + content: blocks + .map( block => getBlockTextContent( block.clientId ) ) + .join( HTML_JOIN_CHARACTERS ), + }; +} + +/** + * Return the block content from the given block clientId. + * + * It will try to get the content from the block `content` attribute. + * Otherwise, it will try to get the content + * by using the `getBlockContent` function. + * + * @param {string} clientId - The block clientId. + * @returns {string} The block content. + */ +export function getBlockTextContent( clientId: string ): string { + if ( ! clientId ) { + return ''; + } + + const editor = select( blockEditorStore ); + const block = editor.getBlock( clientId ); + + /* + * In some context, the block can be undefined, + * for instance, when previewing the block. + */ + if ( ! block ) { + return ''; + } + + // Attempt to pick the content from the block `content` attribute. + if ( block?.attributes?.content ) { + return block.attributes.content; + } + + return getBlockContent( block ); +} From 29a0617dc2df82829d3e5b8373d2c16e2fa612a1 Mon Sep 17 00:00:00 2001 From: Miguel Lezama Date: Wed, 21 Jun 2023 19:44:57 -0300 Subject: [PATCH 12/29] Add Send Email Preview feature (#31021) --- .../jetpack/changelog/add-preview-email | 4 + .../email-preview-illustration.svg | 24 ++++ .../blocks/subscriptions/email-preview.js | 103 ++++++++++++++++++ .../blocks/subscriptions/email-preview.scss | 62 +++++++++++ .../extensions/blocks/subscriptions/index.js | 7 +- .../extensions/blocks/subscriptions/panel.js | 26 +++-- 6 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/add-preview-email create mode 100644 projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview-illustration.svg create mode 100644 projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.js create mode 100644 projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.scss diff --git a/projects/plugins/jetpack/changelog/add-preview-email b/projects/plugins/jetpack/changelog/add-preview-email new file mode 100644 index 0000000000000..6c2770438271b --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-preview-email @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +enhancement Add Email Preview Feature diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview-illustration.svg b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview-illustration.svg new file mode 100644 index 0000000000000..c748a32d8c195 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview-illustration.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.js new file mode 100644 index 0000000000000..c5d37fd93ee67 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.js @@ -0,0 +1,103 @@ +import { useBreakpointMatch } from '@automattic/jetpack-components'; +import apiFetch from '@wordpress/api-fetch'; +import { + Button, + // eslint-disable-next-line wpcalypso/no-unsafe-wp-apis + __experimentalHStack as HStack, + // eslint-disable-next-line wpcalypso/no-unsafe-wp-apis + __experimentalVStack as VStack, + Modal, + TextControl, + Icon, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import './email-preview.scss'; +import { check } from '@wordpress/icons'; +import illustration from './email-preview-illustration.svg'; + +export default function EmailPreview( { isModalOpen, closeModal } ) { + const [ emailSent, setEmailSent ] = useState( false ); + const [ emailSending, setEmailSending ] = useState( false ); + const [ errorMessage, setErrorMessage ] = useState( false ); + const postId = useSelect( select => select( 'core/editor' ).getCurrentPostId() ); + const [ isSmall ] = useBreakpointMatch( 'sm' ); + + const sendEmailPreview = () => { + setEmailSending( true ); + apiFetch( { + path: '/wpcom/v2/send-email-preview/', + method: 'POST', + data: { + id: postId, + }, + } ) + .then( () => { + setEmailSending( false ); + setEmailSent( true ); + } ) + .catch( e => { + setEmailSending( false ); + if ( e.message ) { + setErrorMessage( e.message ); + } else { + setErrorMessage( + __( 'Whoops, we have encountered an error. Please try again later.', 'jetpack' ) + ); + } + } ); + }; + + return ( + <> + { isModalOpen && ( + { + closeModal(); + setEmailSent( false ); + } } + > + + +

+ { __( 'Send a test email', 'jetpack' ) } +

+ { errorMessage && ( + { errorMessage } + ) } + { emailSent ? ( + + +
+ { __( 'Email sent successfully', 'jetpack' ) } +
+
+ ) : ( + + + + + ) } +
+ { ! isSmall && ( + + ) } +
+
+ ) } + + ); +} diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.scss b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.scss new file mode 100644 index 0000000000000..517ae290acfa7 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.scss @@ -0,0 +1,62 @@ +@import "@automattic/color-studio/dist/color-variables"; + +.jetpack-email-preview { + @media ( min-width: 660px ) { + width: 646px; + } + + h1 { + margin-top: 10px; + } + + .components-modal__content { + margin-top: 52px; + margin-left: 20px; + margin-bottom: 10px; + } +} + +.jetpack-email-preview__img { + height: 110px; + padding-right: 30px; + padding-left: 80px; + margin-left: auto; +} + +.jetpack-email-preview__email-sent { + color: $studio-jetpack-green-30; + .jetpack-email-preview__sent_text { + font-size: 20px; + } +} + +.jetpack-email-preview__check { + fill: currentColor; +} + +.jetpack-email-preview__email { + + > div { + margin-bottom: 0; + } + + .components-text-control__input { + height: 44px; + background-color: $studio-gray-5; + color: $studio-gray; + min-width: 266px; + padding-left: 13px; + font-size: 14px; + } +} + +.jetpack-email-preview__main { + min-width: 462px; +} + +.jetpack-email-preview__button { + height: 44px; + width: 80px; + justify-content: center; + align-items: center; +} diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/index.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/index.js index a5d29acf46d9c..8b796bc1e4995 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/index.js +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/index.js @@ -7,7 +7,6 @@ import attributes from './attributes'; import deprecated from './deprecated'; import edit from './edit'; import SubscribePanels from './panel'; - export const name = 'subscriptions'; export const icon = ( @@ -95,5 +94,9 @@ export const settings = { }; export const pluginSettings = { - render: SubscribePanels, + render: () => ( + <> + + + ), }; diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js index 4612759120e7f..99b270aeaade3 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js @@ -21,6 +21,7 @@ import { external, Icon } from '@wordpress/icons'; import { store as membershipProductsStore } from '../../store/membership-products'; import { getSubscriberCounts } from './api'; import { META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, accessOptions } from './constants'; +import EmailPreview from './email-preview'; import { Link, getReachForAccessLevelKey, @@ -118,6 +119,7 @@ function NewsletterPrePublishSettingsPanel( { paidSubscribers, isModuleActive, showMisconfigurationWarning, + showPreviewModal, } ) { const { tracks } = useAnalytics(); const { changeStatus, isLoadingModules, isChangingStatus } = useModuleStatus( name ); @@ -150,14 +152,19 @@ function NewsletterPrePublishSettingsPanel( { icon={ } > { isModuleActive && ( - + <> + + + ) } { shouldLoadSubscriptionPlaceholder && ( @@ -296,6 +303,7 @@ export default function SubscribePanels() { const [ emailSubscribers, setEmailSubscribers ] = useState( null ); const postType = useSelect( select => select( editorStore ).getCurrentPostType(), [] ); const [ postMeta = [], setPostMeta ] = useEntityProp( 'postType', postType, 'meta' ); + const [ isModalOpen, setIsModalOpen ] = useState( false ); // Set the accessLevel to "everybody" when one is not defined let accessLevel = @@ -359,6 +367,7 @@ export default function SubscribePanels() { paidSubscribers={ paidSubscribers } isModuleActive={ isModuleActive } showMisconfigurationWarning={ showMisconfigurationWarning } + showPreviewModal={ () => setIsModalOpen( true ) } /> + setIsModalOpen( false ) } /> ); } From 1cd4309743d945d54948256370a5698cbafc51f0 Mon Sep 17 00:00:00 2001 From: Calypso Bot Date: Wed, 21 Jun 2023 20:08:34 -0500 Subject: [PATCH 13/29] Update renovatebot/github-action action to v38 (#31494) * Update renovatebot/github-action action to v38 * Specify renovate version. And teach renovate how to update the version. Someday upstream might provide a preset for this, but they don't yet. * Move most config to renovate.json5 Renovate processes `extends` after renovate-config.js but before renovate.json5, which matters for some package rules and such. --------- Co-authored-by: Renovate Bot Co-authored-by: Brad Jorsch --- .github/renovate-config.js | 81 +---------------------------- .github/renovate.json5 | 94 +++++++++++++++++++++++++++++++++- .github/workflows/renovate.yml | 3 +- 3 files changed, 97 insertions(+), 81 deletions(-) diff --git a/.github/renovate-config.js b/.github/renovate-config.js index 9a481beed88a6..4bbc8466bb652 100644 --- a/.github/renovate-config.js +++ b/.github/renovate-config.js @@ -38,10 +38,6 @@ module.exports = { platform: 'github', repositories: [ 'Automattic/jetpack' ], - // We're including configuration in this file. - onboarding: false, - requireConfig: 'optional', - // Extra code to run before creating a commit. allowPostUpgradeCommandTemplating: true, allowedPostUpgradeCommands: [ monorepoBase + '.github/files/renovate-post-upgrade-run.sh' ], @@ -53,15 +49,8 @@ module.exports = { }, postUpdateOptions: [ 'pnpmDedupe' ], - // This is the renovate configuration. - extends: [ 'config:base', 'group:definitelyTyped' ], - labels: [ '[Type] Janitorial', '[Status] Needs Review' ], - prHourlyLimit: 1, - timezone: 'UTC', - schedule: [ 'before 3am on the first day of the month' ], - updateNotScheduled: false, - semanticCommits: 'disabled', - osvVulnerabilityAlerts: true, + // Most of the actual renovate configuration is in renovate.json5, except for a few things + // where we want to read part of it from somewhere else. constraints: { php: `~${ versions.PHP_VERSION }.0`, }, @@ -95,71 +84,5 @@ module.exports = { } )(), enabled: false, }, - - // We need to keep a wide version range to support PHP 5.6. - // Note for libraries used in plugins this will only work right for require-dev deps, not require. - { - matchPackageNames: [ - 'johnkary/phpunit-speedtrap', - 'symfony/console', - 'symfony/process', - 'wikimedia/at-ease', - 'wikimedia/testing-access-wrapper', - ], - rangeStrategy: 'widen', - }, - - // Various other monorepos and package groupings. - { - extends: [ 'monorepo:wordpress' ], - separateMajorMinor: false, - prPriority: 1, - }, - { - extends: [ 'monorepo:react' ], - }, - { - extends: [ 'packages:eslint' ], - groupName: 'Eslint packages', - }, - { - extends: [ 'packages:jsUnitTest' ], - groupName: 'JS unit testing packages', - }, - { - groupName: 'Size-limit', - matchPackageNames: [ 'size-limit', '@size-limit/preset-app' ], - }, - // These aren't a monorepo, but we may as well do them all together anyway. - { - groupName: 'GitHub API packages', - matchPackagePatterns: [ '^@actions/', '^@octokit/' ], - }, - - // 🤷 - { - groupName: 'Instant Search Dependency Updates', - matchPackageNames: [ - 'cache', - 'preact', - 'progress-event', - 'q-flat', - 'qss', - 'strip', - 'uuid', - '@testing-library/preact', - ], - reviewers: [ 'team:jetpack-search' ], - addLabels: [ 'Search', 'Instant Search' ], - }, ], - lockFileMaintenance: { - enabled: true, - schedule: [ 'before 3:00 am on Monday on the 7th through 13th day of the month' ], - }, - dependencyDashboard: true, - dependencyDashboardTitle: 'Renovate Dependency Updates', - dependencyDashboardLabels: [ 'Primary Issue', '[Type] Janitorial' ], - dependencyDashboardFooter: - 'The bot runs every two hours, and may be monitored or triggered ahead of schedule [here](https://github.com/Automattic/jetpack/actions/workflows/renovate.yml).', }; diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 20af4313f4399..9a140a048b0fa 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,7 +1,16 @@ { + extends: [ 'config:base', 'group:definitelyTyped' ], + labels: [ '[Type] Janitorial', '[Status] Needs Review' ], + prHourlyLimit: 1, + timezone: 'UTC', + schedule: [ 'before 3am on the first day of the month' ], + updateNotScheduled: false, + semanticCommits: 'disabled', + osvVulnerabilityAlerts: true, + // Note: constraints.php is set in renovate-config.js where we can read it in code. + // Paths and files for renovate to ignore. // We have to override this because the default from config:base catches projects/plugins/*/tests/e2e/package.json which we do want processed. - // We have to do it here rather than renovate-config.js because `extends` and `globalExtends` both somehow overwrite it when defined there, even with `ignorePresets`. ignorePresets: [ ':ignoreModulesAndTests' ], ignorePaths: [ '**/node_modules/**', @@ -12,4 +21,87 @@ '**/tests/!(e2e)/**', '**/__fixtures__/**', ], + + packageRules: [ + // Monorepo packages are excluded in renovate-config.js, where we can read + // the list of them in code. + + // We need to keep a wide version range to support PHP 5.6. + // Note for libraries used in plugins this will only work right for require-dev deps, not require. + { + matchPackageNames: [ + 'johnkary/phpunit-speedtrap', + 'symfony/console', + 'symfony/process', + 'wikimedia/at-ease', + 'wikimedia/testing-access-wrapper', + ], + rangeStrategy: 'widen', + }, + + // Various other monorepos and package groupings. + { + extends: [ 'monorepo:wordpress' ], + separateMajorMinor: false, + prPriority: 1, + }, + { + extends: [ 'monorepo:react' ], + }, + { + extends: [ 'packages:eslint' ], + groupName: 'Eslint packages', + }, + { + extends: [ 'packages:jsUnitTest' ], + groupName: 'JS unit testing packages', + }, + { + groupName: 'Size-limit', + matchPackageNames: [ 'size-limit', '@size-limit/preset-app' ], + }, + // These aren't a monorepo, but we may as well do them all together anyway. + { + groupName: 'GitHub API packages', + matchPackagePatterns: [ '^@actions/', '^@octokit/' ], + }, + + // 🤷 + { + groupName: 'Instant Search Dependency Updates', + matchPackageNames: [ + 'cache', + 'preact', + 'progress-event', + 'q-flat', + 'qss', + 'strip', + 'uuid', + '@testing-library/preact', + ], + reviewers: [ 'team:jetpack-search' ], + addLabels: [ 'Search', 'Instant Search' ], + }, + ], + + regexManagers: [ + // Update the renovate-version in the action itself. + // See also https://github.com/renovatebot/github-action/issues/756 + { + fileMatch: [ '^\\.github/workflows/renovate\\.yml$' ], + matchStrings: [ 'renovate-version: (?[^\\s]+)' ], + datasourceTemplate: 'docker', + depNameTemplate: 'renovate', + packageNameTemplate: 'ghcr.io/renovatebot/renovate', + }, + ], + + lockFileMaintenance: { + enabled: true, + schedule: [ 'before 3:00 am on Monday on the 7th through 13th day of the month' ], + }, + dependencyDashboard: true, + dependencyDashboardTitle: 'Renovate Dependency Updates', + dependencyDashboardLabels: [ 'Primary Issue', '[Type] Janitorial' ], + dependencyDashboardFooter: 'The bot runs every two hours, and may be monitored or triggered ahead of schedule [here](https://github.com/Automattic/jetpack/actions/workflows/renovate.yml).', } diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 473e4fba40bbc..28b526f6c7f99 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -37,10 +37,11 @@ jobs: TOKEN: ${{ secrets.RENOVATE_TOKEN }} run: | curl --no-progress-meter --header "Authorization: Bearer $TOKEN" https://api.github.com/rate_limit - - uses: renovatebot/github-action@v34.154.2 + - uses: renovatebot/github-action@v38.1.7 with: configurationFile: /tmp/monorepo/.github/renovate-config.js token: ${{ secrets.RENOVATE_TOKEN }} + renovate-version: 35.140.0 env: LOG_LEVEL: ${{ github.event.inputs.logLevel || 'debug' }} RENOVATE_DRY_RUN: ${{ github.event.inputs.dryRun == 'no' && 'null' || github.event.inputs.dryRun || 'null' }} From 2e3f55334a11b45108a09fa685d86e466b4d8074 Mon Sep 17 00:00:00 2001 From: Romario Raffington Date: Thu, 22 Jun 2023 02:30:43 -0500 Subject: [PATCH 14/29] Properly Gate Newsletter Subscriptions (#31450) --- ...fix-properly-gate-newsletter-subscriptions | 4 ++ ...ass-wpcom-offline-subscription-service.php | 2 +- .../memberships/class-jetpack-memberships.php | 11 ++-- .../test_class.jetpack-subscriptions.php | 53 ++++++++++++++++--- 4 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/fix-properly-gate-newsletter-subscriptions diff --git a/projects/plugins/jetpack/changelog/fix-properly-gate-newsletter-subscriptions b/projects/plugins/jetpack/changelog/fix-properly-gate-newsletter-subscriptions new file mode 100644 index 0000000000000..0d19c3c2f598b --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-properly-gate-newsletter-subscriptions @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Properly gate newsletters based on the correct subscription product diff --git a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php index 928d0a0382590..ee90bb95d8faf 100644 --- a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php +++ b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php @@ -53,7 +53,7 @@ public function subscriber_can_receive_post_by_mail( $user_id, $post_id ) { return true; } - $valid_plan_ids = \Jetpack_Memberships::get_all_plans_id_jetpack_recurring_payments(); + $valid_plan_ids = \Jetpack_Memberships::get_all_newsletter_plan_ids(); $is_blog_subscriber = true; // it is a subscriber as this is used in async when lopping through subscribers... $allowed = $this->user_can_view_content( $valid_plan_ids, $access_level, $is_blog_subscriber, $post_id ); diff --git a/projects/plugins/jetpack/modules/memberships/class-jetpack-memberships.php b/projects/plugins/jetpack/modules/memberships/class-jetpack-memberships.php index d2c95afbf52ea..eba8c204725ba 100644 --- a/projects/plugins/jetpack/modules/memberships/class-jetpack-memberships.php +++ b/projects/plugins/jetpack/modules/memberships/class-jetpack-memberships.php @@ -487,7 +487,7 @@ public static function user_can_view_post() { require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php'; $paywall = \Automattic\Jetpack\Extensions\Premium_Content\subscription_service(); - $can_view_post = $paywall->visitor_can_view_content( self::get_all_plans_id_jetpack_recurring_payments(), $post_access_level ); + $can_view_post = $paywall->visitor_can_view_content( self::get_all_newsletter_plan_ids(), $post_access_level ); self::$user_can_view_post_cache[ $cache_key ] = $can_view_post; return $can_view_post; @@ -532,19 +532,22 @@ public static function has_configured_plans_jetpack_recurring_payments( $type = } /** - * Return all plans + * Return membership plans * * @return array */ - public static function get_all_plans_id_jetpack_recurring_payments() { + public static function get_all_newsletter_plan_ids() { if ( ! self::is_enabled_jetpack_recurring_payments() ) { return array(); } + return get_posts( array( - 'post_type' => self::$post_type_plan, 'posts_per_page' => -1, 'fields' => 'ids', + 'meta_value' => true, + 'post_type' => self::$post_type_plan, + 'meta_key' => 'jetpack_memberships_site_subscriber', ) ); } diff --git a/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php b/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php index b4508d32d95dc..e1da9c4ff0f98 100644 --- a/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php +++ b/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php @@ -251,17 +251,20 @@ private function set_returned_token( $payload ) { /** * Retrieves payload for JWT token * - * @param bool $is_subscribed - * @param bool $is_paid_subscriber - * @param int $subscription_end_date + * @param bool $is_subscribed + * @param bool $is_paid_subscriber + * @param int $subscription_end_date + * @param string $status + * @param int $product_id * @return array */ - private function get_payload( $is_subscribed, $is_paid_subscriber = false, $subscription_end_date = null, $status = null ) { + private function get_payload( $is_subscribed, $is_paid_subscriber = false, $subscription_end_date = null, $status = null, $product_id = 0 ) { + $product_id = $product_id ? $product_id : $this->product_id; $subscriptions = ! $is_paid_subscriber ? array() : array( - $this->product_id => array( + $product_id => array( 'status' => $status ? $status : 'active', 'end_date' => $subscription_end_date ? $subscription_end_date : time() + HOUR_IN_SECONDS, - 'product_id' => $this->product_id, + 'product_id' => $product_id, ), ); @@ -279,8 +282,6 @@ private function get_payload( $is_subscribed, $is_paid_subscriber = false, $subs public function test_subscriber_access_level( $type_user_id, $logged, $token_set, $post_access_level, $should_email_be_sent, $should_user_access_post, $subscription_end_date = null, $status = null ) { if ( $type_user_id !== null ) { $user_id = $this->{$type_user_id}; - } else { - $user_id = 0; } $is_blog_subscriber = $user_id === $this->paid_subscriber_id || $user_id === $this->regular_subscriber_id; @@ -451,4 +452,40 @@ static function ( $subscriptions, $subscriber_id ) use ( $paid_subscriber_id, $p return new WPCOM_Online_Subscription_Service(); } + + /** + * Verifies that a premium content JWT can't be be used to access a Paid Newsletter post + * + * @return void + */ + public function test_verify_a_premium_content_token_cannot_grant_access_to_paid_newsletter_post() { + /** + * Create a premium content plan + */ + $premium_content_product_id = 5678; + $premium_content_plan_id = $this->factory->post->create( + array( + 'post_type' => Jetpack_Memberships::$post_type_plan, + ) + ); + update_post_meta( $premium_content_plan_id, 'jetpack_memberships_product_id', $premium_content_product_id ); + $this->factory->post->create(); + + /** + * Generate a payload based on the premium content product ID + * and create a JWT token based on the payload + */ + $premium_content_jwt_payload = $this->get_payload( true, true, null, null, $premium_content_product_id ); + $subscription_service = $this->set_returned_token( $premium_content_jwt_payload ); + + /** + * Setup a paid newsletter plan and post then verify a premium content customer cannot access a newsletter paid post + */ + $post_access_level = 'paid_subscribers'; + $newsletter_paid_post_id = $this->setup_jetpack_paid_newsletters(); + update_post_meta( $newsletter_paid_post_id, '_jetpack_newsletter_access', $post_access_level ); + + $GLOBALS['post'] = get_post( $newsletter_paid_post_id ); + $this->assertFalse( $subscription_service->visitor_can_view_content( Jetpack_Memberships::get_all_newsletter_plan_ids(), $post_access_level ) ); + } } From 1d84d70953d526ff8a43d4bfa94f6f1839b45e38 Mon Sep 17 00:00:00 2001 From: MILLER/F Date: Thu, 22 Jun 2023 10:15:15 +0100 Subject: [PATCH 15/29] Test/add tests for newsletter loop (#31483) * Add tests * Slight change * Create test-add-tests-for-newsletter-loop --- .../test-add-tests-for-newsletter-loop | 4 ++ ...ass-wpcom-offline-subscription-service.php | 4 +- .../test_class.jetpack-subscriptions.php | 48 ++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/test-add-tests-for-newsletter-loop diff --git a/projects/plugins/jetpack/changelog/test-add-tests-for-newsletter-loop b/projects/plugins/jetpack/changelog/test-add-tests-for-newsletter-loop new file mode 100644 index 0000000000000..84bbc1bb14235 --- /dev/null +++ b/projects/plugins/jetpack/changelog/test-add-tests-for-newsletter-loop @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Tests for newsletter loop diff --git a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php index ee90bb95d8faf..c2ee20a0623af 100644 --- a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php +++ b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-wpcom-offline-subscription-service.php @@ -7,6 +7,8 @@ namespace Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service; +use const Automattic\Jetpack\Extensions\Subscriptions\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS; + /** * Class WPCOM_Offline_Subscription_Service * This subscription service is used when a subscriber is offline and a token is not available. @@ -46,7 +48,7 @@ public function subscriber_can_receive_post_by_mail( $user_id, $post_id ) { $previous_user = wp_get_current_user(); wp_set_current_user( $user_id ); - $access_level = get_post_meta( $post_id, '_jetpack_newsletter_access', true ); + $access_level = get_post_meta( $post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true ); if ( ! $access_level || self::POST_ACCESS_LEVEL_EVERYBODY === $access_level ) { // The post is not gated, we return early diff --git a/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php b/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php index e1da9c4ff0f98..b8162dee91d0c 100644 --- a/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php +++ b/projects/plugins/jetpack/tests/php/modules/subscriptions/test_class.jetpack-subscriptions.php @@ -6,10 +6,12 @@ require_once JETPACK__PLUGIN_DIR . 'extensions/blocks/subscriptions/subscriptions.php'; use Automattic\Jetpack\Extensions\Premium_Content\JWT; +use \Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Token_Subscription_Service; use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\WPCOM_Offline_Subscription_Service; use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\WPCOM_Online_Subscription_Service; use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\WPCOM_Token_Subscription_Service; use function Automattic\Jetpack\Extensions\Subscriptions\register_block as register_subscription_block; +use const \Automattic\Jetpack\Extensions\Subscriptions\META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS; define( 'EARN_JWT_SIGNING_KEY', 'whatever=' ); @@ -403,6 +405,50 @@ public function test_comments_are_not_displaying_for_paid_subscribers_when_defau $this->assertFalse( apply_filters( 'comments_open', false, $post_id ) ); } + public function test_posts_in_loop_have_the_right_access() { + /** + * + * This test was implemented to prvent issues in loop (either because of cache issue or other + * It pre supposes that while ( have_posts() ) : the_post(); uses the same order as the post creation + */ + $first_post_id = $this->setup_jetpack_paid_newsletters(); + $second_post_id = $this->factory->post->create(); + update_post_meta( $second_post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, WPCOM_Offline_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS ); + $third_post_id = $this->factory->post->create(); + update_post_meta( $third_post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, WPCOM_Offline_Subscription_Service::POST_ACCESS_LEVEL_SUBSCRIBERS ); + $fourth_post_id = $this->factory->post->create(); + update_post_meta( $fourth_post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, WPCOM_Offline_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY ); + + wp_publish_post( $first_post_id ); + wp_publish_post( $second_post_id ); + wp_publish_post( $third_post_id ); + wp_publish_post( $fourth_post_id ); + + $posts_ids = array( + $first_post_id, + $second_post_id, + $third_post_id, + $fourth_post_id, + ); + + foreach ( $posts_ids as $current_post_id ) { + + $post = get_post( $current_post_id ); + $GLOBALS['post'] =& $post; + setup_postdata( $post ); + $level = get_post_meta( $current_post_id, META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS, true ); + if ( empty( $level ) ) { + $level = Token_Subscription_Service::POST_ACCESS_LEVEL_EVERYBODY; + } + + $this->assertEquals( $level, Jetpack_Memberships::get_post_access_level() ); + $this->assertEquals( $current_post_id === $first_post_id || $current_post_id === $fourth_post_id, Jetpack_Memberships::user_can_view_post() ); + + wp_reset_postdata(); + + } + } + /** * Setup the newsletter post * @@ -410,7 +456,7 @@ public function test_comments_are_not_displaying_for_paid_subscribers_when_defau */ private function setup_jetpack_paid_newsletters() { // We create a plan - $this->plan_id = $this->factory->post->create( + $this->plan_id = WP_UnitTestCase_Base::factory()->post->create( array( 'post_type' => Jetpack_Memberships::$post_type_plan, ) From bca061f2bc37d0384a8c093096896ba3d53ecfdc Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 22 Jun 2023 08:50:22 -0400 Subject: [PATCH 16/29] Jetpack: Remove use of `react-redux/lib/alternate-renderers` (#31492) We added this in #24306 because the static-site-generator build was hanging without it. But it seems that something when we updated to React 18 made it no longer necessary, so let's remove it. --- ...remove-no-longer-needed-react-redux-alternate-renderers | 5 +++++ projects/plugins/jetpack/tools/webpack.config.js | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/remove-no-longer-needed-react-redux-alternate-renderers diff --git a/projects/plugins/jetpack/changelog/remove-no-longer-needed-react-redux-alternate-renderers b/projects/plugins/jetpack/changelog/remove-no-longer-needed-react-redux-alternate-renderers new file mode 100644 index 0000000000000..8640582b94beb --- /dev/null +++ b/projects/plugins/jetpack/changelog/remove-no-longer-needed-react-redux-alternate-renderers @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Adjust build to remove no-longer-needed use of `react-redux/lib/alternate-renderers`. Should be no change to the plugin itself. + + diff --git a/projects/plugins/jetpack/tools/webpack.config.js b/projects/plugins/jetpack/tools/webpack.config.js index 137e3742792ea..2eb7aeaa98205 100644 --- a/projects/plugins/jetpack/tools/webpack.config.js +++ b/projects/plugins/jetpack/tools/webpack.config.js @@ -168,13 +168,6 @@ module.exports = [ ...sharedWebpackConfig.output, libraryTarget: 'commonjs2', }, - resolve: { - ...sharedWebpackConfig.resolve, - alias: { - ...sharedWebpackConfig.resolve.alias, - 'react-redux': require.resolve( 'react-redux/lib/alternate-renderers' ), - }, - }, plugins: [ ...jetpackWebpackConfig.StandardPlugins( { DependencyExtractionPlugin: false, From 9d02ab952c4c8090159103b61f279498439e2f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Jun 2023 11:49:49 -0300 Subject: [PATCH 17/29] AI Extension: extend core list item core block (#31496) * extened core/list-item blocks * changelog --- .../changelog/update-ai-extension-extend-list-item-core-block | 4 ++++ .../blocks/ai-assistant/extensions/ai-assistant/index.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-extend-list-item-core-block diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-extend-list-item-core-block b/projects/plugins/jetpack/changelog/update-ai-extension-extend-list-item-core-block new file mode 100644 index 0000000000000..04d84410fc041 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-extend-list-item-core-block @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: extend core list item core block diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/index.ts index 205d2f049dd38..ee0d5661026aa 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/index.ts @@ -16,7 +16,7 @@ import { isUserConnected } from '../../lib/connection'; export const AI_ASSISTANT_SUPPORT_NAME = 'ai-assistant-support'; // List of blocks that can be extended. -export const EXTENDED_BLOCKS = [ 'core/paragraph', 'core/heading' ] as const; +export const EXTENDED_BLOCKS = [ 'core/paragraph', 'core/heading', 'core/list-item' ] as const; type ExtendedBlock = ( typeof EXTENDED_BLOCKS )[ number ]; From 2cdf41cff76706db4e6b8a27c3bdbdcf4336056f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Jun 2023 12:21:16 -0300 Subject: [PATCH 18/29] AI Extension: iterate over spelling and grammar prompt item (#31509) * iterate over spelling and gramma prompt item * changelog --- ...-assistant-iterate-over-spelling-and-grammar-prompt | 4 ++++ .../extensions/blocks/ai-assistant/lib/prompt/index.ts | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-assistant-iterate-over-spelling-and-grammar-prompt diff --git a/projects/plugins/jetpack/changelog/update-ai-assistant-iterate-over-spelling-and-grammar-prompt b/projects/plugins/jetpack/changelog/update-ai-assistant-iterate-over-spelling-and-grammar-prompt new file mode 100644 index 0000000000000..250c3346fb38a --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-assistant-iterate-over-spelling-and-grammar-prompt @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: iterate over spelling and grammar prompt item diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index 64163c1eb96be..d64ecf1660f8e 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -69,7 +69,7 @@ export function getInitialSystemPrompt( { Strictly follow these rules: ${ extraRules }- Format your responses in Markdown syntax, ready to be published. -- Execute the request without any acknowledgement to the user. +- Execute the request without any acknowledgment to the user. - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. - If you cannot generate a meaningful response to a user's request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. `; @@ -111,7 +111,7 @@ function getCorrectSpellingPrompt( { return [ { role, - content: `Repeat the following text, correcting any spelling and grammar mistakes, keeping the language of the text:\n\n${ content }`, + content: `Repeat the following text, correcting any spelling and grammar mistakes directly in the text without providing feedback about the corrections, keeping the language of the text: \n\n${ content }`, }, ]; } @@ -400,8 +400,8 @@ export function getPrompt( const { prevMessages } = options; const context = - 'You are an excellent polyglot ghostwriter. ' + - 'Your task is to help the user to create and modify content based on their requests.'; + 'You are an advanced polyglot ghostwriter.' + + 'Your task is to help the user create and modify content based on their requests.'; /* * Create the initial prompt only if there are no previous messages. @@ -413,7 +413,7 @@ export function getPrompt( role: 'system', content: `${ context } Writing rules: -- Execute the request without any acknowledgement to the user. +- Execute the request without any acknowledgment or explanation to the user. - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. - If you cannot generate a meaningful response to a user’s request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. `, From b127b8c627dc834dc3fa4025d1fb50ac6229ac05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donncha=20=C3=93=20Caoimh?= <5656673+donnchawp@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:05:22 +0100 Subject: [PATCH 19/29] WP Super Cache: Simplify the preload loop and improve stopping procedures (#31022) * Simplify the preload loop * changelog * Show the "next preload" message any time it's needed. Not just when it's close to that preload time. * Remove div tags, not needed * Use wpsc_is_preload_active() for stopping Add checks to that function for the stop_preload flag, and if the mutex file is missing then return false. * Simplify stopping the preload. Remove the "preload_cache_stop" flag. * No need for two "stop" flags to stop the preload. * Reset the settings immediately, for quicker shutdown. * Catch "stop preload" flag first, then (corrected) mutex check * This will abort the preload right at the start of the function, if it's cancelled. * Reset settings if preload cancelled, instead of scheduling new task * Fix translators explanation. * No need for $mutex here, only used once * Fix missing variable * Merge with changes in #31017 * Use wpsc_is_preload_active() for "Preload now" button check * Set idle status on cancel, and remove "almost cancelled" message. There's a check on the STOP condition in each loop of the preload, so it stops very quickly now. No configuration or temp files are updated. * Check the preload counter for the "post" loop as well * Fix typo in option name --------- Co-authored-by: Peter Petrov --- .../changelog/update-preload_simplification | 4 + .../plugins/super-cache/partials/preload.php | 4 +- projects/plugins/super-cache/wp-cache.php | 311 +++++++++++------- 3 files changed, 197 insertions(+), 122 deletions(-) create mode 100644 projects/plugins/super-cache/changelog/update-preload_simplification diff --git a/projects/plugins/super-cache/changelog/update-preload_simplification b/projects/plugins/super-cache/changelog/update-preload_simplification new file mode 100644 index 0000000000000..4babf8c11e7ce --- /dev/null +++ b/projects/plugins/super-cache/changelog/update-preload_simplification @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +WP Super Cache: simplify the preload loop and improve stopping procedures. diff --git a/projects/plugins/super-cache/partials/preload.php b/projects/plugins/super-cache/partials/preload.php index b28e2d86aa5e6..39735f91353b6 100644 --- a/projects/plugins/super-cache/partials/preload.php +++ b/projects/plugins/super-cache/partials/preload.php @@ -67,12 +67,10 @@ echo ''; echo ""; -$preload_counter = get_option( 'preload_cache_counter' ); if ( wp_next_scheduled( 'wp_cache_preload_hook' ) || wp_next_scheduled( 'wp_cache_full_preload_hook' ) - || ( is_array( $preload_counter ) && $preload_counter['c'] > 0 ) - || get_transient( 'taxonomy_preload' ) + || wpsc_is_preload_active() ) { $currently_preloading = true; } diff --git a/projects/plugins/super-cache/wp-cache.php b/projects/plugins/super-cache/wp-cache.php index 46e323c23de4e..d68609a92212c 100644 --- a/projects/plugins/super-cache/wp-cache.php +++ b/projects/plugins/super-cache/wp-cache.php @@ -97,6 +97,8 @@ function wpsc_init() { global $wp_cache_debug_log, $wp_cache_debug_ip, $wp_cache_debug_username, $wp_cache_debug_email; global $cache_time_interval, $cache_scheduled_time, $cache_schedule_interval, $cache_schedule_type, $cache_gc_email_me; global $wp_cache_preload_on, $wp_cache_preload_interval, $wp_cache_preload_posts, $wp_cache_preload_taxonomies; + +// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- these are used by various functions but the linter complains. global $wp_cache_preload_email_me, $wp_cache_preload_email_volume; global $wp_cache_mobile, $wp_cache_mobile_enabled, $wp_cache_mobile_browsers, $wp_cache_mobile_prefixes; global $wp_cache_config_file, $wp_cache_config_file_sample; @@ -3253,9 +3255,11 @@ function wpsc_update_idle_preload( $finish_time = null ) { function wp_cron_preload_cache() { global $wpdb, $wp_cache_preload_interval, $wp_cache_preload_posts, $wp_cache_preload_email_me, $wp_cache_preload_email_volume, $cache_path, $wp_cache_preload_taxonomies; - if ( get_option( 'preload_cache_stop' ) ) { - delete_option( 'preload_cache_stop' ); - wp_cache_debug( 'wp_cron_preload_cache: preload cancelled', 1 ); + // check if stop_preload.txt exists and preload should be stopped. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + if ( @file_exists( $cache_path . 'stop_preload.txt' ) ) { + wp_cache_debug( 'wp_cron_preload_cache: preload cancelled. Aborting preload.' ); + wpsc_reset_preload_settings(); return true; } @@ -3291,111 +3295,125 @@ function wp_cron_preload_cache() { $wp_cache_preload_email_me = 0; wp_cache_setting( 'wp_cache_preload_email_me', 0 ); } - if ( - $wp_cache_preload_email_me && - $c === 0 && - ! get_transient( 'taxonomy_preload' ) - ) { - wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Cache Preload Started', 'wp-super-cache' ), home_url(), '' ), ' ' ); - } + + $just_started_preloading = false; /* * Preload taxonomies first. * - * The transient variable "taxonomy_preload" is used as a flag to indicate - * that preloading of taxonomies is ongoing. It is deleted when the preload - * is complete or the site owner cancels the preload. - * If this flag is not set, and if a file containing a list of taxonomies - * exists from a previous preload exists, it will be deleted and a new list - * generated for a brand new preload. */ - if ( $wp_cache_preload_posts == 'all' || $c < $wp_cache_preload_posts ) { + if ( isset( $wp_cache_preload_taxonomies ) && $wp_cache_preload_taxonomies ) { wp_cache_debug( 'wp_cron_preload_cache: doing taxonomy preload.', 5 ); - if ( isset( $wp_cache_preload_taxonomies ) && $wp_cache_preload_taxonomies ) { - $taxonomies = apply_filters( 'wp_cache_preload_taxonomies', array( 'post_tag' => 'tag', 'category' => 'category' ) ); + $taxonomies = apply_filters( + 'wp_cache_preload_taxonomies', + array( + 'post_tag' => 'tag', + 'category' => 'category', + ) + ); - $preload_more_taxonomies = false; + $preload_more_taxonomies = false; - foreach( $taxonomies as $taxonomy => $path ) { - $taxonomy_filename = $cache_path . "taxonomy_" . $taxonomy . ".txt"; + foreach ( $taxonomies as $taxonomy => $path ) { + $taxonomy_filename = $cache_path . 'taxonomy_' . $taxonomy . '.txt'; - if ( ! get_transient( 'taxonomy_preload' ) ) { - @unlink( $taxonomy_filename ); + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + if ( false === @file_exists( $taxonomy_filename ) ) { + + if ( ! $just_started_preloading && $wp_cache_preload_email_me ) { + // translators: 1: site url + wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Cache Preload Started', 'wp-super-cache' ), home_url(), '' ), ' ' ); } - set_transient( 'taxonomy_preload', 1, 600 ); - if ( false == @file_exists( $taxonomy_filename ) ) { - $out = ''; - $records = get_terms( $taxonomy ); - foreach( $records as $term ) { - $out .= get_term_link( $term ). "\n"; - } - $fp = fopen( $taxonomy_filename, 'w' ); - if ( $fp ) { - fwrite( $fp, $out ); - fclose( $fp ); - } - $details = explode( "\n", $out ); - } else { - $details = explode( "\n", file_get_contents( $taxonomy_filename ) ); + $just_started_preloading = true; + $out = ''; + $records = get_terms( $taxonomy ); + foreach ( $records as $term ) { + $out .= get_term_link( $term ) . "\n"; + } + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen + $fp = fopen( $taxonomy_filename, 'w' ); + if ( $fp ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite + fwrite( $fp, $out ); + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose + fclose( $fp ); + } + $details = explode( "\n", $out ); + } else { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $details = explode( "\n", file_get_contents( $taxonomy_filename ) ); + } + if ( count( $details ) > 0 && $details[0] !== '' ) { + $rows = array_splice( $details, 0, WPSC_PRELOAD_POST_COUNT ); + if ( $wp_cache_preload_email_me && $wp_cache_preload_email_volume === 'many' ) { + // translators: 1: Site URL, 2: Taxonomy name, 3: Number of posts done, 4: Number of posts to preload + wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Refreshing %2$s taxonomy from %3$d to %4$d', 'wp-super-cache' ), home_url(), $taxonomy, $c, ( $c + WPSC_PRELOAD_POST_COUNT ) ), 'Refreshing: ' . print_r( $rows, 1 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r } - if ( count( $details ) > 0 && $details[0] !== '' ) { - $rows = array_splice( $details, 0, WPSC_PRELOAD_POST_COUNT ); - if ( $wp_cache_preload_email_me && $wp_cache_preload_email_volume === 'many' ) { - // translators: 1: Site URL, 2: Taxonomy name, 3: Number of posts done, 4: Number of posts to preload - wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Refreshing %2$s taxonomy from %3$d to %4$d', 'wp-super-cache' ), home_url(), $taxonomy, $c, ( $c + WPSC_PRELOAD_POST_COUNT ) ), 'Refreshing: ' . print_r( $rows, 1 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r + + foreach ( (array) $rows as $url ) { + set_time_limit( 60 ); + if ( $url === '' ) { + continue; } - foreach( (array)$rows as $url ) { - set_time_limit( 60 ); - if ( $url == '' ) - continue; - $url_info = parse_url( $url ); - $dir = get_supercache_dir() . $url_info[ 'path' ]; - wp_cache_debug( "wp_cron_preload_cache: delete $dir", 5 ); - wpsc_delete_files( $dir ); - prune_super_cache( trailingslashit( $dir ) . 'feed/', true ); - prune_super_cache( trailingslashit( $dir ) . 'page/', true ); - - wpsc_update_active_preload( 'taxonomies', $taxonomy, $url ); - - wp_remote_get( $url, array('timeout' => 60, 'blocking' => true ) ); - wp_cache_debug( "wp_cron_preload_cache: fetched $url", 5 ); - sleep( 1 ); - - if ( @file_exists( $cache_path . "stop_preload.txt" ) ) { - wp_cache_debug( 'wp_cron_preload_cache: cancelling preload. stop_preload.txt found.', 5 ); - wpsc_reset_preload_settings(); - - if ( $wp_cache_preload_email_me ) { - // translators: Home URL of website - wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Cache Preload Stopped', 'wp-super-cache' ), home_url(), '' ), ' ' ); - } - wpsc_update_idle_preload( time() ); - return true; + $url_info = wp_parse_url( $url ); + $dir = get_supercache_dir() . $url_info['path']; + wp_cache_debug( "wp_cron_preload_cache: delete $dir" ); + wpsc_delete_files( $dir ); + prune_super_cache( trailingslashit( $dir ) . 'feed/', true ); + prune_super_cache( trailingslashit( $dir ) . 'page/', true ); + + wpsc_update_active_preload( 'taxonomies', $taxonomy, $url ); + + wp_remote_get( + $url, + array( + 'timeout' => 60, + 'blocking' => true, + ) + ); + wp_cache_debug( "wp_cron_preload_cache: fetched $url" ); + sleep( 1 ); + + if ( ! wpsc_is_preload_active() ) { + wp_cache_debug( 'wp_cron_preload_cache: cancelling preload process.' ); + wpsc_reset_preload_settings(); + + if ( $wp_cache_preload_email_me ) { + // translators: Home URL of website + wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Cache Preload Stopped', 'wp-super-cache' ), home_url(), '' ), ' ' ); } - } - $fp = fopen( $taxonomy_filename, 'w' ); - if ( $fp ) { - fwrite( $fp, implode( "\n", $details ) ); - fclose( $fp ); + wpsc_update_idle_preload( time() ); + return true; } } - - if ( - $preload_more_taxonomies === false && - count( $details ) > 0 && - $details[0] !== '' - ) { - $preload_more_taxonomies = true; + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen + $fp = fopen( $taxonomy_filename, 'w' ); + if ( $fp ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite + fwrite( $fp, implode( "\n", $details ) ); + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose + fclose( $fp ); } } - if ( $preload_more_taxonomies === true ) { - wpsc_schedule_next_preload(); - return true; + if ( + $preload_more_taxonomies === false && + count( $details ) > 0 && + $details[0] !== '' + ) { + $preload_more_taxonomies = true; } } + + if ( $preload_more_taxonomies === true ) { + wpsc_schedule_next_preload(); + return true; + } + } elseif ( $c === 0 && $wp_cache_preload_email_me ) { + // translators: Home URL of website + wp_mail( get_option( 'admin_email' ), sprintf( __( '[%1$s] Cache Preload Started', 'wp-super-cache' ), home_url(), '' ), ' ' ); } /* @@ -3410,6 +3428,7 @@ function wp_cron_preload_cache() { * before it is incremented by WPSC_PRELOAD_POST_COUNT here. * The time is used to check if preloading has stalled in check_up_on_preloading(). */ + update_option( 'preload_cache_counter', array( @@ -3457,8 +3476,8 @@ function wp_cron_preload_cache() { wpsc_update_active_preload( 'posts', $count, $url ); - if ( @file_exists( $cache_path . "stop_preload.txt" ) ) { - wp_cache_debug( 'wp_cron_preload_cache: cancelling preload. stop_preload.txt found.', 5 ); + if ( ! wpsc_is_preload_active() ) { + wp_cache_debug( 'wp_cron_preload_cache: cancelling preload process.' ); wpsc_reset_preload_settings(); if ( $wp_cache_preload_email_me ) { @@ -3522,6 +3541,16 @@ function wp_cron_preload_cache() { function wpsc_schedule_next_preload() { global $cache_path; + /* + * Edge case: If preload is not active, don't schedule the next preload. + * This can happen if the preload is cancelled by the user right after a loop finishes. + */ + if ( ! wpsc_is_preload_active() ) { + wpsc_reset_preload_settings(); + wp_cache_debug( 'wpsc_schedule_next_preload: preload is not active. not scheduling next preload.' ); + return; + } + if ( defined( 'DOING_CRON' ) ) { wp_cache_debug( 'wp_cron_preload_cache: scheduling the next preload in 3 seconds.' ); wp_schedule_single_event( time() + 3, 'wp_cache_preload_hook' ); @@ -3662,6 +3691,50 @@ function supercache_admin_bar_render() { wpsc_admin_bar_render( $wp_admin_bar ); } +/* + * returns true if preload is active + */ +function wpsc_is_preload_active() { + global $cache_path; + + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + if ( @file_exists( $cache_path . 'stop_preload.txt' ) ) { + return false; + } + + if ( file_exists( $cache_path . 'preload_mutex.tmp' ) ) { + return true; + } + + // check taxonomy preload loop + $taxonomies = apply_filters( + 'wp_cache_preload_taxonomies', + array( + 'post_tag' => 'tag', + 'category' => 'category', + ) + ); + + foreach ( $taxonomies as $taxonomy => $path ) { + $taxonomy_filename = $cache_path . 'taxonomy_' . $taxonomy . '.txt'; + if ( file_exists( $taxonomy_filename ) ) { + return true; + } + } + + // check post preload loop + $preload_cache_counter = get_option( 'preload_cache_counter' ); + if ( + is_array( $preload_cache_counter ) + && isset( $preload_cache_counter['c'] ) + && $preload_cache_counter['c'] > 0 + ) { + return true; + } + + return false; +} + /* * This function will reset the preload cache counter */ @@ -3684,7 +3757,6 @@ function wpsc_reset_preload_settings() { $mutex = $cache_path . 'preload_mutex.tmp'; wp_delete_file( $mutex ); wp_delete_file( $cache_path . 'stop_preload.txt' ); - delete_transient( 'taxonomy_preload' ); wpsc_reset_preload_counter(); $taxonomies = apply_filters( @@ -3702,33 +3774,49 @@ function wpsc_reset_preload_settings() { } function wpsc_cancel_preload() { - global $cache_path; - $next_preload = wp_next_scheduled( 'wp_cache_preload_hook' ); + $next_preload = wp_next_scheduled( 'wp_cache_preload_hook' ); + $next_full_preload = wp_next_scheduled( 'wp_cache_full_preload_hook' ); + + if ( $next_preload || $next_full_preload ) { + wp_cache_debug( 'wpsc_cancel_preload: reset preload settings' ); + wpsc_reset_preload_settings(); + } + if ( $next_preload ) { - delete_transient( 'taxonomy_preload' ); wp_cache_debug( 'wpsc_cancel_preload: unscheduling wp_cache_preload_hook' ); - wpsc_reset_preload_counter(); wp_unschedule_event( $next_preload, 'wp_cache_preload_hook' ); } - $next_preload = wp_next_scheduled( 'wp_cache_full_preload_hook' ); - if ( $next_preload ) { - delete_transient( 'taxonomy_preload' ); - wpsc_reset_preload_counter(); + if ( $next_full_preload ) { wp_cache_debug( 'wpsc_cancel_preload: unscheduling wp_cache_full_preload_hook' ); wp_unschedule_event( $next_preload, 'wp_cache_full_preload_hook' ); } wp_cache_debug( 'wpsc_cancel_preload: creating stop_preload.txt' ); - $fp = @fopen( $cache_path . "stop_preload.txt", 'w' ); + + /* + * Reset the preload settings, but also create the stop_preload.txt file to + * prevent the preload from starting again. + * By creating the stop_preload.txt file, we can be sure the preload will cancel. + */ + wpsc_reset_preload_settings(); + wpsc_create_stop_preload_flag(); + wpsc_update_idle_preload( time() ); +} + +/* + * The preload process checks for a file called stop_preload.txt and will stop if found. + * This function creates that file. + */ +function wpsc_create_stop_preload_flag() { + global $cache_path; + // phpcs:ignore -- WordPress.WP.AlternativeFunctions.file_system_read_fopen WordPress.PHP.NoSilencedErrors.Discouraged + $fp = @fopen( $cache_path . 'stop_preload.txt', 'w' ); + // phpcs:ignore -- WordPress.WP.AlternativeFunctions.file_system_operations_fclose WordPress.PHP.NoSilencedErrors.Discouraged @fclose( $fp ); } function wpsc_enable_preload() { - global $cache_path; - delete_transient( 'taxonomy_preload' ); - @unlink( $cache_path . "preload_mutex.tmp" ); - wp_delete_file( $cache_path . 'stop_preload.txt' ); - wpsc_reset_preload_counter(); + wpsc_reset_preload_settings(); wp_schedule_single_event( time() + 10, 'wp_cache_full_preload_hook' ); } @@ -3764,20 +3852,6 @@ function wpsc_preload_settings( $min_refresh_interval = 'NA' ) { if ( isset( $_POST[ 'preload_off' ] ) ) { wpsc_cancel_preload(); - ?> -
-

- - - -

-
- Date: Thu, 22 Jun 2023 13:20:49 -0300 Subject: [PATCH 20/29] Fix send email preview endpoint (#31499) --------- Co-authored-by: Jeremy Herve --- ...trait-wpcom-rest-api-proxy-request-trait.php | 17 ++++++++++++++--- .../changelog/fix-send-email-preview-endpoint | 4 ++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/fix-send-email-preview-endpoint diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/trait-wpcom-rest-api-proxy-request-trait.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/trait-wpcom-rest-api-proxy-request-trait.php index 8d6d79d74fe3b..242c1568414de 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/trait-wpcom-rest-api-proxy-request-trait.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/trait-wpcom-rest-api-proxy-request-trait.php @@ -21,9 +21,20 @@ trait WPCOM_REST_API_Proxy_Request_Trait { * @return mixed|WP_Error Response from wpcom servers or an error. */ public function proxy_request_to_wpcom_as_user( $request, $path = '' ) { - $blog_id = \Jetpack_Options::get_option( 'id' ); - $path = '/sites/' . rawurldecode( $blog_id ) . rawurldecode( $this->rest_base ) . ( $path ? '/' . rawurldecode( $path ) : '' ); - $api_url = add_query_arg( $request->get_query_params(), $path ); + $blog_id = \Jetpack_Options::get_option( 'id' ); + $path = '/sites/' . rawurldecode( $blog_id ) . rawurldecode( $this->rest_base ) . ( $path ? '/' . rawurldecode( $path ) : '' ); + $query_params = $request->get_query_params(); + + /* + * A rest_route parameter can be added when using plain permalinks. + * It is not necessary to pass them to WordPress.com, + * and may even cause issues with some endpoints. + * Let's remove it. + */ + if ( isset( $query_params['rest_route'] ) ) { + unset( $query_params['rest_route'] ); + } + $api_url = add_query_arg( $query_params, $path ); $request_options = array( 'headers' => array( diff --git a/projects/plugins/jetpack/changelog/fix-send-email-preview-endpoint b/projects/plugins/jetpack/changelog/fix-send-email-preview-endpoint new file mode 100644 index 0000000000000..5a52482855b58 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-send-email-preview-endpoint @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix +Comment: fixes send preview email not working when using plain permalink + From d840a91ef9e657d4295f7b7b68fb62dce318abf8 Mon Sep 17 00:00:00 2001 From: Douglas Henri Date: Thu, 22 Jun 2023 15:31:34 -0300 Subject: [PATCH 21/29] AI Assistant: Add a specific delimiter for content in the prompts (#31515) * add a specific delimiter for the content in the prompts * changelog * remove delimitedContent prop --- .../update-ai-assistant-add-delimiter | 4 +++ .../blocks/ai-assistant/lib/prompt/index.ts | 36 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-assistant-add-delimiter diff --git a/projects/plugins/jetpack/changelog/update-ai-assistant-add-delimiter b/projects/plugins/jetpack/changelog/update-ai-assistant-add-delimiter new file mode 100644 index 0000000000000..b143409cb50c6 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-assistant-add-delimiter @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Assistant: Add a specific delimiter for content in the prompts diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index d64ecf1660f8e..dda8854603709 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -44,6 +44,8 @@ export type PromptItemProps = { const debug = debugFactory( 'jetpack-ai-assistant:prompt' ); +export const delimiter = '####'; + /** * Helper function to get the initial system prompt. * It defines the `context` value in case it isn't provided. @@ -104,6 +106,10 @@ type PromptOptionsProps = { prevMessages?: Array< PromptItemProps >; }; +function getDelimitedContent( content: string ): string { + return `${ delimiter }${ content.replaceAll( delimiter, '' ) }${ delimiter }`; +} + function getCorrectSpellingPrompt( { content, role = 'user', @@ -111,7 +117,9 @@ function getCorrectSpellingPrompt( { return [ { role, - content: `Repeat the following text, correcting any spelling and grammar mistakes directly in the text without providing feedback about the corrections, keeping the language of the text: \n\n${ content }`, + content: `Repeat the text delimited with ${ delimiter }, without the delimiter, correcting any spelling and grammar mistakes directly in the text without providing feedback about the corrections, keeping the language of the text: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -123,7 +131,9 @@ function getSimplifyPrompt( { return [ { role, - content: `Simplify the following text, using words and phrases that are easier to understand and keeping the language of the text:\n\n${ content }`, + content: `Simplify the text delimited with ${ delimiter }, using words and phrases that are easier to understand and keeping the language of the text: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -135,7 +145,9 @@ function getSummarizePrompt( { return [ { role, - content: `Summarize the following text, keeping the language of the text:\n\n${ content }`, + content: `Summarize the text delimited with ${ delimiter }, keeping the language of the text: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -147,7 +159,9 @@ function getExpandPrompt( { return [ { role, - content: `Expand the following text to about double its size, keeping the language of the text:\n\n${ content }`, + content: `Expand the text delimited with ${ delimiter } to about double its size, keeping the language of the text: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -160,7 +174,9 @@ function getTranslatePrompt( { return [ { role, - content: `Translate the following text to ${ language }, preserving the same core meaning and tone:\n\n${ content }`, + content: `Translate the text delimited with ${ delimiter } to ${ language }, preserving the same core meaning and tone: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -173,7 +189,9 @@ function getTonePrompt( { return [ { role, - content: `Rewrite the following text with a ${ tone } tone, keeping the language of the text:\n\n${ content }`, + content: `Rewrite the text delimited with ${ delimiter }, with a ${ tone } tone, keeping the language of the text: ${ getDelimitedContent( + content + ) }`, }, ]; } @@ -218,10 +236,12 @@ export const buildPromptTemplate = ( { const messages = [ getInitialSystemPrompt( { rules } ) ]; if ( relevantContent != null && relevantContent?.length ) { + const sanitizedContent = relevantContent.replaceAll( delimiter, '' ); + if ( ! isContentGenerated ) { messages.push( { - role: 'system', - content: `The specific relevant content for this request, if necessary: ${ relevantContent }`, + role: 'user', + content: `The specific relevant content for this request, if necessary, delimited with ${ delimiter } characters: ${ delimiter }${ sanitizedContent }${ delimiter }`, } ); } } From 14bb293924488207b98ab354447099ca66af2883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= Date: Thu, 22 Jun 2023 15:31:57 -0300 Subject: [PATCH 22/29] AI Extension: reorganize prompt items for AI extension (#31514) * put system request just before the last user one * changelog * do not store the `system` role item * Update projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx Co-authored-by: Douglas Henri --------- Co-authored-by: Douglas Henri --- ...date-ai-extension-re-organize-prompt-items | 4 ++ .../ai-assistant/with-ai-assistant.tsx | 39 ++++++---------- .../blocks/ai-assistant/lib/prompt/index.ts | 44 +++++++++---------- 3 files changed, 38 insertions(+), 49 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-extension-re-organize-prompt-items diff --git a/projects/plugins/jetpack/changelog/update-ai-extension-re-organize-prompt-items b/projects/plugins/jetpack/changelog/update-ai-extension-re-organize-prompt-items new file mode 100644 index 0000000000000..5eda444f583c4 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-extension-re-organize-prompt-items @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: reorganize prompt items for the AI extension diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index f05bd7685abb6..15973249da2bb 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -72,31 +72,22 @@ export const withAIAssistant = createHigherOrderComponent( [ removeBlocks, updateBlockAttributes ] ); - const addAssistantMessage = useCallback( + const updateStoredPrompt = useCallback( ( assistantContent: string ) => { setStoredPrompt( prevPrompt => { - /* - * Add the assistant messages to the prompt. - * - Preserve the first item of the array (`system` role ) - * - Keep the last 4 messages. - */ - - // Pick the first item of the array. - const firstItem = prevPrompt.messages.shift(); - const messages: Array< PromptItemProps > = [ - firstItem, // first item (`system` by default) - ...prevPrompt.messages.splice( -3 ), // last 3 items + /* + * Do not store `system` role items, + * and preserve the last 3 ones. + */ + ...prevPrompt.messages.filter( message => message.role !== 'system' ).slice( -3 ), { role: 'assistant', content: assistantContent, // + 1 `assistant` role item }, ]; - return { - ...prevPrompt, - messages, - }; + return { ...prevPrompt, messages }; } ); }, [ setStoredPrompt ] @@ -105,7 +96,7 @@ export const withAIAssistant = createHigherOrderComponent( const { request } = useSuggestionsFromAI( { prompt: storedPrompt.messages, onSuggestion: setContent, - onDone: addAssistantMessage, + onDone: updateStoredPrompt, autoRequest: false, } ); @@ -121,15 +112,13 @@ export const withAIAssistant = createHigherOrderComponent( clientIdsRef.current = clientIds; setStoredPrompt( prevPrompt => { - const freshPrompt = { - ...prevPrompt, - messages: getPrompt( promptType, { - ...options, - content, - prevMessages: prevPrompt.messages, - } ), - }; + const messages = getPrompt( promptType, { + ...options, + content, + prevMessages: prevPrompt.messages, + } ); + const freshPrompt = { ...prevPrompt, messages }; // Request the suggestion from the AI. request( freshPrompt.messages ); diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index dda8854603709..9ab19f63b6ee8 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -423,48 +423,44 @@ export function getPrompt( 'You are an advanced polyglot ghostwriter.' + 'Your task is to help the user create and modify content based on their requests.'; - /* - * Create the initial prompt only if there are no previous messages. - * Otherwise, let's use the previous messages as the initial prompt. - */ - let prompt: Array< PromptItemProps > = ! prevMessages?.length - ? [ - { - role: 'system', - content: `${ context } + const systemPrompt: PromptItemProps = { + role: 'system', + content: `${ context } Writing rules: - Execute the request without any acknowledgment or explanation to the user. - Avoid sensitive or controversial topics and ensure your responses are grammatically correct and coherent. - If you cannot generate a meaningful response to a user’s request, reply with “__JETPACK_AI_ERROR__“. This term should only be used in this context, it is used to generate user facing errors. `, - }, - ] - : prevMessages; + }; + + // Prompt starts with the previous messages, if any. + const prompt: Array< PromptItemProps > = prevMessages; + // Then, add the `system` prompt to clarify the context. + prompt.push( systemPrompt ); + + // Finally, add the current `user` request. switch ( type ) { case PROMPT_TYPE_CORRECT_SPELLING: - prompt = [ ...prompt, ...getCorrectSpellingPrompt( options ) ]; - break; + return [ ...prompt, ...getCorrectSpellingPrompt( options ) ]; case PROMPT_TYPE_SIMPLIFY: - prompt = [ ...prompt, ...getSimplifyPrompt( options ) ]; - break; + return [ ...prompt, ...getSimplifyPrompt( options ) ]; case PROMPT_TYPE_SUMMARIZE: - prompt = [ ...prompt, ...getSummarizePrompt( options ) ]; - break; + return [ ...prompt, ...getSummarizePrompt( options ) ]; case PROMPT_TYPE_MAKE_LONGER: - prompt = [ ...prompt, ...getExpandPrompt( options ) ]; - break; + return [ ...prompt, ...getExpandPrompt( options ) ]; case PROMPT_TYPE_CHANGE_LANGUAGE: - prompt = [ ...prompt, ...getTranslatePrompt( options ) ]; - break; + return [ ...prompt, ...getTranslatePrompt( options ) ]; case PROMPT_TYPE_CHANGE_TONE: - prompt = [ ...prompt, ...getTonePrompt( options ) ]; - break; + return [ ...prompt, ...getTonePrompt( options ) ]; + + default: + throw new Error( `Unknown prompt type: ${ type }` ); } return prompt; From d159e0ce6fadcb49923b555c770cd115efaef6e8 Mon Sep 17 00:00:00 2001 From: Luiz Kowalski Date: Thu, 22 Jun 2023 16:40:27 -0300 Subject: [PATCH 23/29] AI Extension: handle errors from extended block actions (#31497) * Add support for handling EventSource errors and dispatching it if possible * Add changelog file * Add error handler to set the error on a state variable * Add first version of the Notice to be showed on the extended blocks * Use the ErrorNotice component to show the current error message, if set * Avoid arrow function on the onRemove prop value * Simplify error messages by using native notice dispatcher * Remove unnecessary component * Add external source for error messages and codes * Drop the error messages refactor to keep things simple for now --- .../changelog/add-ai-extension-handle-errors | 4 + .../ai-assistant/with-ai-assistant.tsx | 14 +++- .../hooks/use-suggestions-from-ai/index.ts | 82 ++++++++++++++++++- 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/add-ai-extension-handle-errors diff --git a/projects/plugins/jetpack/changelog/add-ai-extension-handle-errors b/projects/plugins/jetpack/changelog/add-ai-extension-handle-errors new file mode 100644 index 0000000000000..04d8e4b512131 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-ai-extension-handle-errors @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Extension: Handle errors from extended blocks actions. diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx index 15973249da2bb..3e2ad257748bc 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/ai-assistant/with-ai-assistant.tsx @@ -6,6 +6,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; import { useCallback, useState, useRef } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; import React from 'react'; /** * Internal dependencies @@ -13,7 +14,7 @@ import React from 'react'; import AiAssistantDropdown, { AiAssistantDropdownOnChangeOptionsArgProps, } from '../../components/ai-assistant-controls'; -import useSuggestionsFromAI from '../../hooks/use-suggestions-from-ai'; +import useSuggestionsFromAI, { SuggestionError } from '../../hooks/use-suggestions-from-ai'; import { getPrompt } from '../../lib/prompt'; import { getTextContentFromBlocks } from '../../lib/utils/block-content'; /* @@ -38,6 +39,16 @@ export const withAIAssistant = createHigherOrderComponent( const clientIdsRef = useRef< Array< string > >(); const { updateBlockAttributes, removeBlocks } = useDispatch( blockEditorStore ); + const { createNotice } = useDispatch( noticesStore ); + + const showSuggestionError = useCallback( + ( suggestionError: SuggestionError ) => { + createNotice( suggestionError.status, suggestionError.message, { + isDismissible: true, + } ); + }, + [ createNotice ] + ); /** * Set the content of the block. @@ -97,6 +108,7 @@ export const withAIAssistant = createHigherOrderComponent( prompt: storedPrompt.messages, onSuggestion: setContent, onDone: updateStoredPrompt, + onError: showSuggestionError, autoRequest: false, } ); diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts index 951dd2f608a91..92baae8b34b47 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts @@ -4,6 +4,7 @@ import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { useCallback, useEffect, useRef } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; import debugFactory from 'debug'; /** * Types @@ -13,6 +14,23 @@ import type { PromptItemProps } from '../../lib/prompt'; const debug = debugFactory( 'jetpack-ai-assistant:prompt' ); +export type SuggestionError = { + /* + * A string code to refer to the error. + */ + code: string; + + /* + * The user-friendly error message. + */ + message: string; + + /* + * The type of the error. + */ + status: 'info' | 'error'; +}; + type UseSuggestionsFromAIOptions = { /* * Request prompt. @@ -33,6 +51,11 @@ type UseSuggestionsFromAIOptions = { * onDone callback. */ onDone?: ( content: string ) => void; + + /* + * onError callback. + */ + onError?: ( error: SuggestionError ) => void; }; type useSuggestionsFromAIProps = { @@ -69,6 +92,7 @@ export default function useSuggestionsFromAI( { autoRequest = true, onSuggestion, onDone, + onError, }: UseSuggestionsFromAIOptions ): useSuggestionsFromAIProps { // Collect data const { postId, postTitle } = useSelect( select => { @@ -125,12 +149,68 @@ export default function useSuggestionsFromAI( { if ( onDone ) { source?.current?.addEventListener( 'done', handleDone ); } + + if ( onError ) { + source?.current?.addEventListener( 'error_quota_exceeded', () => { + source?.current?.close(); + onError( { + code: 'error_quota_exceeded', + message: __( 'You have reached the limit of requests for this site.', 'jetpack' ), + status: 'info', + } ); + } ); + + source?.current?.addEventListener( 'error_unclear_prompt', () => { + source?.current?.close(); + onError( { + code: 'error_unclear_prompt', + message: __( 'Your request was unclear. Mind trying again?', 'jetpack' ), + status: 'info', + } ); + } ); + + source?.current?.addEventListener( 'error_service_unavailable', () => { + source?.current?.close(); + onError( { + code: 'error_service_unavailable', + message: __( + 'Jetpack AI services are currently unavailable. Sorry for the inconvenience.', + 'jetpack' + ), + status: 'info', + } ); + } ); + + source?.current?.addEventListener( 'error_moderation', () => { + source?.current?.close(); + onError( { + code: 'error_moderation', + message: __( + 'This request has been flagged by our moderation system. Please try to rephrase it and try again.', + 'jetpack' + ), + status: 'info', + } ); + } ); + + source?.current?.addEventListener( 'error_network', () => { + source?.current?.close(); + onError( { + code: 'error_network', + message: __( + 'It was not possible to process your request. Mind trying again?', + 'jetpack' + ), + status: 'info', + } ); + } ); + } } catch ( e ) { // eslint-disable-next-line no-console console.error( e ); } }, - [ postId, onSuggestion, onDone, handleSuggestion, handleDone ] + [ postId, onSuggestion, onDone, onError, handleSuggestion, handleDone ] ); // Request suggestions automatically when ready. From 2565f795fb9b7a6f0f8e12bc3320c2fc61aec7e3 Mon Sep 17 00:00:00 2001 From: Douglas Henri Date: Thu, 22 Jun 2023 17:32:35 -0300 Subject: [PATCH 24/29] AI Assistant: Change delimiter and remove it from responses (#31518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix jetpack ai error check * change delimiter * remove delimiter from response * fix typo in array name * changelog * remove the delimiter at the beginning and end * remove delimiter... --------- Co-authored-by: Damián Suárez --- .../update-ai-assistant-delimiter-check | 4 +++ .../hooks/use-suggestions-from-ai/index.ts | 23 ++++++++++++-- .../use-suggestions-from-openai/index.js | 30 +++++++++++-------- .../blocks/ai-assistant/lib/prompt/index.ts | 2 +- .../ai-assistant/lib/suggestions/index.js | 2 +- 5 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-assistant-delimiter-check diff --git a/projects/plugins/jetpack/changelog/update-ai-assistant-delimiter-check b/projects/plugins/jetpack/changelog/update-ai-assistant-delimiter-check new file mode 100644 index 0000000000000..a82b4f7fad6aa --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-assistant-delimiter-check @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +AI Assistant: Change delimiter and remove it from responses diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts index 92baae8b34b47..8172d6655cef6 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-ai/index.ts @@ -9,8 +9,8 @@ import debugFactory from 'debug'; /** * Types */ +import { PromptItemProps, delimiter } from '../../lib/prompt'; import { SuggestionsEventSource, askQuestion } from '../../lib/suggestions'; -import type { PromptItemProps } from '../../lib/prompt'; const debug = debugFactory( 'jetpack-ai-assistant:prompt' ); @@ -112,7 +112,14 @@ export default function useSuggestionsFromAI( { * @returns {void} */ const handleSuggestion = useCallback( - ( event: CustomEvent ) => onSuggestion( event?.detail ), + ( event: CustomEvent ) => { + /* + * Remove the delimiter string from the suggestion, + * only at the beginning and end of the string. + */ + const delimiterRegEx = new RegExp( `^${ delimiter }|${ delimiter }$`, 'g' ); + onSuggestion( event?.detail?.replace( delimiterRegEx, '' ) ); + }, [ onSuggestion ] ); @@ -122,7 +129,17 @@ export default function useSuggestionsFromAI( { * @param {string} content - The content. * @returns {void} */ - const handleDone = useCallback( ( event: CustomEvent ) => onDone( event?.detail ), [ onDone ] ); + const handleDone = useCallback( + ( event: CustomEvent ) => { + /* + * Remove the delimiter string from the suggestion, + * only at the beginning and end of the string. + */ + const delimiterRegEx = new RegExp( `^${ delimiter }|${ delimiter }$`, 'g' ); + onDone( event?.detail?.replace( delimiterRegEx, '' ) ); + }, + [ onDone ] + ); /** * Request handler. diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-openai/index.js b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-openai/index.js index c372682d8421b..57a54e2c161ea 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-openai/index.js +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/hooks/use-suggestions-from-openai/index.js @@ -10,7 +10,7 @@ import debugFactory from 'debug'; * Internal dependencies */ import { DEFAULT_PROMPT_TONE } from '../../components/tone-dropdown-control'; -import { buildPromptForBlock } from '../../lib/prompt'; +import { buildPromptForBlock, delimiter } from '../../lib/prompt'; import { askJetpack, askQuestion } from '../../lib/suggestions'; import { getContentFromBlocks, getPartialContentToBlock } from '../../lib/utils/block-content'; @@ -134,7 +134,7 @@ const useSuggestionsFromOpenAI = ( { } ); // Create a copy of the messages. - const updatedMessaages = [ ...attributes.messages ] ?? []; + const updatedMessages = [ ...attributes.messages ] ?? []; let lastUserPrompt = {}; @@ -158,7 +158,7 @@ const useSuggestionsFromOpenAI = ( { lastUserPrompt = prompt.pop(); // Populate prompt with the messages. - prompt = [ ...prompt, ...updatedMessaages ]; + prompt = [ ...prompt, ...updatedMessages ]; // Restore the last user prompt. prompt.push( lastUserPrompt ); @@ -202,14 +202,17 @@ const useSuggestionsFromOpenAI = ( { } source?.current?.addEventListener( 'done', e => { - const { detail: assistantResponse } = e; + const { detail } = e; + + // Remove the delimiter from the suggestion. + const assistantResponse = detail.replaceAll( delimiter, '' ); // Populate the messages with the assistant response. const lastAssistantPrompt = { role: 'assistant', content: assistantResponse, }; - updatedMessaages.push( lastUserPrompt, lastAssistantPrompt ); + updatedMessages.push( lastUserPrompt, lastAssistantPrompt ); debugPrompt( 'Add %o\n%s', `[${ lastUserPrompt.role }]`, lastUserPrompt.content ); debugPrompt( 'Add %o\n%s', `[${ lastAssistantPrompt.role }]`, lastAssistantPrompt.content ); @@ -218,15 +221,15 @@ const useSuggestionsFromOpenAI = ( { * Limit the messages to 20 items. * @todo: limit the prompt based on tokens. */ - if ( updatedMessaages.length > 20 ) { - updatedMessaages.splice( 0, updatedMessaages.length - 20 ); + if ( updatedMessages.length > 20 ) { + updatedMessages.splice( 0, updatedMessages.length - 20 ); } stopSuggestion(); updateBlockAttributes( clientId, { content: assistantResponse, - messages: updatedMessaages, + messages: updatedMessages, } ); refreshFeatureData(); } ); @@ -252,9 +255,9 @@ const useSuggestionsFromOpenAI = ( { * Let's clean up the messages array and try again. * @todo: improve the process based on tokens / URL length. */ - updatedMessaages.splice( 0, 8 ); + updatedMessages.splice( 0, 8 ); updateBlockAttributes( clientId, { - messages: updatedMessaages, + messages: updatedMessages, } ); /* @@ -272,7 +275,7 @@ const useSuggestionsFromOpenAI = ( { isGeneratingTitle: attributes.promptType === 'generateTitle', } ); - setLastPrompt( [ ...prompt, ...updatedMessaages, lastUserPrompt ] ); + setLastPrompt( [ ...prompt, ...updatedMessages, lastUserPrompt ] ); } source?.current?.close(); @@ -331,8 +334,9 @@ const useSuggestionsFromOpenAI = ( { source?.current?.addEventListener( 'suggestion', e => { setWasCompletionJustRequested( false ); - debug( '(suggestion)', e.detail ); - updateBlockAttributes( clientId, { content: e.detail } ); + debug( '(suggestion)', e?.detail ); + // Remove the delimiter from the suggestion and update the block. + updateBlockAttributes( clientId, { content: e?.detail?.replaceAll( delimiter, '' ) } ); } ); return source?.current; }; diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts index 9ab19f63b6ee8..413a187b798e0 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/prompt/index.ts @@ -44,7 +44,7 @@ export type PromptItemProps = { const debug = debugFactory( 'jetpack-ai-assistant:prompt' ); -export const delimiter = '####'; +export const delimiter = '````'; /** * Helper function to get the initial system prompt. diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/suggestions/index.js b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/suggestions/index.js index 260922776172d..d7d99e9b58877 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/suggestions/index.js +++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/lib/suggestions/index.js @@ -167,7 +167,7 @@ export class SuggestionsEventSource extends EventTarget { * - the doouble asterisks (bold markdown) */ const replacedMessage = this.fullMessage.replace( /__|(\*\*)/g, '' ); - if ( replacedMessage === 'JETPACK_AI_ERROR' ) { + if ( replacedMessage.startsWith( 'JETPACK_AI_ERROR' ) ) { // The unclear prompt marker was found, so we dispatch an error event this.dispatchEvent( new CustomEvent( 'error_unclear_prompt' ) ); } else if ( 'JETPACK_AI_ERROR'.startsWith( replacedMessage ) ) { From f2297a5b8491219be670fe1c664b2bc88ac0b524 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 22 Jun 2023 17:15:30 -0400 Subject: [PATCH 25/29] ci: Fix renovate (#31528) Apparently the new version of the action requires a new parameter to work in our setup. --- .github/workflows/renovate.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 28b526f6c7f99..b2cb62564105c 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -41,6 +41,7 @@ jobs: with: configurationFile: /tmp/monorepo/.github/renovate-config.js token: ${{ secrets.RENOVATE_TOKEN }} + mount-docker-socket: true renovate-version: 35.140.0 env: LOG_LEVEL: ${{ github.event.inputs.logLevel || 'debug' }} From 253bcb09e87a2d1fb70cac0e5086f3c2901e3739 Mon Sep 17 00:00:00 2001 From: Dognose Date: Fri, 23 Jun 2023 06:58:50 +0800 Subject: [PATCH 26/29] Component: Add Pricing Slider component (#31425) Co-authored-by: Jasper Kang --- .pnpmfile.cjs | 10 ++++ pnpm-lock.yaml | 31 +++++++++- .../update-stats_component_pricing_slider | 4 ++ .../components/pricing-slider/index.tsx | 53 +++++++++++++++++ .../pricing-slider/stories/index.stories.tsx | 44 ++++++++++++++ .../components/pricing-slider/style.scss | 55 ++++++++++++++++++ .../pricing-slider/test/component.tsx | 14 +++++ .../components/pricing-slider/types.ts | 58 +++++++++++++++++++ projects/js-packages/components/index.ts | 1 + projects/js-packages/components/package.json | 8 ++- 10 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 projects/js-packages/components/changelog/update-stats_component_pricing_slider create mode 100644 projects/js-packages/components/components/pricing-slider/index.tsx create mode 100644 projects/js-packages/components/components/pricing-slider/stories/index.stories.tsx create mode 100644 projects/js-packages/components/components/pricing-slider/style.scss create mode 100644 projects/js-packages/components/components/pricing-slider/test/component.tsx create mode 100644 projects/js-packages/components/components/pricing-slider/types.ts diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 55bd248d46c98..b7d1f6b24d46b 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -106,6 +106,16 @@ function fixDeps( pkg ) { pkg.dependencies.svelte2tsx = '^0.6.10'; } + // Missing dep or peer dep on @babel/runtime + // https://github.com/zillow/react-slider/issues/296 + if ( + pkg.name === 'react-slider' && + ! pkg.dependencies?.[ '@babel/runtime' ] && + ! pkg.peerDependencies?.[ '@babel/runtime' ] + ) { + pkg.peerDependencies[ '@babel/runtime' ] = '^7'; + } + return pkg; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a29283e02cc38..69fd77e58898b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,6 +207,9 @@ importers: '@automattic/format-currency': specifier: 1.0.1 version: 1.0.1 + '@babel/runtime': + specifier: ^7 + version: 7.21.5 '@wordpress/browserslist-config': specifier: 5.18.0 version: 5.18.0 @@ -237,6 +240,9 @@ importers: qrcode.react: specifier: 3.1.0 version: 3.1.0(react@18.2.0) + react-slider: + specifier: 2.0.5 + version: 2.0.5(@babel/runtime@7.21.5)(react@18.2.0) devDependencies: '@automattic/jetpack-base-styles': specifier: workspace:* @@ -280,6 +286,9 @@ importers: '@types/react-dom': specifier: 18.0.10 version: 18.0.10 + '@types/react-slider': + specifier: 1.3.1 + version: 1.3.1 '@types/react-test-renderer': specifier: 18.0.0 version: 18.0.0 @@ -304,6 +313,9 @@ importers: require-from-string: specifier: 2.0.2 version: 2.0.2 + resize-observer-polyfill: + specifier: 1.5.1 + version: 1.5.1 typescript: specifier: 5.0.4 version: 5.0.4 @@ -5438,6 +5450,7 @@ packages: /@colors/colors@1.5.0: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + requiresBuild: true dev: true /@dabh/diagnostics@2.0.3: @@ -9927,6 +9940,12 @@ packages: redux: 4.1.1 dev: false + /@types/react-slider@1.3.1: + resolution: {integrity: sha512-4X2yK7RyCIy643YCFL+bc6XNmcnBtt8n88uuyihvcn5G7Lut23eNQU3q3KmwF7MWIfKfsW5NxCjw0SeDZRtgaA==} + dependencies: + '@types/react': 18.0.27 + dev: true + /@types/react-test-renderer@18.0.0: resolution: {integrity: sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==} dependencies: @@ -20756,6 +20775,17 @@ packages: react-is: 18.2.0 dev: true + /react-slider@2.0.5(@babel/runtime@7.21.5)(react@18.2.0): + resolution: {integrity: sha512-MU5gaK1yYCKnbDDN3CMiVcgkKZwMvdqK2xUEW7fFU37NAzRgS1FZbF9N7vP08E3XXNVhiuZnwVzUa3PYQAZIMg==} + peerDependencies: + '@babel/runtime': ^7 + react: ^16 || ^17 || ^18 + dependencies: + '@babel/runtime': 7.21.5 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /react-style-singleton@2.2.1(@types/react@18.0.27)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -21173,7 +21203,6 @@ packages: /resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - dev: false /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} diff --git a/projects/js-packages/components/changelog/update-stats_component_pricing_slider b/projects/js-packages/components/changelog/update-stats_component_pricing_slider new file mode 100644 index 0000000000000..dd01218b0d59c --- /dev/null +++ b/projects/js-packages/components/changelog/update-stats_component_pricing_slider @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add component Pricing Slider for Stat pricing page. diff --git a/projects/js-packages/components/components/pricing-slider/index.tsx b/projects/js-packages/components/components/pricing-slider/index.tsx new file mode 100644 index 0000000000000..c1c9b6617c23f --- /dev/null +++ b/projects/js-packages/components/components/pricing-slider/index.tsx @@ -0,0 +1,53 @@ +import classNames from 'classnames'; +import React from 'react'; +import ReactSlider from 'react-slider'; +import { PricingSliderProps } from './types'; +import './style.scss'; + +/** + * Generate Pricing Slider + * More support from the original ReactSlider component: https://zillow.github.io/react-slider/ + * + * @param {PricingSliderProps} props - Props + * @returns {React.ReactElement} - JSX element + */ +const PricingSlider: React.FC< PricingSliderProps > = ( { + className, + maxValue = 100, + minValue = 0, + step = 1, + value, + onChange, + onBeforeChange, + onAfterChange, + renderThumb, +} ) => { + const componentClassName = classNames( 'jp-components-pricing-slider', className ); + + const renderThumbCallback = renderThumb + ? renderThumb + : ( props, state ) => { + return
{ state.valueNow }
; + }; + + return ( +
+ +
+ ); +}; + +export default PricingSlider; diff --git a/projects/js-packages/components/components/pricing-slider/stories/index.stories.tsx b/projects/js-packages/components/components/pricing-slider/stories/index.stories.tsx new file mode 100644 index 0000000000000..856b0039babaa --- /dev/null +++ b/projects/js-packages/components/components/pricing-slider/stories/index.stories.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; +import PricingSlider from '../index'; +import type { ComponentStory, ComponentMeta } from '@storybook/react'; + +export default { + title: 'JS Packages/Components/Pricing Slider', + component: PricingSlider, +} as ComponentMeta< typeof PricingSlider >; + +// Export additional stories using pre-defined values +const Template: ComponentStory< typeof PricingSlider > = args => ; + +// Export Default story +export const _default = Template.bind( {} ); + +// Export additional stories using chaning values +const TemplateWithChangingValue: ComponentStory< typeof PricingSlider > = args => { + const [ value, setValue ] = useState( 10 ); + const [ endValue, setEndValue ] = useState( 10 ); + const renderThumb = ( props, state ) => { + return ( +
+ { state.valueNow } - { state.valueNow % 2 === 0 ? 'Even' : 'Odd' } +
+ ); + }; + + return ( +
+ +
{ `Value on changing: ${ value }` }
+
{ `Value on change ends: ${ endValue }` }
+
+ ); +}; + +// Export With Default Value story +export const WithDefaultValue = TemplateWithChangingValue.bind( {} ); diff --git a/projects/js-packages/components/components/pricing-slider/style.scss b/projects/js-packages/components/components/pricing-slider/style.scss new file mode 100644 index 0000000000000..b860d99db7544 --- /dev/null +++ b/projects/js-packages/components/components/pricing-slider/style.scss @@ -0,0 +1,55 @@ +@import '@automattic/jetpack-base-styles/style'; + +$thumb-height: 40px; +$track-height: 8px; + +@mixin adjust-track-rail-styles { + height: $track-height; + border-radius: 8px; /* stylelint-disable-line scales/radii */ +} + +// Base styles +.jp-components-pricing-slider__control { + width: 100%; + height: $thumb-height; +} + +.jp-components-pricing-slider__track { + @include adjust-track-rail-styles; + top: calc(($thumb-height / 2) - ($track-height / 2)); + background: var(--jp-gray); + + &.jp-components-pricing-slider__track-0 { + background: var(--jp-green-40); + } + + &.jp-components-pricing-slider__track-1 { + background: var(--jp-gray); + } +} + +.jp-components-pricing-slider__thumb { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + height: $thumb-height; + background-color: var(--jp-white); + border: 1px solid var(--jp-gray); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 6px 8px rgba(0, 0, 0, 0.08); + border-radius: 4px; + color: var(--jp-black); + font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif; + font-style: normal; + font-weight: 600; + font-size: var(--font-body); + line-height: 24px; + letter-spacing: -0.02em; + white-space: nowrap; + cursor: pointer; + + &.jp-components-pricing-slider__thumb--is-active { + // On focus styling + outline: none; + } +} diff --git a/projects/js-packages/components/components/pricing-slider/test/component.tsx b/projects/js-packages/components/components/pricing-slider/test/component.tsx new file mode 100644 index 0000000000000..abf1098c46d18 --- /dev/null +++ b/projects/js-packages/components/components/pricing-slider/test/component.tsx @@ -0,0 +1,14 @@ +import { render, screen } from '@testing-library/react'; +import ResizeObserver from 'resize-observer-polyfill'; +import PricingSlider from '../index'; + +describe( 'PricingSlider', () => { + beforeAll( () => { + global.ResizeObserver = ResizeObserver; + } ); + + it( 'renders the pricing slider', () => { + render( ); + expect( screen.getByTestId( 'pricing-slider' ) ).toBeInTheDocument(); + } ); +} ); diff --git a/projects/js-packages/components/components/pricing-slider/types.ts b/projects/js-packages/components/components/pricing-slider/types.ts new file mode 100644 index 0000000000000..b9d4220102093 --- /dev/null +++ b/projects/js-packages/components/components/pricing-slider/types.ts @@ -0,0 +1,58 @@ +import type { HTMLProps, RefCallback } from 'react'; + +interface HTMLPropsWithRefCallback< T > extends HTMLProps< T > { + ref: RefCallback< T >; +} + +export type PricingSliderProps = { + /** + * The wrapper class name of this PricingSlider component. + */ + className?: string; + + /** + * The maximum value of the slider. + */ + maxValue?: number; + + /** + * The minimum value of the slider. + */ + minValue?: number; + + /** + * The initial value of the slider. + */ + value?: number; + + /** + * The step value of the slider. + */ + step?: number; + + /** + * Callback called on every value change. + * The function will be called with two arguments, the first being the new value(s) the second being thumb index. + */ + onChange?: ( value: number ) => void; + + /** + * Callback called before starting to move a thumb. The callback will only be called if the action will result in a change. + * The function will be called with two arguments, the first being the initial value(s) the second being thumb index. + */ + onBeforeChange?: ( value: number ) => void; + + /** + * Callback called only after moving a thumb has ended. The callback will only be called if the action resulted in a change. + * The function will be called with two arguments, the first being the result value(s) the second being thumb index. + */ + onAfterChange?: ( value: number ) => void; + + /** + * Node to render on the slider. + */ + renderThumb?: ( + props: HTMLPropsWithRefCallback< HTMLDivElement >, + state: { index: number; value: number | ReadonlyArray< number >; valueNow: number } + ) => JSX.Element | null; +}; diff --git a/projects/js-packages/components/index.ts b/projects/js-packages/components/index.ts index c24e60ced8e25..702f94b57eb74 100644 --- a/projects/js-packages/components/index.ts +++ b/projects/js-packages/components/index.ts @@ -28,6 +28,7 @@ export { default as Gridicon } from './components/gridicon'; export { default as IconTooltip } from './components/icon-tooltip'; export { default as ActionButton } from './components/action-button'; export { default as PricingCard } from './components/pricing-card'; +export { default as PricingSlider } from './components/pricing-slider'; export { default as AdminSection } from './components/admin-section/basic'; export { default as AdminSectionHero } from './components/admin-section/hero'; export { default as AdminPage } from './components/admin-page'; diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 71bb04c40aadd..e3207ce56ef08 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -1,11 +1,12 @@ { "name": "@automattic/jetpack-components", - "version": "0.38.1", + "version": "0.39.0-alpha", "description": "Jetpack Components Package", "author": "Automattic", "license": "GPL-2.0-or-later", "dependencies": { "@automattic/format-currency": "1.0.1", + "@babel/runtime": "^7", "@wordpress/browserslist-config": "5.18.0", "@wordpress/components": "25.1.0", "@wordpress/compose": "6.12.0", @@ -15,7 +16,8 @@ "@wordpress/icons": "9.26.0", "classnames": "2.3.2", "prop-types": "^15.7.2", - "qrcode.react": "3.1.0" + "qrcode.react": "3.1.0", + "react-slider": "2.0.5" }, "devDependencies": { "@automattic/jetpack-base-styles": "workspace:*", @@ -32,6 +34,7 @@ "@types/qrcode.react": "1.0.2", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", + "@types/react-slider": "1.3.1", "@types/react-test-renderer": "18.0.0", "@types/testing-library__jest-dom": "5.14.1", "jest": "29.4.3", @@ -40,6 +43,7 @@ "react-dom": "18.2.0", "react-test-renderer": "18.2.0", "require-from-string": "2.0.2", + "resize-observer-polyfill": "1.5.1", "typescript": "5.0.4", "webpack": "5.76.0", "webpack-cli": "4.9.1" From 161937e1c79a446bc5769ade66fed1f83fdff0df Mon Sep 17 00:00:00 2001 From: Calypso Bot Date: Thu, 22 Jun 2023 21:53:24 -0500 Subject: [PATCH 27/29] Update dependency semver to v7.5.2 [SECURITY] (#31532) Co-authored-by: Renovate Bot --- pnpm-lock.yaml | 27 +++++++++---------- .../renovate-npm-semver-vulnerability | 4 +++ projects/packages/forms/package.json | 2 +- tools/cli/package.json | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 projects/packages/forms/changelog/renovate-npm-semver-vulnerability diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69fd77e58898b..c2171726f2898 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -446,7 +446,7 @@ importers: version: 4.3.4 semver: specifier: ^7.3.5 - version: 7.3.5 + version: 7.5.2 devDependencies: '@wordpress/browserslist-config': specifier: 5.18.0 @@ -1548,8 +1548,8 @@ importers: specifier: 1.38.1 version: 1.38.1 semver: - specifier: 7.3.5 - version: 7.3.5 + specifier: 7.5.2 + version: 7.5.2 webpack: specifier: 5.76.0 version: 5.76.0(webpack-cli@4.9.1) @@ -3725,8 +3725,8 @@ importers: specifier: 0.11.10 version: 0.11.10 semver: - specifier: 7.3.5 - version: 7.3.5 + specifier: 7.5.2 + version: 7.5.2 sprintf-js: specifier: 1.1.2 version: 1.1.2 @@ -5443,7 +5443,7 @@ packages: chalk: 4.1.2 find-root: 1.1.0 lodash.groupby: 4.6.0 - semver: 7.3.5 + semver: 7.5.2 webpack: 5.76.0(webpack-cli@4.9.1) dev: false @@ -12540,7 +12540,6 @@ packages: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} dependencies: deep-equal: 2.2.1 - dequal: 2.0.3 dev: true /array-buffer-byte-length@1.0.0: @@ -13885,7 +13884,7 @@ packages: postcss-modules-scope: 3.0.0(postcss@8.4.21) postcss-modules-values: 4.0.0(postcss@8.4.21) postcss-value-parser: 4.2.0 - semver: 7.3.5 + semver: 7.5.2 webpack: 5.76.0(webpack-cli@4.9.1) /css-loader@6.8.1(webpack@5.76.0): @@ -15738,7 +15737,7 @@ packages: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.3.5 + semver: 7.5.2 tapable: 2.2.1 typescript: 5.0.4 webpack: 5.76.0(webpack-cli@4.9.1) @@ -17504,7 +17503,7 @@ packages: jest-util: 29.5.0 natural-compare: 1.4.0 pretty-format: 29.5.0 - semver: 7.3.5 + semver: 7.5.2 transitivePeerDependencies: - supports-color dev: true @@ -19656,7 +19655,7 @@ packages: pm2-deploy: 1.0.2 pm2-multimeter: 0.1.2 promptly: 2.2.0 - semver: 7.3.5 + semver: 7.5.2 source-map-support: 0.5.21 sprintf-js: 1.1.2 vizion: 2.2.1 @@ -19775,7 +19774,7 @@ packages: cosmiconfig: 7.1.0 klona: 2.0.6 postcss: 8.4.21 - semver: 7.3.5 + semver: 7.5.2 webpack: 5.76.0(webpack-cli@4.9.1) dev: true @@ -22751,7 +22750,7 @@ packages: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.3.5 + semver: 7.5.2 typescript: 5.0.4 yargs-parser: 21.1.1 dev: true @@ -22766,7 +22765,7 @@ packages: chalk: 4.1.2 enhanced-resolve: 5.15.0 micromatch: 4.0.5 - semver: 7.3.5 + semver: 7.5.2 typescript: 5.0.4 webpack: 5.76.0(webpack-cli@4.9.1) dev: true diff --git a/projects/packages/forms/changelog/renovate-npm-semver-vulnerability b/projects/packages/forms/changelog/renovate-npm-semver-vulnerability new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/forms/changelog/renovate-npm-semver-vulnerability @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/forms/package.json b/projects/packages/forms/package.json index 5e0b8adbc416d..c36a018bc1fc2 100644 --- a/projects/packages/forms/package.json +++ b/projects/packages/forms/package.json @@ -49,7 +49,7 @@ "redux": "4.0.5", "redux-thunk": "2.3.0", "sass": "1.38.1", - "semver": "7.3.5", + "semver": "7.5.2", "webpack": "5.76.0", "webpack-cli": "4.9.1" }, diff --git a/tools/cli/package.json b/tools/cli/package.json index 33160b20a74a9..c62b77bdd0f50 100644 --- a/tools/cli/package.json +++ b/tools/cli/package.json @@ -40,7 +40,7 @@ "path-name": "1.0.0", "pluralize": "8.0.0", "process": "0.11.10", - "semver": "7.3.5", + "semver": "7.5.2", "sprintf-js": "1.1.2", "yargs": "17.6.2" }, From 0766739b0c1e2e2580e2d64d922ee422c1509585 Mon Sep 17 00:00:00 2001 From: thingalon Date: Fri, 23 Jun 2023 16:15:23 +1000 Subject: [PATCH 28/29] [Boost] Fix UI on older versions of Safari (#31534) * Fall back to JSON.stringify/parse if structuredClone is unavailable * changelog --------- Co-authored-by: Mark George --- .../changelog/fix-structuredClone-fallback | 4 ++++ .../svelte-data-sync-client/src/SyncedStore.ts | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 projects/js-packages/svelte-data-sync-client/changelog/fix-structuredClone-fallback diff --git a/projects/js-packages/svelte-data-sync-client/changelog/fix-structuredClone-fallback b/projects/js-packages/svelte-data-sync-client/changelog/fix-structuredClone-fallback new file mode 100644 index 0000000000000..b374bf2daf873 --- /dev/null +++ b/projects/js-packages/svelte-data-sync-client/changelog/fix-structuredClone-fallback @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fixed support for older versions of Safari diff --git a/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts b/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts index 39a98e5108dd9..f792808a466ef 100644 --- a/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts +++ b/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts @@ -35,6 +35,17 @@ export class SyncedStore< T > { ); } + /** + * Deep clone a JSON-compatible value. Uses structuredClone if available, otherwise uses JSON.parse. + */ + private clone( value: T ): T { + if ( typeof structuredClone === 'function' ) { + return structuredClone( value ); + } + + return JSON.parse( JSON.stringify( value ) ); + } + private createStore( initialValue?: T ): SyncedWritable< T > { const store = writable< T >( initialValue ); @@ -47,7 +58,7 @@ export class SyncedStore< T > { // structuredClone may be necessary because using `set` in Svelte will mutate objects. // By the time the value gets to SyncedStore methods it's already mutated, // and so the previous value will be the same as the current value. - prevValue = isPrimitive ? value : structuredClone( value ); + prevValue = isPrimitive ? value : this.clone( value ); } ); // `set` is a required method in the Writable interface. @@ -123,7 +134,7 @@ export class SyncedStore< T > { // structuredClone is necessary here, // because the updateCallback can mutate the value, // and that's going to fail the comparison in `abortableSynchronize`. - set( updateCallback( structuredClone( prevValue ) ) ); + set( updateCallback( this.clone( prevValue ) ) ); }; return { From 5a3083b871a0e48bf1ebf4f54f874719d3cf0a0b Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 23 Jun 2023 09:10:27 +0200 Subject: [PATCH 29/29] Blaze: add new dashboard menu (#30103) Co-authored-by: Sebastian Barbosa Co-authored-by: Samiff --- projects/packages/blaze/.gitignore | 1 + .../blaze/changelog/add-blaze-dashboard-menu | 4 + projects/packages/blaze/composer.json | 9 +- projects/packages/blaze/package.json | 2 +- projects/packages/blaze/src/class-blaze.php | 130 +++- .../blaze/src/class-dashboard-config-data.php | 145 +++++ .../src/class-dashboard-rest-controller.php | 554 ++++++++++++++++++ .../packages/blaze/src/class-dashboard.php | 194 ++++++ projects/packages/blaze/src/js/editor.js | 25 +- .../packages/blaze/tests/php/test-blaze.php | 46 +- .../blaze/tests/php/test-dashboard.php | 56 ++ .../changelog/add-blaze-dashboard-menu | 5 + .../changelog/add-blaze-dashboard-menu#2 | 5 + projects/plugins/jetpack/composer.lock | 11 +- 14 files changed, 1151 insertions(+), 36 deletions(-) create mode 100644 projects/packages/blaze/changelog/add-blaze-dashboard-menu create mode 100644 projects/packages/blaze/src/class-dashboard-config-data.php create mode 100644 projects/packages/blaze/src/class-dashboard-rest-controller.php create mode 100644 projects/packages/blaze/src/class-dashboard.php create mode 100644 projects/packages/blaze/tests/php/test-dashboard.php create mode 100644 projects/plugins/jetpack/changelog/add-blaze-dashboard-menu create mode 100644 projects/plugins/jetpack/changelog/add-blaze-dashboard-menu#2 diff --git a/projects/packages/blaze/.gitignore b/projects/packages/blaze/.gitignore index b9842877b89c5..f2704eb4882ea 100644 --- a/projects/packages/blaze/.gitignore +++ b/projects/packages/blaze/.gitignore @@ -1,5 +1,6 @@ vendor/ wordpress/ node_modules/ +dist/ build/ .cache/ diff --git a/projects/packages/blaze/changelog/add-blaze-dashboard-menu b/projects/packages/blaze/changelog/add-blaze-dashboard-menu new file mode 100644 index 0000000000000..e0a5cd126ce52 --- /dev/null +++ b/projects/packages/blaze/changelog/add-blaze-dashboard-menu @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add new Blaze Dashboard menu item. diff --git a/projects/packages/blaze/composer.json b/projects/packages/blaze/composer.json index cc6caa8f72c3e..4fcbfe4b1390a 100644 --- a/projects/packages/blaze/composer.json +++ b/projects/packages/blaze/composer.json @@ -4,12 +4,15 @@ "type": "jetpack-library", "license": "GPL-2.0-or-later", "require": { + "automattic/jetpack-assets": "@dev", "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-redirect": "@dev", + "automattic/jetpack-status": "@dev", "automattic/jetpack-sync": "@dev" }, "require-dev": { "yoast/phpunit-polyfills": "1.0.4", - "automattic/jetpack-assets": "@dev", "automattic/jetpack-changelogger": "@dev", "automattic/wordbless": "@dev" }, @@ -59,11 +62,11 @@ "link-template": "https://github.com/automattic/jetpack-blaze/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.6.x-dev" + "dev-trunk": "0.7.x-dev" }, "textdomain": "jetpack-blaze", "version-constants": { - "::PACKAGE_VERSION": "src/class-blaze.php" + "::PACKAGE_VERSION": "src/class-dashboard.php" } }, "config": { diff --git a/projects/packages/blaze/package.json b/projects/packages/blaze/package.json index 0bb9b22646c3b..f65af7852836d 100644 --- a/projects/packages/blaze/package.json +++ b/projects/packages/blaze/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-blaze", - "version": "0.6.1-alpha", + "version": "0.7.0-alpha", "description": "Attract high-quality traffic to your site using Blaze. Using this service, you can advertise a post or page on some of the millions of pages across WordPress.com and Tumblr from just $5 per day.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/blaze/#readme", "bugs": { diff --git a/projects/packages/blaze/src/class-blaze.php b/projects/packages/blaze/src/class-blaze.php index 727b74c5c174c..c0e2e4fb7b982 100644 --- a/projects/packages/blaze/src/class-blaze.php +++ b/projects/packages/blaze/src/class-blaze.php @@ -7,18 +7,18 @@ namespace Automattic\Jetpack; +use Automattic\Jetpack\Blaze\Dashboard as Blaze_Dashboard; +use Automattic\Jetpack\Blaze\Dashboard_REST_Controller as Blaze_Dashboard_REST_Controller; use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State; use Automattic\Jetpack\Connection\Manager as Jetpack_Connection; +use Automattic\Jetpack\Status as Jetpack_Status; use Automattic\Jetpack\Sync\Settings as Sync_Settings; /** * Class for promoting posts. */ class Blaze { - - const PACKAGE_VERSION = '0.6.1-alpha'; - /** * Script handle for the JS file we enqueue in the post editor. * @@ -34,7 +34,8 @@ class Blaze { public static $script_path = '../build/editor.js'; /** - * The configuration method that is called from the jetpack-config package. + * Initializer. + * Used to configure the blaze package, eg when called via the Config package. * * @return void */ @@ -43,6 +44,10 @@ public static function init() { add_action( 'load-edit.php', array( __CLASS__, 'add_post_links_actions' ) ); // In the post editor, add a post-publish panel to allow promoting the post. add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_editor_assets' ) ); + // Add a Blaze Menu. + add_action( 'admin_menu', array( __CLASS__, 'enable_blaze_menu' ), 999 ); + // Adds Blaze rest API + add_action( 'rest_api_init', array( new Blaze_Dashboard_REST_Controller(), 'register_rest_routes' ) ); } /** @@ -57,6 +62,46 @@ public static function add_post_links_actions() { } } + /** + * Is the wp-admin Dashboard enabled? + * + * @return bool + */ + public static function is_dashboard_enabled() { + /** + * Enable a wp-admin dashboard for Blaze campaign management. + * + * @since $$next-version$$ + * + * @param bool $should_enable Should the dashboard be enabled? False by default for now. + */ + return apply_filters( 'jetpack_blaze_dashboard_enable', false ); + } + + /** + * Enable the Blaze menu. + * + * @return void + */ + public static function enable_blaze_menu() { + if ( + self::should_initialize() + && self::is_dashboard_enabled() + ) { + $blaze_dashboard = new Blaze_Dashboard(); + $page_suffix = add_submenu_page( + 'tools.php', + esc_attr__( 'Advertising', 'jetpack-blaze' ), + __( 'Advertising', 'jetpack-blaze' ), + 'manage_options', + 'advertising', + array( $blaze_dashboard, 'render' ), + 100 + ); + add_action( 'load-' . $page_suffix, array( $blaze_dashboard, 'admin_init' ) ); + } + } + /** * Check the WordPress.com REST API * to ensure that the site supports the Blaze feature. @@ -161,6 +206,47 @@ public static function should_initialize() { return apply_filters( 'jetpack_blaze_enabled', $should_initialize ); } + /** + * Get URL to create a Blaze campaign for a specific post. + * + * This can return 2 different types of URL: + * - Calypso Links + * - wp-admin Links if access to the wp-admin Blaze Dashboard is enabled. + * + * @param int $post_id Post ID. + * + * @return array An array with the link, and whether this is a Calypso or a wp-admin link. + */ + public static function get_campaign_management_url( $post_id ) { + if ( self::is_dashboard_enabled() ) { + $admin_url = admin_url( 'tools.php?page=advertising' ); + $hostname = wp_parse_url( get_site_url(), PHP_URL_HOST ); + $blaze_url = sprintf( + '%1$s#!/advertising/%2$s/posts/promote/post-%3$s', + $admin_url, + $hostname, + esc_attr( $post_id ) + ); + + return array( + 'link' => $blaze_url, + 'external' => false, + ); + } + + // Default Calypso link. + $blaze_url = Redirect::get_url( + 'jetpack-blaze', + array( + 'query' => 'blazepress-widget=post-' . esc_attr( $post_id ), + ) + ); + return array( + 'link' => $blaze_url, + 'external' => true, + ); + } + /** * Adds the Promote link to the posts list row action. * @@ -187,24 +273,21 @@ public static function jetpack_blaze_row_action( $post_actions, $post ) { return $post_actions; } - // Might be useful to wrap in a method call for general use without post_id. - $blaze_url = Redirect::get_url( - 'jetpack-blaze', - array( - 'query' => 'blazepress-widget=post-' . esc_attr( $post_id ), - ) + $blaze_url = self::get_campaign_management_url( $post_id ); + $text = _x( 'Blaze', 'Verb', 'jetpack-blaze' ); + $title = get_the_title( $post ); + $label = sprintf( + /* translators: post title */ + __( 'Blaze “%s” to Tumblr and WordPress.com audiences.', 'jetpack-blaze' ), + $title ); - // Add the link, make sure to tooltip hover. - $text = _x( 'Blaze', 'Verb', 'jetpack-blaze' ); - $title = _draft_or_post_title( $post ); - /* translators: post title */ - $label = sprintf( __( 'Blaze “%s” to Tumblr and WordPress.com audiences.', 'jetpack-blaze' ), $title ); $post_actions['blaze'] = sprintf( - '%3$s', - esc_url( $blaze_url ), + '%3$s', + esc_url( $blaze_url['link'] ), esc_attr( $label ), - esc_html( $text ) + esc_html( $text ), + ( true === $blaze_url['external'] ? 'target="_blank" rel="noopener noreferrer"' : '' ) ); return $post_actions; @@ -249,5 +332,16 @@ public static function enqueue_block_editor_assets() { // Adds Connection package initial state. wp_add_inline_script( self::SCRIPT_HANDLE, Connection_Initial_State::render(), 'before' ); + + // Pass additional data to our script. + wp_localize_script( + self::SCRIPT_HANDLE, + 'blazeInitialState', + array( + 'adminUrl' => esc_url( admin_url() ), + 'isDashboardEnabled' => self::is_dashboard_enabled(), + 'siteFragment' => ( new Jetpack_Status() )->get_site_suffix(), + ) + ); } } diff --git a/projects/packages/blaze/src/class-dashboard-config-data.php b/projects/packages/blaze/src/class-dashboard-config-data.php new file mode 100644 index 0000000000000..e6e1b3f3525f9 --- /dev/null +++ b/projects/packages/blaze/src/class-dashboard-config-data.php @@ -0,0 +1,145 @@ +get_data() : $config_data + ) . ';'; + } + + /** + * Return the config for the app. + */ + public function get_data() { + $blog_id = Jetpack_Options::get_option( 'id' ); + $empty_object = json_decode( '{}' ); + return array( + 'admin_page_base' => $this->get_admin_path(), + 'api_root' => esc_url_raw( rest_url() ), + 'blog_id' => $blog_id, + 'enable_all_sections' => false, + 'env_id' => 'production', + 'google_analytics_key' => 'UA-10673494-15', + 'hostname' => wp_parse_url( get_site_url(), PHP_URL_HOST ), + 'i18n_default_locale_slug' => 'en', + 'mc_analytics_enabled' => false, + 'meta' => array(), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'site_name' => \get_bloginfo( 'name' ), + 'sections' => array(), + // Features are inlined in Calypso Blaze app (wp-calypso/apps/blaze-dashboard) + 'features' => array(), + 'intial_state' => array( + 'currentUser' => array( + 'id' => 1000, + 'user' => array( + 'ID' => 1000, + 'username' => 'no-user', + 'localeSlug' => $this->get_site_locale(), + ), + 'capabilities' => array( + "$blog_id" => $this->get_current_user_capabilities(), + ), + ), + 'sites' => array( + 'items' => array( + "$blog_id" => array( + 'ID' => $blog_id, + 'URL' => site_url(), + 'jetpack' => true, + 'visible' => true, + 'capabilities' => $empty_object, + 'products' => array(), + 'plan' => $empty_object, // we need this empty object, otherwise the front end would crash on insight page. + 'options' => array( + 'admin_url' => admin_url(), + 'gmt_offset' => $this->get_gmt_offset(), + ), + ), + ), + 'features' => array( "$blog_id" => array( 'data' => $this->get_plan_features() ) ), + ), + ), + ); + } + + /** + * Get the current site GMT Offset. + * + * @return float The current site GMT Offset by hours. + */ + protected function get_gmt_offset() { + return (float) get_option( 'gmt_offset' ); + } + + /** + * Page base for the Calypso admin page. + */ + protected function get_admin_path() { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( ! isset( $_SERVER['PHP_SELF'] ) || ! isset( $_SERVER['QUERY_STRING'] ) ) { + $parsed = wp_parse_url( admin_url( 'tools.php?page=advertising' ) ); + return $parsed['path'] . '?' . $parsed['query']; + } + // We do this because page.js requires the exactly page base to be set otherwise it will not work properly. + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + return wp_unslash( $_SERVER['PHP_SELF'] ) . '?' . wp_unslash( $_SERVER['QUERY_STRING'] ); + } + + /** + * Get locale acceptable by Calypso. + */ + protected function get_site_locale() { + /** + * In WP, locales are formatted as LANGUAGE_REGION, for example `en`, `en_US`, `es_AR`, + * but Calypso expects language-region, e.g. `en-us`, `en`, `es-ar`. So we need to convert + * them to lower case and replace the underscore with a dash. + */ + $locale = strtolower( get_locale() ); + $locale = str_replace( '_', '-', $locale ); + + return $locale; + } + + /** + * Get the features of the current plan. + */ + protected function get_plan_features() { + $plan = Current_Plan::get(); + if ( empty( $plan['features'] ) ) { + return array(); + } + return $plan['features']; + } + + /** + * Get the capabilities of the current user. + * + * @return array An array of capabilities. + */ + protected function get_current_user_capabilities() { + $user = wp_get_current_user(); + if ( ! $user || is_wp_error( $user ) ) { + return array(); + } + return $user->allcaps; + } +} diff --git a/projects/packages/blaze/src/class-dashboard-rest-controller.php b/projects/packages/blaze/src/class-dashboard-rest-controller.php new file mode 100644 index 0000000000000..5951e33737ae9 --- /dev/null +++ b/projects/packages/blaze/src/class-dashboard-rest-controller.php @@ -0,0 +1,554 @@ +get_site_id(); + if ( is_wp_error( $site_id ) ) { + return; + } + + // WPCOM API routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/blaze/posts(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_blaze_posts' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Credits routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/credits(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_credits' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Campaigns routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/campaigns(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_campaigns' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/campaigns(?P[a-zA-Z0-9-_\/]*)', $site_id ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'edit_dsp_campaigns' ), + 'permission_callback' => array( $this, 'can_user_view_dsp_callback' ), + ) + ); + + // WordAds DSP API Site Campaigns routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/sites/%d/campaigns(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id, $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_site_campaigns' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Search routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/search(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_search' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Users routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/user(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_user' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Templates routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/templates(?P[a-zA-Z0-9-_\/:]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_templates' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Subscriptions routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/subscriptions(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_subscriptions' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Smart routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/smart(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_smart' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/smart(?P[a-zA-Z0-9-_\/]*)', $site_id ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'edit_dsp_smart' ), + 'permission_callback' => array( $this, 'can_user_view_dsp_callback' ), + ) + ); + + // WordAds DSP API Locations routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/locations(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_locations' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Woo countries routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/woo/countries(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_countries' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + + // WordAds DSP API Image routes + register_rest_route( + static::$namespace, + sprintf( '/sites/%d/wordads/dsp/api/v1/image(?P[a-zA-Z0-9-_\/]*)(\?.*)?', $site_id ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_dsp_image' ), + 'permission_callback' => array( $this, 'can_user_view_blaze_posts_callback' ), + ) + ); + } + + /** + * Only administrators can access the API. + * + * @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise. + */ + public function can_user_view_blaze_posts_callback() { + if ( + $this->is_user_connected() + && current_user_can( 'manage_options' ) + ) { + return true; + } + + return $this->get_forbidden_error(); + } + + /** + * Redirect GET requests to WordAds DSP for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_blaze_posts( $req ) { + $site_id = $this->get_site_id(); + if ( is_wp_error( $site_id ) ) { + return array(); + } + + // We don't use sub_path in the blaze posts, only query strings + if ( isset( $params['sub_path'] ) ) { + unset( $req->get_params()['sub_path'] ); + } + + return $this->request_as_user( + sprintf( '/sites/%d/blaze/posts%s', $site_id, $this->build_subpath_with_query_strings( $req->get_params() ) ), + 'v2', + array( 'method' => 'GET' ) + ); + } + + /** + * Only administrators can access the API. + */ + public function can_user_view_dsp_callback() { + // phpcs:ignore WordPress.WP.Capabilities.Unknown + if ( current_user_can( 'manage_options' ) ) { + return true; + } + + return $this->get_forbidden_error(); + } + + /** + * Builds the subpath including the query string to be used in the DSP call + * + * @param array $params The request object parameters. + * @return string + */ + private function build_subpath_with_query_strings( $params ) { + $sub_path = ''; + if ( isset( $params['sub_path'] ) ) { + $sub_path = $params['sub_path']; + unset( $params['sub_path'] ); + } + + if ( isset( $params['rest_route'] ) ) { + unset( $params['rest_route'] ); + } + + if ( ! empty( $params ) ) { + $sub_path = $sub_path . '?' . http_build_query( stripslashes_deep( $params ) ); + } + + return $sub_path; + } + + /** + * Redirect GET requests to WordAds DSP Credits endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_credits( $req ) { + return $this->get_dsp_generic( 'v1/credits', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Campaigns endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_campaigns( $req ) { + return $this->get_dsp_generic( 'v1/campaigns', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Site Campaigns endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_site_campaigns( $req ) { + $site_id = $this->get_site_id(); + if ( is_wp_error( $site_id ) ) { + return array(); + } + + return $this->get_dsp_generic( sprintf( 'v1/sites/%d/campaigns', $site_id ), $req ); + } + + /** + * Redirect GET requests to WordAds DSP Search endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_search( $req ) { + return $this->get_dsp_generic( 'v1/search', $req ); + } + + /** + * Redirect GET requests to WordAds DSP User endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_user( $req ) { + return $this->get_dsp_generic( 'v1/user', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Search endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_templates( $req ) { + return $this->get_dsp_generic( 'v1/templates', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Subscriptions endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_subscriptions( $req ) { + return $this->get_dsp_generic( 'v1/subscriptions', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Subscriptions endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_smart( $req ) { + return $this->get_dsp_generic( 'v1/smart', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Locations endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_locations( $req ) { + return $this->get_dsp_generic( 'v1/locations', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Countries endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_countries( $req ) { + return $this->get_dsp_generic( 'v1/countries', $req ); + } + + /** + * Redirect GET requests to WordAds DSP Countries endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_image( $req ) { + return $this->get_dsp_generic( 'v1/image', $req ); + } + + /** + * Redirect GET requests to WordAds DSP for the site. + * + * @param String $path The Root API endpoint. + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function get_dsp_generic( $path, $req ) { + $site_id = $this->get_site_id(); + if ( is_wp_error( $site_id ) ) { + return array(); + } + + return $this->request_as_user( + sprintf( '/sites/%d/wordads/dsp/api/%s%s', $site_id, $path, $this->build_subpath_with_query_strings( $req->get_params() ) ), + 'v2', + array( 'method' => 'GET' ) + ); + } + + /** + * Redirect POST/PUT/PATCH requests to WordAds DSP Campaigns endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function edit_dsp_campaigns( $req ) { + return $this->edit_dsp_generic( 'v1/campaigns', $req ); + } + + /** + * Redirect POST/PUT/PATCH requests to WordAds DSP Smart endpoint for the site. + * + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function edit_dsp_smart( $req ) { + return $this->edit_dsp_generic( 'v1/smart', $req ); + } + + /** + * Redirect POST/PUT/PATCH requests to WordAds DSP for the site. + * + * @param String $path The Root API endpoint. + * @param WP_REST_Request $req The request object. + * @return array|WP_Error + */ + public function edit_dsp_generic( $path, $req ) { + $site_id = $this->get_site_id(); + if ( is_wp_error( $site_id ) ) { + return array(); + } + + return $this->request_as_user( + sprintf( '/sites/%d/wordads/dsp/api/%s%s', $site_id, $path, $req->get_param( 'sub_path' ) ), + 'v2', + array( 'method' => $req->get_method() ), + $req->get_body() + ); + } + + /** + * Queries the WordPress.com REST API with a user token. + * + * @param String $path The API endpoint relative path. + * @param String $version The API version. + * @param array $args Request arguments. + * @param String $body Request body. + * @param String $base_api_path (optional) the API base path override, defaults to 'rest'. + * @param bool $use_cache (optional) default to true. + * @return array|WP_Error $response Data. + */ + protected function request_as_user( $path, $version = '2', $args = array(), $body = null, $base_api_path = 'wpcom', $use_cache = false ) { + // Arrays are serialized without considering the order of objects, but it's okay atm. + $cache_key = 'BLAZE_REST_RESP_' . md5( implode( '|', array( $path, $version, wp_json_encode( $args ), wp_json_encode( $body ), $base_api_path ) ) ); + + if ( $use_cache ) { + $response_body_content = get_transient( $cache_key ); + if ( false !== $response_body_content ) { + return json_decode( $response_body_content, true ); + } + } + + $response = Client::wpcom_json_api_request_as_user( + $path, + $version, + $args, + $body, + $base_api_path + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body_content = wp_remote_retrieve_body( $response ); + $response_body = json_decode( $response_body_content, true ); + + if ( 200 !== $response_code ) { + return $this->get_wp_error( $response_body, $response_code ); + } + + // Cache the successful JSON response for 5 minutes. + set_transient( $cache_key, $response_body_content, 5 * MINUTE_IN_SECONDS ); + return $response_body; + } + + /** + * Return a WP_Error object with a forbidden error. + */ + protected function get_forbidden_error() { + $error_msg = esc_html__( + 'You are not allowed to perform this action.', + 'jetpack-blaze' + ); + + return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) ); + } + + /** + * Build error object from remote response body and status code. + * + * @param array $response_body Remote response body. + * @param int $response_code Http response code. + * @return WP_Error + */ + protected function get_wp_error( $response_body, $response_code = 500 ) { + $error_code = 'remote-error'; + foreach ( array( 'code', 'error' ) as $error_code_key ) { + if ( isset( $response_body[ $error_code_key ] ) ) { + $error_code = $response_body[ $error_code_key ]; + break; + } + } + + $error_message = isset( $response_body['message'] ) ? $response_body['message'] : 'unknown remote error'; + + return new WP_Error( + $error_code, + $error_message, + array( 'status' => $response_code ) + ); + } + + /** + * Check if the current user is connected. + * On WordPress.com Simple, it is always connected. + * + * @return true + */ + private function is_user_connected() { + if ( ( new Host() )->is_wpcom_simple() ) { + return true; + } + + $connection = new Connection_Manager(); + return $connection->is_connected() && $connection->is_user_connected(); + } + + /** + * Get the site ID. + * + * @return int|WP_Error + */ + private function get_site_id() { + return Connection_Manager::get_site_id(); + } +} diff --git a/projects/packages/blaze/src/class-dashboard.php b/projects/packages/blaze/src/class-dashboard.php new file mode 100644 index 0000000000000..cc2e33daa78b4 --- /dev/null +++ b/projects/packages/blaze/src/class-dashboard.php @@ -0,0 +1,194 @@ + + + + get_data(); + + if ( file_exists( __DIR__ . "/../dist/{$asset_name}.js" ) ) { + // Load local assets for the convenience of development. + Assets::register_script( + $asset_handle, + "../dist/{$asset_name}.js", + __FILE__, + array( + 'enqueue' => true, + 'in_footer' => true, + 'textdomain' => 'jetpack-blaze', + ) + ); + } else { + $css_url = $asset_name . ( is_rtl() ? '.rtl' : '' ) . '.css'; + $css_handle = $asset_handle . '-style'; + + wp_enqueue_script( + $asset_handle, + sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, "{$asset_name}.js" ), + self::JS_DEPENDENCIES, + $this->get_cdn_asset_cache_buster(), + true + ); + wp_enqueue_style( + $css_handle, + sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, $css_url ), + array(), + $this->get_cdn_asset_cache_buster() + ); + } + + wp_add_inline_script( + $asset_handle, + $dashboard_config->get_js_config_data( $config_data ), + 'before' + ); + } + + /** + * Returns cache buster string for assets. + * Development mode doesn't need this, as it's handled by `Assets` class. + */ + protected function get_cdn_asset_cache_buster() { + // Use cached cache buster in production. + $remote_asset_version = get_transient( self::BLAZEDASH_CACHE_BUSTER_CACHE_KEY ); + if ( ! empty( $remote_asset_version ) ) { + return $remote_asset_version; + } + + // If no cached cache buster, we fetch it from CDN and set to transient. + $response = wp_remote_get( sprintf( self::CDN_URL, self::BLAZEDASH_VERSION, 'build_meta.json' ), array( 'timeout' => 5 ) ); + + if ( is_wp_error( $response ) ) { + // fallback to the package version. + return self::PACKAGE_VERSION; + } + + $build_meta = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! empty( $build_meta['cache_buster'] ) ) { + // Cache the cache buster for 15 mins. + set_transient( self::BLAZEDASH_CACHE_BUSTER_CACHE_KEY, $build_meta['cache_buster'], 15 * MINUTE_IN_SECONDS ); + return $build_meta['cache_buster']; + } + + // fallback to the package version. + return self::PACKAGE_VERSION; + } +} diff --git a/projects/packages/blaze/src/js/editor.js b/projects/packages/blaze/src/js/editor.js index e38676d74f017..890b0b66445d3 100644 --- a/projects/packages/blaze/src/js/editor.js +++ b/projects/packages/blaze/src/js/editor.js @@ -1,5 +1,5 @@ import { getRedirectUrl } from '@automattic/jetpack-components'; -import { getSiteFragment, useAnalytics } from '@automattic/jetpack-shared-extension-utils'; +import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; import { Button, PanelRow } from '@wordpress/components'; import { usePrevious } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; @@ -13,6 +13,7 @@ import './editor.scss'; import BlazeIcon from './icon'; const BlazePostPublishPanel = () => { + const { adminUrl, isDashboardEnabled, siteFragment } = window?.blazeInitialState || {}; const { tracks } = useAnalytics(); // Tracks event when clicking on the Blaze link. @@ -40,10 +41,16 @@ const BlazePostPublishPanel = () => { initialOpen: true, }; - const blazeUrl = getRedirectUrl( 'jetpack-blaze', { - site: getSiteFragment(), - query: `blazepress-widget=post-${ postId }`, - } ); + const blazeUrl = () => { + if ( isDashboardEnabled ) { + return `${ adminUrl }tools.php?page=advertising#!/advertising/${ siteFragment }/posts/promote/post-${ postId }`; + } + + return getRedirectUrl( 'jetpack-blaze', { + site: siteFragment, + query: `blazepress-widget=post-${ postId }`, + } ); + }; // Decide when the panel should appear, and be tracked. const shouldDisplayPanel = () => { @@ -98,13 +105,15 @@ const BlazePostPublishPanel = () => { onClick={ trackClick } onKeyDown={ trackClick } > - diff --git a/projects/packages/blaze/tests/php/test-blaze.php b/projects/packages/blaze/tests/php/test-blaze.php index b978b5fabc8f7..b2e2ae5af0d29 100644 --- a/projects/packages/blaze/tests/php/test-blaze.php +++ b/projects/packages/blaze/tests/php/test-blaze.php @@ -66,9 +66,9 @@ public function tear_down() { * * @covers Automattic\Jetpack\Blaze::init */ - public function test_should_not_check_eligibility_by_defuault() { + public function test_should_not_check_eligibility_by_default() { /* - *The post_row_actions action should not be available on init. + * The post_row_actions action should not be available on init. * It only happens on a specific screen. */ $this->assertFalse( has_action( 'post_row_actions' ) ); @@ -79,6 +79,18 @@ public function test_should_not_check_eligibility_by_defuault() { $this->assertFalse( has_filter( 'jetpack_blaze_enabled' ) ); } + /** + * Test that the jetpack_blaze_dashboard_enable filter overwrites eligibility for the dashboard page. + * + * @covers Automattic\Jetpack\Blaze::is_dashboard_enabled + */ + public function test_dashboard_filter_enable() { + $this->assertFalse( Blaze::is_dashboard_enabled() ); + add_filter( 'jetpack_blaze_dashboard_enable', '__return_true' ); + $this->assertTrue( Blaze::is_dashboard_enabled() ); + add_filter( 'jetpack_blaze_dashboard_enable', '__return_false' ); + } + /** * Test that the jetpack_blaze_enabled filter overwrites eligibility, for admins. * @@ -125,6 +137,36 @@ public function test_post_row_added() { add_filter( 'jetpack_blaze_enabled', '__return_false' ); } + /** + * Test if the admin menu is added for admins when we force Blaze to be enabled. + * + * @covers Automattic\Jetpack\Blaze::enable_blaze_menu + */ + public function test_admin_menu_added() { + $this->confirm_add_filters_and_actions_for_screen_starts_clean(); + + // Ensure that no menu is added by default. + $this->assertEmpty( menu_page_url( 'advertising' ) ); + + wp_set_current_user( $this->admin_id ); + + add_filter( 'jetpack_blaze_enabled', '__return_true' ); + + // Test that no menu is added until the feature filter is enabled. + Blaze::enable_blaze_menu(); + + $this->assertEmpty( menu_page_url( 'advertising' ) ); + + // Enable the Dashboard. + add_filter( 'jetpack_blaze_dashboard_enable', '__return_true' ); + + Blaze::enable_blaze_menu(); + $this->assertNotEmpty( menu_page_url( 'advertising' ) ); + + add_filter( 'jetpack_blaze_dashboard_enable', '__return_false' ); + add_filter( 'jetpack_blaze_enabled', '__return_false' ); + } + /** * Test that we avoid enqueuing assets when Blaze is not enabled. * diff --git a/projects/packages/blaze/tests/php/test-dashboard.php b/projects/packages/blaze/tests/php/test-dashboard.php new file mode 100644 index 0000000000000..d493f5d92f89d --- /dev/null +++ b/projects/packages/blaze/tests/php/test-dashboard.php @@ -0,0 +1,56 @@ +expectOutputRegex( '/