Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow chained actions for [#item_title] #913

Merged
merged 16 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,7 @@ module.exports = defineConfig({
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
pageLoadTimeout : 300000,
},
blockHosts: [
'app.formbricks.com'
],
})
80 changes: 70 additions & 10 deletions cypress/e2e/default/import_feed_free.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,72 @@ describe('Test Free - Import Feed', function() {
cy.get('.fz-tabs-menu li a').should('have.length', settings.tabs);
})

it('Create/Verify/Run import', function() {
it('Check chained actions insertion for title', function() {
// Create a new import.
cy.visit('/wp-admin/post-new.php?post_type=feedzy_imports');

// Fill up the form
cy.get('#post_title').clear().type( 'Chained actions: ' + feed.url );
cy.get('#feedzy-import-source').clear().type( feed.url );
cy.get('#feedzy-import-source').next('.fz-input-group-append').find('.add-outside-tags').click();

// Open the Map Content tab
cy.get('#fz-import-map-content > .feedzy-accordion-item__button').click();

// Clear the title field.
cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-left > .fz-group > .tagify > .tagify__input').type('{selectall}{backspace}');

// Add chained actions for title field.
cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-right > .dropdown > .btn').click();
cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-right > .dropdown > .dropdown-menu > [data-field-tag="item_title"] > small').click(); // Select the [#item_title] tag.

// Add a chained action.
cy.get('.fz-action-relative > .components-button').click();
cy.get('.popover-action-list > ul > :nth-child(1)').click(); // Select the "Trim content" action.

// Add another chained action.
cy.get('.fz-action-relative > .components-button').click();
cy.get('.popover-action-list > ul > :nth-child(3)').click(); // Select the "Search and Replace" action.

// Save actions.
cy.get('.fz-save-action').click();

// Save the serialized data directly to the input field. (The Tagify lib used for UI does not update the input field based on Cypress click actions).
cy.get('.tagify__input > .tagify__tag a').invoke('attr', 'data-actions').then((serializedActions) => {
cy.get('[name="feedzy_meta_data[import_post_title]"]').clear({force: true}).invoke('val', '[[{"value": "' + serializedActions + '"}]]' ).blur({force: true});
});

// Save the import.
cy.get('button[type="submit"][name="save"]').scrollIntoView().click({force:true});

// Reopen the import.
cy.visit('/wp-admin/edit.php?post_type=feedzy_imports');
cy.get('tr:nth-of-type(1) .row-title').click();

// Open the Map Content tab
cy.get('#fz-import-map-content > .feedzy-accordion-item__button').click();

// Check the saved actions.
cy.get('.tagify__input > .tagify__tag').should('be.visible');
cy.get('.tagify__input > .tagify__tag a').invoke('attr', 'data-actions').then((serializedActions) => {
expect(serializedActions).to.include('item_title');
expect(serializedActions).to.include('trim');
expect(serializedActions).to.include('search_replace');

// Check if we have right actions.
const actions = JSON.parse(decodeURIComponent(serializedActions));

expect(actions).to.have.length(2);
expect(actions[0].id).to.equal('trim');
expect(actions[0].tag).to.equal('item_title');

expect(actions[1].id).to.equal('search_replace');
expect(actions[1].tag).to.equal('item_title');
} );

})

it('Check the Create/Verify/Run workflow for feed import', function() {
// 1. CREATE
cy.visit('/wp-admin/post-new.php?post_type=feedzy_imports');

Expand Down Expand Up @@ -84,18 +149,15 @@ describe('Test Free - Import Feed', function() {

// show a notice.
//cy.get('div.notice.feedzy-error-critical').should('be.visible');
})

it('Update the new import with VALID url', function() {

cy.visit('/wp-admin/edit.php?post_type=feedzy_imports');

cy.get('tr:nth-of-type(1) .row-title').click();


// fill up the form
cy.get('#post_title').clear().type( feed.url );
cy.get('#feedzy-import-source').clear().type( feed.url );
cy.get('#feedzy-import-source').next('.fz-input-group-append').find('.add-outside-tags').click();


cy.get('button[type="submit"][name="save"]').scrollIntoView().click({force:true});

// check if the import has been setup
Expand All @@ -117,7 +179,7 @@ describe('Test Free - Import Feed', function() {
cy.get('#feedzy_post_terms').invoke('show').then( () => {
cy.get('#feedzy_post_terms option:selected').should('have.length', feed.taxonomy.length);
});

cy.get('[name="feedzy_meta_data[import_post_title]"]').should('have.value', PREFIX + feed.title);
cy.get('[name="feedzy_meta_data[import_post_content]"]').should('have.value', PREFIX + feed.fullcontent.content + feed.content + '\n');

Expand Down Expand Up @@ -254,6 +316,4 @@ describe('Test Free - Import Feed', function() {
cy.get('.wp-block-post-author-name .wp-block-post-author-name__link:contains("wordpress")').should('exist');

})


})
88 changes: 54 additions & 34 deletions includes/admin/feedzy-rss-feeds-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ class Feedzy_Rss_Feeds_Actions {
private static $instance;

/**
* Content actions.
* Serialized content actions. It can contain a mix of magic tags and simple text.
*
* @var string $actions Content actions.
* @var string $raw_serialized_actions Content actions.
*/
private $actions;
private $raw_serialized_actions;

/**
* Setting options.
Expand All @@ -39,7 +39,7 @@ class Feedzy_Rss_Feeds_Actions {
/**
* Extract tags.
*
* @var string $extract_tags Extract tags.
* @var array $extract_tags Extract tags.
*/
private $extract_tags;

Expand Down Expand Up @@ -72,11 +72,11 @@ class Feedzy_Rss_Feeds_Actions {
public $result = '';

/**
* Post content.
* The field content (title, description, post content, date, etc.)
*
* @var string $post_content
* @var string $field_content
*/
public $post_content = '';
public $field_content = '';

/**
* Default value.
Expand Down Expand Up @@ -129,13 +129,13 @@ public static function instance() {
/**
* Run actions.
*
* @param string $actions Item content actions.
* @return string
* @param string $raw_serialized_actions Item content actions.
* @return string|array
*/
public function set_actions( $actions = '' ) {
$this->actions = $actions;
if ( empty( $this->actions ) ) {
return $this->actions;
public function set_raw_serialized_actions( $raw_serialized_actions = '' ) {
$this->raw_serialized_actions = $raw_serialized_actions;
if ( empty( $this->raw_serialized_actions ) ) {
return $this->raw_serialized_actions;
}
$this->extract_tags = $this->extract_magic_tags();
return $this->extract_tags;
Expand All @@ -154,10 +154,23 @@ public function set_settings( $options ) {
/**
* Extract magic tags.
*
* @return string
* @return array|array[]
*/
public function extract_magic_tags() {
preg_match_all( '/\[\[\{(.*)\}\]\]/U', $this->actions, $item_magic_tags, PREG_PATTERN_ORDER );
/**
* Transform the serialized string of magic tags to array.
*
* Input(string): [[{"value":"[{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}]"}]] with a nice weather.
*
* Output:
* [
* [
* [replace_to] => [[{"value":"[{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}]"}]]
* [replace_with] => [{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}]
* ]
* ]
*/
preg_match_all( '/\[\[\{(.*)\}\]\]/U', $this->raw_serialized_actions, $item_magic_tags, PREG_PATTERN_ORDER );
$extract_tags = array();
if ( ! empty( $item_magic_tags[0] ) ) {
$extract_tags = array_map(
Expand All @@ -175,12 +188,14 @@ function( $tag ) {
}

/**
* Get magic tags.
* Get the extracted serialized actions from the Tagify tags. The actions can be a mix of Tagify tags and simple text.
*
* @return string The serialized actions.
*/
public function get_tags() {
public function get_serialized_actions() {
$replace_to = array_column( $this->get_extract_tags(), 'replace_to' );
$replace_with = array_column( $this->get_extract_tags(), 'replace_with' );
return str_replace( $replace_to, $replace_with, $this->actions );
return str_replace( $replace_to, $replace_with, $this->raw_serialized_actions );
}

/**
Expand All @@ -191,17 +206,21 @@ public function get_extract_tags() {
}

/**
* Get actions.
* Get actions. Return pairs of serialized actions and their deserialized versions.
*
* Deserialized version is used to run the action job. While serialized version is used to replace the job result in the input content.
*
* @return array
*/
public function get_actions() {
$replace_with = array_column( $this->get_extract_tags(), 'replace_with' );
$actions = array_map(
function( $action ) {
$replace_with = json_decode( $action );
if ( $replace_with ) {
function( $serialized_actions ) {
$job_actions = json_decode( $serialized_actions );
if ( $job_actions ) {
return array(
'replace_to' => wp_json_encode( $replace_with ),
'replace_with' => $replace_with,
'serialized_actions' => $serialized_actions,
'job_actions' => $job_actions,
);
}
return false;
Expand All @@ -214,46 +233,47 @@ function( $action ) {
/**
* Run action job.
*
* @param string $post_content Post content.
* @param string $field_content Field content. It can contain a mix of magic tags and simple text.
* @param string $import_translation_lang Translation language code.
* @param object $job Post object.
* @param string $language_code Feed language code.
* @param array $item Feed item.
* @param string $default_value Default value.
* @return string
*/
public function run_action_job( $post_content, $import_translation_lang, $job, $language_code, $item, $default_value = '' ) {
public function run_action_job( $field_content, $import_translation_lang, $job, $language_code, $item, $default_value = '' ) {
$this->item = $item;
$this->job = $job;
$this->language_code = $language_code;
$this->translation_lang = $import_translation_lang;
$this->post_content = $post_content;
$this->field_content = $field_content;
$this->default_value = $default_value;
$actions = $this->get_actions();

if ( ! empty( $actions ) ) {
foreach ( $actions as $key => $jobs ) {
if ( ! isset( $jobs['replace_with'] ) ) {
if ( ! isset( $jobs['job_actions'] ) ) {
continue;
}

$this->result = null;
$replace_with = isset( $jobs['replace_with'] ) ? $jobs['replace_with'] : array();
$replace_to = isset( $jobs['replace_to'] ) ? $jobs['replace_to'] : '';
foreach ( $replace_with as $job ) {
$jobs_actions = $jobs['job_actions'];
$replace_to = isset( $jobs['serialized_actions'] ) ? $jobs['serialized_actions'] : '';
foreach ( $jobs_actions as $job ) {
$this->current_job = $job;
$this->result = $this->action_process();
}
if ( 'item_image' === $this->type ) {
$this->post_content = str_replace( $replace_to, $this->result, wp_json_encode( $replace_with ) );
$this->field_content = str_replace( $replace_to, $this->result, wp_json_encode( $jobs_actions ) );
} else {
$this->post_content = str_replace( $replace_to, $this->result, $this->post_content );
$this->field_content = str_replace( $replace_to, $this->result, $this->field_content );
}
}
}
if ( empty( $actions ) && 'item_image' === $this->type ) {
return $default_value;
}
return $this->post_content;
return $this->field_content;
}

/**
Expand Down
27 changes: 16 additions & 11 deletions includes/admin/feedzy-rss-feeds-import.php
Original file line number Diff line number Diff line change
Expand Up @@ -1416,15 +1416,16 @@ private function run_job( $job, $max ) {
$item_date = date( get_option( 'date_format' ) . ' at ' . get_option( 'time_format' ), $item['item_date'] );
$item_date = $item['item_date_formatted'];

// Transform any structure like [[{"value":"[#item_title]"}]] to [#item_title].
$import_title = preg_replace( '/\[\[\{"value":"(\[#[^]]+\])"\}\]\]/', '$1', $import_title );

// Get translated item title.
$translated_title = '';
if ( $import_auto_translation && ( false !== strpos( $import_title, '[#translated_title]' ) || false !== strpos( $post_excerpt, '[#translated_title]' ) ) ) {
$translated_title = apply_filters( 'feedzy_invoke_auto_translate_services', $item['item_title'], '[#translated_title]', $import_translation_lang, $job, $language_code, $item );
}

$import_title = rawurldecode( $import_title );
$import_title = str_replace( PHP_EOL, "\r\n", $import_title );
$import_title = trim( $import_title );

$post_title = str_replace(
array(
'[#item_title]',
Expand All @@ -1451,6 +1452,10 @@ private function run_job( $job, $max ) {
$post_title = apply_filters( 'feedzy_parse_custom_tags', $post_title, $item_obj );
}

$title_action = $this->get_actions_runner( $post_title, 'item_title' );
$post_title = $title_action->get_serialized_actions();
$post_title = $title_action->run_action_job( $post_title, $translated_title, $job, $language_code, $item );

$post_title = apply_filters( 'feedzy_invoke_services', $post_title, 'title', $item['item_title'], $job );

// Get translated item link text.
Expand Down Expand Up @@ -1546,8 +1551,8 @@ private function run_job( $job, $max ) {
$post_content = apply_filters( 'feedzy_invoke_services', $post_content, 'full_content', $full_content, $job );
}
// Item content action.
$content_action = $this->handle_content_actions( $post_content, 'item_content' );
$post_content = $content_action->get_tags();
$content_action = $this->get_actions_runner( $post_content, 'item_content' );
$post_content = $content_action->get_serialized_actions();
// Item content action process.
$post_content = $content_action->run_action_job( $post_content, $import_translation_lang, $job, $language_code, $item );
// Parse custom tags.
Expand Down Expand Up @@ -1832,7 +1837,7 @@ function( $term ) {
// Item image action.
$import_featured_img = rawurldecode( $import_featured_img );
$import_featured_img = trim( $import_featured_img );
$img_action = $this->handle_content_actions( $import_featured_img, 'item_image' );
$img_action = $this->get_actions_runner( $import_featured_img, 'item_image' );
// Item image action process.
$image_source_url = $img_action->run_action_job( $import_featured_img, $import_translation_lang, $job, $language_code, $item, $image_source_url );

Expand Down Expand Up @@ -2349,8 +2354,8 @@ public function render_magic_tags( $default, $tags, $type ) {
$disabled[ str_replace( ':disabled', '', $tag ) ] = $label;
continue;
}
if ( in_array( $type, array( 'import_post_content', 'import_post_featured_img' ), true ) ) {
if ( in_array( $tag, array( 'item_content', 'item_description', 'item_full_content', 'item_categories', 'item_image' ), true ) ) {
if ( in_array( $type, array( 'import_post_content', 'import_post_featured_img', 'import_post_title' ), true ) ) {
if ( in_array( $tag, array( 'item_content', 'item_description', 'item_full_content', 'item_categories', 'item_image', 'item_title' ), true ) ) {
$default .= '<a class="dropdown-item" href="#" data-field-name="' . $type . '" data-field-tag="' . $tag . '" data-action_popup="' . $tag . '">' . $label . ' <small>[#' . $tag . ']</small></a>';
continue;
}
Expand Down Expand Up @@ -2956,16 +2961,16 @@ private function wizard_import_feed() {
}

/**
* Handle item content actions.
* Get the content action runner used for processing the chained actions from the tags.
*
* @param string $actions Item content actions.
* @param string $type Action type.
* @return Feedzy_Rss_Feeds_Actions Instance of Feedzy_Rss_Feeds_Actions.
*/
public function handle_content_actions( $actions = '', $type = '' ) {
public function get_actions_runner( $actions = '', $type = '' ) {
$action_instance = Feedzy_Rss_Feeds_Actions::instance();
$action_instance->type = $type;
$action_instance->set_actions( $actions );
$action_instance->set_raw_serialized_actions( $actions );
$action_instance->set_settings( $this->settings );
return $action_instance;
}
Expand Down
4 changes: 2 additions & 2 deletions includes/gutenberg/build/block.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion includes/views/import-metabox-edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ class="dashicons dashicons-arrow-down-alt2"></span>
?>
</div>
</div>
<div class="fz-input-group-right">
<div class="fz-input-group-right fz-title-action-tags">
<div class="dropdown">
<button type="button" class="btn btn-outline-primary btn-add-fields dropdown-toggle" aria-haspopup="true" aria-expanded="false">
<?php esc_html_e( 'Insert Tag', 'feedzy-rss-feeds' ); ?> <span class="dashicons dashicons-plus-alt2"></span>
Expand Down
Loading
Loading