';
49 |
50 | $upsell_notice .= wp_sprintf(
51 | // translators: %1$s: opening anchor tag, %2$s: closing anchor tag
52 | __( 'Create Reusable Elementor Templates with Feedzy\'s Dynamic Tags Using Feedzy Pro. %1$sLearn more%2$s', 'feedzy-rss-feeds' ),
53 | '
';
58 | wp_localize_script(
59 | 'feedzy-elementor',
60 | 'FeedzyElementorEditor',
61 | array(
62 | 'notice' => $notice_text,
63 | 'security' => wp_create_nonce( FEEDZY_BASEFILE ),
64 | 'pro_title_text' => __( 'Unlock this feature with Feedzy Pro', 'feedzy-rss-feeds' ),
65 | 'upsell_notice' => ( ! feedzy_is_pro() && ! \Feedzy_Rss_Feeds_Ui::had_dismissed_notice() ) ? $upsell_notice : '',
66 | )
67 | );
68 | }
69 |
70 | /**
71 | * Render choose control output in the editor.
72 | *
73 | * Used to generate the control HTML in the editor using Underscore JS
74 | * template. The variables for the class are available using `data` JS
75 | * object.
76 | *
77 | * @since 1.0.0
78 | * @access public
79 | */
80 | public function content_template() {
81 | $control_uid_input_type = '{{value}}';
82 | ?>
83 |
110 |
111 | <# if ( data.description ) { #>
112 |
{{{ data.description }}}
113 | <# } #>
114 | array(),
131 | 'toggle' => true,
132 | );
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/includes/elementor/feedzy-rss-feeds-elementor.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class Feedzy_Rss_Feeds_Elementor {
17 |
18 | /**
19 | * Enqueue scripts.
20 | */
21 | public function feedzy_elementor_widgets_assets() {
22 | wp_register_style( 'feedzy-elementor', FEEDZY_ABSURL . 'css/feedzy-elementor-widget.css', array(), true );
23 | wp_enqueue_style( 'feedzy-elementor' );
24 | }
25 |
26 | /**
27 | * Editor frontend before script.
28 | */
29 | public function feedzy_elementor_before_enqueue_scripts() {
30 | wp_register_style( 'feedzy-rss-feeds-elementor', FEEDZY_ABSURL . 'css/feedzy-rss-feeds.css', array( 'elementor-frontend' ), true, 'all' );
31 | wp_enqueue_style( 'feedzy-rss-feeds-elementor' );
32 | }
33 |
34 | /**
35 | * Register feedzy widget.
36 | *
37 | * @return void
38 | */
39 | public function feedzy_elementor_widgets_registered() {
40 | // We check if the Elementor plugin has been installed / activated.
41 | if ( defined( 'ELEMENTOR_PATH' ) && class_exists( 'Elementor\Widget_Base' ) ) {
42 | require_once FEEDZY_ABSPATH . '/includes/elementor/widgets/register-widget.php';
43 | \Elementor\Plugin::instance()->widgets_manager->register( new Feedzy_Register_Widget() );
44 |
45 | add_action( 'elementor/editor/after_enqueue_styles', array( $this, 'feedzy_elementor_widgets_assets' ) );
46 | }
47 | }
48 |
49 | /**
50 | * Register datetime-local control.
51 | *
52 | * @param \Elementor\Controls_Manager $controls_manager Elementor controls manager.
53 | * @return void
54 | */
55 | public function feedzy_elementor_register_datetime_local_control( $controls_manager ) {
56 | require_once FEEDZY_ABSPATH . '/includes/elementor/controls/datetime-local.php';
57 | require_once FEEDZY_ABSPATH . '/includes/elementor/controls/template-layout.php';
58 | $controls_manager->register( new \Elementor\Control_Date_Time_Local() );
59 | $controls_manager->register( new \Elementor\Control_Template_Layout() );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/includes/feedzy-rss-feeds-activator.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class Feedzy_Rss_Feeds_Activator {
23 |
24 | /**
25 | * Plugin activation action.
26 | *
27 | * Triggers the plugin activation action on plugin activate.
28 | *
29 | * @since 3.0.0
30 | * @access public
31 | */
32 | public static function activate() {
33 | $options = get_option( Feedzy_Rss_Feeds::get_plugin_name(), array() );
34 | $is_fresh_install = get_option( 'feedzy_fresh_install', false );
35 | $old_logger_option = get_option( 'feedzy_logger_flag', 'no' );
36 | if ( $old_logger_option === 'yes' ) {
37 | update_option( 'feedzy_rss_feeds_logger_flag', 'yes' );
38 | update_option( 'feedzy_logger_flag', 'no' );
39 | }
40 | if ( ! isset( $options['is_new'] ) ) {
41 | update_option(
42 | Feedzy_Rss_Feeds::get_plugin_name(),
43 | array(
44 | 'is_new' => 'yes',
45 | )
46 | );
47 | }
48 | if ( ! defined( 'TI_CYPRESS_TESTING' ) && false === $is_fresh_install ) {
49 | update_option( 'feedzy_fresh_install', '1' );
50 | }
51 | add_option( 'feedzy-activated', true );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/includes/feedzy-rss-feeds-deactivator.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class Feedzy_Rss_Feeds_Deactivator {
23 |
24 | /**
25 | * Short Description. (use period)
26 | *
27 | * Long Description.
28 | *
29 | * @since 3.0.0
30 | * @access public
31 | */
32 | public static function deactivate() {
33 | delete_option( 'feedzy-activated' );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/includes/layouts/feedzy-documentation.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
60 |
61 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
107 |
108 |
109 |
110 |
113 |
114 |
--------------------------------------------------------------------------------
/includes/layouts/feedzy-improve.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/includes/layouts/feedzy-support.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
38 |
39 |
67 |
68 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
99 |
100 | ',
106 | ''
107 | )
108 | );
109 | ?>
110 |
111 |
112 |
113 |
114 |
117 |
118 |
119 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/includes/layouts/header.php:
--------------------------------------------------------------------------------
1 |
18 |
27 |
--------------------------------------------------------------------------------
/includes/layouts/integration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 | notice ) { ?>
18 |
19 |
20 |
21 | error ) { ?>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
62 |
63 |
80 |
81 |
111 |
112 |
113 |
114 |
115 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/includes/util/feedzy-rss-feeds-util-scheduler.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class Feedzy_Rss_Feeds_Util_Scheduler {
21 |
22 | /**
23 | * Check if an action hook is scheduled.
24 | *
25 | * @param string $hook The hook to check.
26 | * @param array $args Optional. Arguments to pass to the hook
27 | *
28 | * @return bool|int
29 | */
30 | public static function is_scheduled( string $hook, array $args = array() ) {
31 | if ( function_exists( 'as_next_scheduled_action' ) ) {
32 | // For older versions of AS.
33 | return as_next_scheduled_action( $hook, $args );
34 | }
35 | if ( function_exists( 'as_has_scheduled_action' ) ) {
36 | return as_has_scheduled_action( $hook, $args );
37 | }
38 |
39 | return wp_next_scheduled( $hook, $args );
40 | }
41 |
42 | /**
43 | * Clear scheduled hook.
44 | *
45 | * @param string $hook The name of the hook to clear.
46 | * @param array $args Optional. Arguments that were to be passed to the hook's callback function. Default empty array.
47 | * @return mixed The scheduled action ID if a scheduled action was found, or null if no matching action found. If WP_Cron is used, on success an integer indicating number of events unscheduled, false or WP_Error if unscheduling one or more events fail.
48 | */
49 | public static function clear_scheduled_hook( $hook, $args = array() ) {
50 | if ( function_exists( 'as_unschedule_all_actions' ) ) {
51 | return as_unschedule_all_actions( $hook, $args );
52 | }
53 |
54 | return wp_clear_scheduled_hook( $hook, $args );
55 | }
56 |
57 | /**
58 | * Schedule an event.
59 | *
60 | * @param int $time The first time that the event will occur.
61 | * @param string $recurrence How often the event should recur. See wp_get_schedules() for accepted values.
62 | * @param string $hook The name of the hook that will be triggered by the event.
63 | * @param array $args Optional. Arguments to pass to the hook's callback function. Default empty array.
64 | * @return integer|bool|WP_Error The action ID if Action Scheduler is used. True if event successfully scheduled, False or WP_Error on failure if WP Cron is used.
65 | */
66 | public static function schedule_event( $time, $recurrence, $hook, $args = array() ) {
67 | if ( function_exists( 'as_schedule_recurring_action' ) ) {
68 | $schedules = wp_get_schedules();
69 | if ( isset( $schedules[ $recurrence ] ) ) {
70 | $interval = $schedules[ $recurrence ]['interval'];
71 | return as_schedule_recurring_action( $time, $interval, $hook, $args );
72 | }
73 | }
74 |
75 | return wp_schedule_event( $time, $recurrence, $hook, $args );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/includes/util/feedzy-rss-feeds-util-simplepie.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | class Feedzy_Rss_Feeds_Util_SimplePie extends SimplePie {
27 |
28 | /**
29 | * The shortcode attributes.
30 | *
31 | * @access private
32 | * @var array $sc The shortcode attributes.
33 | */
34 | private static $sc;
35 |
36 | /**
37 | * Whether custom sorting is enabled.
38 | *
39 | * @access private
40 | * @var bool $custom_sorting Whether custom sorting is enabled.
41 | */
42 | private static $custom_sorting = false;
43 |
44 | /**
45 | * Initialize the class and set its properties.
46 | *
47 | * @access public
48 | *
49 | * @param array $sc The shortcode attributes.
50 | */
51 | public function __construct( $sc ) {
52 | self::$sc = $sc;
53 | if ( array_key_exists( 'sort', self::$sc ) && ! empty( self::$sc['sort'] ) ) {
54 | if ( 'date_desc' === self::$sc['sort'] ) {
55 | $this->enable_order_by_date( true );
56 | } else {
57 | self::$custom_sorting = true;
58 | }
59 | }
60 | parent::__construct();
61 | }
62 |
63 | /**
64 | * Sorting callback for items
65 | *
66 | * @access public
67 | * @param SimplePie $a The SimplePieItem.
68 | * @param SimplePie $b The SimplePieItem.
69 | * @return boolean
70 | */
71 | public static function sort_items( $a, $b ) {
72 | if ( self::$custom_sorting ) {
73 | switch ( self::$sc['sort'] ) {
74 | case 'title_desc':
75 | return $a->get_title() <= $b->get_title();
76 | case 'title_asc':
77 | return $a->get_title() > $b->get_title();
78 | case 'date_asc':
79 | return $a->get_date( 'U' ) > $b->get_date( 'U' );
80 | }
81 | }
82 | return parent::sort_items( $a, $b );
83 | }
84 |
85 | /**
86 | * Return the filename (i.e. hash, without path and without extension) of the file to cache a given URL.
87 | *
88 | * @param string $url The URL of the feed to be cached.
89 | * @return string A filename (i.e. hash, without path and without extension).
90 | */
91 | public function get_cache_filename( $url ) {
92 | // Append custom parameters to the URL to avoid cache pollution in case of multiple calls with different parameters.
93 | $url .= $this->force_feed ? '#force_feed' : '';
94 | return call_user_func( $this->cache_name_function, $url );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/includes/views/amazon-product-advertising-view.php:
--------------------------------------------------------------------------------
1 |
73 |
--------------------------------------------------------------------------------
/includes/views/css/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/includes/views/css/chosen-sprite.png
--------------------------------------------------------------------------------
/includes/views/css/chosen-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/includes/views/css/chosen-sprite@2x.png
--------------------------------------------------------------------------------
/includes/views/css/import-metabox-edit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * import-metabox-edit.css
3 | *
4 | * @since 1.2.0
5 | * @package feedzy-rss-feeds-pro
6 | */
7 |
8 | .feedzy-toggle {
9 | visibility: hidden;
10 | position: absolute;
11 | margin-left: -9999px;
12 | }
13 |
14 | .feedzy-toggle + label {
15 | display: block;
16 | position: relative;
17 | outline: none;
18 | cursor: pointer;
19 | -webkit-user-select: none;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | user-select: none;
23 | }
24 |
25 | input.feedzy-toggle-round + label {
26 | width: 2rem;
27 | height: 1rem;
28 | border: 1px solid #5555559c;
29 | border-radius: 22px;
30 | background-color: #f1f1f1;
31 | }
32 |
33 | input.feedzy-toggle-round + label:before,
34 | input.feedzy-toggle-round + label:after {
35 | display: block;
36 | position: absolute;
37 | top: 1px;
38 | bottom: 1px;
39 | left: 1px;
40 | content: "";
41 | }
42 |
43 | input.feedzy-toggle-round + label:before {
44 | right: 1px;
45 | border-radius: 15px;
46 | background-color: #f1f1f1;
47 | -webkit-transition: background 0.4s;
48 | transition: background 0.4s;
49 | }
50 |
51 | input.feedzy-toggle-round + label:after {
52 | width: 0.75rem;
53 | height: 0.75rem;
54 | border-radius: 100%;
55 | background-color: #f6f7f7;
56 | -webkit-transition: margin 0.4s;
57 | transition: margin 0.4s;
58 | }
59 |
60 | input.feedzy-toggle-round:checked + label:before {
61 | background-color:#2271b1;
62 | }
63 |
64 | input.feedzy-toggle-round:checked + label:after {
65 | margin-left: 16px;
66 | background: #f1f1f1;
67 | }
68 | input.feedzy-toggle-round:checked + label{
69 | border-color: #f6f7f7;
70 | }
71 | .feedzy-dialog:not(.feedzy-dialog-content){
72 | display:none;
73 | }
74 |
75 | input.feedzy-toggle-round + label:before,
76 | input.feedzy-toggle-round + label:after {
77 | display: block;
78 | position: absolute;
79 | top: 1px;
80 | bottom: 1px;
81 | left: 1px;
82 | content: "";
83 | }
84 |
85 | input.feedzy-toggle-round + label:before {
86 | right: 1px;
87 | border-radius: 15px;
88 | background-color: #f1f1f1;
89 | -webkit-transition: background 0.4s;
90 | transition: background 0.4s;
91 | }
92 |
93 | input.feedzy-toggle-round + label:after {
94 | border-radius: 100%;
95 | background-color:#5555559c;
96 | -webkit-transition: margin 0.4s;
97 | transition: margin 0.4s;
98 | margin-top:1px;
99 | }
100 | span.feedzy-spinner:not(.is-active) {
101 | display: none;
102 | }
103 | span.feedzy-spinner {
104 | float: none !important;
105 | }
106 | #TB_ajaxContent ul {
107 | list-style: decimal;
108 | margin-left: 20px;
109 | }
110 |
111 | #TB_ajaxContent ul li {
112 | padding: 5px !important;
113 | }
114 |
115 | #TB_ajaxContent p.loading-img {
116 | text-align: center;
117 | }
118 |
119 | #TB_ajaxContent.loaded p.hide-when-loaded {
120 | display: none;
121 | }
122 |
123 | #TB_ajaxContent div.dry_run span {
124 | display: block;
125 | }
126 |
127 | #TB_ajaxContent div.dry_run span i.pass {
128 | color: #149714;
129 | }
130 |
131 | #TB_ajaxContent div.dry_run span i.fail {
132 | color: #ca4a1f;
133 | }
134 |
135 | .feedzy-errors-dialog + .ui-widget-content .ui-dialog-buttonset {
136 | width: 100%;
137 | }
138 | .feedzy-errors-dialog + .ui-widget-content button.feedzy-clear-logs {
139 | margin-left: 0;
140 | }
141 |
142 | .wp-core-ui .fz-export-import-btn,
143 | .wp-core-ui .feedzy-import-limit{
144 | display: inline-flex !important;
145 | align-items: center;
146 | }
147 |
148 | .wp-core-ui .fz-header-action {
149 | display: inline-flex !important;
150 | gap: 8px;
151 | }
152 |
153 | #fz_import_export_upsell .modal-content {
154 | background: #fff;
155 | border-radius: 3px;]
156 | width: auto;
157 | margin: 1.75rem auto ;
158 | }
159 | #fz_import_export_upsell .modal-body {
160 | text-align: center;
161 | }
162 | #fz_import_export_upsell .modal-header {
163 | padding-bottom: 10px;
164 | margin-bottom: 10px;
165 | position: relative;
166 | }
167 | #fz_import_export_upsell .modal-header .dashicons {
168 | font-size: 1.3em;
169 | line-height: inherit;
170 | }
171 | #fz_import_export_upsell .modal-header h2 {
172 | text-align: center;
173 | }
174 | #fz_import_export_upsell .close-modal {
175 | position: absolute;
176 | top: 0;
177 | right: 0;
178 | }
179 | #fz_import_export_upsell .modal-footer .dashicons{
180 |
181 | vertical-align: middle;
182 | font-size: initial;
183 | }
184 | #fz_import_export_upsell .modal-footer {
185 | padding-top: 10px;
186 | margin-top: 10px;
187 | text-align: center;
188 | }
189 | .fz-import-field {
190 | background-color: #fff;
191 | max-width: 400px;
192 | width: 100%;
193 | margin: 0 auto;
194 | position: relative;
195 | padding: 15px;
196 | height: 120px;
197 | display: flex;
198 | align-items: center;
199 | justify-content: center;
200 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
201 | }
202 | .fz-import-field.hidden {
203 | display: none;
204 | }
205 |
--------------------------------------------------------------------------------
/includes/views/misc-view.php:
--------------------------------------------------------------------------------
1 |
2 |
42 |
--------------------------------------------------------------------------------
/includes/views/openai-view.php:
--------------------------------------------------------------------------------
1 |
48 |
--------------------------------------------------------------------------------
/includes/views/openrouter-view.php:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/includes/views/spinnerchief-view.php:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/includes/views/wordai-view.php:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | {
8 | if ( props.data && props.data.length === 0 ) {
9 | return(
10 | <>>
11 | );
12 | }
13 |
14 | return (
15 | <>
16 |
17 |
18 | {props.data.map((value, index) => '' !== value.id && (
19 |
20 | ))}
21 |
22 |
23 | >
24 | );
25 | };
26 | export default SortableContainer(Actions);
--------------------------------------------------------------------------------
/js/Conditions/DateTimeControl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import {
7 | BaseControl,
8 | Button,
9 | DateTimePicker,
10 | Dropdown,
11 | } from '@wordpress/components';
12 |
13 | // eslint-disable-next-line @wordpress/no-unsafe-wp-apis
14 | import { format, __experimentalGetSettings } from '@wordpress/date';
15 |
16 | const DateTimeControl = ({ index, label, value, onChange }) => {
17 | const settings = __experimentalGetSettings();
18 |
19 | return (
20 |
21 | (
24 | <>
25 |
30 | {value
31 | ? format(settings.formats.datetime, value)
32 | : __('Select Date', 'feedzy-rss-feeds')}
33 |
34 | >
35 | )}
36 | renderContent={() => (
37 |
38 | )}
39 | />
40 |
41 | );
42 | };
43 |
44 | export default DateTimeControl;
45 |
--------------------------------------------------------------------------------
/js/Conditions/PanelTab.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import { Button } from '@wordpress/components';
7 |
8 | import { useState } from '@wordpress/element';
9 |
10 | const PanelTab = ({ label, onDelete, initialOpen = false, children }) => {
11 | const [isOpen, setOpen] = useState(initialOpen);
12 |
13 | return (
14 |
15 |
16 |
setOpen(!isOpen)}
19 | >
20 | {label}
21 |
22 |
23 |
setOpen(!isOpen)}
32 | />
33 |
34 |
40 |
41 |
42 | {isOpen &&
{children}
}
43 |
44 | );
45 | };
46 |
47 | export default PanelTab;
48 |
--------------------------------------------------------------------------------
/js/Conditions/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import domReady from '@wordpress/dom-ready';
5 |
6 | import { createRoot, useEffect, useState } from '@wordpress/element';
7 |
8 | /**
9 | * Internal dependencies.
10 | */
11 | import ConditionsControl from './ConditionsControl';
12 |
13 | const dummyConditions = {
14 | match: 'all',
15 | conditions: [
16 | {
17 | field: 'title',
18 | operator: 'contains',
19 | value: 'Sports',
20 | },
21 | ],
22 | };
23 |
24 | const App = () => {
25 | const [conditions, setConditions] = useState({
26 | conditions: [],
27 | match: 'all',
28 | });
29 |
30 | useEffect(() => {
31 | if (!feedzyData.isPro) {
32 | setConditions(dummyConditions);
33 | return;
34 | }
35 |
36 | const field = document.getElementById('feed-post-filters-conditions');
37 | if (field && field.value) {
38 | const parsedConditions = JSON.parse(field.value);
39 | setConditions(
40 | parsedConditions && parsedConditions.conditions
41 | ? parsedConditions
42 | : { conditions: [], match: 'all' }
43 | );
44 | }
45 | }, []);
46 |
47 | useEffect(() => {
48 | if (!feedzyData.isPro) {
49 | return;
50 | }
51 |
52 | document.getElementById('feed-post-filters-conditions').value =
53 | JSON.stringify(conditions);
54 | }, [conditions]);
55 |
56 | return (
57 |
61 | );
62 | };
63 |
64 | domReady(() => {
65 | const root = createRoot(document.getElementById('fz-conditions'));
66 | root.render(
);
67 | });
68 |
--------------------------------------------------------------------------------
/js/FeedBack/feedback-form.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import classnames from 'classnames';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import {
10 | useEffect,
11 | useState
12 | } from '@wordpress/element';
13 |
14 | import {
15 | Button,
16 | Spinner,
17 | TextareaControl
18 | } from '@wordpress/components';
19 |
20 | import { __ } from '@wordpress/i18n';
21 |
22 | const { pluginVersion } = window.feedzyObj || {};
23 |
24 | const collectedInfo = [
25 | {
26 | name: __( 'Plugin version', 'feedzy-rss-feeds' ),
27 | value: pluginVersion
28 | },
29 | {
30 | name: __( 'Feedback', 'feedzy-rss-feeds' ),
31 | value: __( 'Text from the above text area', 'feedzy-rss-feeds' )
32 | }
33 | ];
34 |
35 | const helpTextByStatus = {
36 | error: __( 'There has been an error. Your feedback couldn\'t be sent.', 'feedzy-rss-feeds' ),
37 | emptyFeedback: __( 'Please provide a feedback before submitting the form.', 'feedzy-rss-feeds' )
38 | };
39 |
40 | /**
41 | * Displays a button that opens a modal for sending feedback
42 | *
43 | * @param {import('./type').FeedbackFormProps} props
44 | * @return
45 | */
46 | const FeedbackForm = ({
47 | source,
48 | status,
49 | setStatus
50 | }) => {
51 | const [ feedback, setFeedback ] = useState( '' );
52 | const [ showInfo, setShowInfo ] = useState( false );
53 |
54 | useEffect( () => {
55 | const info = document.querySelector( '.fz-feedback-form .info' );
56 | if ( info ) {
57 | info.style.height = showInfo ? `${ info.querySelector( '.wrapper' )?.clientHeight }px` : '0';
58 | }
59 |
60 | }, [ showInfo ]);
61 |
62 | const sendFeedback = () => {
63 | const trimmedFeedback = feedback.trim();
64 | if ( 5 >= trimmedFeedback.length ) {
65 | setStatus( 'emptyFeedback' );
66 | return;
67 | }
68 |
69 | setStatus( 'loading' );
70 | try {
71 | fetch( 'https://api.themeisle.com/tracking/feedback', {
72 | method: 'POST',
73 | headers: {
74 | 'Content-Type': 'application/json',
75 | 'Accept': 'application/json, */*;q=0.1',
76 | 'Cache-Control': 'no-cache'
77 | },
78 | body: JSON.stringify({
79 | slug: 'feedzy-rss-feeds',
80 | version: pluginVersion,
81 | feedback: trimmedFeedback,
82 | data: {
83 | 'feedback-area': source
84 | }
85 | })
86 | }).then( r => {
87 | if ( ! r.ok ) {
88 | setStatus( 'error' );
89 | return;
90 | }
91 |
92 | setStatus( 'submitted' );
93 | })?.catch( ( error ) => {
94 | console.warn( error.message );
95 | setStatus( 'error' );
96 | });
97 | } catch ( error ) {
98 | console.warn( error.message );
99 | setStatus( 'error' );
100 | }
101 | };
102 |
103 | return (
104 |
163 | );
164 | };
165 |
166 | export default FeedbackForm;
--------------------------------------------------------------------------------
/js/FeedBack/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | /**
5 | * External dependencies
6 | */
7 | import classnames from 'classnames';
8 |
9 | /**
10 | * WordPress dependencies
11 | */
12 | import {
13 | Fragment,
14 | useState
15 | } from '@wordpress/element';
16 |
17 | import {
18 | Modal
19 | } from '@wordpress/components';
20 |
21 | import { __ } from '@wordpress/i18n';
22 |
23 | /**
24 | * Internal dependencies
25 | */
26 | import FeedbackForm from './feedback-form';
27 |
28 | const finishIcon = `${ window.feedzyObj.assetsUrl }img/finish-feedback.svg`;
29 |
30 | const FeedBack = () => {
31 | const [ isOpen, setOpen ] = useState( false );
32 | const [ status, setStatus ] = useState( 'notSubmitted' );
33 | const closeModal = () => setOpen( false );
34 |
35 | document.querySelectorAll( '[role="button"]' ).forEach((button) => {
36 | button.addEventListener('keydown', (evt) => {
37 | if(evt.keyCode === 13 || evt.keyCode === 32) {
38 | button.click();
39 | }
40 | });
41 | });
42 | document.querySelector( '#fz-feedback-btn' ).addEventListener( 'click', () => setOpen( true ) );
43 |
44 | return (
45 |
46 | { isOpen && (
47 |
55 | { 'submitted' !== status ? (
56 |
61 | ) : (
62 |
63 |
66 |
{ __( 'Thank you for your feedback', 'feedzy-rss-feeds' ) }
67 |
{ __( 'Your feedback is highly appreciated and will help us to improve Feedzy RSS Feeds.', 'feedzy-rss-feeds' ) }
68 |
69 | ) }
70 |
71 | ) }
72 |
73 | );
74 | };
75 |
76 | ReactDOM.render(
77 |
,
78 | document.querySelector('#fz-feedback-modal')
79 | );
--------------------------------------------------------------------------------
/js/FeedzyBlock/attributes.js:
--------------------------------------------------------------------------------
1 | // jshint ignore: start
2 |
3 | const attributes = {
4 | feeds: {
5 | type: 'string',
6 | },
7 | max: {
8 | type: 'number',
9 | default: 5,
10 | },
11 | offset: {
12 | type: 'number',
13 | default: 0,
14 | },
15 | feed_title: {
16 | type: 'boolean',
17 | default: true,
18 | },
19 | refresh: {
20 | type: 'string',
21 | default: '12_hours',
22 | },
23 | sort: {
24 | type: 'string',
25 | default: 'default',
26 | },
27 | target: {
28 | type: 'string',
29 | default: '_blank',
30 | },
31 | title: {
32 | type: 'number',
33 | },
34 | meta: {
35 | type: 'boolean',
36 | default: true,
37 | },
38 | lazy: {
39 | type: 'boolean',
40 | default: false,
41 | },
42 | metafields: {
43 | type: 'string',
44 | default: '',
45 | },
46 | multiple_meta: {
47 | type: 'string',
48 | default: '',
49 | },
50 | summary: {
51 | type: 'boolean',
52 | default: true,
53 | },
54 | summarylength: {
55 | type: 'number',
56 | },
57 | keywords_title: {
58 | type: 'string',
59 | },
60 | keywords_inc_on: {
61 | type: 'string',
62 | default: 'title',
63 | },
64 | keywords_ban: {
65 | type: 'string',
66 | },
67 | keywords_exc_on: {
68 | type: 'string',
69 | default: 'title',
70 | },
71 | thumb: {
72 | type: 'string',
73 | default: 'auto',
74 | },
75 | default: {
76 | type: 'object',
77 | },
78 | size: {
79 | type: 'number',
80 | default: 150,
81 | },
82 | http: {
83 | type: 'string',
84 | },
85 | referral_url: {
86 | type: 'string',
87 | },
88 | columns: {
89 | type: 'number',
90 | default: 1,
91 | },
92 | template: {
93 | type: 'string',
94 | default: 'default',
95 | },
96 | price: {
97 | type: 'boolean',
98 | default: true,
99 | },
100 | route: {
101 | type: 'string',
102 | default: 'home',
103 | },
104 | feedData: {
105 | type: 'object',
106 | },
107 | categories: {
108 | type: 'object',
109 | },
110 | from_datetime: {
111 | type: 'string',
112 | },
113 | to_datetime: {
114 | type: 'string',
115 | },
116 | itemTitle: {
117 | type: 'boolean',
118 | default: true,
119 | },
120 | disableStyle: {
121 | type: 'boolean',
122 | default: false,
123 | },
124 | follow: {
125 | type: 'string',
126 | default: 'no',
127 | },
128 | error_empty: {
129 | type: 'string',
130 | default: '',
131 | },
132 | className: {
133 | type: 'string',
134 | default: '',
135 | },
136 | _dryrun_: {
137 | type: 'string',
138 | default: 'no',
139 | },
140 | _dry_run_tags_: {
141 | type: 'string',
142 | default: '',
143 | }
144 | };
145 |
146 | export default attributes;
--------------------------------------------------------------------------------
/js/FeedzyBlock/index.js:
--------------------------------------------------------------------------------
1 | // jshint ignore: start
2 | import { __ } from '@wordpress/i18n';
3 | import { registerBlockType } from '@wordpress/blocks';
4 |
5 | /**
6 | * Block dependencies
7 | */
8 | import './style.scss';
9 | import blockAttributes from './attributes.js';
10 | import Editor from './Editor.js';
11 |
12 | /**
13 | * Register block
14 | */
15 | export default registerBlockType('feedzy-rss-feeds/feedzy-block', {
16 | title: __('Feedzy RSS Feeds (Classic)', 'feedzy-rss-feeds'),
17 | category: 'common',
18 | icon: 'rss',
19 | keywords: [
20 | __('Feedzy RSS Feeds', 'feedzy-rss-feeds'),
21 | __('RSS', 'feedzy-rss-feeds'),
22 | __('Feeds', 'feedzy-rss-feeds'),
23 | ],
24 | supports: {
25 | html: false,
26 | },
27 | attributes: blockAttributes,
28 | edit: Editor,
29 | save() {
30 | // Rendering in PHP
31 | return null;
32 | },
33 | });
34 |
--------------------------------------------------------------------------------
/js/FeedzyBlock/radio-image-control/index.js:
--------------------------------------------------------------------------------
1 | // jshint ignore: start
2 |
3 | /**
4 | * Block dependencies
5 | */
6 | import './style.scss';
7 | import { isEmpty } from 'lodash';
8 | import { BaseControl } from '@wordpress/components';
9 | import { withInstanceId } from '@wordpress/compose';
10 |
11 | function RadioImageControl({
12 | label,
13 | selected,
14 | help,
15 | instanceId,
16 | onChange,
17 | disabled,
18 | options = [],
19 | }) {
20 | const id = `inspector-radio-image-control-${instanceId}`;
21 | const onChangeValue = (event) => onChange(event.target.value);
22 |
23 | return (
24 | !isEmpty(options) && (
25 |
31 |
32 | {options.map((option, index) => (
33 |
37 |
50 |
54 |
55 |
56 |
57 |
{option.label}
58 |
59 | ))}
60 |
61 |
62 | )
63 | );
64 | }
65 |
66 | export default withInstanceId(RadioImageControl);
67 |
--------------------------------------------------------------------------------
/js/FeedzyBlock/radio-image-control/style.scss:
--------------------------------------------------------------------------------
1 | .components-radio-image-control__container {
2 | display: block;
3 | }
4 |
5 | .components-radio-image-control__option {
6 | display: inline-block;
7 | padding: 5px;
8 | }
9 |
10 | .components-radio-image-control {
11 |
12 | label {
13 | display: inline-block;
14 | position: relative;
15 |
16 | img {
17 | border: 1px solid transparent;
18 | max-width: 250px !important;
19 | }
20 | }
21 |
22 | input {
23 | display: none;
24 | }
25 |
26 | input + label {
27 | .image-clickable {
28 | bottom: 0;
29 | height: 100%;
30 | left: 0;
31 | position: absolute;
32 | right: 0;
33 | top: 0;
34 | width: 100%;
35 | }
36 | }
37 |
38 | input:checked + label {
39 | img {
40 | border: 1px solid #3498DB;
41 | -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25);
42 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/js/FeedzyBlock/style.scss:
--------------------------------------------------------------------------------
1 | .wp-block-feedzy-rss-feeds-feedzy-block {
2 | .feedzy-source-wrap {
3 | position: relative;
4 | }
5 | .feedzy-source {
6 | margin-right: 10px;
7 | }
8 | .feedzy-source + .dashicons-arrow-down-alt2 {
9 | position: absolute;
10 | right: 12px;
11 | top: 5px;
12 | z-index: 5;
13 | color: #757575;
14 | cursor: pointer;
15 | }
16 | }
17 |
18 | .loadFeed {
19 | margin-bottom: 10px;
20 | }
21 |
22 | .feedzy-blocks-base-control {
23 | padding-bottom: 10px;
24 |
25 | label {
26 | padding-bottom: 10px;
27 | }
28 |
29 | .feedzy_image_upload {
30 | display: block;
31 | margin-bottom: 10px;
32 | }
33 | }
34 |
35 | .feedzy-select-cat {
36 | width: 100%;
37 |
38 | select {
39 | width: auto;
40 | }
41 | }
42 |
43 | .feedzy-rss {
44 | .rss_image {
45 | span.fetched {
46 | display: inline-block;
47 | position: absolute;
48 | width: 100%;
49 | height: 100%;
50 | background-position: 50%;
51 | background-size: cover;
52 | }
53 | }
54 | }
55 | .feedzy-ui-autocomplete {
56 | max-height: 200px;
57 | overflow-y: auto;
58 | overflow-x: hidden;
59 | padding-right: 20px;
60 | }
61 | .fz-section-header-panel {
62 | &.is-opened {
63 | padding: 0;
64 | }
65 | .header-tab {
66 | display: inline-block;
67 | width: calc( 100% / 3 );
68 | height: auto;
69 | padding: 10px 20px;
70 | text-align: center;
71 | cursor: pointer;
72 |
73 | &.is-selected {
74 | border-bottom: 2px solid #0085ba;
75 | background: #f3f4f5;
76 | }
77 |
78 | &:hover {
79 | &:not(:disabled):not([aria-disabled="true"]):not(.is-secondary):not(.is-primary):not(.is-tertiary):not(.is-link) {
80 | background: #f3f4f5;
81 | box-shadow: none;
82 | }
83 | }
84 |
85 | span {
86 | display: inline-block;
87 | font-size: 12px;
88 |
89 | .dashicon {
90 | display: block;
91 | margin: 0 auto;
92 | font-size: 20px;
93 | }
94 | }
95 | }
96 | }
97 | .block-editor-block-inspector {
98 | .components-base-control {
99 | margin: 24px 0;
100 | }
101 | }
102 | .fz-locked {
103 | cursor: not-allowed;
104 | >div {
105 | &:not(.fz-upsell-notice) {
106 | opacity: 0.4;
107 | pointer-events: none;
108 | }
109 | }
110 | >p {
111 | opacity: 0.4;
112 | pointer-events: none;
113 | }
114 | >.fz-main-label {
115 | opacity: 0.4;
116 | pointer-events: none;
117 | }
118 | }
119 | .fz-pro-label {
120 | background: #4268CF;
121 | display: flex;
122 | flex-direction: row;
123 | justify-content: center;
124 | align-items: center;
125 | padding: 5px 10px;
126 | color: #FFFFFF;
127 | border-radius: 2px;
128 | font-weight: 700;
129 | font-size: 9.152px;
130 | line-height: 10px;
131 | flex: none;
132 | order: 0;
133 | flex-grow: 0;
134 | margin: 0px 10px;
135 | text-transform: uppercase;
136 | }
137 | .fz-upsell-notice {
138 | font-style: italic;
139 | font-weight: 500;
140 | font-size: 12px;
141 | line-height: 18px;
142 | color: #1E1E1E;
143 | }
144 |
145 | .feedzy-template{
146 | .components-radio-image-control__container{
147 | display: flex;
148 | flex-wrap: wrap;
149 | margin: 0 -10px;
150 | .components-radio-image-control__option{
151 | width: 50%;
152 | padding: 0 10px 20px;
153 | label{
154 | height: 74px;
155 | display: flex;
156 | align-items: center;
157 | justify-content: center;
158 | background: #F3F4F5;
159 | border: 1px solid #E2E4E7;
160 | border-radius: 4px;
161 | &:hover{
162 | background: #ffffff;
163 | }
164 | }
165 | span{
166 | display: block;
167 | text-align: center;
168 | padding-top: 6px;
169 | color: #A8A8A8;
170 | font-weight: 500;
171 | font-size: 12px;
172 | line-height: 18px;
173 | }
174 | }
175 | }
176 | &.components-radio-image-control input:checked + label{
177 | border-color: #0071AE;
178 | box-shadow: 0 0 0 1px #0071AE;
179 | img{
180 | border: 0;
181 | box-shadow: none;
182 | }
183 | }
184 | }
185 | .fz-upgrade-alert{
186 | background-color: #F7F7F7;
187 | border-left: 4px solid #4268CF;
188 | padding: 8px 12px;
189 | font-style: italic;
190 | font-size: 13px;
191 | line-height: 15px;
192 | color: #1E1E1E;
193 | position: relative;
194 | a {
195 | color: #4268CF !important;
196 | font-weight: 700;
197 | }
198 | span.dashicons-no-alt{
199 | position: absolute;
200 | right: 4px;
201 | top: 4px;
202 | color: #6F7882;
203 | cursor: pointer;
204 | }
205 | }
206 | .fz-source-upgrade-alert{
207 | width: 100%;
208 | background-color: #CCE5FF;
209 | border-left: 4px solid #4268CF;
210 | padding: 15px 5px;
211 | font-style: italic;
212 | font-size: 13px;
213 | line-height: 15px;
214 | color: #264494;
215 | a{
216 | color: #264494 !important;
217 | font-weight: 700;
218 | }
219 | }
220 | .feedzy-ui-autocomplete li.ui-menu-item {
221 | font-size: 13px;
222 | line-height: 1.4em;
223 | color: #3c434a;
224 | }
--------------------------------------------------------------------------------
/js/FeedzyBlock/utils.js:
--------------------------------------------------------------------------------
1 | // jshint ignore: start
2 |
3 | export const unescapeHTML = value => {
4 | const htmlNode = document.createElement( 'div' );
5 | htmlNode.innerHTML = value;
6 | if( htmlNode.innerText !== undefined ) {
7 | return htmlNode.innerText;
8 | }
9 | return htmlNode.textContent;
10 | };
11 |
12 | export const filterData = ( arr, sortType, allowedKeywords, bannedKeywords, maxSize, offset, includeOn, excludeOn, fromDateTime, toDateTime ) => {
13 | includeOn = 'author' === includeOn ? 'creator' : includeOn;
14 | excludeOn = 'author' === excludeOn ? 'creator' : excludeOn;
15 | fromDateTime = '' !== fromDateTime && 'undefined' !== typeof fromDateTime ? moment( fromDateTime ).format( 'X' ) : false ;
16 | toDateTime = '' !== toDateTime && 'undefined' !== typeof toDateTime ? moment( toDateTime ).format( 'X' ) : false;
17 |
18 | arr = Array.from( arr ).sort( (a, b) => {
19 | let firstElement, secondElement;
20 | if ( sortType === 'date_desc' || sortType === 'date_asc' ) {
21 | firstElement = a.pubDate;
22 | secondElement = b.pubDate;
23 | } else if ( sortType === 'title_desc' || sortType === 'title_asc' ) {
24 | firstElement = a.title.toUpperCase();
25 | secondElement = b.title.toUpperCase();
26 | }
27 | if ( firstElement < secondElement ) {
28 | if ( sortType === 'date_desc' || sortType === 'title_desc' ) {
29 | return 1;
30 | } else {
31 | return -1;
32 | }
33 | }
34 | if ( firstElement > secondElement ) {
35 | if ( sortType === 'date_desc' || sortType === 'title_desc' ) {
36 | return -1;
37 | } else {
38 | return 1;
39 | }
40 | }
41 | // names must be equal
42 | return 0;
43 | }).filter( item => {
44 | if ( allowedKeywords ) {
45 | return allowedKeywords.test( item[ includeOn ] );
46 | }
47 | return true;
48 | }).filter( item => {
49 | if ( bannedKeywords ) {
50 | return ! bannedKeywords.test( item[ excludeOn ] );
51 | }
52 | return true;
53 | }).filter( item => {
54 | let itemDateTime = item.date + ' ' + item.time;
55 | itemDateTime = moment( new Date( itemDateTime ) ).format( 'X' );
56 | if ( fromDateTime && toDateTime ) {
57 | return ( ( fromDateTime <= itemDateTime ) && ( itemDateTime <= toDateTime ) );
58 | }
59 | return true;
60 | }).slice( offset, maxSize + offset );
61 | return arr;
62 | };
63 |
64 | export const inArray = ( value, arr ) => {
65 | if ( arr === undefined ) {
66 | return false;
67 | };
68 | let exists = false;
69 | for( let i = 0; i < arr.length; i++ ) {
70 | let name = arr[i];
71 | if ( name === value ) {
72 | exists = true;
73 | break;
74 | }
75 | }
76 | return exists;
77 | };
78 |
79 | export const arrangeMeta = ( values, fields ) => {
80 | let meta = '';
81 |
82 | if(fields === ''){
83 | fields = 'author, date, time';
84 | }
85 |
86 | let arr = fields.replace(/\s/g,'').split( ',' );
87 |
88 | for(let i = 0; i < arr.length; i++){
89 | if(typeof values[ arr[i] ] !== 'undefined'){
90 | meta = meta + ' ' + values[ arr[i] ];
91 | }
92 | }
93 | return meta;
94 | };
95 |
96 | export const filterCustomPattern = ( keyword = '' ) => {
97 | let pattern = '';
98 | let regex = [];
99 | if ( '' !== keyword && keyword.replace( /[^a-zA-Z]/g, '' ).length <= 500 ) {
100 | keyword
101 | .split( ',' )
102 | .forEach( item => {
103 | item = item.trim();
104 | if ( '' !== item ) {
105 | item = item.split( '+' )
106 | .map( k => {
107 | k = k.trim();
108 | return '(?=.*' + k + ')';
109 | } );
110 | regex.push( item.join( '' ) );
111 | }
112 | } );
113 | pattern = regex.join( '|' );
114 | pattern = '^' + pattern + '.*$';
115 | pattern = new RegExp( pattern, 'i' );
116 | }
117 | return pattern;
118 | };
--------------------------------------------------------------------------------
/js/FeedzyLoop/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schemas.wp.org/trunk/block.json",
3 | "apiVersion": 3,
4 | "name": "feedzy-rss-feeds/loop",
5 | "version": "1.0.0",
6 | "title": "Feedzy Loop",
7 | "category": "common",
8 | "icon": "rss",
9 | "keywords": [
10 | "rss",
11 | "feed",
12 | "feedzy"
13 | ],
14 | "description": "Display curated RSS content in a dynamic, customizable loop directly in the Block Editor—no coding required.",
15 | "attributes": {
16 | "feed": {
17 | "type": "object",
18 | "properties": {
19 | "type": {
20 | "type": "string",
21 | "enum": [ "url", "group" ],
22 | "default": "url"
23 | },
24 | "source": {
25 | "type": [ "number", "array" ],
26 | "default": ""
27 | }
28 | }
29 | },
30 | "query": {
31 | "type": "object",
32 | "properties": {
33 | "max": {
34 | "type": "number",
35 | "default": 5
36 | },
37 | "sort": {
38 | "type": "string",
39 | "enum": [ "default", "date_desc", "date_asc", "title_desc", "title_asc" ],
40 | "default": "default"
41 | },
42 | "refresh": {
43 | "type": "string",
44 | "enum": [ "1_hours", "3_hours", "12_hours", "1_days", "3_days", "15_days" ],
45 | "default": "12_hours"
46 | }
47 | }
48 | },
49 | "layout": {
50 | "type": "object",
51 | "properties": {
52 | "columnCount": {
53 | "type": "number",
54 | "default": 1
55 | }
56 | }
57 | },
58 | "conditions": {
59 | "type": "object",
60 | "properties": {
61 | "match": {
62 | "type": "string",
63 | "enum": [ "all", "any" ],
64 | "default": "all"
65 | },
66 | "conditions": {
67 | "type": "array",
68 | "items": {
69 | "type": "object",
70 | "properties": {
71 | "field": {
72 | "type": "string",
73 | "default": "title"
74 | },
75 | "operator": {
76 | "type": "string",
77 | "default": "contains"
78 | },
79 | "value": {
80 | "type": "string",
81 | "default": ""
82 | }
83 | }
84 | }
85 | }
86 | }
87 | },
88 | "innerBlocksContent": {
89 | "type": "string",
90 | "default": ""
91 | }
92 | },
93 | "supports": {
94 | "align": [ "wide", "full" ],
95 | "anchor": true,
96 | "ariaLabel": true,
97 | "html": true,
98 | "color": {
99 | "gradients": true,
100 | "heading": true,
101 | "button": true,
102 | "link": true
103 | },
104 | "shadow": true,
105 | "spacing": {
106 | "margin": [ "top", "bottom" ],
107 | "padding": true,
108 | "blockGap": true
109 | },
110 | "dimensions": {
111 | "minHeight": true
112 | },
113 | "typography": {
114 | "fontSize": true,
115 | "lineHeight": true
116 | }
117 | },
118 | "editorScript": "file:./index.js",
119 | "editorStyle": "file:./index.css",
120 | "style": "file:./style-index.css",
121 | "textdomain": "feedzy-rss-block"
122 | }
--------------------------------------------------------------------------------
/js/FeedzyLoop/components/FeedControl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import { useState, useEffect, useRef } from '@wordpress/element';
7 |
8 | const FeedControl = ({ value, options, onChange }) => {
9 | const [isOpen, setIsOpen] = useState(false);
10 | const [inputValue, setInputValue] = useState('');
11 | const [selectedOption, setSelectedOption] = useState(null);
12 | const dropdownRef = useRef(null);
13 |
14 | // Initialize component state based on value prop
15 | useEffect(() => {
16 | if (value?.type === 'group' && value.source) {
17 | const selected = options.find((opt) => opt.value === value.source);
18 | setSelectedOption(selected || null);
19 | setInputValue('');
20 | } else if (value?.type === 'url' && Array.isArray(value.source)) {
21 | setSelectedOption(null);
22 | setInputValue(value.source.join(', '));
23 | }
24 | }, [value, options]);
25 |
26 | useEffect(() => {
27 | const handleClickOutside = (event) => {
28 | if (
29 | dropdownRef.current &&
30 | !dropdownRef.current.contains(event.target)
31 | ) {
32 | setIsOpen(false);
33 | }
34 | };
35 |
36 | document.addEventListener('mousedown', handleClickOutside);
37 | return () =>
38 | document.removeEventListener('mousedown', handleClickOutside);
39 | }, []);
40 |
41 | const handleSelectOption = (option) => {
42 | setSelectedOption(option);
43 | setInputValue('');
44 | setIsOpen(false);
45 | onChange({
46 | type: 'group',
47 | source: option.value,
48 | });
49 | };
50 |
51 | const handleInputChange = (e) => {
52 | const current = e.target.value;
53 | setInputValue(current);
54 | setSelectedOption(null);
55 | };
56 |
57 | const handleInputBlur = () => {
58 | onChange({
59 | type: 'url',
60 | source: inputValue
61 | ? inputValue
62 | .split(',')
63 | .map((url) => url.trim())
64 | .filter(Boolean)
65 | : [],
66 | });
67 | };
68 |
69 | const handleClear = () => {
70 | setSelectedOption(null);
71 | setInputValue('');
72 | onChange({
73 | type: 'url',
74 | source: [],
75 | });
76 | };
77 |
78 | return (
79 |
80 |
92 |
93 | {selectedOption && (
94 |
99 |
106 |
113 |
114 |
115 | )}
116 |
setIsOpen(!isOpen)}
118 | className="fz-dropdown-button"
119 | title={__('Select Feed Group', 'feedzy-rss-feeds')}
120 | >
121 |
134 |
141 |
142 |
143 |
144 |
145 | {isOpen && (
146 |
147 | {options.map((option) => (
148 | handleSelectOption(option)}
151 | className={`fz-dropdown-item ${selectedOption?.value === option.value ? 'fz-selected' : ''}`}
152 | >
153 | {option.label}
154 |
155 | ))}
156 |
157 | )}
158 |
159 | );
160 | };
161 |
162 | export default FeedControl;
163 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/components/PatternSelector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import { createBlocksFromInnerBlocksTemplate } from '@wordpress/blocks';
7 |
8 | import {
9 | store as blockEditorStore,
10 | BlockPreview,
11 | } from '@wordpress/block-editor';
12 |
13 | import { Modal } from '@wordpress/components';
14 |
15 | import { useDispatch, useSelect } from '@wordpress/data';
16 |
17 | /**
18 | * Internal dependencies.
19 | */
20 | import variations from '../variations';
21 |
22 | const PatternSelector = ({ setOpen }) => {
23 | const currentBlock = useSelect((select) =>
24 | select(blockEditorStore).getSelectedBlock()
25 | );
26 |
27 | const { clearSelectedBlock, replaceBlock } = useDispatch(blockEditorStore);
28 |
29 | return (
30 |
setOpen(false)}
33 | size="fill"
34 | >
35 |
36 | {variations.map((variation) => {
37 | const block = {
38 | ...currentBlock,
39 | attributes: {
40 | feed: currentBlock?.attributes?.feed,
41 | ...variation?.attributes,
42 | },
43 | innerBlocks: createBlocksFromInnerBlocksTemplate(
44 | variation?.innerBlocks
45 | ),
46 | };
47 |
48 | const onClick = () => {
49 | replaceBlock(currentBlock.clientId, block);
50 | clearSelectedBlock();
51 | setOpen(false);
52 | };
53 |
54 | return (
55 |
{
59 | if (e.key === 'Enter' || e.key === ' ') {
60 | onClick();
61 | }
62 | }}
63 | role="button"
64 | tabIndex="0"
65 | className="fz-pattern"
66 | >
67 |
68 |
69 |
{variation.title}
70 |
71 | );
72 | })}
73 |
74 |
75 | );
76 | };
77 |
78 | export default PatternSelector;
79 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/edit.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies.
4 | */
5 | import {
6 | store as blocksStore,
7 | createBlocksFromInnerBlocksTemplate,
8 | serialize,
9 | } from '@wordpress/blocks';
10 |
11 | import {
12 | store as blockEditorStore,
13 | __experimentalBlockVariationPicker as BlockVariationPicker,
14 | useBlockProps,
15 | InnerBlocks,
16 | } from '@wordpress/block-editor';
17 |
18 | import {
19 | Placeholder as BlockEditorPlaceholder,
20 | Spinner,
21 | } from '@wordpress/components';
22 |
23 | import { useDispatch, useSelect } from '@wordpress/data';
24 |
25 | import { useState } from '@wordpress/element';
26 |
27 | import ServerSideRender from '@wordpress/server-side-render';
28 |
29 | /**
30 | * Internal dependencies.
31 | */
32 | import metadata from './block.json';
33 | import Placeholder from './placeholder';
34 | import Controls from './controls';
35 |
36 | const { name } = metadata;
37 |
38 | const LoadingResponsePlaceholder = () => (
39 |
40 |
41 |
42 | );
43 |
44 | const Edit = ({ attributes, setAttributes, clientId }) => {
45 | const blockProps = useBlockProps();
46 |
47 | const [isEditing, setIsEditing] = useState(!attributes?.feed?.source);
48 | const [isPreviewing, setIsPreviewing] = useState(false);
49 |
50 | const { clearSelectedBlock, replaceInnerBlocks } =
51 | useDispatch(blockEditorStore);
52 |
53 | const isSelected = useSelect(
54 | (select) => {
55 | const { isBlockSelected, hasSelectedInnerBlock } =
56 | select(blockEditorStore);
57 | return (
58 | isBlockSelected(clientId) ||
59 | hasSelectedInnerBlock(clientId, true)
60 | );
61 | },
62 | [clientId]
63 | );
64 |
65 | const innerBlocksContent = useSelect(
66 | (select) => {
67 | const { getBlock } = select(blockEditorStore);
68 | const block = getBlock(clientId);
69 |
70 | return serialize(block?.innerBlocks) ?? '';
71 | },
72 | [clientId]
73 | );
74 |
75 | const hasInnerBlocks = useSelect(
76 | (select) => 0 < select(blockEditorStore).getBlocks(clientId).length,
77 | [clientId]
78 | );
79 |
80 | const variations = useSelect((select) => {
81 | const { getBlockVariations } = select(blocksStore);
82 | return getBlockVariations(name, 'block');
83 | }, []);
84 |
85 | const defaultVariation = useSelect((select) => {
86 | const { getDefaultBlockVariation } = select(blocksStore);
87 | return getDefaultBlockVariation(name, 'block');
88 | }, []);
89 |
90 | const onSaveFeed = () => {
91 | setIsEditing(false);
92 | };
93 |
94 | const onChangeQuery = ({ type, value }) => {
95 | setAttributes({
96 | query: {
97 | ...attributes.query,
98 | [type]: value,
99 | },
100 | });
101 | };
102 |
103 | const onChangeLayout = ({ type, value }) => {
104 | setAttributes({
105 | layout: {
106 | ...attributes.layout,
107 | [type]: value,
108 | },
109 | });
110 | };
111 |
112 | if (isEditing) {
113 | return (
114 |
121 | );
122 | }
123 |
124 | if ((!isSelected || isPreviewing) && innerBlocksContent) {
125 | return (
126 | <>
127 |
137 |
138 |
139 |
147 |
148 | >
149 | );
150 | }
151 |
152 | return (
153 | <>
154 |
164 |
165 |
166 | {hasInnerBlocks ? (
167 |
168 | ) : (
169 | {
172 | if (nextVariation) {
173 | setAttributes(nextVariation.attributes);
174 | replaceInnerBlocks(
175 | clientId,
176 | createBlocksFromInnerBlocksTemplate(
177 | nextVariation.innerBlocks
178 | ),
179 | true
180 | );
181 | clearSelectedBlock();
182 | }
183 | }}
184 | />
185 | )}
186 |
187 | >
188 | );
189 | };
190 |
191 | export default Edit;
192 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/extension.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import {
7 | BlockControls,
8 | store as blockEditorStore,
9 | } from '@wordpress/block-editor';
10 |
11 | import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
12 |
13 | import { createHigherOrderComponent } from '@wordpress/compose';
14 |
15 | import { useSelect } from '@wordpress/data';
16 |
17 | import { addFilter } from '@wordpress/hooks';
18 |
19 | const defaultImage = window.feedzyData.defaultImage;
20 |
21 | const withFeedzyLoopImage = createHigherOrderComponent((BlockEdit) => {
22 | return (props) => {
23 | if ('core/image' !== props.name) {
24 | return
;
25 | }
26 |
27 | const isLoopChild = useSelect((select) => {
28 | return (
29 | select(blockEditorStore).getBlockParentsByBlockName(
30 | props.clientId,
31 | 'feedzy-rss-feeds/loop'
32 | ).length > 0
33 | );
34 | });
35 |
36 | return (
37 | <>
38 |
39 |
40 | {isLoopChild && (
41 |
42 |
43 | {
45 | props.setAttributes({
46 | url: defaultImage,
47 | });
48 | }}
49 | >
50 | {__('Use as Feed Image', 'feedzy-rss-feeds')}
51 |
52 |
53 |
54 | )}
55 | >
56 | );
57 | };
58 | }, 'withMasonryExtension');
59 |
60 | addFilter('editor.BlockEdit', 'feedzy-loop/image', withFeedzyLoopImage);
61 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 |
6 | import { createBlock, registerBlockType } from '@wordpress/blocks';
7 |
8 | import { InnerBlocks } from '@wordpress/block-editor';
9 |
10 | /**
11 | * Internal dependencies
12 | */
13 | import './editor.scss';
14 | import './style.scss';
15 | import './extension';
16 | import metadata from './block.json';
17 | import variations from './variations';
18 | import edit from './edit';
19 |
20 | const { name } = metadata;
21 |
22 | registerBlockType(name, {
23 | ...metadata,
24 | variations,
25 | transforms: {
26 | from: [
27 | {
28 | type: 'block',
29 | blocks: ['core/rss'],
30 | transform: (attributes) => {
31 | const { feedURL } = attributes;
32 |
33 | if (feedURL) {
34 | return createBlock(name, {
35 | feed: { type: 'url', source: [feedURL] },
36 | });
37 | }
38 |
39 | return createBlock(name);
40 | },
41 | },
42 | {
43 | type: 'block',
44 | blocks: ['feedzy-rss-feeds/feedzy-block'],
45 | transform: (attributes) => {
46 | const { feeds } = attributes;
47 |
48 | if (feeds) {
49 | return createBlock(name, {
50 | feed: { type: 'url', source: [feeds] },
51 | });
52 | }
53 |
54 | return createBlock(name);
55 | },
56 | },
57 | ],
58 | },
59 | edit,
60 | save: () => {
61 | return
;
62 | },
63 | });
64 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/placeholder.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies.
4 | */
5 | import { __ } from '@wordpress/i18n';
6 |
7 | import {
8 | BaseControl,
9 | Button,
10 | Placeholder,
11 | Spinner,
12 | } from '@wordpress/components';
13 |
14 | import { useSelect } from '@wordpress/data';
15 |
16 | import { store as coreStore } from '@wordpress/core-data';
17 |
18 | /**
19 | * Internal dependencies.
20 | */
21 | import FeedControl from './components/FeedControl';
22 |
23 | const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => {
24 | const { categories, isLoading } = useSelect((select) => {
25 | const { getEntityRecords, isResolving } = select(coreStore);
26 |
27 | return {
28 | categories: getEntityRecords('postType', 'feedzy_categories') ?? [],
29 | isLoading: isResolving('getEntityRecords', [
30 | 'postType',
31 | 'feedzy_categories',
32 | ]),
33 | };
34 | }, []);
35 |
36 | return (
37 |
42 | {isLoading && (
43 |
44 |
45 |
{__('Fetching…', 'feedzy-rss-feeds')}
46 |
47 | )}
48 |
49 | {!isLoading && (
50 | <>
51 |
55 | ({
59 | label: category?.title?.rendered,
60 | value: category.id,
61 | })),
62 | ]}
63 | onChange={(value) => setAttributes({ feed: value })}
64 | />
65 |
66 |
67 | {__(
68 | 'Enter the full URL of the feed source you wish to display here, or select a Feed Group. Also you can add multiple URLs separated with a comma. You can manage your feed groups from',
69 | 'feedzy-rss-feeds'
70 | )}{' '}
71 |
76 | {__('here', 'feedzy-rss-feeds')}
77 |
78 |
79 |
80 |
81 |
82 | {
85 | if (attributes?.feed?.source) {
86 | onSaveFeed();
87 | }
88 | }}
89 | >
90 | {__('Load Feed', 'feedzy-rss-feeds')}
91 |
92 |
93 |
98 | {__('Validate', 'feedzy-rss-feeds')}
99 |
100 |
101 | >
102 | )}
103 |
104 | );
105 | };
106 |
107 | export default BlockPlaceholder;
108 |
--------------------------------------------------------------------------------
/js/FeedzyLoop/style.scss:
--------------------------------------------------------------------------------
1 | .wp-block-feedzy-rss-feeds-loop {
2 | display: grid;
3 | gap: 24px;
4 |
5 | @media (min-width: 782px) {
6 | @for $i from 2 through 5 {
7 | &.feedzy-loop-columns-#{$i} {
8 | grid-template-columns: repeat(2, 1fr);
9 | }
10 | }
11 | }
12 |
13 | @media (min-width: 960px) {
14 | @for $i from 2 through 5 {
15 | &.feedzy-loop-columns-#{$i} {
16 | grid-template-columns: repeat(#{$i}, 1fr);
17 | }
18 | }
19 | }
20 |
21 | grid-template-columns: repeat(1, 1fr);
22 |
23 | .wp-block-image.is-style-rounded img {
24 | border-radius: 9999px;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/js/Review/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies.
3 | */
4 | import {
5 | __,
6 | sprintf
7 | } from '@wordpress/i18n';
8 |
9 | import apiFetch from '@wordpress/api-fetch';
10 |
11 | import domReady from '@wordpress/dom-ready';
12 |
13 | import {
14 | Button,
15 | Modal
16 | } from '@wordpress/components';
17 |
18 | import {
19 | createRoot,
20 | useState
21 | } from '@wordpress/element';
22 |
23 | const App = () => {
24 | const [ isOpen, setOpen ] = useState( true );
25 |
26 | const closeModal = async () => {
27 | try {
28 | await apiFetch({
29 | path: '/wp/v2/settings',
30 | method: 'POST',
31 | data: {
32 | feedzy_review_notice: 'yes',
33 | },
34 | });
35 | } catch ( error ) {
36 | console.error( __( 'Error updating setting:', 'feedzy-rss-feeds' ), error );
37 | } finally {
38 | setOpen( false );
39 | }
40 | };
41 |
42 | if ( ! isOpen ) {
43 | return null;
44 | }
45 |
46 | return (
47 |
53 | ', // %1$s
62 | '', // %2$s
63 | 100 // %3$s
64 | ),
65 | }}
66 | />
67 |
68 |
', // %1$s
77 | '', // %2$s
78 | '★★★★★ ' // %3$s
79 | ),
80 | }}
81 | />
82 |
83 |
84 |
91 | { __( 'Rate Feedzy on WordPress.org', 'feedzy-rss-feeds' ) }
92 |
93 |
94 |
95 | );
96 | };
97 |
98 | domReady( () => {
99 | const modalContainer = document.createElement( 'div' );
100 | modalContainer.id = 'fz-review-modal';
101 | document.body.appendChild( modalContainer );
102 | const root = createRoot( document.getElementById( 'fz-review-modal' ) );
103 | root.render(
);
104 | });
105 |
--------------------------------------------------------------------------------
/js/categories.js:
--------------------------------------------------------------------------------
1 | /* global feedzy, ajaxurl */
2 | /* jshint unused:false */
3 | (function($) {
4 |
5 | $(document).ready(function(){
6 | init();
7 | });
8 |
9 | function init(){
10 | // listen to the validate button
11 | $('button.validate-category').on('click', function(e){
12 | e.preventDefault();
13 | var button = $(this);
14 | button.html(feedzy.l10n.validating);
15 | $.ajax({
16 | url: ajaxurl,
17 | method: 'POST',
18 | data: {
19 | action: 'feedzy_categories',
20 | _action: 'validate_clean',
21 | security: feedzy.ajax.security,
22 | id: button.attr('data-category-id')
23 | },
24 | success: function(data){
25 | button.html(feedzy.l10n.validated.replace('#', data.data.invalid));
26 | },
27 | complete: function(){
28 | setTimeout(function(){
29 | button.html(feedzy.l10n.validate);
30 | }, 5000 );
31 | }
32 | });
33 | });
34 | }
35 |
36 | })(jQuery, feedzy);
37 |
--------------------------------------------------------------------------------
/js/feedzy-elementor-widget.js:
--------------------------------------------------------------------------------
1 | window.addEventListener( 'elementor/init', function() {
2 |
3 | /**
4 | * Handle select layout template
5 | */
6 | var fzLayoutTemplate = elementor.modules.controls.BaseData.extend({
7 | onReady() {
8 | var self = this;
9 | this.template_control = this.$el.find('.fz-layout-choices input[type="radio"]');
10 | this.template_control.change( function() {
11 | self.saveValue( jQuery( this ).val() );
12 | } );
13 | self.changeUiImage();
14 | },
15 | saveValue(value) {
16 | this.setValue(value);
17 | },
18 | changeUiImage() { // Set UI theme mode.
19 | var themeMode = elementor.settings.editorPreferences.model.get('ui_theme');
20 | var userPrefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
21 |
22 | if ( 'dark' === themeMode || userPrefersDark ) {
23 | this.$el.removeClass( 'fz-el-light-mode' ).addClass( 'fz-el-dark-mode' );
24 | } else {
25 | this.$el.addClass( 'fz-el-light-mode' ).removeClass( 'fz-el-dark-mode' );
26 | }
27 |
28 | this.$el.find( '.img img' ).each( function() {
29 | if ( 'dark' === themeMode ) {
30 | jQuery( this ).attr( 'src', jQuery( this ).attr( 'src' ).replace( '{{ui_mode}}', 'dark' ) );
31 | } else {
32 | jQuery( this ).attr( 'src', jQuery( this ).attr( 'src' ).replace( '{{ui_mode}}', 'light' ) );
33 | }
34 | } );
35 | }
36 | });
37 | elementor.addControlView( 'fz-layout-template', fzLayoutTemplate );
38 |
39 | // Edit widget event.
40 | elementor.hooks.addAction( 'panel/open_editor/widget/feedzy-rss-feeds', function( panel, model, view ) {
41 | var themeMode = elementor.settings.editorPreferences.model.get('ui_theme');
42 | var userPrefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
43 |
44 | if ( FeedzyElementorEditor.notice ) {
45 | if ( jQuery('.fz-pro-notice').length <= 0 && jQuery('.elementor-control-fz-referral-url').length > 0 ) {
46 | // Append notice.
47 | jQuery( FeedzyElementorEditor.notice ).insertAfter( jQuery('.elementor-control-fz-referral-url').parents('div.elementor-controls-stack') );
48 | // Set UI theme mode.
49 | var fzLogo = jQuery('.fz-pro-notice .fz-logo img');
50 | if ( 'dark' === themeMode || userPrefersDark ) {
51 | fzLogo.attr( 'src', fzLogo.attr( 'src' ).replace( '{{ui_mode}}', 'dark' ) );
52 | jQuery('.fz-pro-notice').removeClass( 'fz-light-mode' );
53 | } else {
54 | jQuery('.fz-pro-notice').addClass( 'fz-light-mode' );
55 | fzLogo.attr( 'src', fzLogo.attr( 'src' ).replace( '{{ui_mode}}', 'light' ) );
56 | }
57 | }
58 | }
59 |
60 | if( '' !== FeedzyElementorEditor.upsell_notice ) {
61 | if ( 'dark' === themeMode || userPrefersDark ) {
62 | jQuery( FeedzyElementorEditor.upsell_notice ).addClass( 'dark-mode' ).removeClass( 'light-mode' ).insertAfter( '.elementor-panel-navigation' );
63 | } else {
64 | jQuery( FeedzyElementorEditor.upsell_notice ).addClass( 'light-mode' ).removeClass( 'dark-mode' ).insertAfter( '.elementor-panel-navigation' );
65 | }
66 |
67 | jQuery( document ).on( 'click', '.remove-alert', function() {
68 | var upSellNotice = jQuery(this).parents( '.fz-upsell-notice' );
69 | upSellNotice.fadeOut( 500,
70 | function() {
71 | upSellNotice.remove();
72 | jQuery.post(
73 | ajaxurl,
74 | {
75 | security: FeedzyElementorEditor.security,
76 | action: "feedzy",
77 | _action: "remove_upsell_notice"
78 | }
79 | );
80 | }
81 | );
82 | } );
83 | }
84 | } );
85 |
86 | var proTitleText = function() {
87 | if ( jQuery( '.fz-feat-locked:not(.elementor-control-type-section)' ).length > 0 ) {
88 | jQuery( '.fz-feat-locked:not(.elementor-control-type-section)' ).attr( 'title', FeedzyElementorEditor.pro_title_text );
89 | }
90 | };
91 |
92 | elementor.channels.editor.on( 'section:activated', function() {
93 | proTitleText();
94 | } );
95 | } );
96 |
--------------------------------------------------------------------------------
/js/feedzy-lazy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Plugin Name: FEEDZY RSS Feeds
3 | * Plugin URI: https://themeisle.com/plugins/feedzy-rss-feeds/
4 | * Author: Themeisle
5 | *
6 | * @package feedzy-rss-feeds
7 | */
8 | /* global feedzy */
9 | /* jshint unused:false */
10 | (function($) {
11 |
12 | // load all attributes into the ajax call.
13 | $('.feedzy-lazy:not(.loading)').each(function() {
14 | var $feedzy_block = $(this);
15 | var $attributes = {};
16 | $.each(this.attributes, function() {
17 | if(this.specified && this.name.includes('data-')) {
18 | $attributes[this.name.replace('data-', '')] = this.value;
19 | }
20 | });
21 |
22 | if ( 'true' === $attributes.has_valid_cache ) {
23 | return;
24 | }
25 | delete $attributes.has_valid_cache;
26 | setTimeout( function(){
27 | $feedzy_block.addClass('loading');
28 | $.ajax({
29 | url: feedzy.url,
30 | method: 'POST',
31 | data: {
32 | action: 'feedzy',
33 | _action: 'lazy',
34 | args: $attributes,
35 | nonce: feedzy.nonce
36 | },
37 | beforeSend: function (xhr) {
38 | xhr.setRequestHeader('X-WP-Nonce', feedzy.rest_nonce);
39 | },
40 | success: function(data){
41 | if(data.success){
42 | $feedzy_block.empty().append(data.data.content);
43 | }
44 | },
45 | complete: function(){
46 | $feedzy_block.removeClass('loading');
47 | }
48 | });
49 | }, 1000 );
50 | });
51 | })(jQuery, feedzy);
52 |
--------------------------------------------------------------------------------
/js/feedzy-rss-feeds-ui-mce.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Plugin Name: FEEDZY RSS Feeds
3 | * Plugin URI: https://themeisle.com/plugins/feedzy-rss-feeds/
4 | * Author: Themeisle
5 | *
6 | * @package feedzy-rss-feeds
7 | */
8 | /* global tinymce */
9 | /* jshint unused:false */
10 | (function($) {
11 | tinymce.PluginManager.add('feedzy_mce_button', function( editor, url ) {
12 | editor.addButton( 'feedzy_mce_button', {
13 | title: getTranslation( editor, 'plugin_label' ),
14 | label: getTranslation( editor, 'plugin_label' ),
15 | icon: 'feedzy-icon',
16 | onclick: function() {
17 | editor.windowManager.open( {
18 | title: getTranslation( editor, 'plugin_title' ),
19 | url: getTranslation( editor, 'popup_url' ) + '&action=get_tinymce_form',
20 | width: $( window ).width() * 0.7,
21 | height: ($( window ).height() - 36 - 50) * 0.7,
22 | inline: 1,
23 | id: 'feedzy-rss-insert-dialog',
24 | buttons: [{
25 | text: getTranslation( editor, 'pro_button' ),
26 | id: 'feedzy-rss-button-pro',
27 | onclick: function( e ) {
28 | openProLink( e, editor );
29 | },
30 | },
31 | {
32 | text: getTranslation( editor, 'cancel_button' ),
33 | id: 'feedzy-rss-button-cancel',
34 | onclick: 'close'
35 | },
36 | {
37 | text: getTranslation( editor, 'insert_button' ),
38 | id: 'feedzy-rss-button-insert',
39 | class: 'insert',
40 | onclick: function( e ) {
41 | insertShortcode( e, editor );
42 | },
43 |
44 | }],
45 | }, {
46 | editor: editor,
47 | jquery: $,
48 | wp: wp,
49 | });
50 | }
51 | });
52 | });
53 |
54 | /**
55 | * Gets the translation from the editor (when classic editor is enabled)
56 | * OR
57 | * from the settings array inside the editor (when classic block inside gutenberg)
58 | */
59 | function getTranslation(editor, slug){
60 | var string = editor.getLang('feedzy_tinymce_plugin.' + slug);
61 | // if the string is the same as the slug being requested for, look in the settings.
62 | if(string === '{#feedzy_tinymce_plugin.' + slug + '}'){
63 | string = editor.settings.feedzy_tinymce_plugin[slug];
64 | }
65 | return string;
66 | }
67 |
68 | function insertShortcode( e, editor ) {
69 | var frame = $( e.currentTarget ).find( 'iframe' ).get( 0 );
70 | var content = frame.contentDocument;
71 |
72 | var feedzy_form = $( '*[data-feedzy]', content );
73 | var shortCode = '';
74 | $.each( feedzy_form, function( index, element ) {
75 | if ( ! $( element ).attr( 'disabled' ) ) {
76 | var shortCodeParams = '';
77 | var eName = $( element ).attr( 'data-feedzy' );
78 | var eValue = '';
79 | if ($( element ).is( 'input' )) {
80 | if ($( element ).attr( 'type' ) === 'radio' || $( element ).attr( 'type' ) === 'checkbox') {
81 | if ( $( element ).is( ':checked' ) ) {
82 | eValue = $( element ).val();
83 | }
84 | } else {
85 | eValue = $( element ).val();
86 | }
87 | } else {
88 | eValue = $( element ).val();
89 | }
90 |
91 | if ( eValue !== '' && typeof eValue !== 'undefined' ) {
92 | shortCodeParams = eName + '="' + eValue + '" ';
93 | } else {
94 | if ( eName === 'feeds' ) {
95 | shortCodeParams = eName + '="https://themeisle.com/feed" ';
96 | }
97 | }
98 | shortCode += shortCodeParams;
99 | }
100 | });
101 | editor.insertContent(
102 | '[feedzy-rss ' + shortCode + ']'
103 | );
104 | editor.windowManager.close();
105 | }
106 |
107 | function openProLink( e , editor ) {
108 | window.open( getTranslation( editor, 'pro_url' ), '_blank' );
109 | }
110 | })(jQuery);
111 |
--------------------------------------------------------------------------------
/js/telemetry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Exclude fields from telemetry tracking. They are not relevant or contain sensitive data.
3 | */
4 | const excludedFieldsSlugs = [
5 | '_wp',
6 | 'proxy',
7 | '_key',
8 | '_pass',
9 | 'username',
10 | 'auto',
11 | 'nonce',
12 | 'password',
13 | 'user_ID',
14 | 'post_author',
15 | '_post_status',
16 | 'default_thumbnail_id',
17 | 'default-thumbnail-id'
18 | ];
19 |
20 | /**
21 | * Collect telemetry data from the submit event of the settings forms.
22 | *
23 | */
24 | window.addEventListener( 'load', function() {
25 | if ( ! window.tiTrk ) {
26 | return;
27 | }
28 |
29 | /**
30 | * Forms to track.
31 | */
32 | const envs = [
33 | { pageName: 'main-settings', formSelector: 'form:has(input[value*="feedzy-settings"])', includeFilter: [] },
34 | { pageName: 'categories-settings', formSelector: 'form:has(#feedzy_category_feeds)', includeFilter: [ 'feedzy_' ] },
35 | { pageName: 'import-settings', formSelector: 'form:has(#feedzy-import-form)', includeFilter: [ 'feedzy_meta'] },
36 | ]
37 | .map( env => {
38 | env.formSelector = document.querySelector( env.formSelector )
39 | return env;
40 | } )
41 | .filter( env => {
42 | return env.formSelector;
43 | } );
44 |
45 | if ( ! envs.length ) {
46 | return;
47 | }
48 |
49 | envs.forEach( env => {
50 | env.formSelector.addEventListener( 'submit', function() {
51 | const formData = new FormData( env.formSelector );
52 |
53 | for ( const [ name, value ] of formData.entries() ) {
54 | if ( typeof value === 'undefined' || value === null ) {
55 | continue;
56 | }
57 |
58 | if ( excludedFieldsSlugs.some( ( slug ) => name.includes( slug ) ) ) {
59 | continue;
60 | }
61 |
62 | if ( env.includeFilter.length && ! env.includeFilter.some( ( slug ) => name.includes( slug ) ) ) {
63 | continue;
64 | }
65 |
66 | window.tiTrk.with('feedzy').add({
67 | action: 'snapshot',
68 | feature: env.pageName,
69 | featureComponent: name,
70 | featureValue: value,
71 | });
72 | }
73 |
74 | window.tiTrk.uploadEvents();
75 | });
76 | });
77 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "feedzy-rss-feeds",
3 | "version": "5.0.6",
4 | "description": "Feedzy RSS Feeds - lite version",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/Codeinwp/feedzy-rss-feeds.git"
8 | },
9 | "keywords": [
10 | "wordpress-plugin"
11 | ],
12 | "textdomain": "feedzy-rss-feeds",
13 | "category": "plugins",
14 | "author": "ThemeIsle
",
15 | "license": "GPL-2.0+",
16 | "bugs": {
17 | "url": "https://github.com/Codeinwp/feedzy-rss-feeds/issues"
18 | },
19 | "scripts": {
20 | "build": "npm-run-all build:*",
21 | "build:block": "wp-scripts build --webpack-src-dir=js/FeedzyBlock --output-path=build/block --output-filename=index.js",
22 | "build:loop": "wp-scripts build --webpack-src-dir=js/FeedzyLoop --output-path=build/loop --output-filename=index.js",
23 | "build:onboarding": "wp-scripts build --webpack-src-dir=js/Onboarding --output-path=build/onboarding --output-filename=index.js",
24 | "build:feedback": "wp-scripts build --webpack-src-dir=js/FeedBack --output-path=build/feedback --output-filename=index.js",
25 | "build:actions": "wp-scripts build --webpack-src-dir=js/ActionPopup --output-path=build/action-popup --output-filename=index.js",
26 | "build:conditions": "wp-scripts build --webpack-src-dir=js/Conditions --output-path=build/conditions --output-filename=index.js",
27 | "build:review": "wp-scripts build --webpack-src-dir=js/Review --output-path=build/review --output-filename=index.js",
28 | "dev": "npm-run-all --parallel dev:*",
29 | "dev:block": "wp-scripts start --webpack-src-dir=js/FeedzyBlock --output-path=build/block --output-filename=index.js",
30 | "dev:loop": "wp-scripts start --webpack-src-dir=js/FeedzyLoop --output-path=build/loop --output-filename=index.js",
31 | "dev:onboarding": "wp-scripts start --webpack-src-dir=js/Onboarding --output-path=build/onboarding --output-filename=index.js",
32 | "dev:feedback": "wp-scripts start --webpack-src-dir=js/FeedBack --output-path=build/feedback --output-filename=index.js",
33 | "dev:actions": "wp-scripts start --webpack-src-dir=js/ActionPopup --output-path=build/action-popup --output-filename=index.js",
34 | "dev:conditions": "wp-scripts start --webpack-src-dir=js/Conditions --output-path=build/conditions --output-filename=index.js",
35 | "lint:js": "wp-scripts lint-js ./js",
36 | "release": "semantic-release --debug",
37 | "dist": "bash bin/dist.sh",
38 | "grunt": "grunt",
39 | "test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js",
40 | "test:e2e:debug": "wp-scripts test-playwright --config tests/e2e/playwright.config.js --ui",
41 | "wp-env": "wp-env"
42 | },
43 | "pot": {
44 | "reportmsgidbugsto": "https://github.com/Codeinwp/feedzy-rss-feeds/issues",
45 | "languageteam": "Themeisle Translate ",
46 | "lasttranslator": "Themeisle Translate Team "
47 | },
48 | "devDependencies": {
49 | "@playwright/test": "^1.44.0",
50 | "@semantic-release/changelog": "^5.0.1",
51 | "@semantic-release/exec": "^5.0.0",
52 | "@semantic-release/git": "^9.0.0",
53 | "@wordpress/components": "^27.6.0",
54 | "@wordpress/compose": "^6.35.0",
55 | "@wordpress/data": "^9.28.0",
56 | "@wordpress/dom-ready": "^3.58.0",
57 | "@wordpress/e2e-test-utils-playwright": "^0.26.0",
58 | "@wordpress/element": "^5.35.0",
59 | "@wordpress/env": "^9.10.0",
60 | "@wordpress/eslint-plugin": "^22.0.0",
61 | "@wordpress/i18n": "^4.58.0",
62 | "@wordpress/icons": "^9.49.0",
63 | "@wordpress/media-utils": "^4.49.0",
64 | "@wordpress/scripts": "^27.9.0",
65 | "classnames": "^2.3.2",
66 | "conventional-changelog-simple-preset": "^1.0.24",
67 | "dayjs": "^1.10.4",
68 | "eslint": "^8.57.1",
69 | "grunt": "^1.4.0",
70 | "grunt-version": "^2.0.0",
71 | "grunt-wp-readme-to-markdown": "^2.0.1",
72 | "lodash": "^4.17.21",
73 | "npm-run-all": "^4.1.5",
74 | "query-string": "^7.0.0",
75 | "raw-loader": "^4.0.2",
76 | "replace-in-file": "^6.2.0",
77 | "semantic-release": "^17.4.2",
78 | "semantic-release-slack-bot": "^2.1.0"
79 | },
80 | "dependencies": {
81 | "array-move": "^4.0.0",
82 | "react-joyride": "^2.8.2",
83 | "react-sortable-hoc": "^2.0.0"
84 | },
85 | "overrides": {
86 | "react-sortable-hoc": {
87 | "react": "18.3.1",
88 | "react-dom": "18.3.1"
89 | },
90 | "@wordpress/components": {
91 | "react": "18.3.1",
92 | "react-dom": "18.3.1"
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Themeisle rules for PHP_CodeSnifferr
4 |
5 | .
6 |
7 | node_modules/*
8 | vendor/*
9 | lib/*
10 | tests/*
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | ./tests/
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | set_role( 'administrator' );
39 | wp_update_user(
40 | array(
41 | 'ID' => 1,
42 | 'first_name' => 'Admin',
43 | 'last_name' => 'User',
44 | )
45 | );
46 |
--------------------------------------------------------------------------------
/tests/e2e/config/flaky-tests-reporter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A **flaky** test is defined as a test which passed after auto-retrying.
3 | * - By default, all tests run once if they pass.
4 | * - If a test fails, it will automatically re-run at most 2 times.
5 | * - If it pass after retrying (below 2 times), then it's marked as **flaky**
6 | * but displayed as **passed** in the original test suite.
7 | * - If it fail all 3 times, then it's a **failed** test.
8 | */
9 | /**
10 | * External dependencies
11 | */
12 | import fs from 'fs';
13 | import filenamify from 'filenamify';
14 |
15 |
16 | // Remove "steps" to prevent stringify circular structure.
17 | function formatTestResult( testResult ) {
18 | const result = { ...testResult, steps: undefined };
19 | delete result.steps;
20 | return result;
21 | }
22 |
23 | class FlakyTestsReporter {
24 | failingTestCaseResults = new Map();
25 |
26 | onBegin() {
27 | try {
28 | fs.mkdirSync( 'flaky-tests' );
29 | } catch ( err ) {
30 | if (
31 | err instanceof Error &&
32 | err.code === 'EEXIST'
33 | ) {
34 | // Ignore the error if the directory already exists.
35 | } else {
36 | throw err;
37 | }
38 | }
39 | }
40 |
41 | onTestEnd( test, testCaseResult ) {
42 | const testPath = test.location.file;
43 | const testTitle = test.title;
44 |
45 | switch ( test.outcome() ) {
46 | case 'unexpected': {
47 | if ( ! this.failingTestCaseResults.has( testTitle ) ) {
48 | this.failingTestCaseResults.set( testTitle, [] );
49 | }
50 | this.failingTestCaseResults
51 | .get( testTitle )
52 | .push( formatTestResult( testCaseResult ) );
53 | break;
54 | }
55 | case 'flaky': {
56 | fs.writeFileSync(
57 | `flaky-tests/${ filenamify( testTitle ) }.json`,
58 | JSON.stringify( {
59 | version: 1,
60 | runner: '@playwright/test',
61 | title: testTitle,
62 | path: testPath,
63 | results: this.failingTestCaseResults.get( testTitle ),
64 | } ),
65 | 'utf-8'
66 | );
67 | break;
68 | }
69 | default:
70 | break;
71 | }
72 | }
73 |
74 | onEnd() {
75 | this.failingTestCaseResults.clear();
76 | }
77 |
78 | printsToStdio() {
79 | return false;
80 | }
81 | }
82 |
83 | module.exports = FlakyTestsReporter;
84 |
--------------------------------------------------------------------------------
/tests/e2e/config/global-setup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import { request } from '@playwright/test';
5 |
6 | /**
7 | * WordPress dependencies
8 | */
9 | import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
10 |
11 | async function globalSetup( config ) {
12 | const { storageState, baseURL } = config.projects[ 0 ].use;
13 | const storageStatePath =
14 | typeof storageState === 'string' ? storageState : undefined;
15 |
16 | const requestContext = await request.newContext( {
17 | baseURL,
18 | } );
19 |
20 | const requestUtils = new RequestUtils( requestContext, {
21 | storageStatePath,
22 | } );
23 |
24 | // Authenticate and save the storageState to disk.
25 | await requestUtils.setupRest();
26 |
27 | // Reset the test environment before running the tests.
28 | await Promise.all( [
29 | requestUtils.activateTheme( 'twentytwentyone' ),
30 | // Disable this test plugin as it's conflicting with some of the tests.
31 | // We already have reduced motion enabled and Playwright will wait for most of the animations anyway.
32 | // requestUtils.deactivatePlugin(
33 | // 'gutenberg-test-plugin-disables-the-css-animations'
34 | // ),
35 | requestUtils.deleteAllPosts(),
36 | requestUtils.deleteAllBlocks(),
37 | requestUtils.resetPreferences(),
38 | ] );
39 |
40 | await requestContext.dispose();
41 | }
42 |
43 | export default globalSetup;
44 |
--------------------------------------------------------------------------------
/tests/e2e/playwright.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * External dependencies
3 | */
4 | import os from 'os';
5 | import { fileURLToPath } from 'url';
6 | import { defineConfig, devices } from '@playwright/test';
7 |
8 | /**
9 | * WordPress dependencies
10 | */
11 | const baseConfig = require( '@wordpress/scripts/config/playwright.config' );
12 |
13 | const config = defineConfig( {
14 | ...baseConfig,
15 | reporter: process.env.CI
16 | ? [ [ 'github' ], [ './config/flaky-tests-reporter.js' ] ]
17 | : 'list',
18 | workers: 1,
19 | globalSetup: fileURLToPath(
20 | new URL( './config/global-setup.js', 'file:' + __filename ).href
21 | ),
22 | projects: [
23 | {
24 | name: 'chromium',
25 | use: { ...devices[ 'Desktop Chrome' ] },
26 | grepInvert: /-chromium/,
27 | },
28 | // {
29 | // name: 'webkit',
30 | // use: {
31 | // ...devices[ 'Desktop Safari' ],
32 | // /**
33 | // * Headless webkit won't receive dataTransfer with custom types in the
34 | // * drop event on Linux. The solution is to use `xvfb-run` to run the tests.
35 | // * ```sh
36 | // * xvfb-run npm run test:e2e
37 | // * ```
38 | // * See `.github/workflows/end2end-test-playwright.yml` for advanced usages.
39 | // */
40 | // headless: os.type() !== 'Linux',
41 | // },
42 | // grep: /@webkit/,
43 | // grepInvert: /-webkit/,
44 | // },
45 | // {
46 | // name: 'firefox',
47 | // use: { ...devices[ 'Desktop Firefox' ] },
48 | // grep: /@firefox/,
49 | // grepInvert: /-firefox/,
50 | // },
51 | ],
52 | } );
53 |
54 | export default config;
55 |
--------------------------------------------------------------------------------
/tests/e2e/specs/feed.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright';
5 | import {
6 | tryCloseTourModal,
7 | deleteAllFeedImports,
8 | addFeeds,
9 | runFeedImport
10 | } from '../utils';
11 |
12 | test.describe( 'Feed Settings', () => {
13 |
14 | const FEED_URL = 'https://s3.amazonaws.com/verti-utils/sample-feed-import.xml';
15 |
16 | test.beforeEach( async ( { requestUtils } ) => {
17 | await deleteAllFeedImports( requestUtils );
18 | await requestUtils.deleteAllPosts();
19 | await requestUtils.deleteAllMedia();
20 | } );
21 |
22 | test( 'adding an URL feed', async({ editor, page }) => {
23 |
24 | const importName = 'Test Title: adding an URL feed';
25 |
26 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_imports');
27 | await tryCloseTourModal( page );
28 |
29 | await page.getByPlaceholder('Add a name for your import').fill(importName);
30 |
31 | // Add feed URL via tag input.
32 | await page.getByPlaceholder('Paste your feed URL and click').fill(FEED_URL);
33 | await page.getByPlaceholder('Paste your feed URL and click').press('Enter');
34 | await expect( page.getByText( FEED_URL ) ).toBeVisible();
35 |
36 | await addFeeds( page, [FEED_URL] );
37 |
38 | await page.getByRole('button', { name: 'Save', exact: true }).click({ force: true, clickCount: 1 });
39 |
40 | await expect( page.getByRole('cell', { name: importName }) ).toBeVisible();
41 |
42 | await page.getByRole('cell', { name: importName }).hover(); // Display the actions.
43 | await page.getByLabel(`Edit “${importName}`).click();
44 |
45 | expect( await page.getByPlaceholder('Add a name for your import').inputValue() ).toBe(importName);
46 | await expect( page.getByText( FEED_URL ) ).toBeVisible();
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tests/e2e/specs/loop.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright';
5 |
6 | test.describe('Feedzy Loop', () => {
7 | const FEED_URL =
8 | 'https://s3.amazonaws.com/verti-utils/sample-feed-import.xml';
9 | const POST_TITLE = `Feedzy Loop Test ${Math.floor(Math.random() * 1000)}`;
10 |
11 | test('add Feedzy Loop Block', async ({ editor, page }) => {
12 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_categories');
13 | await page.getByLabel('Add title').click();
14 | await page.keyboard.type('Group One');
15 |
16 | await page.locator('textarea[name="feedzy_category_feed"]').click();
17 | await page.keyboard.type(FEED_URL);
18 | await page
19 | .getByRole('button', { name: 'Publish', exact: true })
20 | .click();
21 | await page.waitForTimeout(1000);
22 |
23 | await page.goto('/wp-admin/post-new.php');
24 |
25 | if (
26 | (await page.$(
27 | '.edit-post-welcome-guide .components-modal__header button'
28 | )) !== null
29 | ) {
30 | await page.click(
31 | '.edit-post-welcome-guide .components-modal__header button'
32 | );
33 | }
34 |
35 | await page.getByLabel('Add title').click();
36 | await page.keyboard.type(POST_TITLE);
37 |
38 | await page.getByLabel('Add block').click();
39 |
40 | await page.getByPlaceholder('Search').click();
41 | await page.keyboard.type('Feedzy Loop');
42 | await page.waitForTimeout(1000);
43 |
44 | await page.getByRole('option', { name: ' Feedzy Loop' }).click();
45 | await page.waitForTimeout(1000);
46 |
47 | await page.getByPlaceholder('Enter URLs or select a').click();
48 | await page.keyboard.type(FEED_URL);
49 |
50 | const loadFeedButton = await page.getByRole('button', {
51 | name: 'Load Feed',
52 | exact: true,
53 | });
54 | const isDisabled = await loadFeedButton.isDisabled();
55 | expect(isDisabled).toBe(false);
56 | await loadFeedButton.click();
57 | await page.waitForTimeout(1000);
58 |
59 | await page.getByLabel('Display curated RSS content').click();
60 | await page.waitForTimeout(1000);
61 |
62 | // Now that we have tested we can insert URL, we can test the Feed Group.
63 |
64 | await page
65 | .getByLabel('Block: Feedzy Loop')
66 | .locator('div')
67 | .nth(1)
68 | .click();
69 | await page.getByRole('button', { name: 'Edit Feed' }).click();
70 |
71 | await page.getByRole('button', { name: 'Select Feed Group' }).click();
72 | await page.locator('.fz-dropdown-item').first().click();
73 |
74 | await page
75 | .getByRole('button', { name: 'Load Feed', exact: true })
76 | .click();
77 | await page.waitForTimeout(1000);
78 |
79 | await page
80 | .getByRole('button', { name: 'Publish', exact: true })
81 | .click();
82 | await page.waitForTimeout(1000);
83 |
84 | await page
85 | .getByLabel('Editor publish')
86 | .getByRole('button', { name: 'Publish', exact: true })
87 | .click();
88 | await page.waitForTimeout(5000);
89 |
90 | const snackbar = await page.getByTestId('snackbar');
91 | const snackbarText = await snackbar.textContent();
92 | expect(snackbarText).toContain('Post published.');
93 |
94 | await page.goto('/wp-admin/edit.php');
95 |
96 | const postTitle = await page.locator('a.row-title').first();
97 | await postTitle.hover();
98 | await page.getByLabel('View “' + POST_TITLE + '”').click();
99 |
100 | await page.waitForTimeout(5000);
101 |
102 | // We want to confirm .wp-block-feedzy-rss-feeds-loop is present and it has 5 children
103 | const feedzyLoop = await page.$('.wp-block-feedzy-rss-feeds-loop');
104 | expect(feedzyLoop).not.toBeNull();
105 |
106 | const feedzyLoopChildren = await feedzyLoop.$$(':scope > *');
107 | expect(feedzyLoopChildren.length).toBe(5);
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/tests/e2e/specs/upsell.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright';
5 | import { tryCloseTourModal, deleteAllFeedImports } from '../utils';
6 |
7 | test.describe( 'Upsell', () => {
8 | test.beforeEach( async ( { requestUtils, page } ) => {
9 | await deleteAllFeedImports( requestUtils );
10 | await requestUtils.deleteAllPosts();
11 |
12 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_imports');
13 | await tryCloseTourModal( page );
14 | } );
15 |
16 |
17 | test( 'filters', async({ editor, page }) => {
18 | await page.getByRole('button', { name: 'Step 2 Filters' }).click({ force: true });
19 |
20 | // Hover over text named Filter by Keyword
21 | const filtersTab = page.locator('#feedzy-import-form > div.feedzy-accordion > div:nth-child(2)');
22 |
23 | // It should have 1 elements with .only-pro-content class.
24 | await expect( filtersTab.locator('.pro-label').count() ).resolves.toBe(1);
25 |
26 | } );
27 |
28 |
29 | test( 'general settings', async({ editor, page }) => {
30 | await page.getByRole('button', { name: 'Step 4 General feed settings' }).click({ force: true });
31 |
32 |
33 |
34 | await page.locator('.fz-form-group:has( #feed-post-default-thumbnail )').hover({ force: true });
35 | let upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=fallback-image"]');
36 | await expect( upgradeAlert ).toBeVisible();
37 |
38 | await page.locator('.fz-form-group:has( #fz-event-schedule )').scrollIntoViewIfNeeded()
39 | await page.locator('.fz-form-group:has( #fz-event-schedule )').hover({ force: true });
40 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=schedule-import-job"]');
41 | await expect( upgradeAlert ).toBeVisible();
42 |
43 | await page.locator('.fz-form-group:has( #delete-attached-media )').hover({ force: true });
44 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=delete-featured-image"]');
45 | await expect( upgradeAlert ).toBeVisible();
46 |
47 | await page.locator('.fz-form-group:has( #feedzy_mark_duplicate )').hover({ force: true });
48 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=remove-duplicates"]');
49 | await expect( upgradeAlert ).toBeVisible();
50 | } );
51 | });
52 |
53 | test.describe( 'List Page Upsell', () => {
54 | test.beforeEach( async ( { requestUtils, page } ) => {
55 | await page.goto('/wp-admin/edit.php?post_type=feedzy_imports');
56 | } );
57 |
58 | test('Import/Export', async ({ editor, page }) => {
59 | // Locate and click the "Import Job" link
60 | const importButton = page.locator('.fz-export-import-btn');
61 | await expect(importButton).toBeVisible();
62 | await importButton.click();
63 |
64 | // Wait for the popup to become visible
65 | const upsellPopup = page.locator('#fz_import_export_upsell');
66 | await expect(upsellPopup).toBeVisible();
67 |
68 | // Locate and check the "Upgrade to PRO" link inside the popup
69 | const upgradeToProLink = upsellPopup.locator('a', { hasText: 'Upgrade to PRO' });
70 | await expect(upgradeToProLink).toBeVisible();
71 |
72 | // Get the URL from the "Upgrade to PRO" link
73 | const upsellLink = new URL(await upgradeToProLink.getAttribute('href'));
74 |
75 | // Validate the URL parameters
76 | expect(upsellLink.host).toBe('themeisle.com');
77 | expect(upsellLink.searchParams.get('utm_source')).toBe('wpadmin');
78 | expect(upsellLink.searchParams.get('utm_medium')).toBe('edit');
79 | expect(upsellLink.searchParams.get('utm_content')).toBe('feedzy-rss-feeds');
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/tests/test-image-import.php:
--------------------------------------------------------------------------------
1 | getMethod( 'try_save_featured_image' );
23 | $try_save_featured_image->setAccessible( true );
24 |
25 | // Check that NON-IMAGE URL returns invalid
26 | $import_errors = array();
27 | $import_info = array();
28 | $arguments = array( 'a random string', 0, 'Post Title', &$import_errors, &$import_info, array() );
29 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments );
30 |
31 | $this->assertFalse( $response );
32 |
33 | $this->assertTrue( count( $import_errors ) > 0 );
34 | $this->assertEquals( 'Invalid Featured Image URL: a random string', $import_errors[0] );
35 |
36 |
37 | // For the next test, we will use a valid URL, but the image does not exist. We will check that the error is logged and is the expected one.
38 | add_filter( 'themeisle_log_event', function( $product, $message, $type, $file, $line ) {
39 | if ( $type === 'error' ) {
40 | $this->assertTrue( strpos( $message, 'Unable to download file' ) !== false );
41 | }
42 | }, 10, 5 );
43 |
44 | $import_errors = array();
45 | $import_info = array();
46 | $arguments = array( 'https://example.com/path_to_image/image.jpeg', 0, 'Post Title', &$import_errors, &$import_info, array() );
47 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments );
48 |
49 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set.
50 | $this->assertFalse( $response );
51 | $this->assertTrue( empty( $import_errors ) );
52 |
53 | $import_errors = array();
54 | $import_info = array();
55 | $arguments = array( 'https://example.com/path_to_image/image w space in name.jpeg', 0, 'Post Title', &$import_errors, &$import_info, array() );
56 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments );
57 |
58 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set.
59 | $this->assertFalse( $response );
60 | $this->assertTrue( empty( $import_errors ) );
61 | }
62 |
63 | public function test_import_image_special_characters() {
64 | $feedzy = new Feedzy_Rss_Feeds_Import( 'feedzy-rss-feeds', '1.2.0' );
65 |
66 | $reflector = new ReflectionClass( $feedzy );
67 | $try_save_featured_image = $reflector->getMethod( 'try_save_featured_image' );
68 | $try_save_featured_image->setAccessible( true );
69 |
70 | $import_errors = array();
71 | $import_info = array();
72 |
73 | $arguments = array( 'https://example.com/path_to_image/çöp.jpg?itok=ZYU_ihPB', 0, 'Post Title', &$import_errors, &$import_info, array() );
74 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments );
75 |
76 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set.
77 | $this->assertFalse( $response );
78 | $this->assertTrue( empty( $import_errors ) );
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/test-post-access.php:
--------------------------------------------------------------------------------
1 | get_rand_name();
32 | $admin_id = $this->factory->user->create(
33 | array(
34 | 'role' => 'administrator',
35 | )
36 | );
37 | wp_set_current_user( $admin_id );
38 | $p = $this->factory->post->create_and_get(
39 | array(
40 | 'post_title' => $random_name,
41 | 'post_type' => 'feedzy_categories',
42 | 'post_author' => $admin_id,
43 | )
44 | );
45 | do_action( 'save_post', $p->ID, $p );
46 | $this->assertEquals( $p->post_title, $random_name );
47 | $this->assertEquals( $p->post_type, 'feedzy_categories' );
48 |
49 | $this->assertTrue( feedzy_current_user_can() );
50 | $this->assertTrue( current_user_can( 'edit_post', $p->ID ) );
51 |
52 |
53 | $contributor_id = $this->factory->user->create(
54 | array(
55 | 'role' => 'contributor',
56 | )
57 | );
58 | wp_set_current_user( $contributor_id );
59 |
60 | $this->assertFalse( feedzy_current_user_can() );
61 | $this->assertFalse( current_user_can( 'edit_post', $p->ID ) );
62 |
63 | }
64 |
65 | public function test_contributor_user_with_errors() {
66 | $feedzy = new Feedzy_Rss_Feeds_Admin('feedzy', 'latest');
67 | $contributor_id = $this->factory->user->create(
68 | array(
69 | 'role' => 'contributor',
70 | )
71 | );
72 | wp_set_current_user( $contributor_id );
73 | $post_id = $this->factory->post->create( array( 'post_author' => get_current_user_id() ) );
74 | $GLOBALS['post'] = get_post( $post_id );
75 | // Mock feed object and errors.
76 | $feed = (object) array( 'multifeed_url' => array( 'http://example.com/feed' ) );
77 | $errors = array( 'Error 1' );
78 |
79 |
80 | $actual_output = $feedzy->feedzy_default_error_notice( $errors, $feed, 'http://example.com/feed' );
81 |
82 | $this->assertEquals( '', $actual_output );
83 | }
84 | public function test_author_user_with_errors_admin_screen() {
85 | $feedzy = new Feedzy_Rss_Feeds_Admin('feedzy', 'latest');
86 |
87 | $contributor_id = $this->factory->user->create(
88 | array(
89 | 'role' => 'author',
90 | )
91 | );
92 | wp_set_current_user( $contributor_id );
93 |
94 | $post_id = $this->factory->post->create( array( 'post_author' => get_current_user_id() ) );
95 | $GLOBALS['post'] = get_post( $post_id );
96 | // Mock feed object and errors.
97 | $feed = (object) array( 'multifeed_url' => array( 'http://example.com/feed' ) );
98 | $errors = array( 'Error 1' );
99 |
100 | set_current_screen('admin.php');
101 | $actual_output = $feedzy->feedzy_default_error_notice( $errors, $feed, 'http://example.com/feed' );
102 |
103 | $this->assertEquals( '', $actual_output );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/uninstall.php:
--------------------------------------------------------------------------------
1 |