diff --git a/assets/admin/css/admin.css b/assets/admin/css/admin.css index 6b89743d..d3244fe0 100644 --- a/assets/admin/css/admin.css +++ b/assets/admin/css/admin.css @@ -229,8 +229,6 @@ textarea#episode_embed_code { .ssp-settings table input.regular-text { max-width: 90%; background: #F1F5F9; } - .ssp-settings .validate-api-credentials-message { - margin-left: 30px; } .ssp-settings .loader { position: relative; } .ssp-settings .loader:after { @@ -253,6 +251,32 @@ textarea#episode_embed_code { .ssp-settings .ssp-sync-msg.success .sync-overview { color: #4caf50; } +.ssp-main-settings .error { + color: #DF4E4F; } + +.ssp-main-settings .hidden { + display: none; } + +.ssp-main-settings .disconnect-castos { + margin-right: 8px; + vertical-align: middle; } + +.ssp-main-settings.tab-castos-hosting .loader { + position: relative; } + .ssp-main-settings.tab-castos-hosting .loader:after { + content: ''; + display: block; + background: url(../img/loader2.svg) no-repeat; + position: absolute; + right: -50px; + top: -9px; + background-size: contain; + width: 44px; + height: 44px; } + +.connect-castos-message { + margin-left: 20px; } + .ssp-sync-podcast { display: flex; justify-content: space-between; @@ -477,6 +501,8 @@ textarea#episode_embed_code { right: 350px; } .ssp-onboarding-step-4 .ssp-onboarding__steps::after { right: 180px; } + .ssp-onboarding-step-4 .ssp-onboarding__settings-body { + padding: 24px 24px 36px; } .ssp-onboarding-step-4__info { display: flex; flex-direction: column; @@ -669,7 +695,7 @@ textarea#episode_embed_code { z-index: 0; visibility: hidden; transition: .6s; - margin-top: -444px; } + margin-top: -345px; } .ssp-onboarding-step-4__form--opened { visibility: visible; margin-top: 0; } @@ -759,6 +785,12 @@ textarea#episode_embed_code { text-align: right; margin-top: 25px; position: relative; } + .ssp-onboarding__submit span.connect-castos-message { + position: absolute; + left: 0; + bottom: -24px; + margin-left: 0; + display: block; } .ssp-onboarding__submit button[type=submit], .ssp-onboarding__submit .button { position: relative; height: 50px; @@ -804,39 +836,56 @@ textarea#episode_embed_code { background: #516178; } .ssp-onboarding__submit .button span { line-height: 50px; } - .ssp-onboarding__submit .validate-token { + .ssp-onboarding__submit .castos-connect { float: left; position: relative; padding: 0 20px 0 45px; } - .ssp-onboarding__submit .validate-token:after { + .ssp-onboarding__submit .castos-connect:after { content: ''; display: block; position: absolute; - background: url(../img/validate.svg) no-repeat; - background-size: cover; - left: 19px; - top: 17px; - width: 14px; - height: 18px; - filter: none; } - .ssp-onboarding__submit .validate-token.validating { - background: #374151; } - .ssp-onboarding__submit .validate-token.validating:hover, .ssp-onboarding__submit .validate-token.validating:active, .ssp-onboarding__submit .validate-token.validating:focus { - background: #374151; } - .ssp-onboarding__submit .validate-token.validating:after { + background: url(../img/connect.svg) no-repeat; + left: 13px; + top: 13px; + width: 25px; + height: 23px; + filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(198deg) brightness(101%) contrast(102%); + background-size: contain; } + .ssp-onboarding__submit .castos-connect:disabled { + background: #9CA3AF !important; + color: #D1D5DB !important; } + .ssp-onboarding__submit .castos-connect:disabled:after { + filter: invert(94%) sepia(8%) saturate(150%) hue-rotate(177deg) brightness(90%) contrast(93%); + transition: none; } + .ssp-onboarding__submit .castos-connect.connecting { + background: #DE7373 !important; + color: #FFFFFF !important; } + .ssp-onboarding__submit .castos-connect.connecting:hover, .ssp-onboarding__submit .castos-connect.connecting:active, .ssp-onboarding__submit .castos-connect.connecting:focus, .ssp-onboarding__submit .castos-connect.connecting:disabled { + background: #DE7373; } + .ssp-onboarding__submit .castos-connect.connecting:after { width: 17px; height: 18px; - background-image: url(../img/validating.svg); } - .ssp-onboarding__submit .validate-token.valid { - background: #10B981; - padding: 0 16px 0 45px; } - .ssp-onboarding__submit .validate-token.valid:hover, .ssp-onboarding__submit .validate-token.valid:active, .ssp-onboarding__submit .validate-token.valid:focus { + background-image: url(../img/connecting.svg); + animation: rotation 2s infinite linear; + filter: none; + transition: none; + top: 15px; + left: 15px; } + .ssp-onboarding__submit .castos-connect.connected { + background: #10B981 !important; + color: #FFFFFF !important; + padding: 0 16px 0 45px; + opacity: .7; } + .ssp-onboarding__submit .castos-connect.connected:hover, .ssp-onboarding__submit .castos-connect.connected:active, .ssp-onboarding__submit .castos-connect.connected:focus { background: #10B981; } - .ssp-onboarding__submit .validate-token.valid:after { - width: 16px; - height: 12px; - top: 19px; - background-image: url(../img/checkbox.svg); } + .ssp-onboarding__submit .castos-connect.connected:after { + background-image: url(../img/connect.svg); + left: 13px; + top: 13px; + width: 25px; + height: 23px; + filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(198deg) brightness(101%) contrast(102%); + background-size: contain; } .ssp-onboarding__image-info { position: absolute; display: flex; @@ -874,10 +923,6 @@ textarea#episode_embed_code { transform: rotate(-45deg); } .ssp-onboarding__delete-image:hover:before, .ssp-onboarding__delete-image:hover:after { background: #fff; } - .ssp-onboarding .validate-api-credentials-message { - position: absolute; - left: 0; - bottom: -18px; } .ssp-onboarding__links { display: flex; text-align: left; } diff --git a/assets/admin/img/connect.svg b/assets/admin/img/connect.svg new file mode 100644 index 00000000..463acfb4 --- /dev/null +++ b/assets/admin/img/connect.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/admin/img/connecting.svg b/assets/admin/img/connecting.svg new file mode 100644 index 00000000..23295e5b --- /dev/null +++ b/assets/admin/img/connecting.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/admin/img/loader2.svg b/assets/admin/img/loader2.svg new file mode 100644 index 00000000..25f1ca19 --- /dev/null +++ b/assets/admin/img/loader2.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/admin/js/onboarding.js b/assets/admin/js/onboarding.js index 9c46ad01..d4866290 100644 --- a/assets/admin/js/onboarding.js +++ b/assets/admin/js/onboarding.js @@ -5,7 +5,7 @@ jQuery(document).ready(function($) { $imgName = $imgInfo.find('.js-onboarding-img-name'), $fields = $('.js-onboarding-field'), $btn = $('.js-onboarding-btn'), - $validateTokenBtn = $('.js-onboarding-validate-token'), + $connectCastosBtn = $('.js-onboarding-castos-connect'), $hostingStep2 = $('.js-hosting-form'), $accordion = $('.js-accordion'), $dragable = $('.js-onboarding-dragable'), @@ -54,29 +54,33 @@ jQuery(document).ready(function($) { }); }, initTokenValidation = function(){ - $validateTokenBtn.on('validated', function () { - //don't use $btn since it has custom validation - var $form = $validateTokenBtn.closest('form'), - $nextButton = $form.find('button[type=submit]'); - $(this).removeClass('validating'); - if ($validateTokenBtn.hasClass('valid')) { + $connectCastosBtn.on('connected', function (e, response) { + var $form = $connectCastosBtn.closest('form'), + $nextButton = $form.find('button[type=submit]'), + $me = $(this), + $msg = $form.find('.connect-castos-message'), + $field = $('.js-onboarding-castos-connect-field'); + $me.removeClass('connecting'); + $msg.show(); + + if ("success" === response.status) { + $me.html($me.data('connected-txt')); + $field.attr('disabled', 'disabled'); $nextButton.removeAttr('disabled'); - $form.find('.validate-api-credentials-message').html(''); - $(this).html($(this).data('valid-txt')); } else { - $(this).html($(this).data('initial-txt')); + $me.html($me.data('initial-txt')); $nextButton.attr('disabled', 'disabled'); } }); - $validateTokenBtn.on('click', function(){ - $(this).addClass('validating').html($(this).data('validating-txt')); + $connectCastosBtn.on('connecting', function(){ + $(this).addClass('connecting').html($(this).data('connecting-txt')); }); - $('.js-onboarding-validate-token-field').on('change paste keyup', function(){ - var $nextButton = $validateTokenBtn.closest('form').find('button[type=submit]'); - $validateTokenBtn.html($validateTokenBtn.data('initial-txt')); - $validateTokenBtn.removeClass('valid'); + $('.js-onboarding-castos-connect-field').on('change paste keyup', function(){ + var $nextButton = $connectCastosBtn.closest('form').find('button[type=submit]'); + $connectCastosBtn.html($connectCastosBtn.data('initial-txt')); + $connectCastosBtn.removeClass('valid'); $nextButton.attr('disabled', 'disabled'); }); }, diff --git a/assets/admin/scss/_admin.scss b/assets/admin/scss/_admin.scss index 3c41d689..e85ff61b 100644 --- a/assets/admin/scss/_admin.scss +++ b/assets/admin/scss/_admin.scss @@ -271,6 +271,12 @@ textarea#episode_embed_code { } } +#podmotor_account_api_token { + width: 100%; + min-width: 100%; + display: block; + margin-bottom: 8px; +} .ssp-settings-integrations { table label { @@ -305,10 +311,6 @@ textarea#episode_embed_code { } } - .validate-api-credentials-message { - margin-left: 30px; - } - .loader { position: relative; &:after { @@ -341,6 +343,42 @@ textarea#episode_embed_code { } } +.ssp-main-settings { + .error { + color: $clr_red_400; + } + + .hidden { + display: none; + } + + .disconnect-castos { + margin-right: 8px; + vertical-align: middle; + } + + &.tab-castos-hosting { + .loader { + position: relative; + &:after { + content: ''; + display: block; + background: url(../img/loader2.svg) no-repeat; + position: absolute; + right: -50px; + top: -9px; + background-size: contain; + width: 44px; + height: 44px; + } + } + } +} + +.connect-castos-message { + margin-left: 20px; +} + .ssp-sync-podcast { display: flex; justify-content: space-between; diff --git a/assets/admin/scss/_onboarding.scss b/assets/admin/scss/_onboarding.scss index 9f28a3c8..559ff30a 100644 --- a/assets/admin/scss/_onboarding.scss +++ b/assets/admin/scss/_onboarding.scss @@ -156,7 +156,7 @@ &-step-2, &-step-3, &-step-4, &-step-5 { .ssp-onboarding__steps::after { - background: #DF4E4F; + background: $clr_red_400; width: initial; } } @@ -191,6 +191,10 @@ right: 180px; } + .ssp-onboarding__settings-body { + padding: 24px 24px 36px; + } + &__info { display: flex; flex-direction: column; @@ -317,7 +321,7 @@ li { &:hover { svg path, svg rect { - fill: #DF4E4F; + fill: $clr_red_400; } } } @@ -452,7 +456,7 @@ z-index: 0; visibility: hidden; transition: .6s; - margin-top: -444px; + margin-top: -345px; &--opened { visibility: visible; @@ -585,13 +589,21 @@ margin-top: 25px; position: relative; + span.connect-castos-message { + position: absolute; + left: 0; + bottom: -24px; + margin-left: 0; + display: block; + } + button[type=submit], .button { position: relative; height: 50px; border-radius: 6px; border: 0; color: #FFFFFF; - background: #DF4E4F; + background: $clr_red_400; font-size: 18px; padding: 0 55px 0 33px; transition: .3s; @@ -650,7 +662,7 @@ } } - .validate-token { + .castos-connect { float: left; position: relative; padding: 0 20px 0 45px; @@ -659,42 +671,62 @@ content: ''; display: block; position: absolute; - background: url(../img/validate.svg) no-repeat; - background-size: cover; - left: 19px; - top: 17px; - width: 14px; - height: 18px; - filter: none; + background: url(../img/connect.svg) no-repeat; + left: 13px; + top: 13px; + width: 25px; + height: 23px; + filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(198deg) brightness(101%) contrast(102%); + background-size: contain; } - &.validating { - background: #374151; + &:disabled { + background: #9CA3AF !important; + color: #D1D5DB !important; + &:after { + //filter: invert(92%) sepia(17%) saturate(74%) hue-rotate(176deg) brightness(92%) contrast(88%); + filter: invert(94%) sepia(8%) saturate(150%) hue-rotate(177deg) brightness(90%) contrast(93%); + transition: none; + } + } - &:hover, &:active, &:focus { - background: #374151; + &.connecting { + background: #DE7373 !important; + color: #FFFFFF !important; + + &:hover, &:active, &:focus, &:disabled { + background: #DE7373; } &:after { width: 17px; height: 18px; - background-image: url(../img/validating.svg); + background-image: url(../img/connecting.svg); + animation: rotation 2s infinite linear; + filter: none; + transition: none; + top: 15px; + left: 15px; } } - &.valid { - background: #10B981; + &.connected { + background: #10B981 !important; + color: $clr_white !important; padding: 0 16px 0 45px; - &:hover, &:active, &:focus { background: #10B981; } + opacity: .7; &:after { - width: 16px; - height: 12px; - top: 19px; - background-image: url(../img/checkbox.svg); + background-image: url(../img/connect.svg); + left: 13px; + top: 13px; + width: 25px; + height: 23px; + filter: invert(100%) sepia(100%) saturate(0%) hue-rotate(198deg) brightness(101%) contrast(102%); + background-size: contain; } } } @@ -732,7 +764,7 @@ display: block; height: 1.5px; width: 14px; - background: #DF4E4F; + background: $clr_red_400; position: absolute; left: 7px; top: 46%; @@ -752,12 +784,6 @@ } } - .validate-api-credentials-message { - position: absolute; - left: 0; - bottom: -18px; - } - &__links { display: flex; text-align: left; @@ -822,7 +848,7 @@ width: 30px; height: 30px; background: #FFF; - color: #DF4E4F; + color: $clr_red_400; font-size: 20px; font-weight: 600; border-radius: 50%; diff --git a/assets/css/settings.css b/assets/css/settings.css index 95cb20c5..de8323bb 100644 --- a/assets/css/settings.css +++ b/assets/css/settings.css @@ -18,7 +18,7 @@ height: auto; } -#ssp-main-settings { +#ssp-main-settings.castos-disconnected { width: 75%; float: left; } @@ -36,11 +36,6 @@ overflow: hidden; } -#ssp-sidebar .sidebar-content.castos-connected { - border: none; - background: none; -} - #ssp-sidebar .sidebar-content h3 { color: #6c25d0; margin: -20px -20px 0 -20px; diff --git a/assets/js/settings.js b/assets/js/settings.js index d6bad924..8dffb72b 100644 --- a/assets/js/settings.js +++ b/assets/js/settings.js @@ -5,11 +5,8 @@ */ jQuery(document).ready(function($) { - - var $podmotorAccountEmail = $("#podmotor_account_email"), - $podmotorAccountAPIToken = $("#podmotor_account_api_token"), - $parentCategories = $('.js-parent-category'), - $validateBtn = $("#validate_api_credentials"); + var $podmotorAccountAPIToken = $("#podmotor_account_api_token"), + $parentCategories = $('.js-parent-category'); const { __ } = wp.i18n; @@ -35,103 +32,106 @@ jQuery(document).ready(function($) { } function initCastosAPICredentials() { - var disableSubmitButton = function () { - /** - * If either API field is empty, disable the submit button - */ - if ($podmotorAccountEmail.val() === '' || $podmotorAccountAPIToken.val() === '') { - $("#ssp-settings-submit").prop("disabled", "disabled"); - } - - /** - * If the user changes the email, disable the submit button - */ - $podmotorAccountEmail.on("change paste keydown keyup", function () { - $("#ssp-settings-submit").prop("disabled", "disabled"); - }); - - /** - * If the user changes the account api key, disable the submit button - */ - $podmotorAccountAPIToken.on("change paste keydown keyup", function () { - $("#ssp-settings-submit").prop("disabled", "disabled"); - }); - }, - /** - * Validate the api credentials - */ - validateAPICredentials = function () { - $validateBtn.on("click", function () { - - var podmotor_account_email = $("#podmotor_account_email").val(), - podmotor_account_api_token = $("#podmotor_account_api_token").val(), - nonce = $("#podcast_settings_tab_nonce").val(), - $msg = $('.validate-api-credentials-message'); - - if (!$msg.length) { - $msg = $(''); - $validateBtn.parent().append($msg); - } - - $msg.html("Validating API credentials..."); - - $validateBtn.addClass('loader'); - - $.ajax({ - method: "GET", - url: ajaxurl, - data: { - action: "validate_castos_credentials", - api_token: podmotor_account_api_token, - email: podmotor_account_email, - nonce: nonce - } - }) - .done(function (response) { - $validateBtn.removeClass('loader'); - if (response.status === 'success') { - $(".validate-api-credentials-message").html("Credentials Valid. Please click 'Save Settings' to save Credentials."); - $("#ssp-settings-submit").prop("disabled", ""); - $validateBtn.val('Valid Credentials'); - $validateBtn.addClass('valid'); - } else { - $validateBtn.addClass('invalid'); - $(".validate-api-credentials-message").html(response.message); - } - $validateBtn.trigger('validated'); - }); - }); - }, - /** - * Disconnect Castos checkbox on change, renders a confirmation message to the user. - */ - disconnectCastos = function () { - $('#podmotor_disconnect').on('change', function (event) { - var $checkbox = $(this); - - // if the change is to uncheck the checkbox - if (!$checkbox.is(':checked')) { - return; - } - - var $message = 'If you disconnect from Castos hosting you will no longer be able to upload media files to the Castos hosting platform. If you’re no longer a Castos customer your media files may no longer be available to your listeners.'; - var user_input = confirm($message); - if (user_input !== true) { - // Ensures this code runs AFTER the browser handles click however it wants. - setTimeout(function () { - $checkbox.removeAttr('checked'); - }, 0); - event.preventDefault(); - event.stopPropagation(); - } - }); - } - - if ($podmotorAccountEmail.length > 0 && $podmotorAccountAPIToken.length > 0) { - disableSubmitButton(); - validateAPICredentials(); - disconnectCastos(); + var $connectBtn = $(".castos-connect"), + disableConnectButton = function(){ + $connectBtn.prop("disabled", "disabled"); + }, + enableConnectButton = function(){ + $connectBtn.prop("disabled", "").removeClass('disabled'); + }, + connectButtonStates = function () { + if ($podmotorAccountAPIToken.length) { + $connectBtn.show(); + } + $podmotorAccountAPIToken.on("focus change paste keydown keyup", function () { + $podmotorAccountAPIToken.val() ? enableConnectButton() : disableConnectButton(); + }); + + $podmotorAccountAPIToken.on("focus", function(){ + $('.connect-castos-message').html('').removeClass('error'); + }); + }, + /** + * Validate the api credentials + */ + initConnect = function () { + $connectBtn.on('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + $connectBtn.prop('disabled', 'disabled'); + $connectBtn.trigger('connecting'); + + var podmotor_account_api_token = $('#podmotor_account_api_token').val(), + nonce = $('#podcast_settings_tab_nonce').val(), + $msg = $('.connect-castos-message'); + + if ($msg.length) { + $msg.html('').removeClass('error'); + } else { + $msg = $(''); + $connectBtn.parent().append($msg); + } + + $connectBtn.addClass('loader'); + + $.ajax({ + method: 'GET', + url: ajaxurl, + data: { + action: 'connect_castos', + api_token: podmotor_account_api_token, + nonce: nonce, + }, + }) + .done(function (response) { + $connectBtn.trigger('connected', response); + if (response.status === 'success') { + $connectBtn.addClass('connected'); + if ( ! $connectBtn.data( 'no-reload' ) ) { + window.location.reload(); + } else { + $msg.html( response.message ); + } + } else { + $connectBtn.removeClass('loader'); + $msg.addClass('error'); + $msg.html(response.message); + } + }) + }) + }, + /** + * Disconnect Castos checkbox on change, renders a confirmation message to the user. + */ + initDisconnect = function () { + var $disconnect = $('#disconnect_castos'); + $disconnect.on('click', function (event) { + var $message = 'If you disconnect from Castos hosting you will no longer be able to upload media files to the Castos hosting platform. If you’re no longer a Castos customer your media files may no longer be available to your listeners.'; + var user_input = confirm($message); + if (user_input === true) { + $disconnect.addClass('loader'); + $disconnect.parent().find('label').remove(); + $.ajax({ + method: 'GET', + url: ajaxurl, + data: { + action: 'disconnect_castos', + nonce: $('#podcast_settings_tab_nonce').val(), + }, + }) + .done(function (response) { + window.location.reload(); + }) + } + }); + } + + if ($podmotorAccountAPIToken.length > 0) { + connectButtonStates(); + initConnect(); } + + initDisconnect(); } function initSubcategoryFiltration(){ diff --git a/php/classes/controllers/class-app-controller.php b/php/classes/controllers/class-app-controller.php index 51816e0f..a7c8ba3f 100644 --- a/php/classes/controllers/class-app-controller.php +++ b/php/classes/controllers/class-app-controller.php @@ -273,11 +273,11 @@ protected function bootstrap() { $this->widgets_controller = new Widgets_Controller( $this->file, $this->version ); - $this->ajax_handler = new Ajax_Handler( $this->castos_handler ); + $this->admin_notices_handler = new Admin_Notifications_Handler(); - $this->podping_handler = new Podping_Handler( $this->logger ); + $this->ajax_handler = new Ajax_Handler( $this->castos_handler, $this->admin_notices_handler ); - $this->admin_notices_handler = new Admin_Notifications_Handler( $this->token ); + $this->podping_handler = new Podping_Handler( $this->logger ); $this->assets_controller = new Assets_Controller(); diff --git a/php/classes/controllers/class-cron-controller.php b/php/classes/controllers/class-cron-controller.php index 61fae3b2..3be3dfd6 100644 --- a/php/classes/controllers/class-cron-controller.php +++ b/php/classes/controllers/class-cron-controller.php @@ -101,7 +101,7 @@ public function upload_scheduled_episodes() { foreach ( $this->episodes_respository->get_scheduled_episodes() as $episode ) { $response = $this->castos_handler->upload_episode_to_castos( $episode ); - if ( 'success' === $response['status'] ) { + if ( $response->success ) { $this->unschedule_episode( $episode->ID ); $this->episodes_respository->update_episode_sync_status( $episode->ID, Sync_Status::SYNC_STATUS_SYNCED ); $this->episodes_respository->delete_episode_sync_error( $episode->ID ); @@ -115,18 +115,17 @@ public function upload_scheduled_episodes() { } } - if ( isset( $response['code'] ) && 404 == $response['code'] ) { + if ( 404 == $response->code ) { $castos_episode_id = get_post_meta( $episode->ID, 'podmotor_episode_id', true ); - // Episode does not exists anymore, remove connection + // File does not exist anymore, remove connection if ( $castos_episode_id ) { delete_post_meta( $episode->ID, 'podmotor_episode_id' ); delete_post_meta( $episode->ID, 'podmotor_file_id' ); } $this->unschedule_episode( $episode->ID ); - - $logger->log( sprintf( 'Cron: could not upload episode %d', $episode->ID ) ); + $logger->log( sprintf( 'Cron: file does not exists on Castos, stop syncing: %d', $episode->ID ) ); } } diff --git a/php/classes/controllers/class-players-controller.php b/php/classes/controllers/class-players-controller.php index cb6f2c8b..6373f98b 100644 --- a/php/classes/controllers/class-players-controller.php +++ b/php/classes/controllers/class-players-controller.php @@ -131,8 +131,11 @@ public function get_ajax_playlist_items() { * * @return string */ - public function render_html_player( $episode_id, $skip_empty_audio = true, $context = 'block' ) { + public function render_html_player( $episode_id, $skip_empty_audio = true, $context = 'block', $args = array() ) { $template_data = $this->episode_repository->get_player_data( $episode_id, null, false ); + if ( isset( $args['className'] ) ) { + $template_data['class'] .= ' ' . $args['className']; + } if ( $skip_empty_audio && empty( $template_data['audio_file'] ) ) { $show_with_warning = is_admin() || @@ -276,6 +279,7 @@ public function render_playlist_player( $episodes, $atts ) { global $wp; $template_data['current_url'] = home_url( $wp->request ); $template_data['player_id'] = $player_id; + $template_data['class'] = $atts['class']; if ( in_array( $atts['style'], array( 'light', 'dark' ) ) ) { $template_data['player_mode'] = $atts['style']; @@ -285,7 +289,7 @@ public function render_playlist_player( $episodes, $atts ) { $template_data['playlist'][] = $this->episode_repository->get_player_data( $episode->ID ); } - return $this->renderer->render_deprecated( $template_data, 'players/castos-player' ); + return $this->renderer->fetch( 'players/castos-player', $template_data ); } /** @@ -317,6 +321,7 @@ public function render_playlist_compact_player( $tracks, $atts, $width, $height $safe_type = esc_attr( $atts['type'] ); $safe_style = esc_attr( $atts['style'] ); + $class = $atts['class']; static $instance = 0; $instance ++; @@ -326,9 +331,9 @@ public function render_playlist_compact_player( $tracks, $atts, $width, $height do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] ); } - return $this->renderer->render_deprecated( - compact('safe_style', 'safe_type', 'data', 'width', 'height'), - 'players/playlist-compact-player' + return $this->renderer->fetch( + 'players/playlist-compact-player', + compact('safe_style', 'safe_type', 'data', 'width', 'height', 'class') ); } diff --git a/php/classes/controllers/class-podcast-post-types-controller.php b/php/classes/controllers/class-podcast-post-types-controller.php index a1507798..f8557084 100644 --- a/php/classes/controllers/class-podcast-post-types-controller.php +++ b/php/classes/controllers/class-podcast-post-types-controller.php @@ -731,15 +731,17 @@ public function sync_episode( $id, $post ) { return; } + $this->episode_repository->delete_episode_sync_error( $post->ID ); + $response = $this->castos_handler->upload_episode_to_castos( $post ); - if ( 'success' === $response['status'] ) { - $podmotor_episode_id = $response['episode_id']; - if ( $podmotor_episode_id ) { - update_post_meta( $id, 'podmotor_episode_id', $podmotor_episode_id ); + if ( $response->success ) { + if ( $response->castos_episode_id ) { + update_post_meta( $id, 'podmotor_episode_id', $response->castos_episode_id ); } - $this->admin_notices_handler->add_predefined_flash_notice( - Admin_Notifications_Handler::NOTICE_API_EPISODE_SUCCESS + $this->admin_notices_handler->add_flash_notice( + $response->message, + Admin_Notifications_Handler::SUCCESS ); // if uploading was scheduled before, lets unschedule it @@ -747,16 +749,23 @@ public function sync_episode( $id, $post ) { $this->episode_repository->update_episode_sync_status( $post->ID, Sync_Status::SYNC_STATUS_SYNCED ); $this->episode_repository->delete_sync_error( $post->ID ); } else { - // Schedule uploading with a cronjob.1 + // Schedule uploading with a cronjob. // If it's 404, something wrong with the file ID. We don't try to reupload it since result will be the same. - if ( 404 != $response['code'] ) { + if ( in_array( $response->code, array( 403, 404 ) ) ) { + $this->admin_notices_handler->add_flash_notice( + $response->message, + Admin_Notifications_Handler::ERROR + ); + } else { update_post_meta( $id, Cron_Controller::SYNC_SCHEDULE_META, true ); update_post_meta( $id, Cron_Controller::ATTEMPTS_META, 1 ); + $this->admin_notices_handler->add_predefined_flash_notice( + Admin_Notifications_Handler::NOTICE_API_EPISODE_ERROR + ); } - $this->admin_notices_handler->add_predefined_flash_notice( - Admin_Notifications_Handler::NOTICE_API_EPISODE_ERROR - ); + $this->episode_repository->update_episode_sync_status( $post->ID, Sync_Status::SYNC_STATUS_FAILED ); + $this->episode_repository->update_episode_sync_error( $post->ID, $response->message ); } } diff --git a/php/classes/controllers/class-settings-controller.php b/php/classes/controllers/class-settings-controller.php index 35621adf..61560d3d 100644 --- a/php/classes/controllers/class-settings-controller.php +++ b/php/classes/controllers/class-settings-controller.php @@ -348,7 +348,7 @@ public function register_settings() { $is_section_valid = true; if ( isset( $section_data['condition_callback'] ) ) { $callback = $section_data['condition_callback']; - if ( is_string( $callback ) && function_exists( $callback ) ) { + if ( is_callable( $callback ) ) { $is_section_valid = call_user_func( $callback ); } } @@ -619,7 +619,10 @@ public function settings_page() { $tab = empty( $q_args['tab'] ) ? 'general' : $q_args['tab']; $html .= $this->show_page_messages(); - $html .= '
' . "\n"; + + $class = 'ssp-main-settings tab-' . esc_attr( $tab ) . ' '; + $class .= ssp_is_connected_to_castos() ? 'castos-connected' : 'castos-disconnected'; + $html .= '
' . "\n"; $html .= $this->show_page_tabs(); $html .= $this->show_tab_before_settings( $tab ); $html .= $this->show_tab_settings( $tab ); @@ -805,10 +808,18 @@ protected function show_tab_after_settings( $tab ) { $disable_save_button_on_tabs = array( 'extensions', 'import' ); if ( ! in_array( $tab, $disable_save_button_on_tabs ) ) { + $button_text = isset( $this->settings[$tab]['button_text'] ) ? + $this->settings[$tab]['button_text'] : + __( 'Save Settings', 'seriously-simple-podcasting' ); + $button_class = 'button-primary ssp-settings-submit'; + $button_class .= isset( $this->settings[$tab]['button_class'] ) ? + ' ' . $this->settings[$tab]['button_class'] : ''; + // Submit button $html .= '

' . "\n"; $html .= '' . "\n"; - $html .= '' . "\n"; + $html .= '' . "\n"; $html .= '

' . "\n"; } @@ -953,14 +964,13 @@ public function maybe_disconnect_from_castos( $new_value ) { * @return string */ public function render_seriously_simple_sidebar() { + if(ssp_is_connected_to_castos()){ + return ''; + } $image_dir = $this->assets_url . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR; - $link = 'https://castos.com/1ksubs?utm_source=WordPress&utm_medium=Settings&utm_campaign=Banner'; - $is_connected = ssp_is_connected_to_castos(); - $img = $is_connected ? - '' : - ''; + $img = ''; - return $this->renderer->fetch( 'settings-sidebar', compact( 'img', 'is_connected' ) ); + return $this->renderer->fetch( 'settings-sidebar', compact( 'img' ) ); } public function render_seriously_simple_extensions() { diff --git a/php/classes/entities/class-abstract-entity.php b/php/classes/entities/class-abstract-entity.php index 02f528f8..6dbd76eb 100644 --- a/php/classes/entities/class-abstract-entity.php +++ b/php/classes/entities/class-abstract-entity.php @@ -26,7 +26,7 @@ abstract class Abstract_Entity { * * @param array $properties Array of message properties. */ - public function __construct( $properties ) { + public function __construct( $properties = array() ) { foreach ( get_object_vars( $this ) as $k => $v ) { if ( is_array( $properties ) ) { $this->fill_with_array( $properties, $k ); diff --git a/php/classes/entities/class-castos-response-episode.php b/php/classes/entities/class-castos-response-episode.php new file mode 100644 index 00000000..c41bacca --- /dev/null +++ b/php/classes/entities/class-castos-response-episode.php @@ -0,0 +1,23 @@ +body && isset( $this->body['episode']['id'] ) ){ + $this->castos_episode_id = $this->body['episode']['id']; + } + } +} diff --git a/php/classes/entities/class-castos-response.php b/php/classes/entities/class-castos-response.php new file mode 100644 index 00000000..938e5a51 --- /dev/null +++ b/php/classes/entities/class-castos-response.php @@ -0,0 +1,53 @@ +code = wp_remote_retrieve_response_code( $raw_response ); + $this->body = json_decode( wp_remote_retrieve_body( $raw_response ), true ); + $this->message = isset( $this->body['message'] ) ? $this->translate( $this->body['message'] ) : ''; + $this->success = 200 === $this->code ? true : $this->success; + $this->status = 200 === $this->code ? 'success' : $this->status; + } + + public function translate( $text ) { + try { + $translations = array ( + wp_hash( 'Authentication failed! Invalid or missing Access Token!' ) => __( 'Authentication failed! Invalid or missing Access Token!' ), + wp_hash( 'Seriously Simple Podcasting has successfully connected to your Castos account.' ) => __( 'Seriously Simple Podcasting has successfully connected to your Castos account.' ), + ); + $msg_key = wp_hash( $text ); + if ( ! array_key_exists( $msg_key, $translations ) ) { + throw new \Exception( 'Text is not translatable' ); + } + + return $translations[ $msg_key ]; + + } catch ( \Exception $e ) { + return $text; + } + } +} diff --git a/php/classes/handlers/class-admin-notifications-handler.php b/php/classes/handlers/class-admin-notifications-handler.php index cfc8de79..c7b85444 100644 --- a/php/classes/handlers/class-admin-notifications-handler.php +++ b/php/classes/handlers/class-admin-notifications-handler.php @@ -45,21 +45,13 @@ class Admin_Notifications_Handler implements Service { const ERROR = 'error'; const SUCCESS = 'success'; - /** - * @var Castos_Handler $castos_handler - * */ - protected $castos_handler; /** * Admin_Notifications_Handler constructor. - * - * @param Castos_Handler $castos_handler - */ - public function __construct( $castos_handler ) { + **/ + public function __construct() { $this->init_useful_variables(); - $this->castos_handler = $castos_handler; - return $this; } diff --git a/php/classes/handlers/class-ajax-handler.php b/php/classes/handlers/class-ajax-handler.php index 310af068..49699d52 100644 --- a/php/classes/handlers/class-ajax-handler.php +++ b/php/classes/handlers/class-ajax-handler.php @@ -14,13 +14,20 @@ class Ajax_Handler { * */ protected $castos_handler; + /** + * @var Admin_Notifications_Handler $admin_notices_handler + * */ + protected $admin_notices_handler; + /** * Ajax_Handler constructor. * * @param Castos_Handler $castos_handler + * @param Admin_Notifications_Handler $admin_notices_handler */ - public function __construct( $castos_handler ) { + public function __construct( $castos_handler, $admin_notices_handler ) { $this->castos_handler = $castos_handler; + $this->admin_notices_handler = $admin_notices_handler; $this->bootstrap(); } @@ -32,8 +39,9 @@ public function bootstrap() { // Add ajax action for plugin rating add_action( 'wp_ajax_ssp_rated', array( $this, 'rated' ) ); - // Add ajax action for plugin rating. - add_action( 'wp_ajax_validate_castos_credentials', array( $this, 'validate_podmotor_api_credentials' ) ); + add_action( 'wp_ajax_connect_castos', array( $this, 'connect_castos' ) ); + + add_action( 'wp_ajax_disconnect_castos', array( $this, 'disconnect_castos' ) ); // Add ajax action for customising episode embed code add_action( 'wp_ajax_update_episode_embed_code', array( $this, 'update_episode_embed_code' ) ); @@ -168,30 +176,50 @@ protected function get_podcast_name( $podcast_id ) { return __( 'Error', 'seriously-simple-podcasting' ); } + public function disconnect_castos() { + $this->castos_handler->remove_api_credentials(); + $this->admin_notices_handler->add_flash_notice( + __( 'Castos account successfully disconnected.', 'seriously-simple-podcasting' ) + ); + wp_send_json_success(); + } + /** * Validate the Seriously Simple Hosting api credentials */ - public function validate_podmotor_api_credentials() { + public function connect_castos() { try { $this->nonce_check('ss_podcasting_castos-hosting'); $this->user_capability_check(); - if ( ! isset( $_GET['api_token'] ) || ! isset( $_GET['email'] ) ) { + if ( ! isset( $_GET['api_token'] ) ) { throw new \Exception( __('Castos arguments not set', 'seriously-simple-podcasting') ); } $account_api_token = sanitize_text_field( $_GET['api_token'] ); - $account_email = sanitize_text_field( $_GET['email'] ); - $response = $this->castos_handler->validate_api_credentials( $account_api_token, $account_email ); - wp_send_json( $response ); + $response = $this->castos_handler->connect( $account_api_token ); + if ( ! $response->success ) { + throw new \Exception( $response->message ); + } + + $this->castos_handler->set_token( $account_api_token ); + + $this->admin_notices_handler->add_flash_notice( $response->message, Admin_Notifications_Handler::SUCCESS ); + + wp_send_json( [ + 'status' => $response->status, + 'message' => $response->message, + ] ); } catch ( \Exception $e ) { + usleep( 500000 ); // Add a 0.5s delay to ensure smoother transitions on the frontend. + $this->castos_handler->remove_api_credentials(); $this->send_json_error( $e->getMessage() ); } } /** - * Update the epiaode embed code via ajax + * Update the episode embed code via ajax * @return void */ public function update_episode_embed_code() { diff --git a/php/classes/handlers/class-castos-handler.php b/php/classes/handlers/class-castos-handler.php index 1bf41868..f2698bbd 100644 --- a/php/classes/handlers/class-castos-handler.php +++ b/php/classes/handlers/class-castos-handler.php @@ -6,6 +6,8 @@ use Exception; use SeriouslySimplePodcasting\Entities\API_File_Data; use SeriouslySimplePodcasting\Entities\API_Podcast; +use SeriouslySimplePodcasting\Entities\Castos_Response; +use SeriouslySimplePodcasting\Entities\Castos_Response_Episode; use SeriouslySimplePodcasting\Entities\Sync_Status; use SeriouslySimplePodcasting\Entities\Episode_File_Data; use SeriouslySimplePodcasting\Helpers\Log_Helper; @@ -135,10 +137,55 @@ protected function update_response( $key, $value ) { } public function remove_api_credentials() { - delete_option( self::API_EMAIL_OPTION ); + delete_option( self::API_EMAIL_OPTION ); // Not used since 3.5.0 delete_option( self::API_TOKEN_OPTION ); } + /** + * Connect to Castos API and validate API credentials + * + * @param string $account_api_token + * + * @return Castos_Response + */ + public function connect( $account_api_token ) { + + $response = new Castos_Response(); + + if ( empty( $account_api_token ) ) { + $response->message = 'Invalid API Token.'; + + return $response; + } + + $api_url = SSP_CASTOS_APP_URL . 'api/v2/ssp/connect'; + + $this->logger->log( 'Connecting to Castos : API URL', $api_url ); + + $args = array( + 'timeout' => 45, + 'body' => array( + 'website' => get_home_url(), + ), + 'headers' => array( + 'Authorization' => 'Bearer ' . $account_api_token, + ), + ); + + $app_response = wp_remote_post( $api_url, $args ); + + $this->logger->log( 'Connect to Castos: App Response', $app_response ); + + $response->update( $app_response ); + + return $response; + } + + public function set_token( $token ) { + update_option( self::API_TOKEN_OPTION, $token ); + } + + /** * Connect to Castos API and validate API credentials * @@ -260,115 +307,89 @@ public function trigger_podcast_import() { * * @param \WP_Post $post * - * @return array + * @return Castos_Response_Episode */ public function upload_episode_to_castos( $post ) { - $this->setup_default_response(); - - if ( empty( $post ) || empty( $post->ID ) || empty( $post->post_title ) ) { - $this->update_response( 'message', 'Invalid Podcast data' ); - $this->logger->log( 'Invalid Podcast data when uploading podcast data' ); - - return $this->response; - } - - /** - * Don't trigger this unless we have a valid PodcastMotor file id - */ - $podmotor_file_id = get_post_meta( $post->ID, 'podmotor_file_id', true ); - if ( empty( $podmotor_file_id ) ) { - $this->update_response( 'message', 'Invalid Podcast file data' ); - $this->logger->log( 'Invalid Podcast file data when uploading podcast data' ); + $response = new Castos_Response_Episode(); - return $this->response; - } - - $api_url = SSP_CASTOS_APP_URL . 'api/v2/posts/create'; - $podmotor_episode_id = get_post_meta( $post->ID, 'podmotor_episode_id', true ); - if ( $podmotor_episode_id ) { - $api_url = SSP_CASTOS_APP_URL . 'api/v2/posts/update'; - } - - $series_id = ssp_get_episode_series_id( $post->ID ); - - $post_body = array( - 'token' => $this->api_token(), - 'post_id' => $post->ID, - 'post_title' => $post->post_title, - 'post_content' => $this->get_episode_content( $post->ID, $series_id ), - 'keywords' => get_keywords_for_episode( $post->ID ), - 'series_number' => get_post_meta( $post->ID, 'itunes_season_number', true ), - 'episode_number' => get_post_meta( $post->ID, 'itunes_episode_number', true ), - 'episode_type' => get_post_meta( $post->ID, 'itunes_episode_type', true ), - 'post_date' => $post->post_date, - 'post_date_gmt' => $post->post_date_gmt, - 'file_id' => $podmotor_file_id, - 'series_id' => $series_id, - ); - - if ( ! empty( $podmotor_episode_id ) ) { - $post_body['id'] = $podmotor_episode_id; - } + try { + if ( empty( $post ) || empty( $post->ID ) || empty( $post->post_title ) ) { + throw new \Exception( __( 'Invalid episode data when uploading to Castos', 'seriously-simple-podcasting' ) ); + } - $this->logger->log( 'API URL', $api_url ); + $api_url = SSP_CASTOS_APP_URL . 'api/v2/posts/create'; - $episode_image_url = $this->get_episode_image_url( $post ); - if ( ! empty( $episode_image_url ) ) { - // Todo: change 'featured_image_url' to 'cover_image_url' after API update - $post_body['featured_image_url'] = $episode_image_url; - } + /** + * Don't trigger this unless we have a valid file id + */ + $podmotor_file_id = get_post_meta( $post->ID, 'podmotor_file_id', true ); + if ( empty( $podmotor_file_id ) ) { + throw new \Exception(__( 'Invalid file data when uploading the episode to Castos', 'seriously-simple-podcasting') ); + } - $this->logger->log( 'Parameter post_body Contents', $post_body ); + /** + * Don't trigger this unless we have a valid file id + */ + $podmotor_episode_id = get_post_meta( $post->ID, 'podmotor_episode_id', true ); + if ( $podmotor_episode_id ) { + $api_url = SSP_CASTOS_APP_URL . 'api/v2/posts/update'; + } - /** - * Convert to JSON so that we send it with the Content-Type of application/json - * On some WordPress installs the Content-Type defaults to text/html - * Just setting the Content-Type to application/json was not enough, so the post_body has to be converted - * to JSON as well. - */ - $post_body = wp_json_encode( $post_body ); + $series_id = ssp_get_episode_series_id( $post->ID ); + + $post_body = array( + 'token' => $this->api_token(), + 'post_id' => $post->ID, + 'post_title' => $post->post_title, + 'post_content' => $this->get_episode_content( $post->ID, $series_id ), + 'keywords' => get_keywords_for_episode( $post->ID ), + 'series_number' => get_post_meta( $post->ID, 'itunes_season_number', true ), + 'episode_number' => get_post_meta( $post->ID, 'itunes_episode_number', true ), + 'episode_type' => get_post_meta( $post->ID, 'itunes_episode_type', true ), + 'post_date' => $post->post_date, + 'post_date_gmt' => $post->post_date_gmt, + 'file_id' => $podmotor_file_id, + 'series_id' => $series_id, + ); - $options = array( - 'body' => $post_body, - 'headers' => array( - 'Content-Type' => 'application/json', - ), - 'timeout' => 60, - ); + if ( ! empty( $podmotor_episode_id ) ) { + $post_body['id'] = $podmotor_episode_id; + } - $app_response = wp_remote_post( $api_url, $options ); + $this->logger->log( 'API URL', $api_url ); - if ( is_wp_error( $app_response ) ) { - $this->logger->log( 'An unknown error occurred sending podcast data to castos: ' . $app_response->get_error_message() ); - $this->update_response( 'message', 'An unknown error occurred: ' . $app_response->get_error_message() ); + $episode_image_url = $this->get_episode_image_url( $post ); + if ( ! empty( $episode_image_url ) ) { + $post_body['featured_image_url'] = $episode_image_url; + } - return $this->response; - } + $this->logger->log( 'Post body', $post_body ); - if ( isset( $app_response['response']['code'] ) ) { - $this->update_response( 'code', intval( $app_response['response']['code'] ) ); - } + $options = array( + 'body' => wp_json_encode( $post_body ), + 'headers' => array( + 'Content-Type' => 'application/json', + ), + 'timeout' => 60, + ); - $this->logger->log( 'Upload Podcast app_response', $app_response ); + $app_response = wp_remote_post( $api_url, $options ); - $response_object = json_decode( wp_remote_retrieve_body( $app_response ) ); + $this->logger->log( 'Upload episode app_response', $app_response ); - $this->logger->log( 'Upload Podcast Response', $response_object ); + if ( is_wp_error( $app_response ) ) { + throw new \Exception( $app_response->get_error_message() ); + } - if ( ! isset( $response_object->status ) || ! $response_object->status || empty( $response_object->success ) ) { - $this->logger->log( 'An error occurred uploading the episode data to Castos', $response_object ); - $this->update_response( 'message', 'An error occurred uploading the episode data to Castos' ); + $response->update( $app_response ); - return $this->response; + return $response; + } catch(\Exception $e) { + $this->logger->log( __METHOD__ . ': ' . $e->getMessage() . '; $post:' . serialize( $post ) ); + $response->message = $e->getMessage(); + return $response; } - - $this->logger->log( 'Podcast episode successfully uploaded to Castos with episode id ' . $response_object->episode->id ); - $this->update_response( 'status', 'success' ); - $this->update_response( 'message', 'Podcast episode successfully uploaded to Castos' ); - $this->update_response( 'episode_id', $response_object->episode->id ); - - return $this->response; } diff --git a/php/classes/integrations/blocks/class-castos-blocks.php b/php/classes/integrations/blocks/class-castos-blocks.php index 0638b332..eb87f355 100644 --- a/php/classes/integrations/blocks/class-castos-blocks.php +++ b/php/classes/integrations/blocks/class-castos-blocks.php @@ -273,7 +273,7 @@ public function register_castos_blocks() { ), ), 'render_callback' => function ( $args ) { - return ssp_frontend_controller()->players_controller->render_html_player( $args['episodeId'] ); + return ssp_frontend_controller()->players_controller->render_html_player( $args['episodeId'], true, 'block', $args ); } ) ); @@ -449,6 +449,10 @@ protected function register_playlist_player() { $args['order'] = $attributes['order']; } + if ( ! empty( $attributes['className'] ) ) { + $args['class'] = $attributes['className']; + } + $podcast_playlist = new Podcast_Playlist(); return $podcast_playlist->shortcode( $args ); diff --git a/php/classes/repositories/class-episode-repository.php b/php/classes/repositories/class-episode-repository.php index 13683add..9848d50e 100644 --- a/php/classes/repositories/class-episode-repository.php +++ b/php/classes/repositories/class-episode-repository.php @@ -26,6 +26,7 @@ class Episode_Repository implements Service { use Useful_Variables; const META_SYNC_STATUS = 'sync_status'; + const META_SYNC_ERROR = 'ssp_sync_episode_error'; /** @@ -477,6 +478,7 @@ public function update_episode_sync_status( $episode_id, $status ) { return update_post_meta( $episode_id, self::META_SYNC_STATUS, $status ); } + /** * @param int $episode_id * @@ -630,6 +632,7 @@ public function get_player_data( $id, $current_post = null, $skip_empty_audio = 'excerpt' => ssp_get_episode_excerpt( $episode->ID ), 'player_id' => wp_rand(), 'add_empty_warning' => false, + 'class' => '', ); return apply_filters( 'ssp_html_player_data', $template_data ); diff --git a/php/classes/rest/class-rest-api-controller.php b/php/classes/rest/class-rest-api-controller.php index 4c15bec2..1fd93e0c 100644 --- a/php/classes/rest/class-rest-api-controller.php +++ b/php/classes/rest/class-rest-api-controller.php @@ -2,6 +2,7 @@ namespace SeriouslySimplePodcasting\Rest; +use SeriouslySimplePodcasting\Entities\Sync_Status; use SeriouslySimplePodcasting\Handlers\Options_Handler; use SeriouslySimplePodcasting\Handlers\Series_Handler; use SeriouslySimplePodcasting\Repositories\Episode_Repository; @@ -520,13 +521,22 @@ public function get_rest_audio_player( $object, $field_name, $request ) { */ public function get_episode_player_data( $object, $field_name, $request ) { if ( ! empty( $object['id'] ) ) { - $options_handler = new Options_Handler(); - $episode_id = $object['id']; - $player_data = array( + $options_handler = new Options_Handler(); + $episode_id = $object['id']; + $sync_status = ssp_episode_sync_status( $episode_id ); + + $player_data = array( 'playerMode' => get_option( 'ss_podcasting_player_mode', 'dark' ), 'subscribeUrls' => $options_handler->get_subscribe_urls( $episode_id, 'rest_api' ), 'rssFeedUrl' => $this->episode_repository->get_feed_url( $episode_id ), 'embedCode' => preg_replace( '/(\r?\n){2,}/', '\n\n', get_post_embed_html( 500, 350, $episode_id ) ), + 'syncStatus' => array( + 'isSynced' => Sync_Status::SYNC_STATUS_SYNCED === $sync_status->status, + 'status' => $sync_status->status, + 'error' => $sync_status->error, + 'message' => $sync_status->message, + 'title' => $sync_status->title, + ), ); return $player_data; diff --git a/php/classes/shortcodes/class-podcast-playlist.php b/php/classes/shortcodes/class-podcast-playlist.php index 5d94b1fd..e51d75cc 100644 --- a/php/classes/shortcodes/class-podcast-playlist.php +++ b/php/classes/shortcodes/class-podcast-playlist.php @@ -49,7 +49,7 @@ public function shortcode( $params ) { $this->prepare_properties(); $atts = $this->prepare_atts( $params ); - $episodes = $this->ss_podcasting->players_controller->get_playlist_episodes( $atts ); + $episodes = $this->ss_podcasting->episode_repository->get_episodes( $atts ); if ( empty ( $episodes ) ) { return ''; @@ -111,13 +111,14 @@ protected function prepare_atts( $params ) { 'orderby' => 'menu_order ID', 'include' => '', 'exclude' => '', - 'style' => 'light', + 'style' => get_option( 'ss_podcasting_player_mode', 'light' ), 'player_style' => 'standard', 'tracklist' => true, 'tracknumbers' => true, 'images' => true, 'limit' => 10, 'page' => 1, + 'class' => '', ), $params, 'podcast_playlist' diff --git a/php/config/settings/hosting.php b/php/config/settings/hosting.php index 3d77049d..1f054c5a 100644 --- a/php/config/settings/hosting.php +++ b/php/config/settings/hosting.php @@ -4,37 +4,26 @@ * */ return array( 'title' => __( 'Hosting', 'seriously-simple-podcasting' ), + 'button_text' => __( 'Connect', 'seriously-simple-podcasting' ), + 'button_class' => 'castos-connect disabled hidden', 'sections' => array( 'credentials' => array( + 'condition_callback' => function () { + return ! ssp_is_connected_to_castos(); + }, 'title' => __( 'Podcast Hosting', 'seriously-simple-podcasting' ), 'description' => sprintf( __( 'Connect your WordPress site to your %s account.', 'seriously-simple-podcasting' ), 'Castos' ), 'fields' => array( - array( - 'id' => 'podmotor_account_email', - 'type' => 'text', - 'label' => __( 'Your email', 'seriously-simple-podcasting' ), - 'description' => __( 'The email address you used to register your Castos account.', 'seriously-simple-podcasting' ), - 'default' => '', - 'placeholder' => __( 'email@domain.com', 'seriously-simple-podcasting' ), - 'callback' => 'esc_email', - 'class' => 'regular-text', - ), array( 'id' => 'podmotor_account_api_token', 'type' => 'text', - 'label' => __( 'Castos API key', 'seriously-simple-podcasting' ), - 'description' => __( 'Your Castos API key. Available from your Castos account dashboard.', 'seriously-simple-podcasting' ), + 'label' => __( 'Castos API token', 'seriously-simple-podcasting' ), + 'description' => __( 'Your Castos API token. Available from your Castos account dashboard.', 'seriously-simple-podcasting' ), 'default' => '', 'placeholder' => __( 'Enter your api key', 'seriously-simple-podcasting' ), 'callback' => 'sanitize_text_field', 'class' => 'regular-text', ), - array( - 'id' => 'validate_api_credentials', - 'type' => 'button', - 'label' => esc_attr( __( 'Verify Credentials', 'seriously-simple-podcasting' ) ), - 'class' => 'button-primary', - ), ), ), 'sync' => array( @@ -67,13 +56,13 @@ 'no_store' => true, 'fields' => array( array( - 'id' => 'podmotor_disconnect', - 'label' => __( 'Disconnect Castos', 'seriously-simple-podcasting' ), + 'id' => 'disconnect_castos', + 'label' => __( 'Disconnect', 'seriously-simple-podcasting' ), 'description' => __( 'Select this if you wish to disconnect your Castos account.', 'seriously-simple-podcasting' ), - 'type' => 'checkbox', + 'type' => 'button', 'default' => '', 'callback' => 'wp_strip_all_tags', - 'class' => 'disconnect-castos', + 'class' => 'disconnect-castos button', ), ), ), diff --git a/php/includes/ssp-functions.php b/php/includes/ssp-functions.php index f8a50e1b..62636cfa 100644 --- a/php/includes/ssp-functions.php +++ b/php/includes/ssp-functions.php @@ -7,8 +7,10 @@ use SeriouslySimplePodcasting\Handlers\Castos_Handler; use SeriouslySimplePodcasting\Handlers\CPT_Podcast_Handler; use SeriouslySimplePodcasting\Handlers\Images_Handler; +use SeriouslySimplePodcasting\Helpers\Log_Helper; use SeriouslySimplePodcasting\Interfaces\Service; use SeriouslySimplePodcasting\Renderers\Renderer; +use SeriouslySimplePodcasting\Repositories\Episode_Repository; use SeriouslySimplePodcasting\Repositories\Series_Repository; // Exit if accessed directly. @@ -668,33 +670,39 @@ function ssp_get_feed_category_output( $level, $series_id ) { * @since 1.0.0 */ function ssp_readfile_chunked( $file, $retbytes = true ) { + try { + $chunksize = 1 * ( 1024 * 1024 ); + $cnt = 0; - $chunksize = 1 * ( 1024 * 1024 ); - $cnt = 0; + $handle = fopen( $file, 'r' ); + if ( false === $handle ) { + return false; + } - $handle = fopen( $file, 'r' ); - if ( false === $handle ) { - return false; - } + while ( ! feof( $handle ) ) { + $buffer = fread( $handle, $chunksize ); + echo $buffer; + ob_flush(); + flush(); - while ( ! feof( $handle ) ) { - $buffer = fread( $handle, $chunksize ); - echo $buffer; - ob_flush(); - flush(); + if ( $retbytes ) { + $cnt += strlen( $buffer ); + } + } - if ( $retbytes ) { - $cnt += strlen( $buffer ); + $status = fclose( $handle ); + + if ( $retbytes && $status ) { + return $cnt; } - } - $status = fclose( $handle ); + return $status; + } catch ( \Throwable $e ) { + $logger = new Log_Helper(); + $logger->log( 'Error in ' . __FUNCTION__ . ': ' . $e->getMessage(), 'File: ' . $file ); - if ( $retbytes && $status ) { - return $cnt; + return false; } - - return $status; } } @@ -742,9 +750,8 @@ function ssp_is_connected_to_castos() { if ( $cache = wp_cache_get( $cache_key ) ) { return $cache; } - $podmotor_email = get_option( 'ss_podcasting_podmotor_account_email', '' ); $podmotor_api_token = get_option( 'ss_podcasting_podmotor_account_api_token', '' ); - if ( ! empty( $podmotor_email ) && ! empty( $podmotor_api_token ) ) { + if ( ! empty( $podmotor_api_token ) ) { $is_connected = true; } @@ -1308,7 +1315,8 @@ function ssp_get_the_feed_item_content( $post = null ) { $content = preg_replace( '/