diff --git a/.github/workflows/build-dev-artifacts.yml b/.github/workflows/build-dev-artifacts.yml index 3165840e2..ff9e81db3 100755 --- a/.github/workflows/build-dev-artifacts.yml +++ b/.github/workflows/build-dev-artifacts.yml @@ -26,7 +26,8 @@ jobs: - name: Create zip run: | npm ci - npm run build + npm run build + npm version patch --no-git-tag-version CURRENT_VERSION=$(node -p -e "require('./package.json').version") COMMIT_HASH=$(git rev-parse --short HEAD) DEV_VERSION="${CURRENT_VERSION}-dev.${COMMIT_HASH}" diff --git a/composer.lock b/composer.lock index 33b3ec27e..a855ba171 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.47", + "version": "3.3.48", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "59499ba103c0369c98b9e0d878826939e3e8e408" + "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/59499ba103c0369c98b9e0d878826939e3e8e408", - "reference": "59499ba103c0369c98b9e0d878826939e3e8e408", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", + "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.47" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.48" }, - "time": "2025-07-21T14:06:29+00:00" + "time": "2025-08-11T16:47:24+00:00" } ], "packages-dev": [ diff --git a/css/feedzy-rss-feed-import.css b/css/feedzy-rss-feed-import.css index d2c83da18..75daee626 100644 --- a/css/feedzy-rss-feed-import.css +++ b/css/feedzy-rss-feed-import.css @@ -8,7 +8,7 @@ * Author: Themeisle */ -#feedzy_import_feeds{ +#feedzy_import_feeds { background: transparent; border: 0; box-shadow: none; @@ -18,49 +18,62 @@ padding-right: 12px; box-sizing: border-box; } -#feedzy_import_feeds > .postbox-header{ + +#feedzy_import_feeds>.postbox-header { display: none; } -.post-type-feedzy_imports:not(.edit-php) .page-title-action, .post-type-feedzy_imports:not(.edit-php) .wp-heading-inline { + +.post-type-feedzy_imports:not(.edit-php) .page-title-action, +.post-type-feedzy_imports:not(.edit-php) .wp-heading-inline { display: none !important; } -.post-type-feedzy_imports:not(.edit-php) #wpcontent{ + +.post-type-feedzy_imports:not(.edit-php) #wpcontent { padding-left: 0; } -.post-type-feedzy_imports:not(.edit-php) #post-body-content{ + +.post-type-feedzy_imports:not(.edit-php) #post-body-content { display: none; } -.post-type-feedzy_imports:not(.edit-php) .wrap{ + +.post-type-feedzy_imports:not(.edit-php) .wrap { margin-top: 0; margin-right: 0; } -.post-type-feedzy_imports:not(.edit-php) .wrap > .wp-heading-inline, -.post-type-feedzy_imports:not(.edit-php) .wrap > .wp-header-end{ + +.post-type-feedzy_imports:not(.edit-php) .wrap>.wp-heading-inline, +.post-type-feedzy_imports:not(.edit-php) .wrap>.wp-header-end { display: none; } -.post-type-feedzy_imports:not(.edit-php) #poststuff{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff { padding-top: 0; } -.post-type-feedzy_imports:not(.edit-php) #poststuff .inside{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff .inside { margin-top: 0; padding-left: 0; padding-right: 0; } -.post-type-feedzy_imports:not(.edit-php) #poststuff #post-body.columns-2{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff #post-body.columns-2 { margin-right: 0; } -.post-type-feedzy_imports:not(.edit-php) #post-body.columns-2 #postbox-container-1{ + +.post-type-feedzy_imports:not(.edit-php) #post-body.columns-2 #postbox-container-1 { width: 100%; float: none; } + .feedzy_page_feedzy-settings #wpcontent, .feedzy_page_feedzy-support #wpcontent, -.feedzy_page_feedzy-integration #wpcontent{ +.feedzy_page_feedzy-integration #wpcontent { padding-left: 0; } + .feedzy_page_feedzy-settings .feedzy-header, .feedzy_page_feedzy-support .feedzy-header, -.feedzy_page_feedzy-integration .feedzy-header{ +.feedzy_page_feedzy-integration .feedzy-header { margin-bottom: 40px; } @@ -68,7 +81,7 @@ color: #ff0000; } -tr.feedzy-import-status-row > td { +tr.feedzy-import-status-row>td { margin: 0; padding: 0 } @@ -140,47 +153,60 @@ td.feedzy-has-popup { .feedzy-onboarding-modal { max-width: 350px; } + .feedzy-onboarding-modal .components-modal__header { text-align: center; } + .feedzy-onboarding-modal .components-modal__header h1 { width: 100%; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-content { text-align: center; margin: 10px 0 15px; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-action { text-align: center; margin: 10px 0; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-action .components-button { padding: 2px 20px; font-size: 14px; margin-right: 10px; } + .components-modal__screen-overlay { background-color: rgba(0, 0, 0, 0.5); } + .react-joyride__tooltip { font-size: 13px !important; } -.react-joyride__tooltip > div { + +.react-joyride__tooltip>div { text-align: left !important; } -.react-joyride__tooltip div:nth-of-type( 2) { + +.react-joyride__tooltip div:nth-of-type(2) { margin-top: 5px !important; } + .react-joyride__tooltip button { text-decoration: underline; } + .react-joyride__beacon span:first-child { background-color: #4268CF !important; } + .react-joyride__beacon span:last-child { background-color: rgba(66, 104, 207, 0.4) !important; border: 2px solid #4268CF !important; } + .react-joyride__tooltip button[data-action="primary"] { background: #4268cf !important; border-color: #006a95 #00648c #00648c !important; @@ -193,20 +219,25 @@ td.feedzy-has-popup { line-height: 28px !important; font-size: 14px !important; } + .react-joyride__tooltip button[data-action="back"] { color: #23282d !important; font-size: 14px !important; } + .react-notification-root .notification-container-bottom-left { bottom: 40px; } + .react-notification-root .notification-container-mobile-bottom { bottom: 60px; } + .feedzy-onboarding-modal-action .components-button.is-primary, .feedzy-onboarding-modal-action .components-button.is-primary:hover:not(:disabled) { background: #4268cf; } + .feedzy-onboarding-modal-action .components-button.is-secondary, .feedzy-onboarding-modal-action .components-button.is-secondary:hover:not(:disabled), .feedzy-onboarding-modal-action .components-button.is-tertiary:hover:not(:disabled) { @@ -279,6 +310,11 @@ i.mce-i-feedzy-icon { text-align: center; } +/* NOTE: It will also exclude the Black Friday notice. */ +.notice:not(.fz-notice):not(.themeisle-sale) { + display: none; +} + @media screen and (max-width: 782px) { tr.feedzy-import-status-row table { width: 100%; diff --git a/css/metabox-settings.css b/css/metabox-settings.css index fae7c4c5b..8701a53cb 100644 --- a/css/metabox-settings.css +++ b/css/metabox-settings.css @@ -42,6 +42,7 @@ .fz-input-group{flex-wrap: wrap;} .fz-input-group .fz-input-group-right{width: 100%; padding-left: 0; padding-top: 8px;} .fz-input-group .fz-input-group-right .btn.dropdown-toggle{width: 100%;} + .fz-input-group .fz-insert-tags{width: auto; padding: 0;} .fz-form-wrap .form-block.form-block-two-column .fz-left{width: 100%; padding-right: 0; padding-bottom: 24px;} .fz-form-wrap .form-block.form-block-two-column .fz-right{width: 100%;} .fz-form-action .fz-left{width: 100%; padding-bottom: 16px;} diff --git a/css/settings.css b/css/settings.css index 10d681fe5..a412151c0 100644 --- a/css/settings.css +++ b/css/settings.css @@ -93,6 +93,9 @@ .mx-320{ max-width: 320px; } +.position-relative{position: relative;} +.position-absolute{position: absolute;} + .feedzy-wrap a:not(#fz-feedback-btn):focus{ box-shadow: none; outline: 0; @@ -140,12 +143,43 @@ } /* feedzy accordion style start */ +.feedzy-accordion-item :is( + .feedzy-accordion__step-title, + .btn, + .cta-text a, + .dashicons, + .h1, .h2, .h3, .h4, .h5, .h6, + .fz-panel-tab__header__label, + .fz-tabs-menu ul li a, + .fz-form-wrap :is( + .form-label, + .form-control, + .form-control .tagify__tag-text, + .chosen-container :is( + .chosen-single, + .chosen-choices + ), + .chosen-container-multi .chosen-choices :is( + li.search-choice, + li.search-field input[type="text"] + ) + ), + .fz-condition-control :is( + .components-input-control__label, + .components-select-control__input, + input, + button + ) +) { + font-size: 15px; +} .feedzy-accordion-item{ background: #ffffff; border: 1px solid #D9D9D9; } .feedzy-accordion-item__title{ position: relative; + padding: 8px 15px; } .fdz-upgrade-link{ width: 100%; @@ -169,34 +203,38 @@ border: 0; background: transparent; outline: 0; - padding: 30px 90px 30px 30px; + padding: 10px; } .feedzy-accordion-item .feedzy-accordion__step-number{ padding-bottom: 10px; } .feedzy-accordion-item .feedzy-accordion__step-title{ color: #050505; + line-height: 1.3; } .feedzy-accordion-item .feedzy-accordion__icon{ position: absolute; right: 30px; top: 50%; - margin-top: -24px; - width: 48px; - height: 48px; + margin-top: -15px; + width: 30px; + height: 30px; background: #F0F2F5; border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 20px; - line-height: 1; + line-height: 1.5; color: #757575; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; } +.feedzy-accordion-item .feedzy-accordion__icon .dashicons{ + line-height: 1.5; +} .feedzy-accordion-item .feedzy-accordion__icon.feedzy-accordion__icon--success{ width: 24px; height: 24px; @@ -235,6 +273,8 @@ -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; + font-size: 10px; + margin-right: 5px; } .feedzy-accordion .feedzy-accordion-item .is-active .feedzy-accordion__step-number{ opacity: 1; @@ -270,8 +310,10 @@ border-bottom: 0; } .fz-form-wrap .form-label{ - display: block; + display: flex; margin-bottom: 8px; + align-items: center; + gap: 10px; } .fz-form-wrap .form-control { @@ -297,6 +339,9 @@ height: auto; min-height: 48px; padding-top: 2px; + line-height: 0; + display: flex; + align-items: center; } .fz-form-wrap .form-control:focus { @@ -377,6 +422,16 @@ fieldset[disabled] .form-control { .fz-input-group .fz-input-group-right .dropdown-toggle{ min-width: 150px; } +.fz-input-group .fz-insert-tags{ + top: 7px; + right: 10px; +} +.fz-input-group .fz-insert-tags button.dropdown-toggle { + padding: 5px 20px; + border: 2px solid; + border-radius: 5px; + min-width: auto; +} .fz-input-group .help-text{ padding-top: 8px; } @@ -558,11 +613,40 @@ fieldset[disabled] .form-control { } .fz-form-wrap .chosen-container .chosen-results li{ padding: 8px; - font-weight: 500; + font-weight: 600; font-size: 16px; line-height: 19px; color: #050505; } +.fz-form-wrap .chosen-container .chosen-results li.group-result{ + font-weight: normal; + text-transform: uppercase; + color: #000000bd; + pointer-events: none; +} +.fz-form-wrap .chosen-container .chosen-results li.feedzy-pro-terms{ + pointer-events: none; + font-weight: 500; +} +.fz-form-wrap .chosen-container .chosen-results li.feedzy-separator{ + font-size: 0; + padding: 0; + border-bottom: 1px solid #757575; + height: 0; +} +.fz-form-wrap .chosen-container .chosen-results li.feedzy-pro-term{ + display: flex; + align-items: center; + gap: 10px; + color: #757575; + pointer-events: none; +} +.fz-form-wrap .chosen-container .chosen-results li.feedzy-pro-term .pro-label{ + background: #80a2ff; + padding-left: 7px; + padding-right: 7px; + border-radius: 5px; +} .fz-form-wrap .chosen-container .chosen-results li:hover, .fz-form-wrap .chosen-container .chosen-results li.result-selected{ background: #F2F2F2; @@ -1048,9 +1132,10 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ } /* FZ tab style start */ -.fz-tabs-menu ul{ +.fz-tabs-menu ul { display: flex; flex-wrap: wrap; + align-items: flex-end; border-bottom: 1px solid #D9D9D9; } @@ -1063,40 +1148,26 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ padding: 15px 16px; text-decoration: none; position: relative; - margin-bottom: 8px; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; + + border-bottom: 4px solid transparent; } .fz-tabs-menu ul li { margin-bottom: 0; } -.fz-tabs-menu ul li a:after{ - content: ""; - width: 100%; - height: 4px; - position: absolute; - bottom: -8px; - left: 0; - background: #4268CF; - opacity: 0; - -webkit-transition: all 0.3s ease-in-out; - -moz-transition: all 0.3s ease-in-out; - -o-transition: all 0.3s ease-in-out; - transition: all 0.3s ease-in-out; -} .fz-tabs-menu ul li a:not(.active):hover{ background: rgba(66, 104, 207, .1); } .fz-tabs-menu ul li a.active{ color: #4268CF; + border-bottom-color: #4268CF; } -.fz-tabs-menu ul li a.active:after{ - opacity: 1; -} + .fz-tabs-menu ul li a .pro-label{ font-size: 10px; line-height: 1; @@ -1106,9 +1177,7 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ } .fz-tabs-menu ul li a:focus { color: #4268CF; -} -.fz-tabs-menu ul li a:focus:after { - opacity: 1; + border-bottom-color: #4268CF; } .fz-tabs-content .fz-tab-content{ @@ -1864,10 +1933,12 @@ span.error-message .components-external-link .components-visually-hidden{ border-radius: 8px; } .popover-action-list ul{ + display: flex; + flex-direction: column; + gap: 14px; margin: 0; } .popover-action-list ul li{ - padding-bottom: 13px; margin-bottom: 0; font-size: 16px; font-weight: 400; @@ -2179,6 +2250,7 @@ li.draggable-item .components-panel__body-toggle.components-button{ } .popover-action-list ul li.fz-action-disabled { cursor: not-allowed !important; + color: #757575; } .fz-action-panel .fz-chat-cpt-action .fz-notice-wrap, @@ -2425,7 +2497,7 @@ li.draggable-item .components-panel__body-toggle.components-button{ padding: 24px 0; } -.fz-condition-control.is-upsell { +.is-upsell { opacity: 0.6; } @@ -2559,6 +2631,61 @@ li.draggable-item .components-panel__body-toggle.components-button{ border-bottom: 0; padding: 24px 0 0; } + +.fz-fallback-images { + display: flex; + flex-wrap: wrap; + gap: 1em; + margin: 1em 0 1em; +} +.feedzy-wrap .fz-spacing{ + margin: 1em 0 1em; +} +.fz-radio-label { + font-weight: 600; + color: #333; + margin-bottom: 4px; +} +.fz-radio-description { + color: #666; + font-size: 13px; + line-height: 1.4; + display: block; +} +.fz-radio-title { + font-size: 16px; + font-weight: 600; + color: #050505; +} + +.validate-feeds-actions { + display: flex;; +} +.validate-feeds-actions .spinner:not(.is-active) { + display: none; +} +.validate-feeds-actions .spinner.is-active { + display: inline-block; +} +.feedzy-preview-list { + padding-left: 15px; +} +.feedzy-preview-list li { + list-style: disc; +} +.feedzy-preview-list li a { + text-decoration: none; +} +.feedzy-preview-list li time { + font-size: 11px; + font-weight: 500; + color: #757575; +} + +#wpbody-content span.dashicons { + line-height: 1.3; +} + @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } @@ -2573,4 +2700,270 @@ li.draggable-item .components-panel__body-toggle.components-button{ .support-box-list > ul{ grid-template-columns: repeat(2, 1fr); } -} \ No newline at end of file +} + +.fz-license-badge { + padding: 2px 8px; + font-size: 10px; + text-transform: uppercase; + border-radius: 5px; + letter-spacing: 0.5px; + color: #ffffff; + background: linear-gradient(135deg, #4268CF 0%, #3458BC 100%); +} + +.feedzy-helper-notice { + margin: 15px 0; + padding: 16px; + background: #e8f4fd; + border-left: 4px solid #2271b1; + font-size: 14px; +} + +.feedzy-helper-notice p { + font-size: 14px; +} + +.feedzy-helper-notice__title { + color: #0073aa; + font-weight: 600; + font-size: 16px; + margin-bottom: 8px; +} + +button.feedzy-action-button { + padding: 0; + font-size: 16px; + font-weight: 400; + background: none; + border: none; + width: 100%; + cursor: pointer; + height: unset; +} + +/* Feedzy Logs */ +.fz-logs { + padding: 10px; + margin: 10px 0; +} + +.fz-logs h3 { + margin-bottom: 10px; + font-size: 1.25em; + color: #333; +} + +/* Logs view container */ +.fz-logs-view { + display: flex; + flex-direction: column; + gap: 2px; +} + +/* Individual log container */ +.fz-log-container { + display: flex; + gap: 10px; + background-color: #fff; + border: 1px solid #e0e0e0; + border-left-width: 5px; + padding: 8px 10px; + transition: background-color 0.1s ease; + font-size: 0.875em; +} + +.fz-log-container:hover { + background-color: #f5f5f5; +} + +/* Left section */ +.fz-log-container__left { + flex: 0 0 50%; + min-width: 0; +} + +/* Header with level and date */ +.fx-log-container__header { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 4px; + font-size: 1.1em; +} + +/* Log level styling */ +.fx-log-container__header > *:first-child { + padding: 2px 6px; + border-radius: 3px; + font-size: 0.7em; + font-weight: 600; + letter-spacing: 0.3px; + text-transform: uppercase; + min-width: 50px; + text-align: center; +} + +/* Date styling */ +.fz-log-container__date { + color: #757575; + font-size: 0.85em; +} + +/* Message styling */ +.fz-log-container__message { + color: #212529; + line-height: 1.3; + word-wrap: break-word; + overflow-wrap: break-word; + font-size: 1em; + margin-top: 10px; +} + +/* Right section - Context */ +.fz-log-container__right { + flex: 1; + min-width: 0; +} + +/* Context styling - compact */ +.fz-log-container__context { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 5px; + padding: 6px 8px; + font-family: 'Courier New', Consolas, Monaco, monospace; + font-size: 0.9em; + line-height: 1.3; + color: #495057; + white-space: pre-wrap; + word-break: break-all; + overflow-wrap: break-word; +} + +.fz-log-container--error { + border-left-color: red; +} + +.fz-log-container--info { + border-left-color: blue; +} + +.fz-log-container--debug { + border-left-color: green; +} + +.fz-log-container--warning { + border-left-color: yellow; +} + +.fz-log-container--critical { + border-left-color: violet; +} + +.fz-logs-header { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + margin-bottom: 1rem; +} + +.fz-logs-header-actions { + display: flex; + flex-direction: row; + gap: 0.5rem; +} + +.fz-logs-header-title { + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: baseline; +} + +.fz-block__column { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.fz-group__row { + display: flex; + flex-direction: row; + gap: 1rem; + align-items: center; +} + +.fz-group__left { + justify-content: flex-end; +} + +.btn-outline-primary.fz-is-destructive { + color: #cc1818; + border-color: #cc1818; +} + +.btn-outline-primary.fz-is-destructive:hover { + color: #cc1818; + border-color: #cc1818; + box-shadow: inset 0 0 0 1px #cc1818, inset 0 0 0 2px #f7f9fd; + background: #fdf7f7; +} + +.fz-log-file-size-wrapper { + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: baseline; +} + +.fz-log-file-size-wrapper .dashicons { + font-size: 1.9em; +} + +.fz-hidden { + display: none; +} + +.fz-quick-link-actions { + display: flex; + gap: 1rem; + margin-bottom: 20px; +} + +#feedzy-validate-feed .dashicons { + font-size: 20px; +} + +.fz-schedule-counter { + text-align: right; + margin-bottom: 10px; + color: #757575; + font-size: 13px; +} + +.fz-schedules-table { + border-collapse: collapse; +} + +.fz-schedules-table th { + font-weight: 600; + background: #f6f7f7; +} + +.fz-schedules-table td { + padding: 10px; +} + +.fz-schedule-attributes { + color: #050505; +} + +.fz-delete-schedule.fz-is-destructive { + background: #dc3545; + border-color: #dc3545; + color: #ffffff; + font-size: 12px; + padding: 4px 12px; +} diff --git a/feedzy-rss-feed.php b/feedzy-rss-feed.php index bf944d720..8e7287482 100644 --- a/feedzy-rss-feed.php +++ b/feedzy-rss-feed.php @@ -152,7 +152,7 @@ function ( $compatibilities ) { $compatibilities['FeedzyPRO'] = array( 'basefile' => defined( 'FEEDZY_PRO_BASEFILE' ) ? FEEDZY_PRO_BASEFILE : '', 'required' => '2.4', - 'tested_up' => '3.0', + 'tested_up' => '3.1', ); return $compatibilities; } @@ -271,6 +271,8 @@ function feedzy_register_parrot( $plugins ) { * @param string $type Error type. * @param string $file File where the event occurred. * @param int $line Line number where the event occurred. + * + * @deprecated 5.1.0 Use Feedzy_Rss_Feeds_Log instead. */ function feedzy_themeisle_log_event( $name, $msg, $type, $file, $line ) { if ( FEEDZY_NAME === $name ) { @@ -279,35 +281,6 @@ function feedzy_themeisle_log_event( $name, $msg, $type, $file, $line ) { } } -/** - * Store import job errors in metadata. - * - * @param string $name Name. - * @param string $msg Error message. - * @param string $type Error type. - * - * @return void - */ -function feedzy_import_job_logs( $name, $msg, $type ) { - if ( ! in_array( $type, apply_filters( 'feedzy_allowed_store_log_types', array( 'error' ) ), true ) ) { - return; - } - if ( ! wp_doing_ajax() || wp_doing_cron() ) { - return; - } - if ( apply_filters( 'feedzy_skip_store_error_logs', false ) ) { - return; - } - global $themeisle_log_event; - - if ( ! empty( $themeisle_log_event ) && count( $themeisle_log_event ) >= 200 ) { - return; - } - - $themeisle_log_event[] = $msg; -} -add_action( 'themeisle_log_event', 'feedzy_import_job_logs', 20, 3 ); - add_filter( 'feedzy_rss_feeds_float_widget_metadata', function () { diff --git a/includes/abstract/feedzy-rss-feeds-admin-abstract.php b/includes/abstract/feedzy-rss-feeds-admin-abstract.php index 65ff80f13..2f7b3b25c 100644 --- a/includes/abstract/feedzy-rss-feeds-admin-abstract.php +++ b/includes/abstract/feedzy-rss-feeds-admin-abstract.php @@ -156,11 +156,10 @@ public function get_usage_data( $data ) { $shortcodes = $wpdb->get_var( "SELECT count(*) FROM {$wpdb->prefix}posts WHERE post_status IN ('publish', 'private') AND post_content LIKE '%[feedzy-rss %'" ); //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $data = array( - 'categories' => $categories, - 'imports' => $imports, - 'shortcodes' => $shortcodes, - 'license' => $license, - 'days_since_install' => round( ( time() - get_option( 'feedzy_rss_feeds_install', time() ) ) / DAY_IN_SECONDS ), + 'categories' => $categories, + 'imports' => $imports, + 'shortcodes' => $shortcodes, + 'license' => $license, ); $settings = apply_filters( 'feedzy_get_settings', null ); @@ -168,7 +167,6 @@ public function get_usage_data( $data ) { if ( ! is_array( $settings ) || empty( $settings ) ) { return $data; } - $general_settings = array(); $config = array(); @@ -197,6 +195,17 @@ public function get_usage_data( $data ) { } } + if ( isset( $settings['custom_schedules'] ) && is_array( $settings['custom_schedules'] ) ) { + $data['custom_schedules_count'] = count( $settings['custom_schedules'] ); + } + + $logger = Feedzy_Rss_Feeds_Log::get_instance(); + $data['logger'] = array( + 'can_send_email' => $logger->can_send_email(), + 'has_email' => ! empty( $logger->get_email_address() ), + 'file_size' => $logger->get_log_file_size(), + ); + return $data; } @@ -527,6 +536,8 @@ public function rest_route() { ), ) ); + + Feedzy_Rss_Feeds_Log::get_instance()->register_endpoints(); } /** @@ -620,6 +631,8 @@ public function get_short_code_attributes( $atts ) { 'default' => '', // thumbs pixel size. 'size' => '', + // default aspect ratio for the image. + 'aspectRatio' => '1', // only display item if title contains specific keywords (Use comma(,) and plus(+) keyword). 'keywords_title' => '', // only display item if title OR content contains specific keywords (Use comma(,) and plus(+) keyword). @@ -859,7 +872,14 @@ function ( $time ) use ( $cache_time ) { if ( ! $wp_filesystem->exists( $dir ) ) { $done = $wp_filesystem->mkdir( $dir ); if ( false === $done ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to create directory %s', $dir ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::warning( + sprintf( 'Unable to create SimplePie cache directory: %s', $dir ), + array( + 'feed_url' => $feed_url, + 'cache' => $cache, + 'sc' => $sc, + ) + ); } } $feed->set_cache_location( $dir ); @@ -900,14 +920,36 @@ function ( $time ) use ( $cache_time ) { } if ( ! empty( $error ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Error while parsing feed: %s', $error ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::error( + // translators: %1$s is the feed URL, %2$s is the error message. + sprintf( __( 'Error while parsing feed URL "%1$s": %2$s', 'feedzy-rss-feeds' ), $feed_url, $error ), + array( + 'feed_url' => $feed_url, + 'cache' => $cache, + 'sc' => $sc, + ) + ); // curl: (60) SSL certificate problem: unable to get local issuer certificate. if ( strpos( $error, 'SSL certificate' ) !== false ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Got an SSL Error (%s), retrying by ignoring SSL', $error ), 'debug', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::warning( + sprintf( 'Got an SSL Error (%s), retrying by ignoring SSL', $error ), + array( + 'feed_url' => $feed_url, + 'cache' => $cache, + 'sc' => $sc, + ) + ); $feed = $this->init_feed( $feed_url, $cache, $sc, false ); } elseif ( is_string( $feed_url ) || ( is_array( $feed_url ) && 1 === count( $feed_url ) ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, 'Trying to use raw data', 'debug', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using raw data for feed: %s', $feed_url ), + array( + 'cache' => $cache, + 'sc' => $sc, + ) + ); + $data = wp_remote_retrieve_body( wp_safe_remote_get( $feed_url, array( 'user-agent' => $default_agent ) ) ); $cloned_feed->set_raw_data( $data ); $cloned_feed->init(); @@ -918,7 +960,14 @@ function ( $time ) use ( $cache_time ) { $feed = $cloned_feed; } } else { - do_action( 'themeisle_log_event', FEEDZY_NAME, 'Cannot use raw data as this is a multifeed URL', 'debug', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::debug( + 'Cannot use raw data as this is a multi-feed URL', + array( + 'feed_url' => $feed_url, + 'cache' => $cache, + 'sc' => $sc, + ) + ); } } return $feed; @@ -1404,44 +1453,54 @@ private function get_feed_item_filter( $sc, $sizes, $item, $feed_url, $index, $i $item_link = $item->get_feed()->get_permalink(); } } - $new_link = apply_filters( 'feedzy_item_url_filter', $item_link, $sc, $item ); + $new_link = apply_filters( 'feedzy_item_url_filter', $item_link, $sc, $item ); + $amp_running = function_exists( 'amp_is_request' ) && amp_is_request(); + $content_thumb = ''; - // Fetch image thumbnail. + $thumbnail_to_use = ''; if ( 'yes' === $sc['thumb'] || 'auto' === $sc['thumb'] ) { - $the_thumbnail = $this->feedzy_retrieve_image( $item, $sc ); - $content_thumb = ''; + // Fetch image thumbnail. + $thumbnail_to_use = $this->feedzy_retrieve_image( $item, $sc ); + $thumbnail_to_use = $this->feedzy_image_encode( $thumbnail_to_use ); + + if ( empty( $thumbnail_to_use ) && 'yes' === $sc['thumb'] ) { + $thumbnail_to_use = $sc['default']; + } + } else { + $thumbnail_to_use = $sc['default']; + } + + if ( ! empty( $thumbnail_to_use ) && is_string( $thumbnail_to_use ) ) { + $img_style = ''; + + if ( isset( $sizes['height'] ) && is_numeric( $sizes['height'] ) ) { + $img_style .= 'height:' . $sizes['height'] . 'px;'; + } + + if ( isset( $sc['aspectRatio'] ) && '1' !== $sc['aspectRatio'] ) { + $img_style .= 'aspect-ratio:' . $sc['aspectRatio'] . '; object-fit: fill;'; + } + if ( - is_string( $the_thumbnail ) && ! empty( $the_thumbnail ) && + isset( $sizes['width'] ) && is_numeric( $sizes['width'] ) && ( - 'yes' === $sc['thumb'] || + $sizes['width'] !== $sizes['height'] || // Note: Custom modification via filters. ( - 'auto' === $sc['thumb'] && - ! strpos( $the_thumbnail, 'img/feedzy.svg' ) + isset( $sc['aspectRatio'] ) && + ( + ( 'auto' === $sc['aspectRatio'] && $amp_running ) || // Note: AMP compatibility. Auto without `height` breaks the layout. + '1' === $sc['aspectRatio'] // Note: Backward compatiblity. + ) ) ) ) { - $the_thumbnail = $this->feedzy_image_encode( $the_thumbnail ); - $content_thumb .= ''; - if ( ! isset( $sc['amp'] ) || 'no' !== $sc['amp'] ) { - $content_thumb .= ''; - } + $img_style .= 'width:' . $sizes['width'] . 'px;'; } - if ( empty( $the_thumbnail ) && 'yes' === $sc['thumb'] ) { - $content_thumb .= ''; - if ( ! isset( $sc['amp'] ) || 'no' !== $sc['amp'] ) { - $content_thumb .= ''; - } - } - $content_thumb = apply_filters( 'feedzy_thumb_output', $content_thumb, $feed_url, $sizes, $item ); - } else { - $content_thumb = ''; - $content_thumb .= ''; - if ( ! isset( $sc['amp'] ) || 'no' !== $sc['amp'] ) { - $content_thumb .= ''; - } - $content_thumb = apply_filters( 'feedzy_thumb_output', $content_thumb, $feed_url, $sizes, $item ); + $content_thumb .= ''; + $content_thumb = apply_filters( 'feedzy_thumb_output', $content_thumb, $feed_url, $sizes, $item ); } + $content_title = html_entity_decode( $item->get_title(), ENT_QUOTES, 'UTF-8' ); if ( is_numeric( $sc['title'] ) ) { $length = intval( $sc['title'] ); @@ -1593,17 +1652,28 @@ private function get_feed_item_filter( $sc, $sizes, $item, $feed_url, $index, $i if ( empty( $item_content ) ) { $item_content = esc_html__( 'Post Content', 'feedzy-rss-feeds' ); } + + $img_style = ''; + if ( isset( $sizes['height'] ) ) { + $img_style = 'height:' . $sizes['height'] . 'px;'; + if ( isset( $sc['aspectRatio'] ) && '1' !== $sc['aspectRatio'] ) { + $img_style .= 'aspect-ratio:' . $sc['aspectRatio'] . ';'; + } elseif ( isset( $sizes['width'] ) ) { + $img_style .= 'width:' . $sizes['width'] . 'px;'; + } + } + $item_array = array( 'feed_url' => $item->get_feed()->subscribe_url(), 'item_unique_hash' => wp_hash( $item->get_permalink() ), 'item_img_class' => 'rss_image', - 'item_img_style' => 'width:' . $sizes['width'] . 'px; height:' . $sizes['height'] . 'px;', + 'item_img_style' => $img_style, 'item_url' => $new_link, 'item_url_target' => $sc['target'], 'item_url_follow' => isset( $sc['follow'] ) && 'yes' === $sc['follow'] ? 'nofollow' : '', 'item_url_title' => $item->get_title(), 'item_img' => $content_thumb, - 'item_img_path' => $this->feedzy_retrieve_image( $item, $sc ), + 'item_img_path' => isset( $sc['thumb'] ) && ( 'yes' === $sc['thumb'] || 'auto' === $sc['thumb'] ) ? $this->feedzy_retrieve_image( $item, $sc ) : '', 'item_title' => $content_title, 'item_content_class' => 'rss_content', 'item_content_style' => '', @@ -1911,7 +1981,15 @@ public function feedzy_image_encode( $img_url ) { } $filtered_url = apply_filters( 'feedzy_image_encode', esc_url( $img_url ), $img_url ); - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Changing image URL from %s to %s', $img_url, $filtered_url ), 'debug', __FILE__, __LINE__ ); + + Feedzy_Rss_Feeds_Log::debug( + 'Change featured image via feedzy_image_encode', + array( + 'old_url' => $img_url, + 'new_url' => $filtered_url, + ) + ); + return $filtered_url; } diff --git a/includes/admin/feedzy-rss-feeds-actions.php b/includes/admin/feedzy-rss-feeds-actions.php index ffd130ece..e7bb78154 100644 --- a/includes/admin/feedzy-rss-feeds-actions.php +++ b/includes/admin/feedzy-rss-feeds-actions.php @@ -493,13 +493,25 @@ private function chat_gpt_rewrite() { $content = wp_strip_all_tags( $content ); $content = substr( $content, 0, apply_filters( 'feedzy_chat_gpt_content_limit', 3000 ) ); $prompt_content = $this->current_job->data->ChatGPT; - $ai_provider = 'openai'; + + $additional_data = array( + 'ai_provider' => 'openai', + ); + if ( isset( $this->current_job->data ) && isset( $this->current_job->data->aiProvider ) ) { - $ai_provider = $this->current_job->data->aiProvider; + $additional_data['ai_provider'] = $this->current_job->data->aiProvider; } + + if ( + 'openai' === $additional_data['ai_provider'] && + isset( $this->current_job->data ) && isset( $this->current_job->data->aiModel ) + ) { + $additional_data['ai_model'] = $this->current_job->data->aiModel; + } + $content = str_replace( array( '{content}' ), array( $content ), $prompt_content ); $openai = new \Feedzy_Rss_Feeds_Pro_Openai(); - $rewrite_content = $openai->call_api( $this->settings, $content, '', array( 'ai_provider' => $ai_provider ) ); + $rewrite_content = $openai->call_api( $this->settings, $content, '', $additional_data ); // Replace prompt content string for specific cases. $rewrite_content = str_replace( explode( '{content}', $prompt_content ), '', trim( $rewrite_content, '"' ) ); return $rewrite_content; diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index dcf455803..6d6ff973f 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -128,6 +128,34 @@ public function enqueue_styles() { } wp_register_style( $this->plugin_name, FEEDZY_ABSURL . 'css/feedzy-rss-feeds.css', array(), $this->version, 'all' ); } + + /** + * Helper function to enqueue the license script with localization + * + * @access public + * @return void + */ + private function enqueue_license_script() { + wp_enqueue_script( + $this->plugin_name . '_license', + FEEDZY_ABSURL . 'js/feedzy-license.js', + array( 'jquery' ), + $this->version, + true + ); + + wp_localize_script( + $this->plugin_name . '_license', + 'feedzyLicense', + array( + 'l10n' => array( + 'licenseKey' => __( 'License Key', 'feedzy-rss-feeds' ), + 'checkBtn' => __( 'Check License', 'feedzy-rss-feeds' ), + 'errorMsg' => __( 'An error occurred while checking the license. Please try again.', 'feedzy-rss-feeds' ), + ), + ) + ); + } /** * Register the stylesheets for the admin area. @@ -203,9 +231,12 @@ public function enqueue_styles_admin() { 'media_iframe_button' => __( 'Set default image', 'feedzy-rss-feeds' ), 'action_btn_text_1' => __( 'Choose image', 'feedzy-rss-feeds' ), 'action_btn_text_2' => __( 'Replace image', 'feedzy-rss-feeds' ), + 'delete_btn_label' => __( 'Delete', 'feedzy-rss-feeds' ), ), ) ); + + $this->enqueue_license_script(); } $upsell_screens = array( 'feedzy-rss_page_feedzy-settings', 'feedzy-rss_page_feedzy-admin-menu-pro-upsell' ); @@ -214,16 +245,39 @@ public function enqueue_styles_admin() { $asset_file = include FEEDZY_ABSPATH . '/build/action-popup/index.asset.php'; wp_enqueue_script( $this->plugin_name . '_action_popup', FEEDZY_ABSURL . 'build/action-popup/index.js', array_merge( $asset_file['dependencies'], array( 'wp-editor', 'wp-api' ) ), $asset_file['version'], true ); + $openai_model = ''; + $open_router_model = ''; + $integration_settings = get_option( 'feedzy-rss-feeds-settings', array() ); + + $all_open_ai_models = apply_filters( 'feedzy_openai_models', array() ); + $deprecated_open_ai_models = apply_filters( 'feedzy_openai_deprecated_models', array() ); + $active_open_ai_models = array_values( array_diff( $all_open_ai_models, $deprecated_open_ai_models ) ); + + if ( ! empty( $integration_settings['openai_api_model'] ) ) { + $openai_model = $integration_settings['openai_api_model']; + } + + if ( ! empty( $integration_settings['openrouter_api_model'] ) ) { + $open_router_model = $integration_settings['openrouter_api_model']; + } + wp_localize_script( $this->plugin_name . '_action_popup', 'feedzyData', array( - 'isPro' => feedzy_is_pro(), - 'isBusinessPlan' => apply_filters( 'feedzy_is_license_of_type', false, 'business' ), - 'isAgencyPlan' => apply_filters( 'feedzy_is_license_of_type', false, 'agency' ), - 'apiLicenseStatus' => $this->api_license_status(), - 'isHighPrivileges' => current_user_can( 'manage_options' ), - 'languageList' => $this->get_lang_list(), + 'isPro' => feedzy_is_pro(), + 'isBusinessPlan' => apply_filters( 'feedzy_is_license_of_type', false, 'business' ), + 'isAgencyPlan' => apply_filters( 'feedzy_is_license_of_type', false, 'agency' ), + 'apiLicenseStatus' => $this->api_license_status(), + 'isHighPrivileges' => current_user_can( 'manage_options' ), + 'languageList' => $this->get_lang_list(), + 'integrationSettings' => get_option( 'feedzy-rss-feeds-settings' ), + 'integrations' => array( + 'openAIModel' => $openai_model, + 'openRouterModel' => $open_router_model, + ), + 'activeOpenAIModels' => $active_open_ai_models, + 'deprecatedOpenAIModels' => $deprecated_open_ai_models, ) ); @@ -278,8 +332,15 @@ public function enqueue_styles_admin() { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( 'feedzy_page_feedzy-support' === $screen->base && ( ( isset( $_GET['tab'] ) && 'improve' === $_GET['tab'] ) || ( 'edit' !== $screen->base && 'feedzy_imports' === $screen->post_type ) ) ) { - + $tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : ''; + + if ( 'feedzy_page_feedzy-support' === $screen->base && + ( + ( 'improve' === $tab ) + || ( 'edit' !== $screen->base && 'feedzy_imports' === $screen->post_type ) + || ( 'license' === $tab ) + ) + ) { $asset_file = include FEEDZY_ABSPATH . '/build/feedback/index.asset.php'; wp_enqueue_script( $this->plugin_name . '_feedback', FEEDZY_ABSURL . 'build/feedback/index.js', array_merge( $asset_file['dependencies'], array( 'wp-editor', 'wp-api', 'lodash' ) ), $asset_file['version'], true ); wp_enqueue_style( 'wp-block-editor' ); @@ -293,6 +354,8 @@ public function enqueue_styles_admin() { ) ); + $this->enqueue_license_script(); + wp_set_script_translations( $this->plugin_name . '_feedback', 'feedzy-rss-feeds' ); } @@ -401,7 +464,7 @@ public function add_modals() { openModal('#feedzy-add-new-import'); event.preventDefault(); }); - + // Function to open the modal function openModal(modal) { jQuery(modal).show(); @@ -422,30 +485,55 @@ function closeModal(e) { jQuery(document).on('keyup', function (e) { if (e.key === "Escape") closeModal(); }); + }); + + @@ -532,21 +687,34 @@ class="button button-primary button-large">

' . $invalid - . ' -

' . __( 'Learn how to organize feeds in Groups', 'feedzy-rss-feeds' ) . '

+ . ' +
+ + +
'; echo wp_kses( $output, apply_filters( 'feedzy_wp_kses_allowed_html', array() ) ); } + /** + * Render the feed preview metabox. + * + * @return void + */ + public function render_feed_preview() { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { + return; + } + $feeds = get_post_meta( get_the_ID(), 'feedzy_category_feed', true ); + $feed_urls = $this->normalize_urls( $feeds ); + if ( empty( $feed_urls ) ) { + echo '

' . esc_html__( 'No feeds available for preview.', 'feedzy-rss-feeds' ) . '

'; + return; + } + $shortcode = array( + 'sort' => 'date_desc', + 'thumb' => 'no', + 'max' => 0, + ); + $atts = $this->get_short_code_attributes( $shortcode ); + $atts = $this->sanitize_attr( $atts, $feed_urls ); + $sizes = array( + 'width' => 0, + 'height' => 0, + ); + $feed = $this->fetch_feed( $feed_urls, $atts['refresh'], $atts ); + $feed_items = apply_filters( 'feedzy_get_feed_array', array(), $atts, $feed, $feed_urls, $sizes ); + $total_items = count( $feed_items ); + + $max_items_preview_count = 5; + $preview_feed_items = array_slice( $feed_items, 0, $max_items_preview_count ); + ?> + + + +
+
    + +
  • > + + + +
    + +
  • + +
+
+ getTimestamp() + $array->getOffset(); + + return sprintf( + // translators: %s is the time difference. + __( '%s ago', 'feedzy-rss-feeds' ), + human_time_diff( $item_publish_time, $localtime ) + ); + } + /** * Utility method to save metabox data to * custom post type. @@ -841,6 +1141,8 @@ public function feedzy_category_columns( $columns ) { $columns['actions'] = __( 'Actions', 'feedzy-rss-feeds' ); } + $new_columns = $this->array_insert_before( 'slug', $columns, 'source', __( 'Source', 'feedzy-rss-feeds' ) ); + return $columns; } @@ -885,6 +1187,33 @@ public function manage_feedzy_category_columns( $column, $post_id ) { case 'actions': echo wp_kses_post( sprintf( '', __( 'Click to remove invalid URLs from this category', 'feedzy-rss-feeds' ), $post_id, __( 'Validate & Clean', 'feedzy-rss-feeds' ) ) ); break; + case 'source': + $src = get_post_meta( $post_id, 'feedzy_category_feed', true ); + if ( empty( $src ) ) { + $src = __( 'No Source Configured', 'feedzy-rss-feeds' ); + } else { + $urls = $this->normalize_urls( $src ); + $src = ''; + if ( is_array( $urls ) ) { + + foreach ( $urls as $key => $url ) { + $too_long = 130; + if ( strlen( $src ) > $too_long ) { + $src .= '...'; + break; + } else { + $src .= '' . $url . ''; + if ( count( $urls ) > $key + 1 ) { + $src .= ', '; + } + } + } + } else { + $src .= '' . esc_html( $urls ) . ''; + } + } + echo wp_kses_post( $src ); + break; default: break; } @@ -923,6 +1252,7 @@ public function feedzy_filter_plugin_row_meta( $links, $file ) { * * @since 3.0.12 * @access public + * @return void */ public function feedzy_menu_pages() { $capability = feedzy_current_user_can(); @@ -934,35 +1264,35 @@ public function feedzy_menu_pages() { add_submenu_page( 'feedzy-admin-menu', - __( 'Settings', 'feedzy-rss-feeds' ), - __( 'Settings', 'feedzy-rss-feeds' ), + __( 'Dashboard', 'feedzy-rss-feeds' ), + __( 'Dashboard', 'feedzy-rss-feeds' ), 'manage_options', - 'feedzy-settings', + 'feedzy-support', array( $this, - 'feedzy_settings_page', + 'render_support', ) ); add_submenu_page( 'feedzy-admin-menu', - __( 'Integration', 'feedzy-rss-feeds' ), - __( 'Integration', 'feedzy-rss-feeds' ), + __( 'Settings', 'feedzy-rss-feeds' ), + '↳ ' . __( 'Settings', 'feedzy-rss-feeds' ), 'manage_options', - 'feedzy-integration', + 'feedzy-settings', array( $this, - 'feedzy_integration_page', + 'feedzy_settings_page', ) ); add_submenu_page( 'feedzy-admin-menu', - __( 'Support', 'feedzy-rss-feeds' ), - __( 'Support', 'feedzy-rss-feeds' ) . '', + __( 'Integration', 'feedzy-rss-feeds' ), + '↳ ' . __( 'Integration', 'feedzy-rss-feeds' ), 'manage_options', - 'feedzy-support', + 'feedzy-integration', array( $this, - 'render_support', + 'feedzy_integration_page', ) ); @@ -981,37 +1311,55 @@ public function feedzy_menu_pages() { add_action( "load-$hook", array( $this, 'feedzy_load_setup_wizard_page' ) ); add_action( 'adminmenu', array( $this, 'feedzy_hide_wizard_menu' ) ); } - if ( ! defined( 'REVIVE_NETWORK_VERSION' ) ) { - $rss_to_social = __( 'RSS to Social', 'feedzy-rss-feeds' ) . ''; - add_action( - 'admin_footer', - function () { - ?> - - '; + add_action( + 'admin_footer', + function () { + ?> + + isset( $_POST['proxy-pass'] ) ? sanitize_text_field( wp_unslash( $_POST['proxy-pass'] ) ) : '', ); break; + case 'schedules': + if ( feedzy_is_pro() ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $custom_schedules = isset( $_POST['fz-custom-schedule-interval'] ) ? (array) wp_unslash( $_POST['fz-custom-schedule-interval'] ) : array(); + $settings['custom_schedules'] = array(); + if ( ! empty( $custom_schedules ) ) { + $cron_timout = defined( 'WP_CRON_LOCK_TIMEOUT' ) ? WP_CRON_LOCK_TIMEOUT : 60; + + foreach ( $custom_schedules as $key => $value ) { + $interval = isset( $value['interval'] ) ? absint( $value['interval'] ) : $cron_timout; + $display = isset( $value['display'] ) ? sanitize_text_field( $value['display'] ) : ''; + + if ( + is_numeric( $interval ) && $cron_timout <= $interval && + ! empty( $display ) + ) { + $settings['custom_schedules'][ $key ] = array( + 'interval' => $interval, + 'display' => $display, + ); + } + } + } + } + break; default: $settings = apply_filters( 'feedzy_save_tab_settings', $settings, $post_tab ); } @@ -1166,7 +1539,13 @@ private function add_proxy( $url ) { if ( $settings && isset( $settings['proxy'] ) && is_array( $settings['proxy'] ) && ! empty( $settings['proxy'] ) ) { // if even one constant is defined, escape. if ( defined( 'WP_PROXY_HOST' ) || defined( 'WP_PROXY_PORT' ) || defined( 'WP_PROXY_USERNAME' ) || defined( 'WP_PROXY_PASSWORD' ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, 'Some proxy constants already defined; ignoring proxy settings', 'info', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::info( + 'Some proxy constants already defined; ignoring proxy settings', + array( + 'url' => $url, + 'settings' => $settings['proxy'], + ) + ); return; } @@ -1223,7 +1602,14 @@ public function http_request_args( $args ) { public function add_user_agent( $ua ) { $settings = apply_filters( 'feedzy_get_settings', null ); if ( $settings && isset( $settings['header']['user-agent'] ) && ! empty( $settings['header']['user-agent'] ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Override user-agent from %s to %s', $ua, $settings['header']['user-agent'] ), 'info', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::info( + 'Overriding user-agent', + array( + 'old_user_agent' => $ua, + 'new_user_agent' => $settings['header']['user-agent'], + ) + ); + $ua = $settings['header']['user-agent']; } @@ -1242,7 +1628,14 @@ public function add_user_agent( $ua ) { public function send_through_proxy( $return_value, $uri, $check, $home ) { $proxied = defined( 'FEEZY_URL_THRU_PROXY' ) ? FEEZY_URL_THRU_PROXY : null; if ( $proxied && ( ( is_array( $proxied ) && in_array( $uri, $proxied, true ) ) || $uri === $proxied ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'sending %s through proxy', $uri ), 'info', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::info( + 'Sending through proxy', + array( + 'uri' => $uri, + 'check' => $check, + 'home' => $home, + ) + ); return true; } @@ -1441,7 +1834,7 @@ public function get_source_validity_error( $message = '', $post = '', $css_class if ( $invalid ) { if ( empty( $css_class ) ) { - $css_class = 'notice notice-error notice-alt feedzy-error-critical'; + $css_class = 'notice notice-error notice-alt fz-notice feedzy-error-critical'; } $message .= '

' . $text . ':

    '; foreach ( $invalid as $url ) { @@ -1477,6 +1870,23 @@ public function ajax() { } wp_send_json_success( array( 'invalid' => count( $invalid ) ) ); break; + case 'validate_feeds_group': + $feeds = isset( $_POST['feeds'] ) ? sanitize_text_field( wp_unslash( $_POST['feeds'] ) ) : ''; + if ( empty( $feeds ) ) { + wp_send_json_error( __( 'No feeds provided for validation.', 'feedzy-rss-feeds' ) ); + } + $feeds = $this->normalize_urls( $feeds ); + if ( ! is_array( $feeds ) ) { + $feeds = array( $feeds ); + } + $valid = $this->get_valid_source_urls( $feeds, '1_mins', false ); + $invalid = array_diff( $feeds, $valid ); + wp_send_json_success( + array( + 'valid' => $valid, + 'invalid' => $invalid, + ) + ); } } @@ -1564,6 +1974,10 @@ public function feedzy_load_setup_wizard_page() { * Enqueue setup wizard required scripts. */ public function feedzy_enqueue_setup_wizard_scripts() { + if ( ! did_action( 'wp_enqueue_media' ) ) { + wp_enqueue_media(); + } + wp_enqueue_style( $this->plugin_name . '_chosen' ); wp_enqueue_style( $this->plugin_name . '_smart_wizard', FEEDZY_ABSURL . 'css/smart_wizard_all.min.css', array(), $this->version ); wp_enqueue_style( $this->plugin_name . '_setup_wizard', FEEDZY_ABSURL . 'includes/views/css/style-wizard.css', array( $this->plugin_name . '-settings' ), $this->version, 'all' ); @@ -1589,6 +2003,18 @@ public function feedzy_enqueue_setup_wizard_scripts() { 'firstButtonText' => __( 'Create Page', 'feedzy-rss-feeds' ), 'secondButtonText' => __( 'Do not create', 'feedzy-rss-feeds' ), ), + 'mediaUploadText' => array( + 'iframeTitle' => __( 'Select image', 'feedzy-rss-feeds' ), + 'iframeButton' => __( 'Set default image', 'feedzy-rss-feeds' ), + 'actionButtonTextOne' => __( 'Choose image', 'feedzy-rss-feeds' ), + 'actionButtonTextTwo' => __( 'Replace image', 'feedzy-rss-feeds' ), + 'actionButtonTextThree' => __( 'Remove image', 'feedzy-rss-feeds' ), + ), + 'dryRun' => array( + 'loading' => '

    ' . __( 'Processing the source and loading the items that will be imported when it runs', 'feedzy-rss-feeds' ) . '...

    ' + . '

    ', + 'title' => __( 'Importable Items', 'feedzy-rss-feeds' ), + ), ) ); } @@ -1605,7 +2031,20 @@ public function feedzy_dismiss_wizard( $redirect_to_dashboard = true ) { update_option( 'feedzy_fresh_install', $status ); delete_option( 'feedzy_wizard_data' ); if ( false !== $redirect_to_dashboard ) { - wp_safe_redirect( remove_query_arg( array( 'action', 'status' ) ) ); + + $cleaned_url = remove_query_arg( array( 'page', 'action', 'status' ) ); + $parsed_url = wp_parse_url( $cleaned_url ); + + // Default to dashboard if no page is set. + if ( + isset( $parsed_url['path'] ) && + strpos( $parsed_url['path'], '/wp-admin/admin.php' ) !== false && + empty( $parsed_url['query'] ) + ) { + $cleaned_url = add_query_arg( 'page', 'feedzy-support', $cleaned_url ); + } + + wp_safe_redirect( $cleaned_url ); exit; } return true; @@ -1809,7 +2248,12 @@ private function setup_wizard_subscribe_process() { $segment = 1; $response = array( 'status' => 1, - 'redirect_to' => add_query_arg( 'post_type', 'feedzy_imports', admin_url( 'edit.php' ) ), + 'redirect_to' => add_query_arg( + array( + 'page' => 'feedzy-support', + ), + admin_url( 'admin.php' ) + ), 'message' => __( 'Redirecting to Feedzy dashboard', 'feedzy-rss-feeds' ), ); @@ -1834,30 +2278,48 @@ private function setup_wizard_subscribe_process() { } else { $response = array( 'status' => 1, - 'redirect_to' => add_query_arg( 'post_type', 'feedzy_imports', admin_url( 'edit.php' ) ), + 'redirect_to' => add_query_arg( + array( + 'page' => 'feedzy-support', + ), + admin_url( 'admin.php' ) + ), 'message' => __( 'Redirecting to Feedzy dashboard', 'feedzy-rss-feeds' ), ); } } elseif ( 'page_builder' === $integrate_with ) { - $post_edit_link = get_edit_post_link( $page_id, 'db' ); - // Get elementor edit page link. - if ( defined( 'ELEMENTOR_PATH' ) && class_exists( 'Elementor\Widget_Base' ) ) { - $segment = 3; - $post_edit_link = add_query_arg( - array( - 'post' => $page_id, - 'action' => 'elementor', + if ( empty( $page_id ) ) { + $response = array( + 'status' => 1, + 'redirect_to' => add_query_arg( + array( + 'page' => 'feedzy-support', + ), + admin_url( 'admin.php' ) ), - admin_url( 'post.php' ) + 'message' => __( 'Redirecting to Feedzy dashboard', 'feedzy-rss-feeds' ), ); } else { - $segment = 4; + $post_edit_link = get_edit_post_link( $page_id, 'db' ); + // Get elementor edit page link. + if ( defined( 'ELEMENTOR_PATH' ) && class_exists( 'Elementor\Widget_Base' ) ) { + $segment = 3; + $post_edit_link = add_query_arg( + array( + 'post' => $page_id, + 'action' => 'elementor', + ), + admin_url( 'post.php' ) + ); + } else { + $segment = 4; + } + $response = array( + 'status' => 1, + 'redirect_to' => $post_edit_link, + 'message' => __( 'Redirecting to draft page', 'feedzy-rss-feeds' ), + ); } - $response = array( - 'status' => 1, - 'redirect_to' => $post_edit_link, - 'message' => __( 'Redirecting to draft page', 'feedzy-rss-feeds' ), - ); } if ( $with_subscribe && is_email( $email ) ) { update_option( 'feedzy_rss_feeds_logger_flag', 'yes' ); @@ -1892,6 +2354,7 @@ private function setup_wizard_subscribe_process() { if ( ! is_wp_error( $request_res ) ) { $body = json_decode( wp_remote_retrieve_body( $request_res ) ); if ( 'success' === $body->code ) { + update_option( 'feedzy_onboarding_user_subscribed', 'yes' ); $this->feedzy_dismiss_wizard( false ); wp_send_json( $response ); } @@ -1909,6 +2372,74 @@ private function setup_wizard_subscribe_process() { } } + /** + * AJAX method to subscribe user to Feedzy newsletter via dashboard notice. + * + * @since 5.1.0 + * @access public + * @return void + */ + public function feedzy_dashboard_subscribe() { + check_ajax_referer( 'feedzy_subscribe_nonce', '_wpnonce' ); + + $email = ! empty( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; + $skip = ! empty( $_POST['skip'] ) ? sanitize_text_field( wp_unslash( $_POST['skip'] ) ) : ''; + + if ( 'yes' === $skip ) { + $this->dismiss_subscribe_notice(); + wp_send_json_success(); + } + + if ( empty( $email ) || ! is_email( $email ) ) { + wp_send_json_error( + array( + 'message' => __( 'Validation failed', 'feedzy-rss-feeds' ), + ) + ); + } + + update_option( 'feedzy_rss_feeds_logger_flag', 'yes' ); + + $request_res = wp_remote_post( + FEEDZY_SUBSCRIBE_API, + array( + 'timeout' => 100, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'body' => wp_json_encode( + array( + 'slug' => 'feedzy-rss-feeds', + 'site' => home_url(), + 'email' => $email, + 'data' => array( 'source' => 'dashboard-notice' ), + ) + ), + ) + ); + + if ( ! is_wp_error( $request_res ) ) { + $this->dismiss_subscribe_notice(); + wp_send_json_success(); + } else { + wp_send_json_error( + array( + 'message' => $request_res->get_error_message(), + ) + ); + } + } + + /** + * Dismiss subscribe notice. + * + * @since 5.1.0 + * @return void + */ + public function dismiss_subscribe_notice() { + update_option( 'feedzy_dismiss_subscribe_notice_dashboard', 'yes' ); + } + /** * Create draft page. * @@ -2215,7 +2746,7 @@ public function add_banner_anchor() { add_action( 'admin_notices', function () { - echo '
    '; + echo '
    '; }, 999 ); @@ -2513,4 +3044,179 @@ public function add_black_friday_data( $configs ) { return $configs; } + + /** + * Validate the feed URL and check if it's a valid RSS/Atom feed. + * + * @return void + */ + public function validate_feed() { + try { + if ( + ! isset( $_POST['nonce'] ) || + ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), FEEDZY_BASEFILE ) + ) { + wp_send_json_error( array( 'message' => __( 'Security check failed.', 'feedzy-rss-feeds' ) ) ); + } + + $feed_urls = isset( $_POST['feed_url'] ) ? sanitize_text_field( wp_unslash( $_POST['feed_url'] ) ) : ''; + + if ( empty( $feed_urls ) ) { + wp_send_json_error( array( 'message' => __( 'Feed URL cannot be empty.', 'feedzy-rss-feeds' ) ) ); + } + + $urls = array_map( 'trim', explode( ',', $feed_urls ) ); + $urls = array_filter( $urls ); + + if ( empty( $urls ) ) { + wp_send_json_error( array( 'message' => __( 'No valid URLs provided.', 'feedzy-rss-feeds' ) ) ); + } + + $results = array(); + + foreach ( $urls as $feed_url ) { + $feed_url = esc_url_raw( $feed_url ); + + if ( ! filter_var( $feed_url, FILTER_VALIDATE_URL ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Invalid URL format', 'feedzy-rss-feeds' ), + ); + continue; + } + + $feed = $this->fetch_feed( array( $feed_url ), '1_mins', array() ); + + if ( is_wp_error( $feed ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Error fetching feed: ', 'feedzy-rss-feeds' ) . $feed->get_error_message(), + ); + continue; + } + + if ( + ! is_object( $feed ) || + ! method_exists( $feed, 'get_item_quantity' ) + ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Invalid feed object returned', 'feedzy-rss-feeds' ), + ); + continue; + } + + try { + $items = $feed->get_item_quantity(); + $title = $feed->get_title(); + $error = $feed->error(); + + if ( is_array( $error ) && ! empty( $error ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Error fetching feed: ', 'feedzy-rss-feeds' ) . implode( ', ', $error ), + ); + continue; + } + + if ( 0 === $items ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'warning', + 'message' => __( 'Feed is empty', 'feedzy-rss-feeds' ), + ); + continue; + } + + $results[] = array( + 'url' => $feed_url, + 'status' => 'success', + 'message' => $title . sprintf( + /* translators: %d is the number of items found in the feed */ + _n( + '%d item found', + '%d items found', + $items, + 'feedzy-rss-feeds' + ), + $items + ), + 'items' => $items, + 'title' => $title, + ); + + } catch ( Throwable $e ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + /* translators: %s is the error message */ + 'message' => sprintf( __( 'Error reading feed: %s', 'feedzy-rss-feeds' ), $e->getMessage() ), + ); + } + } + + wp_send_json_success( + array( + 'results' => $results, + ) + ); + } catch ( Throwable $e ) { + wp_send_json_error( + array( + /* translators: %s is the error message */ + 'message' => sprintf( __( 'An error occurred: %s', 'feedzy-rss-feeds' ), $e->getMessage() ), + ) + ); + } + } + + /** + * Append custom schedules to the existing schedules. + * + * @since 5.1.0 + * @param array $schedules Existing schedules. + * @return array Modified schedules with custom schedules appended. + */ + public function append_custom_cron_schedules( $schedules ) { + if ( ! feedzy_is_pro() ) { + return $schedules; + } + + $saved_settings = apply_filters( 'feedzy_get_settings', array() ); + if ( ! empty( $saved_settings['custom_schedules'] ) && is_array( $saved_settings['custom_schedules'] ) ) { + $custom_schedules = $saved_settings['custom_schedules']; + + foreach ( $custom_schedules as $key => $value ) { + if ( ! empty( $value['interval'] ) && ! empty( $value['display'] ) ) { + $schedules[ $key ] = array( + 'interval' => intval( $value['interval'] ), + 'display' => sanitize_text_field( $value['display'] ), + ); + } + } + } + + return $schedules; + } + + /** + * Add slugs for internal cron schedules. + * + * @param string[] $cron_slugs The cron slugs to be modified. + * @return string[] + */ + public function internal_cron_schedule_slugs( $cron_slugs ) { + $wp_standard_schedules = array( + 'hourly', + 'twicedaily', + 'daily', + 'weekly', + ); + + return array_merge( $wp_standard_schedules, $cron_slugs ); + } } diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index a0c0c4d7e..650315a67 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -24,6 +24,7 @@ * Class Feedzy_Rss_Feeds_Import */ class Feedzy_Rss_Feeds_Import { + /** * The ID of this plugin. * @@ -121,11 +122,22 @@ public function upsell_content( $content, $area, $location ) { * @access public */ public function enqueue_styles() { - wp_enqueue_style( $this->plugin_name, FEEDZY_ABSURL . 'css/feedzy-rss-feed-import.css', array(), $this->version, 'all' ); - wp_register_style( $this->plugin_name . '_chosen', FEEDZY_ABSURL . 'includes/views/css/chosen.css', array(), $this->version, 'all' ); - wp_register_script( $this->plugin_name . '_chosen_script', FEEDZY_ABSURL . 'includes/views/js/chosen.js', array( 'jquery' ), $this->version, true ); + $screen = get_current_screen(); - if ( get_current_screen()->post_type === 'feedzy_imports' ) { + if ( + 'feedzy_imports' === $screen->post_type || + 'feedzy_categories' === $screen->post_type || + 'feedzy_page_feedzy-integration' === $screen->id || + 'feedzy_page_feedzy-settings' === $screen->id || + 'feedzy_page_feedzy-support' === $screen->id || + 'feedzy_page_feedzy-setup-wizard' === $screen->id + ) { + wp_enqueue_style( $this->plugin_name, FEEDZY_ABSURL . 'css/feedzy-rss-feed-import.css', array(), $this->version, 'all' ); + wp_register_style( $this->plugin_name . '_chosen', FEEDZY_ABSURL . 'includes/views/css/chosen.css', array(), $this->version, 'all' ); + wp_register_script( $this->plugin_name . '_chosen_script', FEEDZY_ABSURL . 'includes/views/js/chosen.js', array( 'jquery' ), $this->version, true ); + } + + if ( 'feedzy_imports' === $screen->post_type ) { if ( ! did_action( 'wp_enqueue_media' ) ) { wp_enqueue_media(); } @@ -149,15 +161,25 @@ public function enqueue_styles() { $this->plugin_name . '_metabox_edit_script', 'feedzy', array( - 'ajax' => array( + 'ajax' => array( 'security' => wp_create_nonce( FEEDZY_BASEFILE ), + 'url' => admin_url( 'admin-ajax.php' ), + ), + 'pages' => array( + 'logs' => add_query_arg( + array( + 'page' => 'feedzy-settings', + 'tab' => 'logs', + ), + admin_url( 'admin.php' ) + ), ), - 'i10n' => array( + 'i10n' => array( 'importing' => __( 'Importing', 'feedzy-rss-feeds' ) . '...', 'run_now' => __( 'Run Now', 'feedzy-rss-feeds' ), 'dry_run_loading' => '

    ' . __( 'Processing the source and loading the items that will be imported when it runs', 'feedzy-rss-feeds' ) . '...

    ' - . '

    ' . __( 'Please note that if some of these items have already have been imported in previous runs with the same filters, they may be shown here but will not be imported again.', 'feedzy-rss-feeds' ) . '

    ' - . '

    ', + . '

    ' . __( 'Please note that if some of these items have already have been imported in previous runs with the same filters, they may be shown here but will not be imported again.', 'feedzy-rss-feeds' ) . '

    ' + . '

    ', 'dry_run_title' => __( 'Importable Items', 'feedzy-rss-feeds' ), 'delete_post_message' => __( 'Would you also like to delete all the imported posts for this import job?', 'feedzy-rss-feeds' ), 'media_iframe_title' => __( 'Select image', 'feedzy-rss-feeds' ), @@ -166,6 +188,7 @@ public function enqueue_styles() { 'action_btn_text_2' => __( 'Replace image', 'feedzy-rss-feeds' ), 'author_helper' => __( 'We display up to 100 users. If the desired username isn’t listed, type the exact existing username manually to save it.', 'feedzy-rss-feeds' ), 'clearLogButton' => __( 'Clear Log', 'feedzy-rss-feeds' ), + 'goToLogsTab' => __( 'See more details', 'feedzy-rss-feeds' ), 'okButton' => __( 'Ok', 'feedzy-rss-feeds' ), 'removeErrorLogsMsg' => __( 'Removed all error logs.', 'feedzy-rss-feeds' ), // translators: %d select images count. @@ -176,6 +199,11 @@ public function enqueue_styles() { feedzy_is_pro() ? 'dashicons-upload' : 'dashicons-lock', esc_html__( 'Upload Import', 'feedzy-rss-feeds' ) ), + 'is_pro' => feedzy_is_pro(), + 'validation_messages' => array( + 'invalid_feed_url' => __( 'Invalid feed URL.', 'feedzy-rss-feeds' ), + 'error_validating_feed_url' => __( 'Error validating feed URL.', 'feedzy-rss-feeds' ), + ), ), ) ); @@ -185,11 +213,11 @@ public function enqueue_styles() { /** * Add attributes to $item_array. * - * @param array $item_array The item attributes array. - * @param object $item The feed item. - * @param array $sc The shortcode attributes array. - * @param int $index The item number. - * @param int $item_index The real index of this item in the feed. + * @param array $item_array The item attributes array. + * @param SimplePie\Item $item The feed item. + * @param array $sc The shortcode attributes array. + * @param int $index The item number. + * @param int $item_index The real index of this item in the feed. * @return mixed * @since 1.0.0 * @access public @@ -204,17 +232,84 @@ public function add_data_to_item( $item_array, $item, $sc = null, $index = null, $item_array['item'] = $item; $item_array['item_index'] = $item_index; + $item_array = $this->handle_youtube_content( $item_array, $item, $sc ); + + return $item_array; + } + + /** + * Fetches additional information for each item. + * + * @param array $item_array The item attributes array. + * @param SimplePie\Item $item The feed item. + * @param array $sc The shortcode attributes array. This will be empty through the block editor. + * + * @return array + */ + private function handle_youtube_content( $item_array, $item, $sc ) { + $url = ''; + if ( array_key_exists( 'item_url', $item_array ) ) { + $url = $item_array['item_url']; + } elseif ( $item ) { + $url = $item->get_permalink(); + } + + if ( empty( $url ) ) { + return $item_array; + } + + $host = wp_parse_url( $url, PHP_URL_HOST ); + + // Remove all dots in the hostname so that shortforms such as youtu.be can also be resolved. + $host = str_replace( array( '.', 'www' ), '', $host ); + + if ( ! in_array( $host, array( 'youtubecom', 'youtube' ), true ) ) { + // Not a YouTube link, return the item array as is. + return $item_array; + } + + $tags = $item->get_item_tags( \SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group' ); + $desc = ''; + if ( $tags ) { + $desc_tag = $tags[0]['child'][ \SimplePie\SimplePie::NAMESPACE_MEDIARSS ]['description']; + if ( $desc_tag ) { + $desc = $desc_tag[0]['data']; + } + } + + if ( ! empty( $desc ) ) { + if ( empty( $item_array['item_content'] ) ) { + $item_array['item_content'] = $desc; + } + + if ( + ( empty( $sc ) || 'yes' === $sc['summary'] ) && + empty( $item_array['item_description'] ) + ) { + if ( + is_numeric( $sc['summarylength'] ) && + strlen( $desc ) > $sc['summarylength'] + ) { + $desc = preg_replace( '/\s+?(\S+)?$/', '', substr( $desc, 0, $sc['summarylength'] ) ) . ' […]'; + } + $item_array['item_description'] = $desc; + } + } + + $embed_video_shortcode = '[embed]' . $url . '[/embed]'; + $should_overwrite = str_contains( $item_array['item_content'], 'Post Content' ); + $item_array['item_content'] = ( $should_overwrite ? '' : $item_array['item_content'] ) . $embed_video_shortcode; + return $item_array; } /** * Retrieve the categories. * - * @param string $dumb The initial categories (only a placeholder argument for the filter). - * @param object $item The feed item. + * @param string $dumb The initial categories (only a placeholder argument for the filter). + * @param SimplePie\Item $item The feed item. * * @return string - * @since ? * @access public */ public function retrieve_categories( $dumb, $item ) { @@ -222,11 +317,7 @@ public function retrieve_categories( $dumb, $item ) { $categories = $item->get_categories(); if ( $categories ) { foreach ( $categories as $category ) { - if ( is_string( $category ) ) { - $cats[] = $category; - } else { - $cats[] = $category->get_label(); - } + $cats[] = $category->get_label(); } } @@ -384,6 +475,9 @@ public function feedzy_import_feed_options() { $mark_duplicate_tag = get_post_meta( $post->ID, 'mark_duplicate_tag', true ); $import_post_author = get_post_meta( $post->ID, 'import_post_author', true ); $filter_conditions = get_post_meta( $post->ID, 'filter_conditions', true ); + $import_remove_html = get_post_meta( $post->ID, 'import_remove_html', true ); + $import_remove_html = 'yes' === $import_remove_html ? 'checked' : ''; + $import_order = get_post_meta( $post->ID, 'import_order', true ); if ( empty( $filter_conditions ) ) { $filter_conditions = apply_filters( @@ -420,10 +514,23 @@ public function feedzy_import_feed_options() { $import_content = '[[{"value":"%5B%7B%22id%22%3A%22%22%2C%22tag%22%3A%22item_content%22%2C%22data%22%3A%7B%7D%7D%5D"}]]'; } - if ( feedzy_is_pro() && empty( $import_post_term ) ) { + if ( feedzy_is_pro() && empty( $import_post_term ) && 'post-new.php' === $pagenow ) { $import_post_term = '[#auto_categories]'; } + if ( feedzy_is_pro() ) { + $custom_terms = array( + '[#item_categories]' => __( 'Item Categories', 'feedzy-rss-feeds' ), + '[#auto_categories]' => __( 'Auto Categories by keyword', 'feedzy-rss-feeds' ), + ); + } elseif ( ! feedzy_is_pro() ) { + $custom_terms = array( + '[#item_categories]' => __( 'Item Categories', 'feedzy-rss-feeds' ) . sprintf( '%s', __( 'PRO', 'feedzy-rss-feeds' ) ), + '[#auto_categories]' => __( 'Auto Categories by keyword', 'feedzy-rss-feeds' ) . sprintf( '%s', __( 'PRO', 'feedzy-rss-feeds' ) ), + ); + } + $custom_post_term = wp_json_encode( $custom_terms ); + $import_link_author_admin = get_post_meta( $post->ID, 'import_link_author_admin', true ); $import_link_author_public = get_post_meta( $post->ID, 'import_link_author_public', true ); @@ -436,6 +543,12 @@ public function feedzy_import_feed_options() { $import_link_author[1] = 'checked'; } + // default values when creating a import. + if ( 'post-new.php' === $pagenow ) { + $import_date = '[#item_date]'; + $import_featured_img = '[[{"value":"%5B%7B%22id%22%3A%22%22%2C%22tag%22%3A%22item_image%22%2C%22data%22%3A%7B%7D%7D%5D"}]]'; + } + // maybe more options are required from pro? $pro_options = apply_filters( 'feedzy_metabox_options', array(), $post->ID ); @@ -445,30 +558,20 @@ public function feedzy_import_feed_options() { if ( empty( $import_feed_limit ) ) { $import_feed_limit = 10; } - $import_feed_delete_days = intval( get_post_meta( $post->ID, 'import_feed_delete_days', true ) ); - if ( empty( $import_feed_delete_days ) ) { - $import_feed_delete_days = ! empty( $this->free_settings['general']['feedzy-delete-days'] ) ? (int) $this->free_settings['general']['feedzy-delete-days'] : 0; - } - $import_feed_delete_media = get_post_meta( $post->ID, 'import_feed_delete_media', true ); - if ( empty( $import_feed_delete_media ) ) { - $import_feed_delete_media = ! empty( $this->free_settings['general']['feedzy-delete-media'] ) ? 'yes' : 'no'; + $default_thumbnail_id = 0; + $inherited_thumbnail_id = ! empty( $this->free_settings['general']['default-thumbnail-id'] ) ? (int) $this->free_settings['general']['default-thumbnail-id'] : 0; + $custom_thumbnail_id = get_post_meta( $post->ID, 'default_thumbnail_id', true ); + + if ( is_numeric( $custom_thumbnail_id ) ) { + $default_thumbnail_id = $custom_thumbnail_id; } - $import_feed_delete_media = 'yes' === $import_feed_delete_media ? 'checked' : ''; - $default_thumbnail_id = 0; if ( feedzy_is_pro() ) { - $default_thumbnail_id = get_post_meta( $post->ID, 'default_thumbnail_id', true ); - if ( - empty( $default_thumbnail_id ) && - '0' !== $default_thumbnail_id // Can use the fallback image from Global Settings. - ) { - $default_thumbnail_id = ! empty( $this->free_settings['general']['default-thumbnail-id'] ) ? (int) $this->free_settings['general']['default-thumbnail-id'] : 0; - } + $import_schedule = array( + 'fz_cron_schedule' => ! empty( $this->free_settings['general']['fz_cron_schedule'] ) ? $this->free_settings['general']['fz_cron_schedule'] : '', + ); } - $import_schedule = array( - 'fz_cron_schedule' => ! empty( $this->free_settings['general']['fz_cron_schedule'] ) ? $this->free_settings['general']['fz_cron_schedule'] : '', - ); $fz_cron_schedule = get_post_meta( $post->ID, 'fz_cron_schedule', true ); if ( ! empty( $fz_cron_schedule ) ) { @@ -495,7 +598,8 @@ public function feedzy_import_feed_options() { * * @return int The range. Capped if the user is not PRO. */ - public function items_limit( $range, $post ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + public function items_limit( $range, $post ) { + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed if ( ! feedzy_is_pro() ) { $range = range( 10, 10, 10 ); } @@ -594,15 +698,15 @@ public function save_feedzy_import_feed_meta( $post_id, $post ) { $data_meta['import_auto_translation'] = isset( $data_meta['import_auto_translation'] ) ? $data_meta['import_auto_translation'] : 'no'; // Check feeds external image URL checkbox checked OR not. $data_meta['import_use_external_image'] = isset( $data_meta['import_use_external_image'] ) ? $data_meta['import_use_external_image'] : 'no'; + // Check feeds remove html checkbox checked OR not. + $data_meta['import_remove_html'] = isset( $data_meta['import_remove_html'] ) ? $data_meta['import_remove_html'] : 'no'; + $data_meta['import_post_term'] = isset( $data_meta['import_post_term'] ) ? $data_meta['import_post_term'] : ''; // If it is filter_conditions we want to escape it. if ( isset( $data_meta['filter_conditions'] ) ) { $data_meta['filter_conditions'] = wp_slash( $data_meta['filter_conditions'] ); } - // Check feeds remove attached media checkbox checked OR not. - $data_meta['import_feed_delete_media'] = isset( $data_meta['import_feed_delete_media'] ) ? $data_meta['import_feed_delete_media'] : 'no'; - // $data_meta['feedzy_post_author'] should be the author username. We convert it to the author ID. if ( ! empty( $data_meta['import_post_author'] ) ) { $author = get_user_by( 'login', $data_meta['import_post_author'] ); @@ -786,7 +890,7 @@ public function manage_feedzy_import_columns( $column, $post_id ) { $src = sprintf( '%s: %s%s%s', __( 'Feed Group', 'feedzy-rss-feeds' ), '', $src, '' ); } } elseif ( empty( $src ) ) { - $src = __( 'No Source Configured', 'feedzy-rss-feeds' ); + $src = __( 'No Source Configured', 'feedzy-rss-feeds' ); } else { $src = sprintf( '%s: %s%s%s', __( 'Feed Group', 'feedzy-rss-feeds' ), '', $src, '' ); } @@ -834,7 +938,7 @@ public function manage_feedzy_import_columns( $column, $post_id ) { } $msg .= $this->get_last_run_details( $post_id ); - echo( $msg ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ( $msg ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ( 'publish' === $post->post_status ) { printf( '

    ', esc_attr( $post_id ), esc_attr__( 'Run Now', 'feedzy-rss-feeds' ) ); @@ -1268,8 +1372,20 @@ private function get_taxonomies() { */ private function run_now() { check_ajax_referer( FEEDZY_BASEFILE, 'security' ); + + $job_id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ); + $job = get_post( $job_id ); + + Feedzy_Rss_Feeds_Log::info( + sprintf( + 'Manual run for import: %s', + isset( $job->post_title ) ? $job->post_title : 'Unknown Job' + ), + array( + 'job_id' => $job_id, + ) + ); - $job = get_post( filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ) ); $count = $this->run_job( $job, 100 ); $msg = 0 < $count ? __( 'Successfully run!', 'feedzy-rss-feeds' ) : __( 'Nothing imported!', 'feedzy-rss-feeds' ); @@ -1279,7 +1395,7 @@ private function run_now() { array( 'msg' => $msg, 'import_success' => 0 < $count, - ) + ) ); } @@ -1380,6 +1496,13 @@ function ( $errors, $feed, $url ) { implode( ',', $tags ) ); + Feedzy_Rss_Feeds_Log::debug( + 'Dry run shortcode generated', + array( + 'shortcode' => $shortcode, + ) + ); + wp_send_json_success( array( 'output' => do_shortcode( $shortcode ) ) ); } @@ -1423,14 +1546,40 @@ public function run_cron( $max = 100, $job_id = 0 ) { foreach ( $feedzy_imports as $job ) { try { $result = $this->run_job( $job, $max ); + + Feedzy_Rss_Feeds_Log::debug( + 'Cron job run for: ' . $job->post_title, + array( + 'job_id' => $job->ID, + 'result' => $result, + ) + ); + if ( empty( $result ) ) { $this->run_job( $job, $max ); + + Feedzy_Rss_Feeds_Log::debug( + 'Previous run did not return any results, running again for job: ' . $job->post_title, + array( + 'job_id' => $job->ID, + 'result' => $result, + ) + ); } do_action( 'feedzy_run_cron_extra', $job ); } catch ( Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( '[Feedzy Run Cron][Post title: ' . ( ! empty( $job->post_title ) ? $job->post_title : '' ) . '] Error: ' . $e->getMessage() ); } + + Feedzy_Rss_Feeds_Log::error( + // translators: %1$s is the import job title, %2$s is the error message. + sprintf( __( 'Error when running "%1$s": %2$s', 'feedzy-rss-feeds' ), $job->post_title, $e->getMessage() ), + array( + 'job_id' => $job->ID, + 'error' => $e->getMessage(), + ) + ); } } } @@ -1448,6 +1597,7 @@ public function run_cron( $max = 100, $job_id = 0 ) { */ private function run_job( $job, $max ) { Feedzy_Rss_Feeds_Usage::get_instance()->track_rss_import(); + Feedzy_Rss_Feeds_Log::get_instance()->enable_error_messages_retention(); global $themeisle_log_event; $source = get_post_meta( $job->ID, 'source', true ); @@ -1478,6 +1628,38 @@ private function run_job( $job, $max ) { $mark_duplicate_tag = get_post_meta( $job->ID, 'mark_duplicate_tag', true ); $mark_duplicate_tag = feedzy_is_pro() && ! empty( $mark_duplicate_tag ) ? preg_replace( '/[\[\]#]/', '', $mark_duplicate_tag ) : ''; $max = $import_feed_limit; + $import_remove_html = get_post_meta( $job->ID, 'import_remove_html', true ); + $import_order = get_post_meta( $job->ID, 'import_order', true ); + + Feedzy_Rss_Feeds_Log::info( + 'Running import job: ' . $job->post_title . ' (ID: ' . $job->ID . ')', + array( + 'job_id' => $job->ID, + 'source' => $source, + 'max' => $max, + 'status' => $job->post_status, + 'exc_key' => $exc_key, + 'inc_key' => $inc_key, + 'inc_on' => $inc_on, + 'exc_on' => $exc_on, + 'import_title' => $import_title, + 'import_date' => $import_date, + 'post_excerpt' => $post_excerpt, + 'import_content' => $import_content, + 'import_featured_img' => $import_featured_img, + 'import_post_type' => $import_post_type, + 'import_post_term' => $import_post_term, + 'import_feed_limit' => $import_feed_limit, + 'import_item_img_url' => $import_item_img_url, + 'import_remove_duplicates' => $import_remove_duplicates, + 'import_selected_language' => $import_selected_language, + 'from_datetime' => $from_datetime, + 'mark_duplicate_tag' => $mark_duplicate_tag, + 'filter_conditions' => $filter_conditions, + 'import_auto_translation' => $import_auto_translation, + 'import_translation_lang' => $import_translation_lang, + ) + ); if ( empty( $filter_conditions ) ) { $filter_conditions = apply_filters( @@ -1552,10 +1734,19 @@ private function run_job( $job, $max ) { 'multiple_meta' => 'no', 'refresh' => '55_mins', 'filters' => $filter_conditions, + 'sort' => $import_order, ), $job ); + Feedzy_Rss_Feeds_Log::info( + 'Running job options via feedzy_shortcode_options', + array( + 'job_id' => $job->ID, + 'options' => $options, + ) + ); + $admin = Feedzy_Rss_Feeds::instance()->get_admin(); $options = $admin->sanitize_attr( $options, $source ); @@ -1583,18 +1774,35 @@ private function run_job( $job, $max ) { $import_info = array(); $results = $this->get_job_feed( $options, $import_content, true ); $language_code = $results['feed']->get_language(); - + $xml_results = ''; if ( str_contains( $import_content, '_full_content' ) ) { $xml_results = $this->get_job_feed( $options, '[#item_content]', true ); } - + if ( is_wp_error( $results ) ) { - $import_errors[] = $results->get_error_message(); + // BUG: If $results is error, the import run details will not show the results even if the errors are set. + Feedzy_Rss_Feeds_Log::error( + sprintf( + // translators: %s is the error message. + __( 'Error when fetching the feed items: %s', 'feedzy-rss-feeds' ), + $results->get_error_message() + ), + array( + 'job_id' => $job->ID, + 'errors' => $results->get_error_messages(), + 'options' => $options, + 'source' => $source, + ) + ); + + $import_errors = Feedzy_Rss_Feeds_Log::get_instance()->get_error_messages_accumulator(); + Feedzy_Rss_Feeds_Log::get_instance()->disable_error_messages_retention(); + update_post_meta( $job->ID, 'import_errors', $import_errors ); update_post_meta( $job->ID, 'imported_items_count', 0 ); - return; + return 0; } $result = $results['items']; @@ -1612,6 +1820,13 @@ private function run_job( $job, $max ) { if ( empty( $import_title ) && empty( $import_content ) ) { $import_errors[] = __( 'Title & Content are both empty.', 'feedzy-rss-feeds' ); $start_import = false; + + Feedzy_Rss_Feeds_Log::warning( + 'Import job cannot start because both Title and Content mapping are empty.', + array( + 'job_id' => $job->ID, + ) + ); } if ( ! $start_import ) { @@ -1625,7 +1840,16 @@ private function run_job( $job, $max ) { $duplicates = array(); $items_found = array(); $found_duplicates = array(); + $result_count = count( $result ); + foreach ( $result as $key => $item ) { + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Processing item %1$s/%2$s', $key, $result_count ), + array( + 'item_url' => $item['item_url'], + ) + ); + $item_obj = $item; // find item index key when import full content. if ( ! empty( $xml_results ) ) { @@ -1670,11 +1894,30 @@ function ( $tag ) use ( $item_obj, $item ) { $found_duplicates[ $item_hash ] = get_post_meta( $p, 'feedzy_' . $mark_duplicate_key, true ); $duplicates[ $item['item_url'] ] = $item['item_title']; wp_delete_post( $p, true ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Deleted a duplicate post: %1$s', $item['item_title'] ), + array( + 'item_url' => $item['item_url'], + 'post_id' => $p, + 'hash' => $item_hash, + ) + ); } } } if ( $is_duplicate ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Ignoring %s as it is a duplicate (%s hash).', $item_hash, $use_new_hash ? 'new' : 'old' ), 'warn', __FILE__, __LINE__ ); + do_action( + 'feedzy_log_event', + array( + 'type' => 'info', + 'output' => sprintf( 'Ignoring URl %1$s with hash %2$s as it is a duplicate (%3$s hash).', $item['item_url'], $item_hash, $use_new_hash ? 'new' : 'old' ), + 'file' => __FILE__, + 'line' => __LINE__, + ) + ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Ignoring item %1$s as it is a duplicate (%2$s hash).', $item['item_url'], $use_new_hash ? 'new' : 'old' ) + ); ++$index; $duplicates[ $item['item_url'] ] = $item['item_title']; continue; @@ -1690,10 +1933,17 @@ function ( $tag ) use ( $item_obj, $item ) { $author = $item['item_author']->get_email(); } } - } else { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Author is empty for %s.', $item['item_title'] ), 'warn', __FILE__, __LINE__ ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Found author: %s', $author ), + array( + 'item_author' => $item['item_author'], + 'item_url' => $item['item_url'], + ) + ); } + // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date $item_date = wp_date( get_option( 'date_format' ) . ' at ' . get_option( 'time_format' ), $item['item_date'] ); $item_date = $item['item_date_formatted']; @@ -1790,8 +2040,20 @@ function ( $attr, $key ) { if ( $rewrite_service_endabled && false !== strpos( $post_title, '[#title_feedzy_rewrite]' ) ) { $title_feedzy_rewrite = apply_filters( 'feedzy_invoke_content_rewrite_services', $item['item_title'], '[#title_feedzy_rewrite]', $job, $item ); $post_title = str_replace( '[#title_feedzy_rewrite]', $title_feedzy_rewrite, $post_title ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using Feedzy rewrite service for item title: %1$s', $item['item_title'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_content_rewrite_services', + 'service_output' => $title_feedzy_rewrite, + ) + ); } + if ( is_string( $post_title ) ) { + $post_title = wp_strip_all_tags( $post_title ); + } + $image_html = ''; if ( ! empty( $item['item_img_path'] ) ) { $image_html = ''; @@ -1801,6 +2063,16 @@ function ( $attr, $key ) { $translated_description = ''; if ( $import_auto_translation && ( false !== strpos( $import_content, '[#translated_description]' ) || false !== strpos( $post_excerpt, '[#translated_description]' ) ) ) { $translated_description = apply_filters( 'feedzy_invoke_auto_translate_services', $item['item_full_description'], '[#translated_description]', $import_translation_lang, $job, $language_code, $item ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using auto-translation service for item description: %1$s', $item['item_full_description'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_auto_translate_services', + 'service_output' => $translated_description, + 'language_code' => $language_code, + ) + ); } // Get translated item content. @@ -1808,6 +2080,15 @@ function ( $attr, $key ) { if ( $import_auto_translation && ( false !== strpos( $import_content, '[#translated_content]' ) || false !== strpos( $post_excerpt, '[#translated_content]' ) ) ) { $translated_content = ! empty( $item['item_content'] ) ? $item['item_content'] : $item['item_description']; $translated_content = apply_filters( 'feedzy_invoke_auto_translate_services', $translated_content, '[#translated_content]', $import_translation_lang, $job, $language_code, $item ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using auto-translation service for item content: %1$s', $item['item_content'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_auto_translate_services', + 'service_output' => $translated_description, + ) + ); } // Used as a new line character in import content. @@ -1858,6 +2139,20 @@ function ( $attr, $key ) { __( 'Full content is empty. Error: %s', 'feedzy-rss-feeds' ), $full_content_error ); + + Feedzy_Rss_Feeds_Log::error( + sprintf( + // translators: %s: Error message for empty full content. + __( 'Full content is empty. Error: %s', 'feedzy-rss-feeds' ), + $full_content_error + ), + array( + 'job_id' => $job->ID, + 'import_errors' => $import_errors, + 'item_url' => $item['item_url'], + 'source' => $source, + ) + ); } $post_content = str_replace( @@ -1888,18 +2183,47 @@ function ( $attr, $key ) { if ( $import_auto_translation && false !== strpos( $post_content, '[#translated_full_content]' ) ) { $translated_full_content = apply_filters( 'feedzy_invoke_auto_translate_services', $item['item_url'], '[#translated_full_content]', $import_translation_lang, $job, $language_code, $item ); $post_content = str_replace( '[#translated_full_content]', rtrim( $translated_full_content, '.' ), $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using auto-translation service for item full content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_auto_translate_services', + 'service_output' => $translated_full_content, + 'language_code' => $language_code, + ) + ); } // Rewriter item content from feedzy API. if ( $rewrite_service_endabled && false !== strpos( $post_content, '[#content_feedzy_rewrite]' ) ) { $item_content = ! empty( $item['item_content'] ) ? $item['item_content'] : $item['item_description']; $content_feedzy_rewrite = apply_filters( 'feedzy_invoke_content_rewrite_services', $item_content, '[#content_feedzy_rewrite]', $job, $item ); $post_content = str_replace( '[#content_feedzy_rewrite]', $content_feedzy_rewrite, $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using Feedzy rewrite service for item content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_content_rewrite_services', + 'service_input' => $item_content, + 'service_output' => $content_feedzy_rewrite, + ) + ); } // Rewriter item full content from feedzy API. if ( $rewrite_service_endabled && false !== strpos( $post_content, '[#full_content_feedzy_rewrite]' ) ) { $full_content_feedzy_rewrite = apply_filters( 'feedzy_invoke_content_rewrite_services', $item['item_url'], '[#full_content_feedzy_rewrite]', $job, $item ); $post_content = str_replace( '[#full_content_feedzy_rewrite]', $full_content_feedzy_rewrite, $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using Feedzy rewrite service for item full content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_content_rewrite_services', + 'service_output' => $full_content_feedzy_rewrite, + ) + ); } // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date @@ -1951,6 +2275,11 @@ function ( $attr, $key ) { $post_author = ! empty( $import_post_author ) ? $import_post_author : $job->post_author; + // strip all HTML tag when remove html option is enabled. + if ( 'yes' === $import_remove_html ) { + $post_content = wp_strip_all_tags( $post_content ); + } + $new_post = apply_filters( 'feedzy_insert_post_args', array( @@ -1972,9 +2301,15 @@ function ( $attr, $key ) { // no point creating a post if either the title or the content is null. if ( is_null( $post_title ) || is_null( $post_content ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'NOT creating a new post as title (%s) or content (%s) is null.', $post_title, $post_content ), 'info', __FILE__, __LINE__ ); ++$index; - $import_errors[] = __( 'Title or Content is empty.', 'feedzy-rss-feeds' ); + + Feedzy_Rss_Feeds_Log::error( + __( 'Title or Content is empty.', 'feedzy-rss-feeds' ), + array( + 'job_id' => $job->ID, + 'new_post' => $new_post, + ) + ); continue; } @@ -1992,7 +2327,18 @@ function ( $attr, $key ) { } else { $img_success = false; } + + Feedzy_Rss_Feeds_Log::debug( + 'Set the image source URL from item image tag for attachment post type.', + array( + 'job_id' => $job->ID, + 'feed_img_tag' => $feed_img_tag, + 'image_source_url' => $image_source_url, + 'item_img_path' => $item['item_img_path'], + ) + ); } elseif ( strpos( $feed_img_tag, '[#item_custom' ) !== false ) { + $value = ''; if ( $this->feedzy_is_business() || $this->feedzy_is_personal() ) { $value = apply_filters( 'feedzy_parse_custom_tags', $feed_img_tag, $item_obj ); } @@ -2002,14 +2348,39 @@ function ( $attr, $key ) { } else { $img_success = false; } + + Feedzy_Rss_Feeds_Log::debug( + 'Set the image source URL from custom tag for attachment post type.', + array( + 'job_id' => $job->ID, + 'feed_img_tag' => $feed_img_tag, + 'image_source_url' => $image_source_url, + 'item_custom' => $value, + 'is_business' => $this->feedzy_is_business(), + 'is_personal' => $this->feedzy_is_personal(), + ) + ); } else { $image_source_url = $feed_img_tag; $img_title = pathinfo( basename( $image_source_url ), PATHINFO_FILENAME ); } if ( ! empty( $image_source_url ) ) { - $img_success = $this->try_save_featured_image( $image_source_url, 0, $img_title, $import_errors, $import_info, $new_post ); + $img_success = $this->try_save_featured_image( $image_source_url, 0, $img_title, $import_info, $new_post ); $new_post_id = $img_success; + + Feedzy_Rss_Feeds_Log::debug( + 'Try to save featured image for attachment post type.', + array( + 'job_id' => $job->ID, + 'feed_img_tag' => $feed_img_tag, + 'img_title' => $img_title, + 'post_id' => $new_post_id, + 'image_source_url' => $image_source_url, + 'is_business' => $this->feedzy_is_business(), + 'is_personal' => $this->feedzy_is_personal(), + ) + ); } if ( ! $img_success ) { @@ -2027,18 +2398,34 @@ function ( $attr, $key ) { } if ( 0 === $new_post_id || is_wp_error( $new_post_id ) ) { - $error_reason = 'N/A'; if ( is_wp_error( $new_post_id ) ) { - $error_reason = $new_post_id->get_error_message(); - if ( ! empty( $error_reason ) ) { - $import_errors[] = $error_reason; - } + Feedzy_Rss_Feeds_Log::error( + sprintf( + // translators: %1$s is the item URL, %2$s is the error message. + __( 'Could not save the "%1$s" as post: %2$s', 'feedzy-rss-feeds' ), + esc_url( $item['item_url'] ), + $new_post_id->get_error_message() + ), + array( + 'job_id' => $job->ID, + 'new_post' => $new_post, + ) + ); } - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to create a new post with params %s. Error: %s', print_r( $new_post, true ), $error_reason ), 'error', __FILE__, __LINE__ ); + ++$index; continue; } - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'created new post with ID %d with post_content %s', $new_post_id, $post_content ), 'debug', __FILE__, __LINE__ ); + + Feedzy_Rss_Feeds_Log::info( + 'Created a new post: ' . $new_post['post_title'], + array( + 'job_id' => $job->ID, + 'post_id' => $new_post_id, + 'post_link' => get_permalink( $new_post_id ), + ) + ); + if ( ! in_array( $item_hash, $found_duplicates, true ) ) { $imported_items[] = $item_hash; ++$count; @@ -2081,7 +2468,13 @@ function ( $term ) { } $result = wp_set_object_terms( $new_post_id, intval( $term_id ), $taxonomy, true ); - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'After creating post in %s/%d, result = %s', $taxonomy, $term_id, print_r( $result, true ) ), 'debug', __FILE__, __LINE__ ); + + Feedzy_Rss_Feeds_Log::info( + sprintf( 'Set term "%1$s" with ID %2$s for feedzy import ID %3$s', $taxonomy, $term_id, $new_post_id ), + array( + 'job_id' => $job->ID, + ) + ); } // If the default category is not used, remove it. @@ -2105,13 +2498,13 @@ function ( $term ) { $job, $item_obj, $new_post_id, - $import_errors, + Feedzy_Rss_Feeds_Log::get_instance()->get_error_messages_accumulator(), $import_info, array( 'translation_lang' => $import_translation_lang, 'language_code' => $language_code, 'item' => $item, - ) + ) ); if ( ! empty( $import_featured_img ) && 'attachment' !== $import_post_type ) { @@ -2128,6 +2521,16 @@ function ( $term ) { } else { $img_success = false; } + + Feedzy_Rss_Feeds_Log::debug( + 'Found an image for [#item_image]', + array( + 'job_id' => $job->ID, + 'feed_img_tag' => $feed_img_tag, + 'image_source_url' => $image_source_url, + 'item_img_path' => $item['item_img_path'], + ) + ); } elseif ( ( $this->feedzy_is_business() || $this->feedzy_is_personal() ) && // PRO feature. false !== strpos( $feed_img_tag, '[#item_custom' ) @@ -2138,6 +2541,18 @@ function ( $term ) { } else { $img_success = false; } + + Feedzy_Rss_Feeds_Log::debug( + 'Set the image source URL from custom tag for created post.', + array( + 'job_id' => $job->ID, + 'feed_img_tag' => $feed_img_tag, + 'image_source_url' => $image_source_url, + 'item_custom' => $value, + 'is_business' => $this->feedzy_is_business(), + 'is_personal' => $this->feedzy_is_personal(), + ) + ); } elseif ( wp_http_validate_url( $import_featured_img ) ) { $image_source_url = $import_featured_img; $img_title = pathinfo( basename( $image_source_url ), PATHINFO_FILENAME ); @@ -2167,24 +2582,41 @@ function ( $term ) { ) ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Fetching image from Graby for item %1$s with URL %2$s', $item['item_url'], FEEDZY_PRO_FETCH_ITEM_IMG_URL ), + array( + 'job_id' => $job->ID, + 'response' => $response, + ) + ); + if ( ! is_wp_error( $response ) ) { - if ( array_key_exists( 'response', $response ) && array_key_exists( 'code', $response['response'] ) && intval( $response['response']['code'] ) !== 200 ) { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'error in response = %s', print_r( $response, true ) ), 'error', __FILE__, __LINE__ ); - } $body = wp_remote_retrieve_body( $response ); if ( ! is_wp_error( $body ) ) { $response_data = json_decode( $body, true ); if ( isset( $response_data['url'] ) ) { $image_source_url = $response_data['url']; + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Fetched image from Graby for item %1$s with URL %2$s', $item['item_url'], $image_source_url ), + array( + 'job_id' => $job->ID, + 'image_source_url' => $image_source_url, + ) + ); } - } else { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'error in body = %s', print_r( $body, true ) ), 'error', __FILE__, __LINE__ ); } } else { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'error in request = %s', print_r( $response, true ) ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::error( + // translators: %1$s is the item URL, %2$s is the error message. + sprintf( __( 'Error fetching image from Graby for item "%1$s": %2$s', 'feedzy-rss-feeds' ), $item['item_url'], $response->get_error_message() ), + array( + 'job_id' => $job->ID, + 'response' => $response, + 'item_url' => $item['item_url'], + 'source' => $source, + ) + ); } } @@ -2200,9 +2632,24 @@ function ( $term ) { if ( 'yes' === $import_item_img_url ) { // Set external image URL. update_post_meta( $new_post_id, 'feedzy_item_external_url', $image_source_url ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Replaced image URL with external URL for post ID %1$s: %2$s', $new_post_id, $image_source_url ), + array( + 'job_id' => $job->ID, + ) + ); } else { // if import_featured_img is a tag. - $img_success = $this->try_save_featured_image( $image_source_url, $new_post_id, $img_title, $import_errors, $import_info ); + $img_success = $this->try_save_featured_image( $image_source_url, $new_post_id, $img_title, $import_info ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Saved featured image for post ID %1$s: %2$s', $new_post_id, $image_source_url ), + array( + 'job_id' => $job->ID, + 'image_source_url' => $image_source_url, + ) + ); } } } @@ -2216,6 +2663,13 @@ function ( $term ) { $default_thumbnail_id = $default_thumbnail; } $img_success = set_post_thumbnail( $new_post_id, $default_thumbnail_id ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Try to set default thumbnail for post ID %1$s with ID %2$s. Success: %3$s', $new_post_id, $default_thumbnail_id, $img_success ? 'yes' : 'no' ), + array( + 'job_id' => $job->ID, + ) + ); } if ( ! $img_success ) { @@ -2231,9 +2685,26 @@ function ( $term ) { update_post_meta( $new_post_id, 'feedzy_job', $job->ID ); update_post_meta( $new_post_id, 'feedzy_item_author', sanitize_text_field( $author ) ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Update post meta for "%s"', $new_post['post_title'] ), + array( + 'feedzy_item_url' => esc_url_raw( $item['item_url'] ), + 'feedzy_item_author' => sanitize_text_field( $author ), + 'feedzy_job' => $job->ID, + 'post_id' => $new_post_id, + ) + ); + // Verify that the `$mark_duplicate_key` does not match `'item_url'` to ensure the condition applies only when a different tag is specified. if ( $mark_duplicate_key && 'item_url' !== $mark_duplicate_key ) { update_post_meta( $new_post_id, 'feedzy_' . $mark_duplicate_key, $duplicate_tag_value ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Mark post (%s) as duplicated', $new_post_id ), + array( + 'feedzy_' . $mark_duplicate_key => $duplicate_tag_value, + ) + ); } // we can use this to associate the items that were imported in a particular run. @@ -2250,25 +2721,42 @@ function ( $term ) { update_post_meta( $job->ID, 'imported_items_count', $count ); if ( $import_image_errors > 0 ) { - $import_errors[] = sprintf( - // translators: %1$d is the number of items without images, %2$d is the total number of items imported. - __( 'Unable to find an image for %1$d out of %2$d items imported', 'feedzy-rss-feeds' ), - $import_image_errors, - $count + Feedzy_Rss_Feeds_Log::error( + sprintf( + // translators: %1$d is the number of items without images, %2$d is the total number of items imported. + __( 'Unable to find an image for %1$d out of %2$d items imported', 'feedzy-rss-feeds' ), + $import_image_errors, + $count + ), + array( + 'job_id' => $job->ID, + 'source' => $source, + ) ); } - - if ( ! empty( $themeisle_log_event ) ) { - $import_errors = array_merge( $themeisle_log_event, $import_errors ); - } - update_post_meta( $job->ID, 'import_errors', $import_errors ); - + // the order of these matters in how they are finally shown in the summary. $import_info['total'] = $items_found; $import_info['duplicates'] = $duplicates; + $import_errors = Feedzy_Rss_Feeds_Log::get_instance()->get_error_messages_accumulator(); + Feedzy_Rss_Feeds_Log::get_instance()->disable_error_messages_retention(); + update_post_meta( $job->ID, 'import_info', $import_info ); + update_post_meta( $job->ID, 'import_errors', $import_errors ); + Feedzy_Rss_Feeds_Log::info( + sprintf( + 'Import completed for job ID (%1$s).', + $job->ID + ), + array( + 'job_id' => $job->ID, + 'import_info' => $import_info, + 'import_errors' => $import_errors, + ) + ); + return $count; } @@ -2311,7 +2799,7 @@ public function get_job_feed( $options, $import_content = null, $raw_feed_also = $feed = $admin->fetch_feed( $feed_url, isset( $options['refresh'] ) ? $options['refresh'] : '12_hours', $options ); $feed->force_feed( true ); - $feed->enable_order_by_date( false ); + $feed->enable_order_by_date( isset( $options['sort'] ) && ! empty( $options['sort'] ) ); if ( is_string( $feed ) ) { return array(); @@ -2437,7 +2925,6 @@ private function convert_url_to_ascii( $url ) { * @param string $img_source_url The download source URL for the image. * @param integer $post_id The post ID. * @param string $post_title Post title. - * @param array $import_errors Array of import error messages. * @param array $import_info Array of import information messages. * @param array $post_data Additional post data. * @@ -2446,7 +2933,7 @@ private function convert_url_to_ascii( $url ) { * @since 1.2.0 * @access private */ - private function try_save_featured_image( $img_source_url, $post_id, $post_title, &$import_errors, &$import_info, $post_data = array() ) { + private function try_save_featured_image( $img_source_url, $post_id, $post_title, &$import_info, $post_data = array() ) { if ( ! function_exists( 'post_exists' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } @@ -2459,11 +2946,28 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title // This is necessary because FILTER_VALIDATE_URL only validates against ASCII URLs. $escaped_url = $this->convert_url_to_ascii( $img_source_url ); if ( filter_var( $escaped_url, FILTER_VALIDATE_URL ) === false ) { - $import_errors[] = 'Invalid Featured Image URL: ' . $img_source_url; + Feedzy_Rss_Feeds_Log::error( + // translators: %s is the invalid image URL. + sprintf( __( 'Invalid image URL: %s', 'feedzy-rss-feeds' ), $img_source_url ), + array( + 'post_id' => $post_id, + 'post_title' => $post_title, + ) + ); + return false; } - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Trying to save the featured image for %s and postID %d', $img_source_url, $post_id ), 'debug', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Save the image as featured image with upload in Media Library for post ID' ), + array( + 'post_id' => $post_id, + 'post_title' => $post_title, + 'img_source_url' => $img_source_url, + 'escaped_url' => $escaped_url, + 'post_data' => $post_data, + ) + ); require_once ABSPATH . 'wp-admin/includes/image.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; @@ -2473,7 +2977,14 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title $img_source_url = trim( $img_source_url, chr( 0xC2 ) . chr( 0xA0 ) ); $local_file = download_url( $img_source_url ); if ( is_wp_error( $local_file ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to download file = %s and postID %d', print_r( $local_file, true ), $post_id ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::error( + // translators: %s is the image source URL. + sprintf( __( 'Unable to download image: %s', 'feedzy-rss-feeds' ), $img_source_url ), + array( + 'post_id' => $post_id, + 'errors' => $local_file->get_error_messages(), + ) + ); return false; } @@ -2501,7 +3012,16 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title if ( $renamed ) { $local_file = $new_local_file; } else { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to rename file for postID %d', $post_id ), 'error', __FILE__, __LINE__ ); + + Feedzy_Rss_Feeds_Log::error( + // translators: %s the name of the post. + sprintf( __( 'Could not rename temporary file for: %s', 'feedzy-rss-feeds' ), get_the_title( $post_id ) ), + array( + 'post_id' => $post_id, + 'local_file' => $local_file, + 'new_local_file' => $new_local_file, + ) + ); return false; } @@ -2512,7 +3032,14 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title $id = media_handle_sideload( $file_array, $post_id, $post_title, $post_data ); if ( is_wp_error( $id ) ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to attach file for postID %d = %s', $post_id, print_r( $id, true ) ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::error( + // translators: %s is the image source URL. + sprintf( __( 'Cannot upload the image to Media Library: %s', 'feedzy-rss-feeds' ), $img_source_url ), + array( + 'post_id' => $post_id, + 'errors' => $id->get_error_messages(), + ) + ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink unlink( $file_array['tmp_name'] ); @@ -2520,7 +3047,14 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title return false; } } else { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Found an existing attachment(ID: %d) image for %s and postID %d', $id, $img_source_url, $post_id ), 'debug', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Reusing existing attachment image for post ID %d', $post_id ), + array( + 'post_id' => $post_id, + 'img_source_url' => $img_source_url, + 'attachment_id' => $id, + ) + ); } if ( ! empty( $post_data ) ) { @@ -2529,9 +3063,24 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title $success = set_post_thumbnail( $post_id, $id ); if ( false === $success ) { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Unable to attach file for postID %d for no apparent reason', $post_id ), 'error', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::error( + // translators: %s is the post title. + sprintf( __( 'Could not set the thumbnail for: %s', 'feedzy-rss-feeds' ), get_the_title( $post_id ) ), + array( + 'post_id' => $post_id, + 'attachment_id' => $id, + 'image_source_url' => $img_source_url, + ) + ); } else { - do_action( 'themeisle_log_event', FEEDZY_NAME, sprintf( 'Attached file as featured image for postID %d', $post_id ), 'info', __FILE__, __LINE__ ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Attached image with ID (%d) to post ID (%d)', $id, $post_id ), + array( + 'post_id' => $post_id, + 'attachment_id' => $id, + 'image_source_url' => $img_source_url, + ) + ); } return $success; @@ -2580,7 +3129,17 @@ public function add_cron() { ) ); + if ( ! empty( $import_job_crons ) ) { + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Registering cron job with schedule: %s', $schedule ), + array( + 'job_id' => 0, + 'schedule' => $schedule, + 'import_job_crons' => $import_job_crons, + ) + ); + foreach ( $import_job_crons as $job_id ) { $fz_cron_schedule = get_post_meta( $job_id, 'fz_cron_schedule', true ); if ( false === Feedzy_Rss_Feeds_Util_Scheduler::is_scheduled( 'feedzy_cron', array( 100, $job_id ) ) ) { @@ -2607,16 +3166,16 @@ public function admin_notices() { } if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { - echo wp_kses_post( '

    ' . __( 'WP Cron is disabled. Your feeds would not get updated. Please contact your hosting provider or system administrator', 'feedzy-rss-feeds' ) . '

    ' ); + echo wp_kses_post( '

    ' . __( 'WP Cron is disabled. Your feeds would not get updated. Please contact your hosting provider or system administrator', 'feedzy-rss-feeds' ) . '

    ' ); } if ( false === Feedzy_Rss_Feeds_Util_Scheduler::is_scheduled( 'feedzy_cron' ) ) { - echo wp_kses_post( '

    ' . __( 'Unable to register cron job. Your feeds might not get updated', 'feedzy-rss-feeds' ) . '

    ' ); + echo wp_kses_post( '

    ' . __( 'Unable to register cron job. Your feeds might not get updated', 'feedzy-rss-feeds' ) . '

    ' ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['imported'] ) ) { - echo wp_kses_post( '

    ' . __( 'Successfully imported.', 'feedzy-rss-feeds' ) . '

    ' ); + echo wp_kses_post( '

    ' . __( 'Successfully imported.', 'feedzy-rss-feeds' ) . '

    ' ); } } @@ -2793,14 +3352,17 @@ public function integration_tabs( $tabs ) { public function save_tab_settings( $settings, $tab ) { if ( ! isset( $_POST['nonce'] ) || - ! wp_verify_nonce( sanitize_text_field( wp_unslash( 'nonce' ) ), $tab ) + ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), $tab ) ) { return array(); } if ( 'misc' === $tab ) { - $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; - $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; + $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; + $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; + $settings['header']['user-agent'] = isset( $_POST['user-agent'] ) ? sanitize_text_field( wp_unslash( $_POST['user-agent'] ) ) : ''; + $settings['general']['feedzy-delete-days'] = isset( $_POST['feedzy-delete-days'] ) ? absint( wp_unslash( $_POST['feedzy-delete-days'] ) ) : ''; + $settings['general']['feedzy-delete-media'] = isset( $_POST['feedzy-delete-media'] ) ? absint( wp_unslash( $_POST['feedzy-delete-media'] ) ) : ''; } return $settings; @@ -3299,7 +3861,7 @@ public function fetch_custom_fields() { } // phpcs:ignore - $query_result = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT($wpdb->postmeta.meta_key) FROM $wpdb->posts LEFT JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id WHERE $wpdb->posts.post_type = '%s' AND $wpdb->postmeta.meta_key != '' AND $wpdb->postmeta.meta_key NOT RegExp '(^[_0-9].+$)' AND $wpdb->postmeta.meta_key NOT RegExp '(^[_feedzy].+$)' AND $wpdb->postmeta.meta_key NOT RegExp '(^[0-9]+$)'$like LIMIT 0, 9999", $post_type ), ARRAY_A ); + $query_result = $wpdb->get_results($wpdb->prepare("SELECT DISTINCT($wpdb->postmeta.meta_key) FROM $wpdb->posts LEFT JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id WHERE $wpdb->posts.post_type = '%s' AND $wpdb->postmeta.meta_key != '' AND $wpdb->postmeta.meta_key NOT RegExp '(^[_0-9].+$)' AND $wpdb->postmeta.meta_key NOT RegExp '(^[_feedzy].+$)' AND $wpdb->postmeta.meta_key NOT RegExp '(^[0-9]+$)'$like LIMIT 0, 9999", $post_type), ARRAY_A); $acf_fields = array(); if ( function_exists( 'acf_get_field_groups' ) ) { @@ -3458,9 +4020,9 @@ public function feedzy_import_clone_success_notice() { // Display success notice if clone succeed. if ( isset( $_GET['saved'] ) && 'fz_duplicate_import_job_created' === $_GET['saved'] ) { // phpcs:ignore WordPress.Security.NonceVerification ?> -
    -

    -
    +
    +

    +
    'all', + 'conditions' => array(), + ); + + if ( ! empty( $excluded_post_title ) ) { + $filter_conditions['conditions'] = array( + array( + 'field' => 'title', + 'operator' => 'not_contains', + 'value' => $excluded_post_title, + ), + ); + } + + update_post_meta( $job_id, 'filter_conditions', wp_json_encode( $filter_conditions ) ); + update_post_meta( $job_id, 'default_thumbnail_id', $fallback_image ); + $response = array( 'status' => true, ); @@ -3587,25 +4169,7 @@ public function load_edit_screen() { * Add import export section. */ public function add_import_export_section() { - if ( ! feedzy_is_pro() ) : - ?> - - + ?> \ No newline at end of file diff --git a/includes/layouts/feedzy-support.php b/includes/layouts/feedzy-support.php index e7e975744..6eb700669 100644 --- a/includes/layouts/feedzy-support.php +++ b/includes/layouts/feedzy-support.php @@ -1,13 +1,21 @@ -
    + +?> +
    -
    @@ -19,7 +27,7 @@ class="">
  1. + class="">
  2. @@ -27,10 +35,30 @@ class="">">
  3. + +
  4. + +
  5. +
  6. +
  7. + + + + +
  8. +
  9. + + + + +
  10. @@ -54,6 +82,9 @@ class="">"> -

    - ', - '' - ) - ); - ?> -

    - - - +

    + ', + '' + ) + ); + ?> +

    + + + -

    - +

    +

    + @@ -98,4 +132,4 @@ class="">
  11. -
    +
    \ No newline at end of file diff --git a/includes/layouts/feedzy-tutorial.php b/includes/layouts/feedzy-tutorial.php index a2aea99e8..244c3b582 100644 --- a/includes/layouts/feedzy-tutorial.php +++ b/includes/layouts/feedzy-tutorial.php @@ -6,6 +6,26 @@ --> 'feedzy-settings', + ], + admin_url( 'admin.php' ) +); +$feed_imports_link = add_query_arg( + [ + 'post_type' => 'feedzy_imports', + ], + admin_url( 'edit.php' ) +); +$logs_link = add_query_arg( + [ + 'tab' => 'logs', + ], + $settings_link +); + ?>
      @@ -27,7 +47,7 @@

    @@ -35,8 +55,35 @@
  12. @@ -138,6 +139,11 @@
    + + +
+
+
+
+ +
+ +
+
+
+ +
+ + + + + +
+ +
+
+
+
+
-
- +
+ +
+ +
@@ -239,7 +278,7 @@ class="pro-label free-label"><

- +
diff --git a/includes/util/feedzy-rss-feeds-conditions.php b/includes/util/feedzy-rss-feeds-conditions.php index 559773a97..b52d4d022 100644 --- a/includes/util/feedzy-rss-feeds-conditions.php +++ b/includes/util/feedzy-rss-feeds-conditions.php @@ -247,10 +247,6 @@ public function is_condition_met( $condition, $value ): bool { * @return bool True if the conditions are met, false otherwise. */ public function evaluate_conditions( $default_value, $attrs, $item, $feed_url, $index ): bool { - if ( feedzy_is_new() && ! feedzy_is_pro() ) { - return $default_value; - } - if ( ! isset( $attrs['filters'] ) || empty( $attrs['filters'] ) ) { return $default_value; } @@ -299,6 +295,9 @@ public function evaluate_conditions( $default_value, $attrs, $item, $feed_url, $ $image = $admin->feedzy_retrieve_image( $item, $attrs ); $value = $image; break; + case 'link': + $value = $item->get_link(); + break; default: $value = ''; break; diff --git a/includes/views/amazon-product-advertising-view.php b/includes/views/amazon-product-advertising-view.php index 98f854227..984e4673a 100644 --- a/includes/views/amazon-product-advertising-view.php +++ b/includes/views/amazon-product-advertising-view.php @@ -1,10 +1,13 @@ + +
- -
- +
+ +
+ +
-
- -
+
+
-
+
@@ -115,9 +119,6 @@ class="dashicons dashicons-arrow-down-alt2">
- - -
@@ -131,13 +132,6 @@ class="dashicons dashicons-arrow-down-alt2">
-
-

- -

-
  • @@ -157,10 +151,9 @@ class="dashicons dashicons-arrow-down-alt2">
-
- +
- -
+ +
', + // translators: %1$s: magic tag, %2$s: opening anchor tag, %3$s: closing anchor tag. + __( 'Auto-create categories from source: %1$s %2$s Learn More %3$s', 'feedzy-rss-feeds' ), + '[#item_categories]', + '', '' ) ); ?>
- -
- [#item_categories]', - '', - '' - ) - ); - ?> -
-
- [#auto_categories]', - '', - '' - ) - ); +
+ [#auto_categories]', + '', + '' + ) + ); ?> -
+
+
@@ -241,9 +221,6 @@ class="dashicons dashicons-arrow-down-alt2">
-
+
+ +
-
-
- +
+ +
@@ -310,15 +289,23 @@ class="dashicons dashicons-arrow-down-alt2">
-
-
+
+
+ +
?>
-
- -
@@ -350,15 +326,24 @@ class="dashicons dashicons-arrow-down-alt2">
-
-
- + class="form-control fz-input-tagify" + value="" /> +
+ +
-
- -
@@ -402,15 +376,23 @@ class="form-control fz-textarea-tagify">
-
-
+
+
+ +
-
- -
@@ -463,7 +434,6 @@ class="fz-switch-toggle" type="checkbox" value="yes"
-
+
+ +
-
- -
@@ -566,9 +533,6 @@ class="fz-switch-toggle" type="checkbox" value="yes"
-
-
-
- -
-
-

-
-
-
-
- > - -
-
-
- - -
-
-
-
- -
-

PRO' : ''; ?>

-
-
-
- - /> -
- -
-
-
-
- -
- -
-

PRO' : ''; ?>

-
-
-
- - -
- -
-
-
-
+
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
+
+
+
-
- -

PRO' : ''; ?>

-
-
-
-
- > - -
-
- + +
+

PRO' : ''; ?>

-
-
-
- -
- -
-

PRO' : ''; ?>

-
-
-
- - -
- 1 ) { - ?> - - - - +
+
+ + +
+ ', '' ) ); ?> +
- -
- - - -
-
-
-
-
- -
- -
-

PRO' : ''; ?>

-
-
-
- - -
- ', '' ) ); ?> +
+
+

-
-
-
- -
- -
-

PRO' : ''; ?>

-
-
-
- -
+
- $daily ), $schedules ); } - $duplicate_schedule = array(); + $internal_cron_schedules = apply_filters( 'feedzy_internal_cron_schedule_slugs', array() ); + $duplicate_schedule = array(); foreach ( $schedules as $slug => $schedule ) : if ( empty( $schedule['interval'] ) || in_array( $schedule['interval'], $duplicate_schedule, true ) ) { continue; } $duplicate_schedule[] = $schedule['interval']; + $display_text = $schedule['display']; + + if ( ! in_array( $slug, $internal_cron_schedules, true ) ) { + $display_text .= ' (' . esc_html__( 'externally created)', 'feedzy-rss-feeds' ) . ')'; + } ?> -
+
+
+
+ +
+
+

+ +

+
+
+
+
+ +
+ + + +
+ + > + +
+ +
+ +
+ ' . esc_html__( 'General fallback image not found (may have been deleted)', 'feedzy-rss-feeds' ) . '
'; + } + ?> +
+ +
+
+ +
+
+ + +
+ ' . esc_html__( 'Feedzy Settings', 'feedzy-rss-feeds' ) . '' + ) + ); + ?> +
+
+ +
+ + > + +
+ +
+ +
+
+ +
+
+ + +
+ + + + + + + +
+
+
+
+
- -
-
-

PRO' : ''; ?>

- -
-
- +
+
+ +
+
+

+ +

+
+
+
+
+ + > + +
- +
+ + +
+
-
-
- -
- +
+ +
+

PRO' : ''; ?>

+
+
+
+ + /> +
+ +
+
+
+
+ +
+
+

+
+
+
+
+ > + +
- + +
+
+
+ +
+
+

+
+
+
+ +
+
+ + +
+
+ + +
+
+

PRO' : ''; ?>

+ +
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+
- +
+
+
diff --git a/includes/views/js/import-metabox-edit.js b/includes/views/js/import-metabox-edit.js index bdd2bf458..352c75673 100644 --- a/includes/views/js/import-metabox-edit.js +++ b/includes/views/js/import-metabox-edit.js @@ -3,136 +3,312 @@ * import-metabox-edit.js * * @since 1.2.0 - * @package feedzy-rss-feeds-pro + * @package */ /* global jQuery, ajaxurl, feedzy, tb_show */ (function ($) { function scroll_to_class(element_class, removed_height) { - var scroll_to = $(element_class).offset().top - removed_height; + const scroll_to = $(element_class).offset().top - removed_height; if ($(window).scrollTop() !== scroll_to) { - $("html, body").stop().animate({ scrollTop: scroll_to }, 0); + $('html, body').stop().animate({ scrollTop: scroll_to }, 0); } } function bar_progress(progress_line_object, direction) { - var number_of_steps = progress_line_object.data("number-of-steps"); - var now_value = progress_line_object.data("now-value"); - var new_value = 0; - if (direction === "right") { + const number_of_steps = progress_line_object.data('number-of-steps'); + const now_value = progress_line_object.data('now-value'); + let new_value = 0; + if (direction === 'right') { new_value = now_value + 100 / number_of_steps; - } else if (direction === "left") { + } else if (direction === 'left') { new_value = now_value - 100 / number_of_steps; } progress_line_object - .attr("style", "width: " + new_value + "%;") - .data("now-value", new_value); + .attr('style', 'width: ' + new_value + '%;') + .data('now-value', new_value); } function add_source() { - var field_name = $(this).data("field-name"); - var field_tag = $(this).data("field-tag"); - $('[name="feedzy_meta_data[' + field_name + ']"]').data('tagify') + const field_name = $(this).data('field-name'); + const field_tag = $(this).data('field-tag'); + $('[name="feedzy_meta_data[' + field_name + ']"]') + .data('tagify') .addTags(field_tag); - $('[data-toggle="dropdown"]').parent().removeClass("open"); + $('[data-toggle="dropdown"]').parent().removeClass('open'); $('[name="feedzy_meta_data[' + field_name + ']"]').focus(); return false; } - function append_outside_tag() { - var outsideWrap = $( this ).parents( '.fz-form-group, .fz-input-group' ); - var tags = outsideWrap.find( '.form-control' ).val(); + /** + * Lock a button with a spinner icon. + * @param {HTMLElement} button The button to lock. + * @return {{ release: () => void }} An object with a release method to unlock the button. + */ + function lock_btn_with_spinner(button) { + const $button = $(button); + const $icon = $button.find('i'); + const originalClasses = $icon.attr('class'); + + $button.prop('disabled', true); + $icon.attr('class', 'spinner is-active'); + + return { + release() { + $button.prop('disabled', false); + $icon.attr('class', originalClasses); + }, + }; + } - if ( '' === tags ) { + /** + * Add valid URLs to the tag list. + * + * @this {HTMLElement} this - The button that was clicked to trigger the function. + * + * @return {Promise} + */ + async function add_valid_urls_to_tag_list() { + const parentFormGroup = $(this).parents( + '.fz-form-group, .fz-input-group' + ); + const tags = parentFormGroup.find('.form-control').val(); + const $input = parentFormGroup.find('.form-control'); + + if ('' === tags) { return false; } - outsideWrap.find( '.form-control .tag-list' )?.val(''); - if ( outsideWrap.next( '.tag-list' ).length > 0 ) { - outsideWrap. - next( '.tag-list' ) - .find( 'input.fz-tagify-outside, input.fz-tagify--outside' ) - .data('tagify') - .addTags( tags ); + $('.fz-validation-message').remove(); + + const { release: unlockButton } = lock_btn_with_spinner(this); + + try { + const validationResult = await validate_feed_url(tags); + + handle_validation_response(validationResult); + + if (!validationResult.success) { + return; + } + + const results = validationResult.data.results || []; + + const validUrls = results + .filter(({ status }) => status === 'success') + .map(({ url }) => url); + const invalidUrls = results + .filter(({ status }) => status !== 'success') + .map(({ url }) => url); + + $input.val(invalidUrls.join(', ')); // Keep invalid URLs in the input field. + + parentFormGroup.find('.form-control .tag-list')?.val(''); // Clear existing tags if any. + + // Add valid URLs to the tag list. + const $tagifyInput = + parentFormGroup.next('.tag-list').length > 0 + ? parentFormGroup + .next('.tag-list') + .find( + 'input.fz-tagify-outside, input.fz-tagify--outside' + ) + : parentFormGroup.find( + 'input.fz-tagify-outside, input.fz-tagify--outside' + ); + $tagifyInput.data('tagify').addTags(validUrls.join(', ')); + } catch (error) { + console.error(error); + } finally { + unlockButton(); + } + } + + /** + * Validate the feed URL. + * + * @param {string} feedUrl + * @return {Promise<{ success: boolean, message: string, data: { results: Array<{ url: string, status: string, message: string }> } }>} The API response object. + */ + async function validate_feed_url(feedUrl) { + if (!feedUrl) { + return { + success: false, + message: + window.feedzy.i10n.validation_messages.invalid_feed_url, + }; + } + + try { + const response = await $.ajax({ + url: window.feedzy.ajax.url, + method: 'POST', + data: { + nonce: window.feedzy.ajax.security, + action: 'feedzy_validate_feed', + feed_url: feedUrl, + }, + }); + + return response; + } catch (error) { + return { + success: false, + message: + window.feedzy.i10n.validation_messages + .error_validating_feed_url + + ': ' + + (error.responseJSON?.message || error.statusText), + }; + } + } + + /** + * Handle validation response and display appropriate messages + * @param {Object} response - The validation response + */ + function handle_validation_response(response) { + if (!response || !response.data || !response.data.results) { + showMessage('✗ ' + response?.message, false); + return; + } + + let validationSummaryHtml = '
'; + + response.data.results.forEach(({ url, status, message }) => { + const icon = + ''; + + validationSummaryHtml += `
`; + validationSummaryHtml += `${icon} ${url}`; + validationSummaryHtml += ` - ${message}`; + validationSummaryHtml += `
`; + }); + + validationSummaryHtml += '
'; + + const hasErrors = response.data.results.some( + ({ status }) => status !== 'success' + ); + + showMessage(validationSummaryHtml, !hasErrors); + } + + function showMessage(message, autoDismiss = true) { + const $container = $('.fz-validation-summary'); + if (!$container.length) { + return; + } + + $container.find('.fz-validation-message').remove(); + + const $message = $('
', { + class: 'fz-validation-message', + html: message, + }); + + $container.append($message); + if (autoDismiss) { + $message.delay(5000).fadeOut(300, () => $message.remove()); } else { - outsideWrap.find( 'input.fz-tagify-outside, input.fz-tagify--outside' ) - .data('tagify') - .addTags( tags ); + const $closeButton = $('
+
+
+ + +
+
+ +
+
+ + free_settings['general']['feedzy-delete-media'] ) && + 1 === intval( $this->free_settings['general']['feedzy-delete-media'] ) + ) { + $delete_media = 1; + } + + $feedzy_delete_days = isset( $this->free_settings['general']['feedzy-delete-days'] ) ? $this->free_settings['general']['feedzy-delete-days'] : 0; + ?> +
+
+ + +
+
+
+
+
+ /> + +
+
+
+
+
+
diff --git a/includes/views/openai-view.php b/includes/views/openai-view.php index 66b649d09..3095f15b0 100644 --- a/includes/views/openai-view.php +++ b/includes/views/openai-view.php @@ -1,10 +1,13 @@ + +
', '' ); echo wp_kses_post( $content ); diff --git a/includes/views/openrouter-view.php b/includes/views/openrouter-view.php index 3f6cc0611..27a0d2334 100644 --- a/includes/views/openrouter-view.php +++ b/includes/views/openrouter-view.php @@ -1,10 +1,13 @@ + +
', '' ); echo wp_kses_post( $content ); diff --git a/includes/views/spinnerchief-view.php b/includes/views/spinnerchief-view.php index feba644da..a289d36e0 100644 --- a/includes/views/spinnerchief-view.php +++ b/includes/views/spinnerchief-view.php @@ -1,16 +1,17 @@ + +
', - '' - ) - ); - ?> + $upgrade_url = tsdk_translate_link( tsdk_utmify( FEEDZY_UPSELL_LINK, 'spinnerchief' ) ); + + // translators: %1$s: opening anchor tag, %2$s: closing anchor tag. + $content .= wp_sprintf( __( 'Unlock more powerful features, by %1$s upgrading to Feedzy Pro %2$s', 'feedzy-rss-feeds' ), '', '' ); + echo wp_kses_post( $content ); + ?>
diff --git a/includes/views/wordai-view.php b/includes/views/wordai-view.php index 9c6511304..8cdf146b7 100644 --- a/includes/views/wordai-view.php +++ b/includes/views/wordai-view.php @@ -1,10 +1,13 @@ + +
', '' ); diff --git a/js/ActionPopup/RewriteActionItem.js b/js/ActionPopup/RewriteActionItem.js new file mode 100644 index 000000000..d26c3b1d5 --- /dev/null +++ b/js/ActionPopup/RewriteActionItem.js @@ -0,0 +1,343 @@ +import { __, sprintf } from '@wordpress/i18n'; +import { unescape } from 'lodash'; +import { Icon, dragHandle, external } from '@wordpress/icons'; + +import { + Button, + ExternalLink, + PanelBody, + PanelRow, + BaseControl, + TextareaControl, + SelectControl, + Notice, +} from '@wordpress/components'; + +import { sortableHandle } from 'react-sortable-hoc'; + +const DragHandle = sortableHandle(() => ( + +)); + +const UpgradeNotice = ({ higherPlanNotice, utmCampaign }) => { + const upsellLink = `https://themeisle.com/plugins/feedzy-rss-feeds/upgrade/?utm_source=wpadmin&utm_medium=import&utm_campaign=${utmCampaign}&utm_content=feedzy-rss-feeds`; + if (window.feedzyData.isPro) { + if (higherPlanNotice) { + return ( + <> +
+ +

+ PRO{' '} + {__( + 'This action requires an upgrade to a higher plan.', + 'feedzy-rss-feeds' + )} +

{' '} + + {__( + 'Upgrade to Feedzy PRO', + 'feedzy-rss-feeds' + )} + +
+
+ + ); + } + return <>; + } + return ( + <> +
+ +

+ PRO{' '} + {__( + 'This action is a Premium feature.', + 'feedzy-rss-feeds' + )} +

{' '} + + {__('Upgrade to Feedzy PRO', 'feedzy-rss-feeds')} + +
+
+ + ); +}; + +const ErrorMessage = ({ provider, isHighPrivileges }) => { + if (isHighPrivileges) { + return ( + + {__('Invalid API Key', 'feedzy-rss-feeds')}{' '} + + + + + ); + } + + return ( + + {__( + 'Invalid API Key, Please contact the administrator', + 'feedzy-rss-feeds' + )} + + ); +}; + +const ProviderSelect = ({ selectedProvider, loopIndex, propRef, isPro }) => ( + + { + propRef.onChangeHandler({ + index: loopIndex, + aiProvider: currentValue ?? '', + aiModel: '', + }); + }} + disabled={!isPro} + /> + +); + +const ModelSelect = ({ + selectedProvider, + selectedAIModel, + defaultModel, + loopIndex, + propRef, + isPro, + providerLicenseStatus, +}) => { + if (selectedProvider !== 'openai') { + return null; + } + + return ( + + { + propRef.onChangeHandler({ + index: loopIndex, + aiModel: + currentValue !== defaultModel ? currentValue : '', + }); + }} + disabled={!isPro || !providerLicenseStatus} + > + {window.feedzyData.activeOpenAIModels.length > 0 && ( + + {window.feedzyData.activeOpenAIModels.map((model) => ( + + ))} + + )} + {window.feedzyData.deprecatedOpenAIModels.length > 0 && ( + + {window.feedzyData.deprecatedOpenAIModels.map( + (model) => ( + + ) + )} + + )} + + + ); +}; + +const PromptControls = ({ + item, + loopIndex, + propRef, + isPro, + providerLicenseStatus, + selectedProvider, +}) => ( + + + propRef.onChangeHandler({ + index: loopIndex, + ChatGPT: currentValue ?? '', + aiProvider: selectedProvider, + }) + } + disabled={!isPro || !providerLicenseStatus} + /> +
+ {['summarize', 'paraphase', 'change_tone'].map((type) => ( + + ))} +
+
+); + +const RewriteActionItem = ({ counter, item, loopIndex, propRef }) => { + const { + isPro, + isBusinessPlan, + isAgencyPlan, + isHighPrivileges, + apiLicenseStatus, + } = window.feedzyData; + + let defaultProvider; + if (apiLicenseStatus.openaiStatus) { + defaultProvider = 'openai'; + } else if (apiLicenseStatus.openRouterStatus) { + defaultProvider = 'openrouter'; + } else { + defaultProvider = 'openai'; + } + + const selectedProvider = item.data.aiProvider || defaultProvider; + const providerLicenseStatus = + selectedProvider === 'openai' + ? apiLicenseStatus.openaiStatus + : apiLicenseStatus.openRouterStatus; + + const defaultModel = + selectedProvider === 'openai' + ? window.feedzyData?.integrations?.openAIModel + : ''; + const selectedAIModel = item.data.aiModel || defaultModel; + + const showError = + isPro && (isBusinessPlan || isAgencyPlan) && !providerLicenseStatus; + const showUpgradeNotice = !isBusinessPlan && !isAgencyPlan; + + return ( +
  • +
    + {showError && ( + + )} + + + + + + + + +
    +
    + +
    +
  • + ); +}; + +export default RewriteActionItem; diff --git a/js/ActionPopup/SortableItem.js b/js/ActionPopup/SortableItem.js index 8dbe2bdbc..77c899fcb 100644 --- a/js/ActionPopup/SortableItem.js +++ b/js/ActionPopup/SortableItem.js @@ -8,7 +8,7 @@ import { close, plus, trash, - external + external, } from '@wordpress/icons'; import { @@ -26,45 +26,95 @@ import { ItemGroup, Item, ToggleControl, - SelectControl + SelectControl, } from '@wordpress/components'; -const DragHandle = sortableHandle(() => ); +import RewriteActionItem from './RewriteActionItem'; -const UpgradeNotice = ({higherPlanNotice, utmCampaign}) => { +const DragHandle = sortableHandle(() => ( + +)); + +const UpgradeNotice = ({ higherPlanNotice, utmCampaign }) => { const upsellLink = `https://themeisle.com/plugins/feedzy-rss-feeds/upgrade/?utm_source=wpadmin&utm_medium=import&utm_campaign=${utmCampaign}&utm_content=feedzy-rss-feeds`; - if ( feedzyData.isPro ) { - if ( higherPlanNotice ) { - return( + if (feedzyData.isPro) { + if (higherPlanNotice) { + return ( <> -
    -

    PRO {__('This action requires an upgrade to a higher plan.', 'feedzy-rss-feeds')}

    { __( 'Upgrade Feedzy PRO Plan', 'feedzy-rss-feeds' ) }
    -
    +
    + +

    + PRO{' '} + {__( + 'This action requires an upgrade to a higher plan.', + 'feedzy-rss-feeds' + )} +

    {' '} + + {__( + 'Upgrade Feedzy PRO Plan', + 'feedzy-rss-feeds' + )} + +
    +
    - ); + ); } - return( - <> - ); + return <>; } - return( + return ( <> -
    -

    PRO {__('This action is a Premium feature.', 'feedzy-rss-feeds')}

    { __( 'Upgrade to Feedzy PRO', 'feedzy-rss-feeds' ) }
    -
    +
    + +

    + PRO{' '} + {__( + 'This action is a Premium feature.', + 'feedzy-rss-feeds' + )} +

    {' '} + + {__('Upgrade to Feedzy PRO', 'feedzy-rss-feeds')} + +
    +
    ); }; const CreditNotice = () => { - return( + return ( <> -
    -

    {__( 'You need more credits to use this actions!', 'feedzy-rss-feeds' )}

    { __( 'Buy Credits', 'feedzy-rss-feeds' ) }
    -
    +
    + +

    + {' '} + {__( + 'You need more credits to use this actions!', + 'feedzy-rss-feeds' + )} +

    + + {__('Buy Credits', 'feedzy-rss-feeds')} + +
    +
    - ) -} + ); +}; const getCurrentLanguage = () => { // This is for backward compatibility for the previous language control that sit outside the action modal. @@ -73,33 +123,64 @@ const getCurrentLanguage = () => { }; const SortableItem = ({ propRef, loopIndex, item }) => { - let counter = loopIndex + 1; - if ( 'trim' === item.id ) { - return( + const counter = loopIndex + 1; + const isInitialOpen = loopIndex === propRef?.lastAddedActionIdx; + + if ('trim' === item.id) { + return (
  • - + propRef.onChangeHandler( { 'index': loopIndex, 'trimLength': currentValue ?? '' } ) } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + trimLength: currentValue ?? '', + }) + } />
    -
    @@ -107,58 +188,127 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'search_replace' === item.id ) { - return( + if ('search_replace' === item.id) { + return (
  • - + propRef.onChangeHandler( { 'index': loopIndex, 'mode': currentValue ?? 'text' } ) } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + mode: currentValue ?? 'text', + }) + } /> propRef.onChangeHandler( { 'index': loopIndex, 'search': currentValue ?? '' } ) } + label={__('Search', 'feedzy-rss-feeds')} + placeholder={__( + 'Enter term or regex', + 'feedzy-rss-feeds' + )} + value={ + item.data.search + ? unescape( + item.data.search.replaceAll( + ''', + "'" + ) + ) + : '' + } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + search: currentValue ?? '', + }) + } /> propRef.onChangeHandler( { 'index': loopIndex, 'searchWith': currentValue ?? '' } ) } + label={__( + 'Replace with', + 'feedzy-rss-feeds' + )} + placeholder={__( + 'Enter term', + 'feedzy-rss-feeds' + )} + value={ + item.data.searchWith + ? unescape( + item.data.searchWith.replaceAll( + ''', + "'" + ) + ) + : '' + } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + searchWith: currentValue ?? '', + }) + } />
    -
    @@ -166,18 +316,43 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'fz_paraphrase' === item.id ) { - return( + if ('fz_paraphrase' === item.id) { + return (
  • - - + +
    -
    @@ -185,118 +360,56 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'chat_gpt_rewrite' === item.id ) { - let defaultProvider = 'openai'; - let providerLicenseStatus = false; - if ( feedzyData.apiLicenseStatus.openaiStatus ) { - defaultProvider = 'openai'; - } else if ( feedzyData.apiLicenseStatus.openRouterStatus ) { - defaultProvider = 'openrouter'; - } - - let selectedProvider = item.data.aiProvider || defaultProvider; - if ( 'openai' === selectedProvider ) { - providerLicenseStatus = feedzyData.apiLicenseStatus.openaiStatus; - } else if ( 'openrouter' === selectedProvider ) { - providerLicenseStatus = feedzyData.apiLicenseStatus.openRouterStatus; - } - return( -
  • -
    - {feedzyData.isPro && (feedzyData.isBusinessPlan || feedzyData.isAgencyPlan) && !providerLicenseStatus && (feedzyData.isHighPrivileges ? {__( 'Invalid API Key', 'feedzy-rss-feeds' )} : {__( 'Invalid API Key, Please contact the administrator', 'feedzy-rss-feeds' )} )} - - - - - propRef.onChangeHandler( { 'index': loopIndex, 'aiProvider': currentValue ?? '' } ) } - disabled={!feedzyData.isPro} - /> - - - propRef.onChangeHandler( { 'index': loopIndex, 'ChatGPT': currentValue ?? '', aiProvider: selectedProvider } ) } - disabled={!feedzyData.isPro || !providerLicenseStatus} - /> -
    - - - -
    -
    -
    -
    -
    -
    - -
    -
  • + if ('chat_gpt_rewrite' === item.id) { + return ( + ); } - if ( 'fz_translate' === item.id ) { - return( -
  • + if ('fz_translate' === item.id) { + return ( +
  • - + - + ({ + label={__( + 'Target Language', + 'feedzy-rss-feeds' + )} + value={ + item.data.lang || getCurrentLanguage() + } + options={Object.entries( + window.feedzyData.languageList + ).map(([key, value]) => ({ label: value, value: key, - })) } - onChange={ ( currentValue ) => propRef.onChangeHandler( { 'index': loopIndex, 'lang': currentValue ?? '' } ) } + }))} + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + lang: currentValue ?? '', + }) + } disabled={!feedzyData.isAgencyPlan} /> @@ -304,9 +417,23 @@ const SortableItem = ({ propRef, loopIndex, item }) => {
    -
    @@ -314,19 +441,65 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'spinnerchief' === item.id ) { - return( + if ('spinnerchief' === item.id) { + return (
  • - {(feedzyData.isPro && feedzyData.isAgencyPlan) && !feedzyData.apiLicenseStatus.spinnerChiefStatus && (feedzyData.isHighPrivileges ? {__( 'Invalid API Key', 'feedzy-rss-feeds' )} : {__( 'Invalid API Key, Please contact the administrator', 'feedzy-rss-feeds' )} )} - - + {feedzyData.isPro && + feedzyData.isAgencyPlan && + !feedzyData.apiLicenseStatus.spinnerChiefStatus && + (feedzyData.isHighPrivileges ? ( + + {__('Invalid API Key', 'feedzy-rss-feeds')}{' '} + + + + + ) : ( + + {__( + 'Invalid API Key, Please contact the administrator', + 'feedzy-rss-feeds' + )} + + ))} + +
    -
    @@ -334,19 +507,62 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'wordAI' === item.id ) { - return( + if ('wordAI' === item.id) { + return (
  • - {(feedzyData.isPro && feedzyData.isAgencyPlan) && !feedzyData.apiLicenseStatus.wordaiStatus && (feedzyData.isHighPrivileges ? {__( 'Invalid API Key', 'feedzy-rss-feeds' )} : {__( 'Invalid API Key, Please contact the administrator', 'feedzy-rss-feeds' )} )} - - + {feedzyData.isPro && + feedzyData.isAgencyPlan && + !feedzyData.apiLicenseStatus.wordaiStatus && + (feedzyData.isHighPrivileges ? ( + + {__('Invalid API Key', 'feedzy-rss-feeds')}{' '} + + + + + ) : ( + + {__( + 'Invalid API Key, Please contact the administrator', + 'feedzy-rss-feeds' + )} + + ))} + +
    -
    @@ -354,42 +570,136 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'fz_image' === item.id ) { - return( -
  • + if ('fz_image' === item.id) { + return ( +
  • - {feedzyData.isPro && (feedzyData.isBusinessPlan || feedzyData.isAgencyPlan) && !feedzyData.apiLicenseStatus.openaiStatus && (feedzyData.isHighPrivileges ? {__( 'Invalid API Key', 'feedzy-rss-feeds' )} : {__( 'Invalid API Key, Please contact the administrator', 'feedzy-rss-feeds' )} )} - + {feedzyData.isPro && + (feedzyData.isBusinessPlan || + feedzyData.isAgencyPlan) && + !feedzyData.apiLicenseStatus.openaiStatus && + (feedzyData.isHighPrivileges ? ( + + {__('Invalid API Key', 'feedzy-rss-feeds')}{' '} + + + + + ) : ( + + {__( + 'Invalid API Key, Please contact the administrator', + 'feedzy-rss-feeds' + )} + + ))} + - + propRef.onChangeHandler( { 'index': loopIndex, 'generateOnlyMissingImages': currentValue ?? '' } ) } - help={ __( 'Only generate the featured image if it\'s missing in the source XML RSS Feed.', 'feedzy-rss-feeds' ) } - disabled={!feedzyData.isPro || !feedzyData.apiLicenseStatus.openaiStatus} + checked={ + item.data.generateOnlyMissingImages ?? + true + } + label={__( + 'Generate only for missing images', + 'feedzy-rss-feeds' + )} + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + generateOnlyMissingImages: + currentValue ?? '', + }) + } + help={__( + "Only generate the featured image if it's missing in the source XML RSS Feed.", + 'feedzy-rss-feeds' + )} + disabled={ + !feedzyData.isPro || + !feedzyData.apiLicenseStatus + .openaiStatus + } /> - + propRef.onChangeHandler( { 'index': loopIndex, 'generateImagePrompt': currentValue ?? '' } ) } - help={ __( 'Add specific instructions to customize the image generation. By default, images are based on the item’s title and content. Use this field to guide the style of the image, for example: Realistic, artistic, comic-style, etc.', 'feedzy-rss-feeds' ) } - disabled={!feedzyData.isPro || !feedzyData.apiLicenseStatus.openaiStatus} + label={__( + 'Additional Prompt', + 'feedzy-rss-feeds' + )} + value={ + item.data.generateImagePrompt + ? unescape( + item.data.generateImagePrompt.replaceAll( + ''', + "'" + ) + ) + : '' + } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + generateImagePrompt: + currentValue ?? '', + }) + } + help={__( + 'Add specific instructions to customize the image generation. By default, images are based on the item’s title and content. Use this field to guide the style of the image, for example: Realistic, artistic, comic-style, etc.', + 'feedzy-rss-feeds' + )} + disabled={ + !feedzyData.isPro || + !feedzyData.apiLicenseStatus + .openaiStatus + } />
    -
    @@ -397,82 +707,148 @@ const SortableItem = ({ propRef, loopIndex, item }) => { ); } - if ( 'modify_links' === item.id ) { - return( -
  • + if ('modify_links' === item.id) { + return ( +
  • - + - + propRef.onChangeHandler( { 'index': loopIndex, 'remove_links': currentValue ?? '' } ) } + checked={item.data.remove_links ?? false} + label={__( + 'Remove links from the content?', + 'feedzy-rss-feeds' + )} + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + remove_links: currentValue ?? '', + }) + } disabled={!feedzyData.isPro} /> - { true !== item.data.remove_links && + {true !== item.data.remove_links && ( propRef.onChangeHandler( { 'index': loopIndex, 'target': currentValue ?? '' } ) } + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + target: currentValue ?? '', + }) + } disabled={!feedzyData.isPro} /> - } - { true !== item.data.remove_links && + )} + {true !== item.data.remove_links && ( propRef.onChangeHandler( { 'index': loopIndex, 'follow': currentValue ?? '' } ) } + label={__( + 'Make this link a "nofollow" link?', + 'feedzy-rss-feeds' + )} + value={item.data.follow || ''} + onChange={(currentValue) => + propRef.onChangeHandler({ + index: loopIndex, + follow: currentValue ?? '', + }) + } options={[ { - label: __('Default', 'feedzy-rss-feeds'), + label: __( + 'Default', + 'feedzy-rss-feeds' + ), value: '', }, { - label: __('No', 'feedzy-rss-feeds'), + label: __( + 'No', + 'feedzy-rss-feeds' + ), value: 'no', }, { - label: __('Yes', 'feedzy-rss-feeds'), + label: __( + 'Yes', + 'feedzy-rss-feeds' + ), value: 'yes', }, ]} disabled={!feedzyData.isPro} /> - } + )}
    -
  • ); } -} +}; export default SortableElement(SortableItem); diff --git a/js/ActionPopup/index.js b/js/ActionPopup/index.js index c59c17d5b..da616f521 100644 --- a/js/ActionPopup/index.js +++ b/js/ActionPopup/index.js @@ -5,93 +5,95 @@ import Actions from './Actions.js'; import { __ } from '@wordpress/i18n'; -import { - Button, - Modal, - ExternalLink, - Popover -} from '@wordpress/components'; +import { Button, Modal, ExternalLink, Popover } from '@wordpress/components'; import { Fragment, useEffect, useRef, useState, - useCallback + useCallback, } from '@wordpress/element'; -import { - Icon, - dragHandle, - close, - plus, - trash -} from '@wordpress/icons'; +import { Icon, dragHandle, close, plus, trash } from '@wordpress/icons'; const ActionModal = () => { // useRef const userRef = useRef(null); // State - const [ isOpen, setOpen ] = useState(false); - const [ isHideMsg, setHideMeg ] = useState(false); - const [ isVisible, setIsVisible ] = useState( false ); - const [ action, setAction ] = useState([]); - const [ shortCode, setShortCode ] = useState(''); - const [ fieldName, setFieldName ] = useState(''); - const [ editModeTag, setEditModeTag ] = useState(null); - const [ isDisabledAddNew, setDisabledAddNew ] = useState(false); - const [ isLoading, setLoading ] = useState(false); - const [ currentCustomRow, setcurrentCustomRow ] = useState(null); + const [isOpen, setOpen] = useState(false); + const [isHideMsg, setHideMeg] = useState(false); + const [isVisible, setIsVisible] = useState(false); + const [action, setAction] = useState([]); + const [shortCode, setShortCode] = useState(''); + const [fieldName, setFieldName] = useState(''); + const [editModeTag, setEditModeTag] = useState(null); + const [isDisabledAddNew, setDisabledAddNew] = useState(false); + const [isLoading, setLoading] = useState(false); + const [currentCustomRow, setcurrentCustomRow] = useState(null); + const [lastAddedActionIdx, setLastAddedActionIdx] = useState(-1); // Close the popup when click on outside the modal. - const exitModalOnOutsideClick = useCallback(( e ) => { - if ( ! isVisible || ! e.target.closest( '.fz-action-popup' ) ) { - return; - } - toggleVisible( false ); - }, [isVisible]); + const exitModalOnOutsideClick = useCallback( + (e) => { + if (!isVisible || !e.target.closest('.fz-action-popup')) { + return; + } + toggleVisible(false); + }, + [isVisible] + ); - useEffect( () => { - window.wp.api.loadPromise.then( () => { + useEffect(() => { + window.wp.api.loadPromise.then(() => { // Fetch user. - userRef.current = new window.wp.api.models.User( { id: 'me' } ); + userRef.current = new window.wp.api.models.User({ id: 'me' }); userRef.current.fetch(); }); }, []); useEffect(() => { - document.addEventListener( 'click', exitModalOnOutsideClick ); + document.addEventListener('click', exitModalOnOutsideClick); return () => { - document.removeEventListener( 'click', exitModalOnOutsideClick ); + document.removeEventListener('click', exitModalOnOutsideClick); }; - }, [isVisible, exitModalOnOutsideClick] ); + }, [isVisible, exitModalOnOutsideClick]); const handleChange = (args) => { - let id = args.index; + const id = args.index; delete args.index; - let prevState = action[id].data || {}; - let updatedState = {...prevState, ...args}; - action[id]['data'] = updatedState; - setAction( () => ([...action.filter((e)=>{return e})])); - + const prevState = action[id].data || {}; + const updatedState = { ...prevState, ...args }; + action[id].data = updatedState; + setAction(() => [ + ...action.filter((e) => { + return e; + }), + ]); }; const demoPromptText = (args) => { - let id = args.index; - let type = args.type; + const id = args.index; + const type = args.type; delete args.index; delete args.type; - let prevState = action[id].data || {}; - let updatedState = {...prevState, ...args}; - if ( 'summarize' === type ) { - updatedState['ChatGPT'] = 'Summarize this article {content} for better SEO.'; - } else if ( 'paraphase' === type ) { - updatedState['ChatGPT'] = 'Rephrase my {content} for better SEO.'; - } else if ( 'change_tone' === type ) { - updatedState['ChatGPT'] = 'Change tone of my {content} for a more friendly approach.'; + const prevState = action[id].data || {}; + const updatedState = { ...prevState, ...args }; + if ('summarize' === type) { + updatedState.ChatGPT = + 'Summarize this article {content} for better SEO.'; + } else if ('paraphase' === type) { + updatedState.ChatGPT = 'Rephrase my {content} for better SEO.'; + } else if ('change_tone' === type) { + updatedState.ChatGPT = + 'Change tone of my {content} for a more friendly approach.'; } - action[id]['data'] = updatedState; - setAction( () => ([...action.filter((e)=>{return e})])); + action[id].data = updatedState; + setAction(() => [ + ...action.filter((e) => { + return e; + }), + ]); }; const openModal = () => { @@ -99,8 +101,8 @@ const ActionModal = () => { setOpen(true); }; const toggleVisible = (status) => { - if ( status ) { - setIsVisible( (state) => !state ); + if (status) { + setIsVisible((state) => !state); } else { setIsVisible(status); } @@ -113,69 +115,85 @@ const ActionModal = () => { setAction([]); setLoading(false); setcurrentCustomRow(null); + setLastAddedActionIdx(-1); }; - const hideIntroMessage = ( status ) => setHideMeg( status ); - const removeAction = ( index ) => { + const hideIntroMessage = (status) => setHideMeg(status); + const removeAction = (index) => { delete action[index]; - setAction( () => ([...action.filter((e)=>{return e})])); + setAction(() => [ + ...action.filter((e) => { + return e; + }), + ]); setDisabledAddNew(false); }; - const addAction = ( actionId ) => { - let actionData = { + const addAction = (actionId) => { + const actionData = { id: actionId, tag: shortCode, - data: { - - } + data: {}, }; - if ( ['fz_translate', 'fz_paraphrase', 'fz_summarize', 'wordAI', 'spinnerchief'].indexOf( actionId ) > -1 ) { + if ( + [ + 'fz_translate', + 'fz_paraphrase', + 'fz_summarize', + 'wordAI', + 'spinnerchief', + ].indexOf(actionId) > -1 + ) { actionData.data[actionId] = true; } - if ( ['fz_translate'].indexOf( actionId ) > -1 ) { - const langInput = document.getElementById('feedzy_auto_translate_lang'); - actionData.data['lang'] = langInput ? langInput.value : 'eng_Latn'; + if (['fz_translate'].indexOf(actionId) > -1) { + const langInput = document.getElementById( + 'feedzy_auto_translate_lang' + ); + actionData.data.lang = langInput ? langInput.value : 'eng_Latn'; } - if ( ['fz_image'].indexOf( actionId ) > -1 ) { - actionData.data['generateOnlyMissingImages'] = true; + if (['fz_image'].indexOf(actionId) > -1) { + actionData.data.generateOnlyMissingImages = true; } - let newAction = [actionData]; - setDisabledAddNew(() => 'item_image' === shortCode ); - setAction(prevState => ([...prevState, ...newAction])); + const newAction = [actionData]; + setDisabledAddNew(() => 'item_image' === shortCode); + setAction((prevState) => [...prevState, ...newAction]); toggleVisible(false); + setLastAddedActionIdx(action.length); }; const onSortEnd = ({ oldIndex, newIndex }) => { - setAction(prevItem => (arrayMoveImmutable(prevItem, oldIndex, newIndex))); + setAction((prevItem) => + arrayMoveImmutable(prevItem, oldIndex, newIndex) + ); }; /** * Hide action popup. */ const hideActionIntroMessage = () => { - if ( isOpen ) { + if (isOpen) { hideIntroMessage(false); } const model = new window.wp.api.models.User({ // eslint-disable-next-line camelcase id: 'me', meta: { - feedzy_hide_action_message: true - } + feedzy_hide_action_message: true, + }, }); const save = model.save(); - save.success( () => { + save.success(() => { userRef.current.fetch(); }); - save.error( ( response ) => { - console.warn( response.responseJSON.message ); + save.error((response) => { + console.warn(response.responseJSON.message); }); }; @@ -183,84 +201,105 @@ const ActionModal = () => { * Save actions. */ const saveAction = () => { - if ( 'function' !== typeof jQuery ) { + if ('function' !== typeof jQuery) { return; } - action?.forEach( ( item, index ) => { - window?.tiTrk?.with('feedzy').add( { feature: 'import_action', featureValue: item?.id, groupId: item?.tag ?? '' } ); + action?.forEach((item, index) => { + window?.tiTrk?.with('feedzy').add({ + feature: 'import_action', + featureValue: item?.id, + groupId: item?.tag ?? '', + }); }); // Serialize the action. - let _action = encodeURIComponent( JSON.stringify( action ) ); - if ( action.length === 0 ) { + let _action = encodeURIComponent(JSON.stringify(action)); + if (action.length === 0) { setAction([]); - _action = encodeURIComponent( JSON.stringify( [ { id: '', tag: shortCode, data: {} } ] ) ); + _action = encodeURIComponent( + JSON.stringify([{ id: '', tag: shortCode, data: {} }]) + ); } - if ( 'import_custom_field' === fieldName ) { - if ( currentCustomRow ) { + if ('import_custom_field' === fieldName) { + if (currentCustomRow) { currentCustomRow.value = _action; } closeModal(); return; } - const inputField = jQuery( `[name="feedzy_meta_data[${fieldName}]"]:is(textarea, input)` ).data('tagify'); + const inputField = jQuery( + `[name="feedzy_meta_data[${fieldName}]"]:is(textarea, input)` + ).data('tagify'); - if ( 'import_post_featured_img' === fieldName ) { + if ('import_post_featured_img' === fieldName) { inputField.removeAllTags(); inputField.addEmptyTag(); inputField.clearPersistedData(); } - if ( null === editModeTag || 'import_post_featured_img' === fieldName ) { - let tagElm = inputField.createTagElem({value: _action}) - inputField.injectAtCaret(tagElm) - let elm = inputField.insertAfterTag(tagElm) - inputField.placeCaretAfterNode(elm) + if (null === editModeTag || 'import_post_featured_img' === fieldName) { + const tagElm = inputField.createTagElem({ value: _action }); + inputField.injectAtCaret(tagElm); + const elm = inputField.insertAfterTag(tagElm); + inputField.placeCaretAfterNode(elm); } else { - inputField.replaceTag(editModeTag.closest( '.fz-content-action' ), {value: _action}); + inputField.replaceTag(editModeTag.closest('.fz-content-action'), { + value: _action, + }); } closeModal(); }; const helperContainer = () => { - return document.querySelector( '.fz-action-popup .fz-action-panel ul' ); + return document.querySelector('.fz-action-popup .fz-action-panel ul'); }; + const actionButtonTitle = action.length + ? __('Save Actions', 'feedzy-rss-feeds') + : __('Skip Actions', 'feedzy-rss-feeds'); + // Click to open action popup. - document.querySelectorAll( '[data-action_popup]' ).forEach( actionItem => { - actionItem.addEventListener( 'click', ( event ) => { + document.querySelectorAll('[data-action_popup]').forEach((actionItem) => { + actionItem.addEventListener('click', (event) => { event.preventDefault(); - if ( userRef.current ) { - if ( ! userRef.current.attributes.meta.feedzy_hide_action_message ) { + if (userRef.current) { + if ( + !userRef.current.attributes.meta.feedzy_hide_action_message + ) { hideActionIntroMessage(); } else { hideIntroMessage(true); } } - let tag = event.target.getAttribute( 'data-action_popup' ) || ''; - let dataFieldName = event.target.getAttribute( 'data-field-name' ) || ''; - if ( '' === tag ) { + const tag = event.target.getAttribute('data-action_popup') || ''; + const dataFieldName = + event.target.getAttribute('data-field-name') || ''; + if ('' === tag) { event.target.closest('.dropdown-item').click(); return; } - setShortCode( tag ); - setFieldName( dataFieldName ); + setShortCode(tag); + setFieldName(dataFieldName); openModal(); - } ); - } ); + }); + }); // Init custom field actions. const initCustomFieldActions = () => { - const customFieldElement = document.querySelectorAll( '.custom_fields .fz-action-icon' ) || []; - if ( customFieldElement.length === 0 ) { + const customFieldElement = + document.querySelectorAll('.custom_fields .fz-action-icon') || []; + if (customFieldElement.length === 0) { return; } - customFieldElement.forEach( actionButton => { - actionButton.addEventListener( 'click', ( event ) => { + customFieldElement.forEach((actionButton) => { + actionButton.addEventListener('click', (event) => { event.preventDefault(); - if ( userRef.current ) { - if ( ! userRef.current.attributes.meta.feedzy_hide_action_message ) { + if (userRef.current) { + if ( + !userRef.current.attributes.meta + .feedzy_hide_action_message + ) { hideActionIntroMessage(); } else { hideIntroMessage(true); @@ -268,183 +307,509 @@ const ActionModal = () => { } let editAction = event?.target?.nextElementSibling?.value || ''; - if ( editAction ) { - editAction = JSON.parse( decodeURIComponent( editAction ) ); - setAction( () => ([...editAction.filter((e)=>{return e.id !== ''})])); + if (editAction) { + editAction = JSON.parse(decodeURIComponent(editAction)); + setAction(() => [ + ...editAction.filter((e) => { + return e.id !== ''; + }), + ]); } - setShortCode( 'custom_field' ); - setFieldName( 'import_custom_field' ); - setcurrentCustomRow( event?.target?.nextElementSibling ); + setShortCode('custom_field'); + setFieldName('import_custom_field'); + setcurrentCustomRow(event?.target?.nextElementSibling); openModal(); - } ); - } ); + }); + }); }; // Attach click event to the newly added custom field row. - document.addEventListener( 'feedzy_new_row_added', initCustomFieldActions ); + document.addEventListener('feedzy_new_row_added', initCustomFieldActions); const initEditHooks = () => { - if ( isLoading ) { + if (isLoading) { return; } // Click to open edit action popup. - setTimeout( function() { - const editActionElement = document.querySelectorAll( '.fz-content-action .tagify__filter-icon' ) || []; - if ( editActionElement.length === 0 ) { + setTimeout(function () { + const editActionElement = + document.querySelectorAll( + '.fz-content-action .tagify__filter-icon' + ) || []; + if (editActionElement.length === 0) { initCustomFieldActions(); initEditHooks(); return; } - if ( editActionElement.length > 0 ) { - editActionElement.forEach( editItem => { - editItem.addEventListener( 'click', ( event ) => { - if ( event.target.parentNode ) { - let editAction = event.target.getAttribute( 'data-actions' ) || ''; - let fieldId = event.target.getAttribute( 'data-field_id' ) || ''; - editAction = JSON.parse( decodeURIComponent( editAction ) ); - setAction(() => ( + if (editActionElement.length > 0) { + editActionElement.forEach((editItem) => { + editItem.addEventListener('click', (event) => { + if (event.target.parentNode) { + let editAction = + event.target.getAttribute('data-actions') || ''; + const fieldId = + event.target.getAttribute('data-field_id') || + ''; + editAction = JSON.parse( + decodeURIComponent(editAction) + ); + setAction(() => editAction - .filter((e) => e.id !== '') - .map((e) => { - // Replace 'fz_summarize' with 'chat_gpt_rewrite' for backward compatible. - if (e.id === 'fz_summarize') { - return { - ...e, - id: 'chat_gpt_rewrite', - data: { ChatGPT: "Summarize this article {content} for better SEO." } - }; - } - return e; - }) - )); - let magicTag = editAction[0] || {}; - let tag = magicTag.tag; + .filter((e) => e.id !== '') + .map((e) => { + // Replace 'fz_summarize' with 'chat_gpt_rewrite' for backward compatible. + if (e.id === 'fz_summarize') { + return { + ...e, + id: 'chat_gpt_rewrite', + data: { + ChatGPT: + 'Summarize this article {content} for better SEO.', + }, + }; + } + return e; + }) + ); + const magicTag = editAction[0] || {}; + const tag = magicTag.tag; setEditModeTag(event.target.parentNode); - setDisabledAddNew(() => Object.keys(magicTag.data).length && 'item_image' === tag ); - let actionGroup = document.querySelector( '.' + fieldId ); - actionGroup.querySelector( '[data-action_popup="' + tag + '"]' ).click(); + setDisabledAddNew( + () => + Object.keys(magicTag.data).length && + 'item_image' === tag + ); + const actionGroup = document.querySelector( + '.' + fieldId + ); + actionGroup + .querySelector( + '[data-action_popup="' + tag + '"]' + ) + .click(); } - } ); - } ); + }); + }); } initCustomFieldActions(); - }, 500 ); + }, 500); }; initEditHooks(); return ( - { isOpen && ( - -
    -
    -
    -

    { __('Add actions to this tag', 'feedzy-rss-feeds') }

    { ! isHideMsg && ( { __( 'New!', 'feedzy-rss-feeds' ) } ) } -
    - -
    -
    - { ! isHideMsg && ( -
    -

    { __( 'Feedzy now supports adding and chaining actions into a single tag. Add an action by clicking the Add new button below. You can add multiple actions in each tag.', 'feedzy-rss-feeds' ) }
    - { __( 'Learn more about this feature.', 'feedzy-rss-feeds' ) }

    -
    - ) } - - { action.length === 0 && ( -
    -

    { __( 'If no action is needed, continue with using the original tag by clicking on the Save Actions button.', 'feedzy-rss-feeds' ) }

    + {isOpen && ( + +
    +
    +
    +

    + {__( + 'Add actions to this tag', + 'feedzy-rss-feeds' + )} +

    {' '} + {!isHideMsg && ( + + {__('New!', 'feedzy-rss-feeds')} + + )}
    - ) } - - {action.length > 0 && ( )} - -
    -
    - - { isVisible && ( -
    -
      - { - 'item_image' === shortCode ? ([ - feedzyData.isPro && ( feedzyData.isBusinessPlan || feedzyData.isAgencyPlan ) ? ( -
    • addAction('fz_image') }>{__( 'Generate with OpenAI', 'feedzy-rss-feeds' )}
    • - ) : ( -
    • addAction('fz_image') }>{__( 'Generate with OpenAI', 'feedzy-rss-feeds' )} PRO
    • - )] - ) : ([ -
    • addAction('trim') }>{__( 'Trim Content', 'feedzy-rss-feeds' )}
    • , - ( - feedzyData.isPro && feedzyData.isAgencyPlan ? ( -
    • addAction('fz_translate') }>{__( 'Translate with Feedzy', 'feedzy-rss-feeds' )}
    • - ) : ( -
    • addAction('fz_translate') }>{__( 'Translate with Feedzy', 'feedzy-rss-feeds' )} PRO
    • - ) - ), -
    • addAction('search_replace') }>{__( 'Search / Replace', 'feedzy-rss-feeds' )}
    • , - ( - 'item_categories' !== shortCode && ( - feedzyData.isPro ? ( -
    • addAction('modify_links') }>{__( 'Modify Links', 'feedzy-rss-feeds' )}
    • - ) : ( -
    • addAction('modify_links') }>{__( 'Modify Links', 'feedzy-rss-feeds' )} PRO
    • - ) - ) - ), - ( - 'item_categories' !== shortCode && ( - feedzyData.isPro && ( feedzyData.isBusinessPlan || feedzyData.isAgencyPlan ) ? ( -
    • addAction('fz_paraphrase') }>{__( 'Paraphrase with Feedzy', 'feedzy-rss-feeds' )}
    • - ) : ( -
    • addAction('fz_paraphrase') }>{__( 'Paraphrase with Feedzy', 'feedzy-rss-feeds' )} PRO
    • - ) - ) - ), - ( - 'item_categories' !== shortCode && ( - feedzyData.isPro && feedzyData.isAgencyPlan ? ( -
    • addAction('spinnerchief') }>{__( 'Spin using SpinnerChief', 'feedzy-rss-feeds' )}
    • - ) : ( -
    • addAction('spinnerchief') }>{__( 'Spin using SpinnerChief', 'feedzy-rss-feeds' )} PRO
    • - ) - ) - ), - ( - 'item_categories' !== shortCode && ( - feedzyData.isPro && feedzyData.isAgencyPlan ? ( -
    • addAction('wordAI') }>{__( 'Spin using WordAI', 'feedzy-rss-feeds' )}
    • + +
    +
    + {!isHideMsg && ( +
    +

    + {__( + 'Feedzy now supports adding and chaining actions into a single tag. Add an action by clicking the Add new button below. You can add multiple actions in each tag.', + 'feedzy-rss-feeds' + )} +
    + + {__( + 'Learn more about this feature.', + 'feedzy-rss-feeds' + )} + +

    +
    + )} + + {action.length === 0 && ( +
    +

    + {__( + 'If no action is needed, continue with using the original tag by clicking on the Save Actions button.', + 'feedzy-rss-feeds' + )} +

    +
    + )} + + {action.length > 0 && ( + + )} + +
    +
    + + {isVisible && ( +
    +
      + {'item_image' === shortCode + ? [ + window.feedzyData + .isPro && + (window.feedzyData + .isBusinessPlan || + window + .feedzyData + .isAgencyPlan) ? ( +
    • + +
    • ) : ( -
    • addAction('wordAI') }>{__( 'Spin using WordAI', 'feedzy-rss-feeds' )} PRO
    • - ) - ) - ), - ( - 'item_categories' !== shortCode && ( - feedzyData.isPro && ( feedzyData.isBusinessPlan || feedzyData.isAgencyPlan ) ? ( -
    • addAction('chat_gpt_rewrite') }>{__( 'Rewrite with AI', 'feedzy-rss-feeds' )}
    • +
    • + {__( + 'Generate with OpenAI', + 'feedzy-rss-feeds' + )} + + PRO + +
    • + ), + ] + : [ +
    • + +
    • , + window.feedzyData + .isPro && + window.feedzyData + .isAgencyPlan ? ( +
    • + +
    • ) : ( -
    • addAction('chat_gpt_rewrite') }>{__( 'Rewrite with AI', 'feedzy-rss-feeds' )} PRO
    • - ) - ) - ) - ]) - } -
    • { __( 'Learn more about this feature.', 'feedzy-rss-feeds' ) }
    • -
    -
    +
  • + {__( + 'Translate with Feedzy', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + ), +
  • + +
  • , + 'item_categories' !== + shortCode && + (window + .feedzyData + .isPro ? ( +
  • + +
  • + ) : ( +
  • + {__( + 'Modify Links', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + )), + 'item_categories' !== + shortCode && + (window + .feedzyData + .isPro && + (window + .feedzyData + .isBusinessPlan || + window + .feedzyData + .isAgencyPlan) ? ( +
  • + +
  • + ) : ( +
  • + {__( + 'Paraphrase with Feedzy', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + )), + 'item_categories' !== + shortCode && + (window + .feedzyData + .isPro && + window + .feedzyData + .isAgencyPlan ? ( +
  • + +
  • + ) : ( +
  • + {__( + 'Spin using SpinnerChief', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + )), + 'item_categories' !== + shortCode && + (window + .feedzyData + .isPro && + window + .feedzyData + .isAgencyPlan ? ( +
  • + +
  • + ) : ( +
  • + {__( + 'Spin using WordAI', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + )), + 'item_categories' !== + shortCode && + (window + .feedzyData + .isPro && + (window + .feedzyData + .isBusinessPlan || + window + .feedzyData + .isAgencyPlan) ? ( +
  • + +
  • + ) : ( +
  • + {__( + 'Rewrite with AI', + 'feedzy-rss-feeds' + )} + + PRO + +
  • + )), + ]} +
  • + + {__( + 'Learn more about this feature.', + 'feedzy-rss-feeds' + )} + +
  • + +
    + )} +
    + {action && ( + )}
    - { action && ( ) }
    -
    - - ) } + + )} - ); - }; + ); +}; -ReactDOM.render( - , - document.querySelector('#fz-action-popup') -); +ReactDOM.render(, document.querySelector('#fz-action-popup')); diff --git a/js/Conditions/ConditionsControl.js b/js/Conditions/ConditionsControl.js index 8bf0103bd..f1eaae0ac 100644 --- a/js/Conditions/ConditionsControl.js +++ b/js/Conditions/ConditionsControl.js @@ -6,11 +6,10 @@ import classNames from 'classnames'; /** * WordPress dependencies. */ -import { __ } from '@wordpress/i18n'; - +import { __, sprintf } from '@wordpress/i18n'; import { Button, SelectControl, TextControl } from '@wordpress/components'; - import { Icon, plus } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; /** * Internal dependencies. @@ -18,6 +17,7 @@ import { Icon, plus } from '@wordpress/icons'; import PanelTab from './PanelTab'; import DateTimeControl from './DateTimeControl'; +const isPro = window.feedzyData.isPro; const SUPPORTED_FIELDS = [ { label: __('Title', 'feedzy-rss-feeds'), @@ -26,15 +26,18 @@ const SUPPORTED_FIELDS = [ { label: __('Description', 'feedzy-rss-feeds'), value: 'description', + disabled: !isPro, }, { label: __('Full Content', 'feedzy-rss-feeds'), value: 'fullcontent', + disabled: !isPro, }, { label: __('Author', 'feedzy-rss-feeds'), value: 'author', unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, }, { label: __('Date', 'feedzy-rss-feeds'), @@ -47,16 +50,24 @@ const SUPPORTED_FIELDS = [ 'contains', 'not_contains', ], + disabled: !isPro, }, { label: __('Featured Image', 'feedzy-rss-feeds'), value: 'featured_image', unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, + }, + { + label: __('Link', 'feedzy-rss-feeds'), + value: 'link', + unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, }, ]; -const isPro = window.feedzyData.isPro; const ConditionsControl = ({ conditions, setConditions }) => { + const [modalOpen, setModelOpen] = useState(false); const onChangeMatch = (value) => { setConditions({ ...conditions, @@ -64,7 +75,18 @@ const ConditionsControl = ({ conditions, setConditions }) => { }); }; + const el = document.querySelector('.editor-sidebar__panel-tabs'); const addCondition = () => { + if (!isPro && 1 <= conditions.conditions.length) { + // the Inspector panel use sticky position with their own stacking context, + // which causes them to appear above our popup overlay. We set their z-index to 0 so the popup covers them. + if (el) { + el.style.zIndex = 0; + } + setModelOpen(true); + return; + } + const conditionsCopy = [...conditions.conditions]; conditionsCopy.push({ @@ -115,128 +137,225 @@ const ConditionsControl = ({ conditions, setConditions }) => { }); }; + const closeModal = () => { + if (el) { + el.style.zIndex = 0; + } + setModelOpen(false); + }; + return ( -
    - - - {conditions.conditions.map((condition, index) => { - const field = SUPPORTED_FIELDS.find( - (i) => i.value === condition.field - ); - const operators = Object.keys( - window?.feedzyConditionsData?.operators - ).filter((key) => !field.unsupportedOperators?.includes(key)); - - return ( - removeCondition(index)} - initialOpen={index === 0} - > - - onChangeCondition(index, value, 'field') - } - disabled={!isPro} - /> - - ({ - label: window.feedzyConditionsData.operators[ - key - ], - value: key, - }))} - help={ - ['contains', 'not_contains'].includes( - condition?.operator - ) - ? __( - 'You can use comma(,) and plus(+) keyword.', - 'feedzy-rss-feeds' - ) - : '' - } - value={condition?.operator} - onChange={(value) => - onChangeCondition(index, value, 'operator') - } - disabled={!isPro} - /> - - {!['has_value', 'empty'].includes( - condition?.operator - ) && ( - <> - {condition?.field === 'date' ? ( - - onChangeCondition( - index, - value, - 'value' - ) - } - disabled={!isPro} - /> - ) : ( - - onChangeCondition( - index, - value, - 'value' - ) - } - disabled={!isPro} - /> + <> +
    + + + {conditions.conditions.map((condition, index) => { + const field = SUPPORTED_FIELDS.find( + (i) => i.value === condition.field + ); + const operators = Object.keys( + window?.feedzyConditionsData?.operators + ).filter( + (key) => !field?.unsupportedOperators?.includes(key) + ); + const fieldOptions = SUPPORTED_FIELDS.map((f) => ({ + label: `${f.label}${f.disabled ? ' (PRO)' : ''}`, + value: f.value, + disabled: f.disabled, + })); + + return ( + removeCondition(index)} + initialOpen={index === 0} + > + + onChangeCondition(index, value, 'field') + } + /> + + - )} - - ); - })} - -
    - + +
    -
    + {modalOpen && ( +
    +
    + +
    +

    + {__( + 'Upgrade to Use Unlimited Conditions', + 'feedzy-rss-feeds' + )} +

    +

    + {__( + 'Filter Condition limit reached', + 'feedzy-rss-feeds' + )} + + {'(' + + sprintf( + // translators: %1$s is the number of imports used, %2$s is the total number of imports allowed. + __( + '%1$s/%2$s used', + 'feedzy-rss-feeds' + ), + '1', + '1' + ) + + ')'} + +

    +
    +
    +

    + {__( + "Your current plan supports only one filter condition. Upgrade to unlock unlimited import configurations and make the most of Feedzy's powerful features!", + 'feedzy-rss-feeds' + )} +

    +
    +
    + + + {__( + '30-day money-back guarantee. No questions asked.', + 'feedzy-rss-feeds' + )} + +
    +
    +
    + )} + ); }; diff --git a/js/Conditions/index.js b/js/Conditions/index.js index db5b5c97a..4fb7ef013 100644 --- a/js/Conditions/index.js +++ b/js/Conditions/index.js @@ -28,11 +28,6 @@ const App = () => { }); useEffect(() => { - if (!feedzyData.isPro) { - setConditions(dummyConditions); - return; - } - const field = document.getElementById('feed-post-filters-conditions'); if (field && field.value) { const parsedConditions = JSON.parse(field.value); @@ -45,10 +40,6 @@ const App = () => { }, []); useEffect(() => { - if (!feedzyData.isPro) { - return; - } - document.getElementById('feed-post-filters-conditions').value = JSON.stringify(conditions); }, [conditions]); diff --git a/js/FeedzyBlock/Editor.js b/js/FeedzyBlock/Editor.js index 91a6022f1..979e7843f 100644 --- a/js/FeedzyBlock/Editor.js +++ b/js/FeedzyBlock/Editor.js @@ -8,6 +8,7 @@ import { Button, Spinner, Disabled, + Notice, } from '@wordpress/components'; import queryString from 'query-string'; @@ -15,6 +16,7 @@ import Inspector from './inspector'; import { __, sprintf } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { Component, Fragment } from '@wordpress/element'; +import { InspectorControls } from '@wordpress/block-editor'; import { unescapeHTML, filterData, @@ -53,6 +55,7 @@ class Editor extends Component { this.onThumb = this.onThumb.bind(this); this.onDefault = this.onDefault.bind(this); this.onSize = this.onSize.bind(this); + this.onAspectRatio = this.onAspectRatio.bind(this); this.onReferralURL = this.onReferralURL.bind(this); this.onColumns = this.onColumns.bind(this); this.onTemplate = this.onTemplate.bind(this); @@ -77,7 +80,7 @@ class Editor extends Component { // reload: when the feed needs to be refetched route: this.props.attributes.route, loading: false, - error: false, + validationResults: [], }; } @@ -103,12 +106,81 @@ class Editor extends Component { } } - loadFeed() { - let url = this.props.attributes.feeds; + async loadFeed() { + const url = this.props.attributes.feeds; if (url === undefined) { return; } + this.setState({ + route: 'home', + loading: true, + validationResults: [], + }); + + if (inArray(url, this.props.attributes.categories)) { + this.loadFeedData(url); + return; + } + + try { + const formData = new FormData(); + formData.append('action', 'feedzy_validate_feed'); + formData.append('feed_url', url); + formData.append('nonce', window.feedzyjs?.nonce); + + const validationResponse = await fetch(window.feedzyjs?.url, { + method: 'POST', + body: formData, + }); + + const validationData = await validationResponse.json(); + + if (validationData.success && validationData.data?.results) { + const results = validationData.data.results; + + this.setState({ + validationResults: results, + loading: false, + }); + + const hasErrors = results.some( + (result) => result.status === 'error' + ); + + if (!hasErrors) { + this.loadFeedData(url); + } + } else if (!validationData.success) { + this.setState({ + validationResults: [ + { + status: 'error', + message: + validationData.data?.message || + __('Validation failed', 'feedzy-rss-feeds'), + }, + ], + loading: false, + }); + } + } catch (error) { + this.setState({ + validationResults: [ + { + status: 'error', + message: __( + 'Failed to validate feed. Please check your connection and try again.', + 'feedzy-rss-feeds' + ), + }, + ], + loading: false, + }); + } + } + + loadFeedData(url) { if (inArray(url, this.props.attributes.categories)) { const category = url; url = queryString.stringify( @@ -123,11 +195,6 @@ class Editor extends Component { url = queryString.stringify({ url }, { arrayFormat: 'bracket' }); } - this.setState({ - route: 'home', - loading: true, - }); - apiFetch({ path: `/feedzy/v1/feed?${url}`, method: 'POST', @@ -142,13 +209,24 @@ class Editor extends Component { this.setState({ route: 'fetched', loading: false, + validationResults: [], }); return data; } this.setState({ route: 'home', loading: false, - error: true, + validationResults: [ + { + status: 'error', + message: + data.message || + __( + 'Feed URL is invalid or unreachable', + 'feedzy-rss-feeds' + ), + }, + ], }); return data; }) @@ -156,7 +234,15 @@ class Editor extends Component { this.setState({ route: 'home', loading: false, - error: true, + validationResults: [ + { + status: 'error', + message: __( + 'Error loading feed. Please check your feed URL and try again.', + 'feedzy-rss-feeds' + ), + }, + ], }); return err; }); @@ -225,7 +311,7 @@ class Editor extends Component { getImageURL(item, background) { let url; - if (item.thumbnail) { + if (item.thumbnail && this.props.attributes.thumb === 'auto') { url = item.thumbnail; } else if (this.props.attributes.default) { url = this.props.attributes.default.url; @@ -247,6 +333,9 @@ class Editor extends Component { featureValue: value, }); this.props.setAttributes({ feeds: value }); + this.setState({ + validationResults: [], + }); } onChangeMax(value) { this.props.setAttributes({ max: !value ? 5 : Number(value) }); @@ -326,6 +415,9 @@ class Editor extends Component { onSize(value) { this.props.setAttributes({ size: !value ? 150 : Number(value) }); } + onAspectRatio(value) { + this.props.setAttributes({ aspectRatio: value }); + } onReferralURL(value) { window.tiTrk?.with('feedzy').add({ feature: 'block-referral-url' }); this.props.setAttributes({ referral_url: value }); @@ -418,12 +510,49 @@ class Editor extends Component { } } + renderValidationResults() { + if ( + !this.state.validationResults || + this.state.validationResults.length === 0 + ) { + return null; + } + + return ( +
    + {this.state.validationResults.map((result, index) => ( + + {result.url && ( + <> + {result.url} +
    + + )} + {result.message} +
    + ))} +
    + ); + } + render() { return ( - {'fetched' === this.state.route && ( - - )} + +
    + {'fetched' === this.state.route && ( + + )} +
    +
    {'home' === this.state.route && (
    -

    {__('Fetching…', 'feedzy-rss-feeds')}

    -
    - ) : ( - -
    - - {this.props.attributes.categories && - this.props.attributes.categories - .length > 0 && ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions - - )} -
    - - + {__( + 'Validating and fetching feed…', 'feedzy-rss-feeds' )} +

    +
    + ) : ( +
    +
    - {__('Validate', 'feedzy-rss-feeds')} - - - {this.state.error && ( -
    +
    + + {this.props.attributes.categories && + this.props.attributes.categories + .length > 0 && ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions + + )} +
    +
    - )} + +
    + + {this.renderValidationResults()} +

    {__( "Enter the full URL of the feed source you wish to display here, or the name of a group you've created. Also you can add multiple URLs just separate them with a comma. You can manage your groups feed from", @@ -508,7 +644,7 @@ class Editor extends Component { {__('here', 'feedzy-rss-feeds')}

    - +
    )}
    @@ -577,17 +713,14 @@ class Editor extends Component { const categories = unescapeHTML(item.categories) || ''; if (this.metaExists('tz=local')) { - let itemDateTimeObj = new Date( - itemDateTime + const itemDateTimeObj = window.moment( + itemDateTime, + 'MMMM D, YYYY h:mm a [UTC] Z' ); - itemDateTimeObj = - itemDateTimeObj.toUTCString(); - itemDate = window.moment - .utc(itemDateTimeObj) - .format('MMMM D, YYYY'); - itemTime = window.moment - .utc(itemDateTimeObj) - .format('h:mm A'); + + itemDate = + itemDateTimeObj.format('MMMM D, YYYY'); + itemTime = itemDateTimeObj.format('h:mm A'); } let author = @@ -669,9 +802,9 @@ class Editor extends Component {
    - + />
    diff --git a/js/FeedzyBlock/attributes.js b/js/FeedzyBlock/attributes.js index 79a831595..c39c401c3 100644 --- a/js/FeedzyBlock/attributes.js +++ b/js/FeedzyBlock/attributes.js @@ -117,7 +117,7 @@ const attributes = { disableStyle: { type: 'boolean', default: false, - }, + }, follow: { type: 'string', default: 'no', @@ -137,7 +137,11 @@ const attributes = { _dry_run_tags_: { type: 'string', default: '', - } + }, + aspectRatio: { + type: 'string', + default: '1', + }, }; -export default attributes; \ No newline at end of file +export default attributes; diff --git a/js/FeedzyBlock/inspector.js b/js/FeedzyBlock/inspector.js index 1d68c3a9c..f4126f923 100644 --- a/js/FeedzyBlock/inspector.js +++ b/js/FeedzyBlock/inspector.js @@ -12,7 +12,6 @@ import RadioImageControl from './radio-image-control/'; import classnames from 'classnames'; import { __, sprintf } from '@wordpress/i18n'; import { applyFilters } from '@wordpress/hooks'; -import { InspectorControls, MediaUpload } from '@wordpress/block-editor'; import { BaseControl, ExternalLink, @@ -23,14 +22,13 @@ import { Button, ToggleControl, SelectControl, - ResponsiveWrapper, Dashicon, } from '@wordpress/components'; +import { FallbackImageLoader } from '../FeedzyLoop/components/FallbackImageLoader'; /** * Create an Inspector Controls wrapper Component */ - class Inspector extends Component { constructor() { super(...arguments); @@ -60,288 +58,775 @@ class Inspector extends Component { return ( - - - + + + + {'content' === this.state.tab && ( + + - - - {__('Content', 'feedzy-rss-feeds')} - - - + + )} + {'fetched' === this.props.state.route && ( + + + + + )} + + + - - - {__('Style', 'feedzy-rss-feeds')} - - - - - {'content' === this.state.tab && ( + {!window.feedzyjs.isPro && ( +
    + {__( + 'Unlock more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + + + + + +
    +

    + {__( + 'Filter feed item by date range.', + 'feedzy-rss-feeds' + )} +

    + + +
    + +
    + )} + + {'fetched' === this.props.state.route && + 'style' === this.state.tab && ( - {this.props.attributes.status !== 0 && ( + + + {this.props.attributes.thumb !== 'no' && ( + {this.props.attributes.thumb !== + 'auto' && ( + + this.props.edit.onDefault( + undefined + ) + } + label={__( + 'Fallback image if no image is found.', + 'feedzy-rss-feeds' + )} + /> + )} - - - - )} - {'fetched' === this.props.state.route && ( - - - - )} + + + Pro + + ), + ]} initialOpen={false} - className="feedzy-item-options" + className={ + window.feedzyjs.isPro + ? 'feedzy-layout' + : 'feedzy-layout fz-locked' + } > - + {__( + 'Unlock this feature and more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__( + 'Feedzy Pro', + 'feedzy-rss-feeds' + )} + +
    + )} + + + + - - - - - - {this.props.attributes.itemTitle && ( + + + )} + {'fetched' === this.props.state.route && + 'advanced' === this.state.tab && ( + + + + - )} + - {this.props.attributes.summary && ( - )} + + + + + + + + Pro @@ -351,8 +836,8 @@ class Inspector extends Component { initialOpen={false} className={ window.feedzyjs.isPro - ? 'feedzy-item-filter' - : 'feedzy-item-filter fz-locked' + ? 'feedzy-pro-options' + : 'feedzy-pro-options fz-locked' } > {!window.feedzyjs.isPro && ( @@ -361,7 +846,7 @@ class Inspector extends Component { 'Unlock this feature and more advanced options with', 'feedzy-rss-feeds' )}{' '} - + {__( 'Feedzy Pro', 'feedzy-rss-feeds' @@ -371,705 +856,97 @@ class Inspector extends Component { )} - + + + + + + - -

    - {__( - 'Filter feed item by date range.', + + - - )} - - {'fetched' === this.props.state.route && - 'style' === this.state.tab && ( - - - - - {this.props.attributes.thumb !== 'no' && ( - - {this.props.attributes.thumb !== - 'auto' && ( -

    - - ( - - {this.props - .attributes - .default !== - undefined && ( - - - {__( - - - - - )} - - - )} - /> -
    - )} - -
    - )} - - - - Pro - - ), - ]} - initialOpen={false} - className={ - window.feedzyjs.isPro - ? 'feedzy-layout' - : 'feedzy-layout fz-locked' - } - > - {!window.feedzyjs.isPro && ( -
    - {__( - 'Unlock this feature and more advanced options with', - 'feedzy-rss-feeds' - )}{' '} - - {__( - 'Feedzy Pro', - 'feedzy-rss-feeds' - )} - -
    - )} - - - - -
    - - - - - - )} - {'fetched' === this.props.state.route && - 'advanced' === this.state.tab && ( - - - - - - - - {__( - 'You can find more info about available meta field values here.', - 'feedzy-rss-feeds' - )} - - - - - - {this.props.attributes.feedData.channel !== - null && ( - - )} - - - - - - - - Pro - - ), - ]} - initialOpen={false} - className={ - window.feedzyjs.isPro - ? 'feedzy-pro-options' - : 'feedzy-pro-options fz-locked' - } - > - {!window.feedzyjs.isPro && ( -
    - {__( - 'Unlock this feature and more advanced options with', - 'feedzy-rss-feeds' - )}{' '} - - {__( - 'Feedzy Pro', - 'feedzy-rss-feeds' - )} - -
    - )} - -
    - - - - - - - - -
    - )} - ); } diff --git a/js/FeedzyBlock/style.scss b/js/FeedzyBlock/style.scss index a94aacf77..bb771e7f5 100644 --- a/js/FeedzyBlock/style.scss +++ b/js/FeedzyBlock/style.scss @@ -1,6 +1,7 @@ .wp-block-feedzy-rss-feeds-feedzy-block { .feedzy-source-wrap { position: relative; + width: max-content; } .feedzy-source { margin-right: 10px; @@ -221,4 +222,9 @@ font-size: 13px; line-height: 1.4em; color: #3c434a; +} +.feedzy-validation-results { + display: flex; + flex-direction: column; + gap: 10px; } \ No newline at end of file diff --git a/js/FeedzyLoop/block.json b/js/FeedzyLoop/block.json index 17ed06cfb..d2ca8f426 100644 --- a/js/FeedzyLoop/block.json +++ b/js/FeedzyLoop/block.json @@ -55,6 +55,13 @@ } } }, + "thumb": { + "type": "string", + "default": "auto" + }, + "fallbackImage": { + "type": "object" + }, "conditions": { "type": "object", "properties": { @@ -88,7 +95,11 @@ "innerBlocksContent": { "type": "string", "default": "" - } + }, + "referral_url": { + "type": "string", + "default": "" + } }, "supports": { "align": [ "wide", "full" ], diff --git a/js/FeedzyLoop/components/FallbackImageLoader.jsx b/js/FeedzyLoop/components/FallbackImageLoader.jsx new file mode 100644 index 000000000..11012140c --- /dev/null +++ b/js/FeedzyLoop/components/FallbackImageLoader.jsx @@ -0,0 +1,100 @@ +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { MediaUpload } from '@wordpress/block-editor'; +import { + Button, + ResponsiveWrapper, + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalHStack as HStack, +} from '@wordpress/components'; + +export function FallbackImageLoader({ + imageValue, + onChangeImage, + onRemoveImage, + label = __('Fallback image if no image is found.', 'feedzy-rss-feeds'), +}) { + const handleSelect = (media) => { + const imageData = { + url: media.url, + width: media.width, + height: media.height, + id: media.id, + }; + onChangeImage(imageData); + }; + + const handleRemove = () => { + onRemoveImage(); + }; + + return ( +
    + + ( + + {imageValue !== undefined && ( + + {__( + + )} + + + {imageValue !== undefined && ( + + )} + + + + + )} + /> +
    + ); +} + +export default FallbackImageLoader; diff --git a/js/FeedzyLoop/controls.js b/js/FeedzyLoop/controls.js index 34abfc9e9..bf652bbbd 100644 --- a/js/FeedzyLoop/controls.js +++ b/js/FeedzyLoop/controls.js @@ -1,7 +1,7 @@ /** * WordPress dependencies. */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { BlockControls, InspectorControls } from '@wordpress/block-editor'; @@ -13,15 +13,27 @@ import { SelectControl, ToolbarButton, ToolbarGroup, + TextControl, + BaseControl, } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { Fragment } from '@wordpress/element'; /** * Internal dependencies. */ import ConditionsControl from '../Conditions/ConditionsControl'; -import PatternSelector from './components/PatternSelector'; +import FallbackImageLoader from './components/FallbackImageLoader.jsx'; + +// Make this available to all components in this module +function decodeHtmlEntities(str) { + if (typeof str !== 'string') { + return str; + } + const textarea = document.createElement('textarea'); + textarea.innerHTML = str; + return textarea.value; +} const Controls = ({ attributes, @@ -32,15 +44,11 @@ const Controls = ({ onChangeQuery, setIsEditing, setIsPreviewing, + variations, + setVariations, }) => { - const [isPatternModalOpen, setIsPatternModalOpen] = useState(false); - return ( <> - {isPatternModalOpen && ( - - )} - - - setIsPatternModalOpen(true)}> - {__('Replace', 'feedzy-rss-feeds')} - - - setIsPreviewing(!isPreviewing)} @@ -67,180 +69,331 @@ const Controls = ({ - - {!isEditing && ( - - - - )} + +
    + {!isEditing && ( + + )} +
    +
    - +
    + {!isEditing && ( + + )} +
    +
    + + ); +}; + +function CustomInspectorControls({ + attributes, + isEditing, + setIsEditing, + onChangeLayout, + onChangeQuery, + setAttributes, + variations, + setVariations, +}) { + return ( + + + - - onChangeLayout({ type: 'columnCount', value }) - } - min={1} - max={5} - /> +
    + {variations?.map((variation) => ( + + ))} +
    +
    - - onChangeQuery({ type: 'max', value }) - } - min={1} - max={20} - /> + + onChangeLayout({ type: 'columnCount', value }) + } + min={1} + max={5} + /> - - {__('Feedzy Loop Documentation', 'feedzy-rss-feeds')} - - - - onChangeQuery({ type: 'sort', value }) - } - /> + onChangeQuery({ type: 'max', value })} + min={1} + max={20} + /> - - onChangeQuery({ type: 'refresh', value }) - } - /> -
    + + {__('Feedzy Loop Documentation', 'feedzy-rss-feeds')} + + + {!isEditing && ( Pro - ), - ]} initialOpen={false} - key="filters" - className="feedzy-item-filter" + title={__('Feed Source', 'feedzy-rss-feeds')} + key="source" > - {!window.feedzyData.isPro && ( -
    - {__( - 'Unlock this feature and more advanced options with', + + + )} + + + - {__('Feedzy Pro', 'feedzy-rss-feeds')} - -
    + ), + value: 'auto', + }, + { + label: __( + 'Yes (with a fallback image)', + 'feedzy-rss-feeds' + ), + value: 'yes', + }, + { + label: __('No', 'feedzy-rss-feeds'), + value: 'no', + }, + ]} + onChange={(value) => setAttributes({ thumb: value })} + className="feedzy-thumb" + /> + + {attributes?.thumb !== 'no' && ( + + {attributes?.thumb !== 'auto' && ( + + setAttributes({ + fallbackImage: imageData, + }) + } + onRemoveImage={() => + setAttributes({ + fallbackImage: undefined, + }) + } + label={__( + 'Fallback image if no image is found.', + 'feedzy-rss-feeds' + )} + /> + )} + + )} +
    + + + {!window.feedzyData?.isPro && ( +
    + {__( + 'Unlock more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + + { + setAttributes({ conditions }); + }} + /> +
    + + Pro + ), + ]} + initialOpen={false} + className={ + window.feedzyData?.isPro + ? 'feedzy-pro-options' + : 'feedzy-pro-options fz-locked' + } + > + {!window.feedzyData?.isPro && ( +
    + {__( + 'Unlock this feature and more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + { + window.tiTrk + ?.with?.('feedzy') + ?.add?.({ feature: 'block-referral-url' }); + setAttributes({ referral_url: value }); + }} + /> +
    +
    + ); +} - { - setAttributes({ conditions }); - }} - /> - - - +function CustomAdvancedControls({ attributes, onChangeQuery }) { + return ( + + onChangeQuery({ type: 'sort', value })} + /> + + onChangeQuery({ type: 'refresh', value })} + /> + ); -}; +} export default Controls; diff --git a/js/FeedzyLoop/edit.js b/js/FeedzyLoop/edit.js index 3a74d77e5..32545790b 100644 --- a/js/FeedzyLoop/edit.js +++ b/js/FeedzyLoop/edit.js @@ -2,6 +2,9 @@ /** * WordPress dependencies. */ + +import { __, sprintf } from '@wordpress/i18n'; + import { store as blocksStore, createBlocksFromInnerBlocksTemplate, @@ -17,12 +20,13 @@ import { import { Placeholder as BlockEditorPlaceholder, + Notice, Spinner, } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; -import { useState } from '@wordpress/element'; +import { Fragment, useEffect, useState } from '@wordpress/element'; import ServerSideRender from '@wordpress/server-side-render'; @@ -45,7 +49,8 @@ const Edit = ({ attributes, setAttributes, clientId }) => { const blockProps = useBlockProps(); const [isEditing, setIsEditing] = useState(!attributes?.feed?.source); - const [isPreviewing, setIsPreviewing] = useState(false); + const [isPreviewing, setIsPreviewing] = useState(true); + const [showPreviewNotice, setShowPreviewNotice] = useState(false); const { clearSelectedBlock, replaceInnerBlocks } = useDispatch(blockEditorStore); @@ -109,48 +114,101 @@ const Edit = ({ attributes, setAttributes, clientId }) => { }); }; - if (isEditing) { - return ( -
    - -
    + useEffect(() => { + const isPreviewNoticeHidden = localStorage.getItem( + 'feedzy-hide-preview-notice' ); - } + if (!isPreviewNoticeHidden) { + setShowPreviewNotice(true); + } + }, []); - if ((!isSelected || isPreviewing) && innerBlocksContent) { - return ( - <> - + const setVariations = (nextVariation = defaultVariation) => { + if (nextVariation) { + setAttributes({ + layout: { + name: nextVariation.name, + }, + ...nextVariation.attributes, + }); + + replaceInnerBlocks( + clientId, + createBlocksFromInnerBlocksTemplate(nextVariation.innerBlocks), + true + ); + clearSelectedBlock(); + } + }; -
    - + ); + } else if ((!isSelected || isPreviewing) && innerBlocksContent) { + blockContent = ( + + {showPreviewNotice && ( + { + setShowPreviewNotice(false); + localStorage.setItem( + 'feedzy-hide-preview-notice', + 'true' + ); }} - LoadingResponsePlaceholder={LoadingResponsePlaceholder} - /> -
    - + > +

    + + {__( + "You're in Preview Mode – This shows how your feed will look to visitors.", + 'feedzy-rss-feeds' + )} + +

    +

    + {sprintf( + // translators: %1$s is button label "Hide Preview". + __( + 'To customize each element (title, meta, description) and adjust layouts, spacing, colors, and typography, click "%1$s" in the toolbar above to enter the advanced editor.', + 'feedzy-rss-feeds' + ), + __('Hide Preview', 'feedzy-rss-feeds') + )} +

    + + )} + + ); + } else if (!hasInnerBlocks && !isEditing) { + blockContent = ( + + ); + } else { + blockContent = ; } return ( - <> + { onChangeQuery={onChangeQuery} setIsEditing={setIsEditing} setIsPreviewing={setIsPreviewing} + variations={variations} + setVariations={setVariations} /> - -
    - {hasInnerBlocks ? ( - - ) : ( - { - if (nextVariation) { - setAttributes(nextVariation.attributes); - replaceInnerBlocks( - clientId, - createBlocksFromInnerBlocksTemplate( - nextVariation.innerBlocks - ), - true - ); - clearSelectedBlock(); - } - }} - /> - )} -
    - +
    {blockContent}
    +
    ); }; diff --git a/js/FeedzyLoop/editor.scss b/js/FeedzyLoop/editor.scss index c37cd05e9..abb059aa5 100644 --- a/js/FeedzyLoop/editor.scss +++ b/js/FeedzyLoop/editor.scss @@ -8,15 +8,16 @@ width: 100%; } } + + // Note: Hide generated invalid Core Image blocks + .wp-block-image:has( :is( img:not([src]), img[src=""] ) ) { + display: none; + } } .fz-condition-control { padding: 0; - &.is-upsell { - opacity: 0.6; - } - .components-button { width: 100%; margin: 0; @@ -25,6 +26,10 @@ } } +.is-upsell { + opacity: 0.6; +} + .fz-panel-tab { z-index: 999999; margin: 24px 0; @@ -229,3 +234,21 @@ input[type="text"].fz-input-field:disabled { outline: 1px solid #0000001a; } } + +.fz-block-variation { + .block-editor-block-variation-picker__variations { + flex-wrap: nowrap; + } + .block-editor-block-variation-picker__variations>li { + width: 35%; + } +} + +.fz-block-variation-picker { + display: flex; + justify-content: space-between; + + &> .components-button svg { + max-width: 70px; + } +} \ No newline at end of file diff --git a/js/FeedzyLoop/placeholder.js b/js/FeedzyLoop/placeholder.js index d36561f4f..9e0160cc3 100644 --- a/js/FeedzyLoop/placeholder.js +++ b/js/FeedzyLoop/placeholder.js @@ -3,16 +3,15 @@ * WordPress dependencies. */ import { __ } from '@wordpress/i18n'; - +import { useState } from '@wordpress/element'; import { BaseControl, Button, Placeholder, Spinner, + Notice, } from '@wordpress/components'; - import { useSelect } from '@wordpress/data'; - import { store as coreStore } from '@wordpress/core-data'; /** @@ -21,6 +20,9 @@ import { store as coreStore } from '@wordpress/core-data'; import FeedControl from './components/FeedControl'; const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => { + const [isValidating, setIsValidating] = useState(false); + const [validationResults, setValidationResults] = useState([]); + const { categories, isLoading } = useSelect((select) => { const { getEntityRecords, isResolving } = select(coreStore); @@ -33,20 +35,132 @@ const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => { }; }, []); + const handleLoadFeed = async () => { + if (!attributes?.feed?.source) { + return; + } + + setIsValidating(true); + setValidationResults([]); + + const isCategory = categories.some( + (cat) => cat.id === attributes.feed.source + ); + + if (isCategory && 'group' === attributes.feed.type) { + onSaveFeed(); + setIsValidating(false); + return; + } + + try { + const formData = new FormData(); + formData.append('action', 'feedzy_validate_feed'); + formData.append('feed_url', attributes.feed.source); + formData.append('nonce', window.feedzyData?.nonce); + + const response = await fetch(window.feedzyData?.url, { + method: 'POST', + body: formData, + }); + + const data = await response.json(); + + if (data.success && data.data?.results) { + const results = data.data.results; + setValidationResults(results); + + const hasErrors = results.some( + (result) => result.status === 'error' + ); + + if (!hasErrors) { + onSaveFeed(); + } + } else if (!data.success) { + setValidationResults([ + { + status: 'error', + message: + data.data?.message || + __('Validation failed', 'feedzy-rss-feeds'), + }, + ]); + } + } catch (error) { + setValidationResults([ + { + status: 'error', + message: __( + 'Failed to validate feed. Please check your connection and try again.', + 'feedzy-rss-feeds' + ), + }, + ]); + } finally { + setIsValidating(false); + } + }; + + const handleFeedChange = (value) => { + setAttributes({ feed: value }); + }; + + const renderValidationResults = () => { + if (!validationResults || validationResults.length === 0) { + return null; + } + + return ( +
    + {validationResults.map((result, index) => ( + + {result.url && ( + <> + {result.url} +
    + + )} + {result.message} +
    + ))} +
    + ); + }; + return ( - {isLoading && ( + {(isLoading || isValidating) && (
    -

    {__('Fetching…', 'feedzy-rss-feeds')}

    +

    + {isValidating + ? __( + 'Validating and fetching feed…', + 'feedzy-rss-feeds' + ) + : __('Loading…', 'feedzy-rss-feeds')} +

    )} - {!isLoading && ( + {!isLoading && !isValidating && ( <> { value: category.id, })), ]} - onChange={(value) => setAttributes({ feed: value })} + onChange={handleFeedChange} /> + {renderValidationResults()} +

    {__( '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', @@ -79,24 +195,9 @@ const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => {

    - - -
    )} diff --git a/js/FeedzyLoop/style.scss b/js/FeedzyLoop/style.scss index fdba7389d..788215921 100644 --- a/js/FeedzyLoop/style.scss +++ b/js/FeedzyLoop/style.scss @@ -23,4 +23,9 @@ .wp-block-image.is-style-rounded img { border-radius: 9999px; } + + // Note: Hide generated invalid Core Image blocks + .wp-block-image:has( :is( img:not([src]), img[src=""] ) ) { + display: none; + } } diff --git a/js/categories.js b/js/categories.js index 6c54f7d55..5ada6de05 100644 --- a/js/categories.js +++ b/js/categories.js @@ -4,6 +4,7 @@ $(document).ready(function(){ init(); + validateFeeds(); }); function init(){ @@ -33,4 +34,34 @@ }); } + // validate feeds group. + function validateFeeds() { + $('#feedzy_category_feeds textarea[name="feedzy_category_feed"]').on('input', function() { + $('.validate-feeds').attr('disabled', 1 > $(this).val().length ); + }); + + $('#feedzy_category_feeds .button.validate-feeds').on('click', function(e) { + e.preventDefault(); + const button = $(this); + button.attr('disabled', true); + $('#feedzy_category_feeds .spinner').addClass('is-active'); + $.ajax({ + url: ajaxurl, + method: 'POST', + data: { + action: 'feedzy_categories', + _action: 'validate_feeds_group', + security: feedzy.ajax.security, + feeds: $('textarea[name="feedzy_category_feed"]').val() + }, + success: function(data) { + if (data.success) { + $('textarea[name="feedzy_category_feed"]').val(data.data.valid.join(', ')); + $('#feedzy_category_feeds .spinner').removeClass('is-active'); + } + } + }) + }); + } + })(jQuery, feedzy); diff --git a/js/feedzy-license.js b/js/feedzy-license.js new file mode 100644 index 000000000..6f740ca59 --- /dev/null +++ b/js/feedzy-license.js @@ -0,0 +1,72 @@ +(function ($) { + /** + * Toggles check button state based on license key input and syncs value across inputs + * @param {window.jQuery} $input - License key input element + */ + const handleLicenseKeyInput = ($input) => { + const licenseKey = $input.val(); + const $checkButton = $('#check_ti_license'); + + if (licenseKey !== '') { + $checkButton.removeAttr('disabled'); + } else { + $checkButton.attr('disabled', true); + } + + $('.fz-license-section input[name="license_key"]').val(licenseKey); + }; + + /** + * Handles license validation response - shows errors or reloads page on success + * @param {Object} response - API response with success flag and message + * @param {window.jQuery} $button - Check license button element + */ + const handleLicenseResponse = (response, $button) => { + if (!response.success) { + const $errorMessage = $( + '

    ' + response.message + '

    ' + ); + $errorMessage.insertAfter( + $('.fz-license-section').find('.help-text') + ); + $button.removeAttr('disabled').removeClass('fz-checking'); + return; + } + window.location.reload(); + }; + + /** + * Validates license via AJAX, disables button and clears previous errors + * @param {Event} e - Click event from check license button + */ + const checkLicense = (e) => { + e.preventDefault(); + const $button = $(e.currentTarget); + + $button.attr('disabled', true).addClass('fz-checking'); + $button + .parents('.fz-license-section') + .find('.feedzy-api-error') + .remove(); + + const licenseData = $button + .parent('.fz-input-group-btn') + .find('input') + .serialize(); + + $.post( + window.ajaxurl, + licenseData, + (response) => handleLicenseResponse(response, $button), + 'json' + ); + }; + + $(document).ready(() => { + $('.fz-license-section #license_key').on('input', function () { + handleLicenseKeyInput($(this)); + }); + + $('.fz-license-section #check_ti_license').on('click', checkLicense); + }); +})(window.jQuery); diff --git a/js/feedzy-setting.js b/js/feedzy-setting.js index 375c581c0..1a0a20958 100644 --- a/js/feedzy-setting.js +++ b/js/feedzy-setting.js @@ -98,50 +98,6 @@ jQuery(function ($) { } }); - - // License key. - jQuery('.fz-license-section #license_key').on('input', function () { - const licenseKey = jQuery(this).val(); - if (licenseKey !== '') { - jQuery('#check_ti_license').removeAttr('disabled'); - } else { - jQuery('#check_ti_license').attr('disabled', true); - } - jQuery('.fz-license-section input[name="license_key"]').val(licenseKey); - }); - - jQuery('.fz-license-section #check_ti_license').on('click', function (e) { - e.preventDefault(); - const _this = jQuery(this); - _this.attr('disabled', true).addClass('fz-checking'); - - _this.parents('.fz-license-section').find('.feedzy-api-error').remove(); - - const LicenseData = _this - .parent('.fz-input-group-btn') - .find('input') - .serialize(); - - jQuery.post( - ajaxurl, - LicenseData, - function (response) { - if (!response.success) { - jQuery( - '

    ' + - response.message + - '

    ' - ).insertAfter( - jQuery('.fz-license-section').find('.help-text') - ); - _this.removeAttr('disabled').removeClass('fz-checking'); - } else { - window.location.reload(); - } - }, - 'json' - ); - }); snackbarNotice(); const initializeAutoCatActions = () => { @@ -214,4 +170,187 @@ jQuery(function ($) { }; initializeAutoCatActions(); + + // Disable the Add Schedule button until all fields are filled. + const validateScheduleForm = () => { + const button = $('#fz-add-schedule'); + + if (!button.length) { + return; + } + + const interval = $('#fz-schedule-interval').val().trim(); + const display = $('#fz-schedule-display').val().trim(); + const name = $('#fz-schedule-name').val().trim(); + + const isValid = interval && display && name; + button.prop('disabled', !isValid); + button.toggleClass('disabled', !isValid); + }; + + // Initial validation check. + validateScheduleForm(); + + // Add event listeners to schedule form inputs. + $('#fz-schedule-interval, #fz-schedule-display, #fz-schedule-name').on( + 'input keyup', + validateScheduleForm + ); + + $('#feedzy-delete-log-file').on('click', function (e) { + e.preventDefault(); + const _this = $(this); + const originalText = _this.html(); + _this.attr('disabled', true).addClass('fz-checking'); + + const deleteUrl = new URL(`${window.wpApiSettings.root}feedzy/v1/logs`); + deleteUrl.searchParams.append('_wpnonce', window.wpApiSettings.nonce); + + fetch(deleteUrl, { + method: 'DELETE', + }) + .then((response) => response.json()) + .then((response) => { + if (!response.success) { + _this.html( + '' + ); + setTimeout(function () { + _this.html(originalText); + _this.removeAttr('disabled').removeClass('fz-checking'); + }, 3000); + } else { + window.location.reload(); + } + }) + .catch((error) => { + _this.html(''); + setTimeout(function () { + _this.html(originalText); + _this.removeAttr('disabled').removeClass('fz-checking'); + }, 3000); + }); + }); + + $('#fz-add-schedule').on('click', function (e) { + e.preventDefault(); + + const formElem = document.querySelector('form:has(.fz-form-wrap)'); + if (formElem && formElem.checkValidity() === false) { + formElem.reportValidity(); + return; + } + + const interval = $('#fz-schedule-interval').val(); + const display = $('#fz-schedule-display').val(); + const name = $('#fz-schedule-name').val(); + + if (!interval || !display || !name) { + return; + } + + $('.fz-schedules-table').show(); + const scheduleTable = $('.fz-schedules-table tbody'); + + const newRow = $('').attr('data-schedule', name); + + const nameCell = $('') + .addClass('fz-schedule-attributes') + .append($('').text(name)); + + const intervalCell = $('') + .addClass('fz-schedule-attributes') + .text(interval); + const displayCell = $('') + .addClass('fz-schedule-attributes') + .text(display); + + const deleteButton = $('