diff --git a/assets/css/build/style-style.scss.css b/assets/css/build/style-style.scss.css index 576df576..07716752 100644 --- a/assets/css/build/style-style.scss.css +++ b/assets/css/build/style-style.scss.css @@ -1 +1 @@ -.http_100,.http_101,.http_102,.http_103{background-color:#e6f7ff}.http_200,.http_201,.http_202,.http_203,.http_204,.http_205,.http_206,.http_207,.http_208,.http_226{background-color:#e6ffe6}.http_300,.http_301,.http_302,.http_303,.http_304,.http_305,.http_306,.http_307,.http_308{background-color:#fff2e6}.http_400,.http_401,.http_402,.http_403,.http_404,.http_405,.http_406,.http_407,.http_408,.http_409,.http_410,.http_411,.http_412,.http_413,.http_414,.http_415,.http_416,.http_417,.http_418,.http_421,.http_422,.http_423,.http_424,.http_425,.http_426,.http_428,.http_429,.http_431,.http_451{background-color:#ffe6e6}.http_500,.http_501,.http_502,.http_503,.http_504,.http_505,.http_506,.http_507,.http_508,.http_510,.http_511{background-color:#ffe6ff}.http_none{background-color:#e6e6e6}@keyframes pulse{0%{background-color:#ebffff}50%{background-color:#f8ebff}50%{background-color:rgba(255,255,255,0)}}#wlf-meta-box-results h2{padding:0;margin-bottom:1em;font-size:19px;color:#db7093}#wlf-meta-box-results .report{border:1px solid #000;padding:5px 7px}#wlf-meta-box-results .report p{margin-bottom:2px}#wlf-meta-box-results .report a{text-align:center;display:block}#wlf-meta-box-results div{margin-bottom:5px}#wlf-meta-box-results.updated-results{animation:pulse 5s}#wlf-meta-box-runner div{margin-bottom:5px}#wlf-meta-box-runner .checkbox_container{display:flex;align-items:flex-end;justify-content:space-between}#wlf-meta-box-runner .text_container label{line-height:25px}#wlf-meta-box-runner .button_container{text-align:center;padding-top:5px}#wlf-meta-box-results__progress{padding:5px;text-align:center;background-color:#00891d;color:#fff;font-weight:500;margin-top:10px;border-radius:10px}#wlf_excluded_links{padding:5px;border:1px solid #cfcfcf;margin:5px 0}#wlf_excluded_links #wlf_excluded_empty{text-align:center;color:#cfcfcf}#wlf_excluded_links .link,#wlf_excluded_links .new-link{display:flex}#wlf_excluded_links .link input,#wlf_excluded_links .new-link input{max-width:100%;width:-webkit-fill-available}#wlf_excluded_links .new-link{margin-bottom:10px;background-color:#cfcfcf;padding:10px}.wlf_settings_postbox{position:relative;min-width:255px;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;margin-bottom:20px;padding-top:0}.wlf_settings_postbox h2{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #c3c4c7;padding:10px;margin:0;font-size:14px;padding:8px 12px;line-height:14px}.wlf_settings_postbox table.form-table th{padding-left:10px;font-size:13px}.wlf_settings_postbox>p.description{padding:10px 15px 5px}.wlf_settings_card{box-shadow:0 4px 12px rgba(0,0,0,.06),0 0 2px rgba(0,0,0,.16);border-radius:8px;overflow:hidden;padding:5px 10px;background-color:#fff;margin-bottom:20px}.wlf_settings_post_types{display:grid;grid-template-columns:auto auto;gap:16px;width:-moz-fit-content;width:fit-content}.wlf_read-only{pointer-events:none;background-color:#f9f9f9}#wlf-events-trigger{padding:5px;border:1px solid #000;background:#d9d9d9}#wlf-events-trigger h2{margin-top:0}#wlf-events-trigger .wlf-event-trigger-row{padding:2px;margin-bottom:5px}#wlf-events-trigger .wlf-event-trigger-row p{margin:0;margin-top:3px}#wlf-events-trigger .wlf-event-trigger-row p span.description{color:#646970}#wlf-events-trigger .wlf-event-trigger-row input[type=text]{width:100%}#wlf-events-trigger .wlf-event-trigger-row p.with-checkbox input[type=checkbox]{margin-left:5px}#wlf-events-trigger .wlf-event-trigger-row .checkbox-grid{display:flex}#wlf-events-trigger .wlf-event-trigger-row .checkbox-grid label{display:block;padding:5px}#wlf-events-trigger .wlf-event-trigger-row #wlf-event-select2-errors{font-size:medium;text-align:center;color:red;font-weight:700}#wlf-events-trigger .select2-container--default .select2-selection--multiple{padding-bottom:0px}.wp-list-table.reports .column-report-link-archive,.wp-list-table.reports .column-report-is-broken,.wp-list-table.reports .column-report-link-checks,.wp-list-table.reports .column-report-link-exclude{max-width:10%;width:135px;text-align:center}@media(max-width: 1200px){.wp-list-table.reports .column-report-link-archive,.wp-list-table.reports .column-report-is-broken,.wp-list-table.reports .column-report-link-checks,.wp-list-table.reports .column-report-link-exclude{max-width:unset;width:unset}}.wp-list-table.reports .column-report-link-checks-last{max-width:30%;width:240px}@media(max-width: 1200px){.wp-list-table.reports .column-report-link-checks-last{max-width:unset;width:unset}}.wp-list-table.reports .dashicons.dashicons-yes-alt{color:#2271b1}.wp-list-table.reports .report-link-url a:first-of-type{display:inline-block;max-width:calc(100% - 30px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.wp-list-table.reports .report-link-url .dashicons{display:inline-block;vertical-align:middle;margin-left:5px}.contextual-help-tabs-wrap .wlf_help_tab h3{font-size:1.2em;margin-bottom:.25em}.contextual-help-tabs-wrap .wlf_help_tab p{margin:.25em 0}#wlf_help_info_bulk_actions{cursor:pointer}#wlf_wizard{padding:20px 30px;background:#fff;border-radius:10px;width:500px;min-width:250px;max-width:calc(100% - 60px);margin-left:auto;margin-right:auto}#wlf_wizard input[type=checkbox]{-webkit-appearance:none;appearance:none;width:40px;height:20px;background:#ccc;border-radius:20px;position:relative;cursor:pointer;transition:background .3s;margin:inherit}#wlf_wizard input[type=checkbox]::before{content:"";position:absolute;width:16px;height:16px;background:#fff;border-radius:50%;top:1px;left:2px;transition:transform .3s}#wlf_wizard input[type=checkbox]:checked{background:#2271b1}#wlf_wizard input[type=checkbox]:checked::before{transform:translateX(20px);top:4px;left:5px}#wlf_wizard input[type=text],#wlf_wizard input[type=password],#wlf_wizard select{width:100%;padding:10px;font-size:19px;max-width:unset}#wlf-wizard__footer{display:flex;width:100%;align-items:center;justify-content:space-between}.wlf-wizard__footer__progress{flex-grow:1;display:flex;align-items:center;justify-content:center;padding:0 25px;width:100%}@media(max-width: 499px){.wlf-wizard__footer__progress{display:none}}.wlf-wizard__footer__progress__bar{width:100%;justify-content:center;align-items:center;display:inline;text-align:center;position:relative}.wlf-wizard__footer__progress__bar .wlf-wizard__footer__progress__bar__inner{background-color:#2271b1;height:5px;position:relative;z-index:1}.wlf-wizard__footer__progress__bar::before{content:"";position:absolute;bottom:0;left:0;height:5px;background-color:#f5f5f5;width:100%;transition:width .3s ease-in-out;z-index:0}.wlf-wizard__content form{display:flex;flex-direction:column}.wlf-wizard__content__field{display:flex;flex-direction:column;margin-bottom:20px;justify-content:center;align-items:flex-start}.wlf-wizard__content__field.is_optional.disabled{display:none}.wlf-wizard__content__field label{font-size:17px;line-height:20px;margin-bottom:6px;font-weight:400}.wlf-wizard__content__inner-field .inner-spaced-between__list{display:flex;align-items:center;justify-content:space-between;padding:3px 25px}.wlf-wizard__content__inner-field .inner-spaced-between__list:hover{background-color:#f9f9f9}.wlf-wizard__content__inner-field .inner-spaced-between{display:flex;align-items:center;justify-content:space-between;width:100%}.wlf-wizard__content__inner-field.checkbox{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%}.wlf-wizard__content__inner-field.checkbox input{margin:10px}.wlf-wizard__content__inner-field.checkboxes{width:100%}.wlf-wizard__content__inner-field p.description{width:100%} +.http_100,.http_101,.http_102,.http_103{background-color:#e6f7ff}.http_200,.http_201,.http_202,.http_203,.http_204,.http_205,.http_206,.http_207,.http_208,.http_226{background-color:#e6ffe6}.http_300,.http_301,.http_302,.http_303,.http_304,.http_305,.http_306,.http_307,.http_308{background-color:#fff2e6}.http_400,.http_401,.http_402,.http_403,.http_404,.http_405,.http_406,.http_407,.http_408,.http_409,.http_410,.http_411,.http_412,.http_413,.http_414,.http_415,.http_416,.http_417,.http_418,.http_421,.http_422,.http_423,.http_424,.http_425,.http_426,.http_428,.http_429,.http_431,.http_451{background-color:#ffe6e6}.http_500,.http_501,.http_502,.http_503,.http_504,.http_505,.http_506,.http_507,.http_508,.http_510,.http_511{background-color:#ffe6ff}.http_none{background-color:#e6e6e6}@keyframes pulse{0%{background-color:#ebffff}50%{background-color:#f8ebff}50%{background-color:rgba(255,255,255,0)}}#wlf-meta-box-results h2{padding:0;margin-bottom:1em;font-size:19px;color:#db7093}#wlf-meta-box-results .report{border:1px solid #000;padding:5px 7px}#wlf-meta-box-results .report p{margin-bottom:2px}#wlf-meta-box-results .report a{text-align:center;display:block}#wlf-meta-box-results div{margin-bottom:5px}#wlf-meta-box-results.updated-results{animation:pulse 5s}#wlf-meta-box-runner div{margin-bottom:5px}#wlf-meta-box-runner .checkbox_container{display:flex;align-items:flex-end;justify-content:space-between}#wlf-meta-box-runner .text_container label{line-height:25px}#wlf-meta-box-runner .button_container{text-align:center;padding-top:5px}#wlf-meta-box-results__progress{padding:5px;text-align:center;background-color:#00891d;color:#fff;font-weight:500;margin-top:10px;border-radius:10px}#wlf_excluded_links{padding:5px;border:1px solid #cfcfcf;margin:5px 0}#wlf_excluded_links #wlf_excluded_empty{text-align:center;color:#cfcfcf}#wlf_excluded_links .link,#wlf_excluded_links .new-link{display:flex}#wlf_excluded_links .link input,#wlf_excluded_links .new-link input{max-width:100%;width:-webkit-fill-available}#wlf_excluded_links .new-link{margin-bottom:10px;background-color:#cfcfcf;padding:10px}.wlf_settings_postbox{position:relative;min-width:255px;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;margin-bottom:20px;padding-top:0}.wlf_settings_postbox h2{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #c3c4c7;padding:10px;margin:0;font-size:14px;padding:8px 12px;line-height:14px}.wlf_settings_postbox table.form-table th{padding-left:10px;font-size:13px}.wlf_settings_postbox>p.description{padding:10px 15px 5px}.wlf_settings_card{box-shadow:0 4px 12px rgba(0,0,0,.06),0 0 2px rgba(0,0,0,.16);border-radius:8px;overflow:hidden;padding:5px 10px;background-color:#fff;margin-bottom:20px}.wlf_settings_post_types{display:grid;grid-template-columns:auto auto;gap:16px;width:-moz-fit-content;width:fit-content}.wlf_read-only{pointer-events:none;background-color:#f9f9f9}.wlf_toggle_setting__invalid_api_keys input{border-color:#dc3232}#invalid_api_creds{text-align:center}#invalid_api_creds p{color:#dc3232;font-weight:600;margin:0}#wlf-events-trigger{padding:5px;border:1px solid #000;background:#d9d9d9}#wlf-events-trigger h2{margin-top:0}#wlf-events-trigger .wlf-event-trigger-row{padding:2px;margin-bottom:5px}#wlf-events-trigger .wlf-event-trigger-row p{margin:0;margin-top:3px}#wlf-events-trigger .wlf-event-trigger-row p span.description{color:#646970}#wlf-events-trigger .wlf-event-trigger-row input[type=text]{width:100%}#wlf-events-trigger .wlf-event-trigger-row p.with-checkbox input[type=checkbox]{margin-left:5px}#wlf-events-trigger .wlf-event-trigger-row .checkbox-grid{display:flex}#wlf-events-trigger .wlf-event-trigger-row .checkbox-grid label{display:block;padding:5px}#wlf-events-trigger .wlf-event-trigger-row #wlf-event-select2-errors{font-size:medium;text-align:center;color:red;font-weight:700}#wlf-events-trigger .select2-container--default .select2-selection--multiple{padding-bottom:0px}.wp-list-table.reports .column-report-link-archive,.wp-list-table.reports .column-report-is-broken,.wp-list-table.reports .column-report-link-checks,.wp-list-table.reports .column-report-link-exclude{max-width:10%;width:135px;text-align:center}@media(max-width: 1200px){.wp-list-table.reports .column-report-link-archive,.wp-list-table.reports .column-report-is-broken,.wp-list-table.reports .column-report-link-checks,.wp-list-table.reports .column-report-link-exclude{max-width:unset;width:unset}}.wp-list-table.reports .column-report-link-checks-last{max-width:30%;width:240px}@media(max-width: 1200px){.wp-list-table.reports .column-report-link-checks-last{max-width:unset;width:unset}}.wp-list-table.reports .dashicons.dashicons-yes-alt{color:#2271b1}.wp-list-table.reports .report-link-url a:first-of-type{display:inline-block;max-width:calc(100% - 30px);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.wp-list-table.reports .report-link-url .dashicons{display:inline-block;vertical-align:middle;margin-left:5px}.contextual-help-tabs-wrap .wlf_help_tab h3{font-size:1.2em;margin-bottom:.25em}.contextual-help-tabs-wrap .wlf_help_tab p{margin:.25em 0}#wlf_help_info_bulk_actions{cursor:pointer}#wlf_wizard{padding:20px 30px;background:#fff;border-radius:10px;width:500px;min-width:250px;max-width:calc(100% - 60px);margin-left:auto;margin-right:auto}#wlf_wizard input[type=checkbox]{-webkit-appearance:none;appearance:none;width:40px;height:20px;background:#ccc;border-radius:20px;position:relative;cursor:pointer;transition:background .3s;margin:inherit}#wlf_wizard input[type=checkbox]::before{content:"";position:absolute;width:16px;height:16px;background:#fff;border-radius:50%;top:1px;left:2px;transition:transform .3s}#wlf_wizard input[type=checkbox]:checked{background:#2271b1}#wlf_wizard input[type=checkbox]:checked::before{transform:translateX(20px);top:4px;left:5px}#wlf_wizard input[type=text],#wlf_wizard input[type=password],#wlf_wizard select{width:100%;padding:10px;font-size:19px;max-width:unset}#wlf_wizard input.invalid{border-color:#dc3232}#wlf-wizard__footer{display:flex;width:100%;align-items:center;justify-content:space-between}.wlf-wizard__footer__progress{flex-grow:1;display:flex;align-items:center;justify-content:center;padding:0 25px;width:100%}@media(max-width: 499px){.wlf-wizard__footer__progress{display:none}}.wlf-wizard__footer__progress__bar{width:100%;justify-content:center;align-items:center;display:inline;text-align:center;position:relative}.wlf-wizard__footer__progress__bar .wlf-wizard__footer__progress__bar__inner{background-color:#2271b1;height:5px;position:relative;z-index:1}.wlf-wizard__footer__progress__bar::before{content:"";position:absolute;bottom:0;left:0;height:5px;background-color:#f5f5f5;width:100%;transition:width .3s ease-in-out;z-index:0}.wlf-wizard__content form{display:flex;flex-direction:column}.wlf-wizard__content__field{display:flex;flex-direction:column;margin-bottom:20px;justify-content:center;align-items:flex-start}.wlf-wizard__content__field.is_optional.disabled{display:none}.wlf-wizard__content__field label{font-size:17px;line-height:20px;margin-bottom:6px;font-weight:400}.wlf-wizard__content__inner-field .inner-spaced-between__list{display:flex;align-items:center;justify-content:space-between;padding:3px 25px}.wlf-wizard__content__inner-field .inner-spaced-between__list:hover{background-color:#f9f9f9}.wlf-wizard__content__inner-field .inner-spaced-between{display:flex;align-items:center;justify-content:space-between;width:100%}.wlf-wizard__content__inner-field.checkbox{display:flex;flex-direction:column;align-items:center;justify-content:center;width:100%}.wlf-wizard__content__inner-field.checkbox input{margin:10px}.wlf-wizard__content__inner-field.checkboxes{width:100%}.wlf-wizard__content__inner-field p.description{width:100%} diff --git a/assets/css/src/admin/_settings.scss b/assets/css/src/admin/_settings.scss index de1091e0..39148ae6 100644 --- a/assets/css/src/admin/_settings.scss +++ b/assets/css/src/admin/_settings.scss @@ -70,4 +70,17 @@ .wlf_read-only { pointer-events: none; background-color: #f9f9f9; +} + +.wlf_toggle_setting__invalid_api_keys input { + border-color: #dc3232; +} + +#invalid_api_creds { + text-align: center; + p { + color: #dc3232; + font-weight: 600; + margin: 0; + } } \ No newline at end of file diff --git a/assets/css/src/admin/_wizard.scss b/assets/css/src/admin/_wizard.scss index 3e116587..de9cbc2a 100644 --- a/assets/css/src/admin/_wizard.scss +++ b/assets/css/src/admin/_wizard.scss @@ -7,7 +7,6 @@ max-width: calc(100% - 60px); margin-left: auto; margin-right: auto; - input[type="checkbox"] { appearance: none; width: 40px; @@ -18,7 +17,6 @@ cursor: pointer; transition: background 0.3s; margin: inherit; - &::before { content: ""; position: absolute; @@ -30,10 +28,8 @@ left: 2px; transition: transform 0.3s; } - &:checked { background: #2271b1; - &::before { transform: translateX(20px); top: 4px; @@ -41,7 +37,6 @@ } } } - input[type="text"], input[type="password"], select { @@ -50,6 +45,9 @@ font-size: 19px; max-width: unset; } + input.invalid { + border-color: #dc3232; + } } #wlf-wizard__footer { @@ -66,7 +64,6 @@ justify-content: center; padding: 0 25px; width: 100%; - @media (max-width: 499px) { display: none; } @@ -78,26 +75,24 @@ align-items: center; display: inline; text-align: center; - position: relative; - - .wlf-wizard__footer__progress__bar__inner { - background-color: #2271b1; - height: 5px; - position: relative; - z-index: 1; - } - - &::before { - content: ""; - position: absolute; - bottom: 0; - left: 0; - height: 5px; - background-color: #f5f5f5; - width: 100%; - transition: width 0.3s ease-in-out; - z-index: 0; - } + position: relative; + .wlf-wizard__footer__progress__bar__inner { + background-color: #2271b1; + height: 5px; + position: relative; + z-index: 1; + } + &::before { + content: ""; + position: absolute; + bottom: 0; + left: 0; + height: 5px; + background-color: #f5f5f5; + width: 100%; + transition: width 0.3s ease-in-out; + z-index: 0; + } } .wlf-wizard__content { @@ -105,18 +100,15 @@ display: flex; flex-direction: column; } - &__field { display: flex; flex-direction: column; margin-bottom: 20px; justify-content: center; align-items: flex-start; - &.is_optional.disabled { display: none; } - label { font-size: 17px; line-height: 20px; @@ -124,44 +116,37 @@ font-weight: 400; } } - &__inner-field { .inner-spaced-between__list { display: flex; align-items: center; justify-content: space-between; padding: 3px 25px; - &:hover { background-color: #f9f9f9; } } - .inner-spaced-between { display: flex; align-items: center; justify-content: space-between; width: 100%; } - &.checkbox { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; - input { margin: 10px; } } - &.checkboxes { width: 100%; } - p.description { width: 100%; } } -} +} \ No newline at end of file diff --git a/functions.php b/functions.php index 9a98d5fd..828e4c59 100644 --- a/functions.php +++ b/functions.php @@ -13,6 +13,7 @@ use WPCOMSpecialProjects\Wayback_Link_Fixer\Plugin; use WPCOMSpecialProjects\Wayback_Link_Fixer\Settings\Settings; use WPCOMSpecialProjects\Wayback_Link_Fixer\Migration\Migrations; +use WPCOMSpecialProjects\Wayback_Link_Fixer\Settings\Settings_Page; use WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\Snapshot_Client; use WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\Link_Checker_Client; use WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\HTTP_Client\HTTP_Snapshot_Client; @@ -105,6 +106,17 @@ function wpcomsp_wayback_link_fixer_get_link_checker_client(): Link_Checker_Clie return apply_filters( 'wlf_link_checker_client', new HTTP_Link_Checker_Client() ); } +/** + * Get the current System Client. + * + * @since 1.3.0 + * + * @return WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\System_Client + */ +function wpcomsp_wayback_link_fixer_get_system_client(): \WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\System_Client { + return apply_filters( 'wlf_system_client', new \WPCOMSpecialProjects\Wayback_Link_Fixer\Wayback_Machine\HTTP_Client\HTTP_System_Client() ); +} + /** * Render a template with args. * @@ -246,18 +258,40 @@ function wpcomsp_wayback_link_fixer_get_date_format(): string { * @return void */ function wpcomsp_wayback_link_fixer_render_not_authenticated_notice(): void { - // If we have authentication details for the Wayback Machine, bail. - if ( Settings::is_archive_api_configured() ) { + $in_unauthenticated_mode = __( 'You are using Link Fixer in unauthenticated mode, which restricts you to 4000 new snapshots per day. To unlock higher limits, please enter your API credentials to authenticate with Archive.org.', 'wpcomsp_wayback_link_fixer' ); + + // If the archive api is not configured. + if ( ! Settings::is_archive_api_configured() ) { + ?> +
+

+ +

+
+ -
-

- -

-
- +
+

+ Please check your settings.. As a result you are in in unauthenticated mode, which restricts you to 4000 new snapshots per day.', 'wpcomsp_wayback_link_fixer' ), + esc_url( admin_url( 'options-general.php?page=' . Settings_Page::PAGE_SLUG ) ) + ) + ); + ?> +

+
+ is_valid_user( $access_key, $secret_key ) + ) { + $this->add_notice( __( 'Invalid Archive.org API credentials. Please verify your Access Key and Secret Key, or leave both fields blank to proceed without authentication.', 'wpcomsp_wayback_link_fixer' ), 'error' ); + $_POST['wlf_wizard_invalid_keys'] = true; // Set a flag to indicate invalid keys. + + // Hold the entered values in post. + $_POST['wlf_wizard_archive_access_key_temp'] = $access_key; + $_POST['wlf_wizard_archive_secret_key_temp'] = $secret_key; + + return; + } // Save the keys. update_option( Settings::ARCHIVE_ORG_ACCESS_KEY, $access_key ); update_option( Settings::ARCHIVE_ORG_SECRET_KEY, $secret_key ); + // Mark the keys as valid. + Settings::update_archive_api_credentials_validity( true ); + // Update the step. $next = sanitize_text_field( wp_unslash( $_POST['wlf-next-step'] ) ); update_option( self::OPTION_NAME, $next ); diff --git a/src/Settings/Settings.php b/src/Settings/Settings.php index a67d7087..a130360c 100644 --- a/src/Settings/Settings.php +++ b/src/Settings/Settings.php @@ -35,7 +35,7 @@ class Settings { public const ARCHIVE_ORG_ACCESS_KEY = self::SETTINGS_PREFIX . 'archive_api_access'; public const FIXER_OPTION = self::SETTINGS_PREFIX . 'fixer_option'; public const ARCHIVE_ORG_STATUS_KEY = self::SETTINGS_PREFIX . 'archive_api_status'; - + public const ARCHIVE_ORG_CREDS_VALID_KEY = self::SETTINGS_PREFIX . 'archive_api_creds_valid'; // Table names. public const LINK_TABLE = self::SETTINGS_PREFIX . 'link_archive'; @@ -401,4 +401,28 @@ public static function own_link_routine_update_interval(): int { public static function show_link_table_debug_data(): bool { return (bool) apply_filters( 'wlf_show_link_table_debug_data', false ); } + + /** + * Check if we have valid credentials for the archive.org API. + * + * @since 1.3.0 + * + * @return boolean + */ + public static function has_valid_archive_api_credentials(): bool { + return get_option( self::ARCHIVE_ORG_CREDS_VALID_KEY, false ) === true; + } + + /** + * Updates the archive.org API credentials validity. + * + * @since 1.3.0 + * + * @param boolean $valid True if the credentials are valid, false otherwise. + * + * @return void + */ + public static function update_archive_api_credentials_validity( bool $valid ): void { + update_option( self::ARCHIVE_ORG_CREDS_VALID_KEY, $valid, false ); + } } diff --git a/src/Settings/Settings_Page.php b/src/Settings/Settings_Page.php index 5e32a89b..85b8e34a 100644 --- a/src/Settings/Settings_Page.php +++ b/src/Settings/Settings_Page.php @@ -44,6 +44,7 @@ public function initialize(): void { add_action( 'admin_init', array( $this, 'register_fields' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'admin_menu', array( $this, 'register_page' ), 20, 0 ); + add_action( 'admin_init', array( $this, 'validate_archive_org_keys' ), 99 ); } /** @@ -364,12 +365,12 @@ private function add_settings_fields(): void { add_settings_section( self::GROUP_IA_SETTINGS, - __( 'Internet Archive API', 'wpcomsp_wayback_link_fixer' ), + __( 'Archive.org API', 'wpcomsp_wayback_link_fixer' ), '__return_empty_string', self::PAGE_SLUG, array( 'before_section' => '
', - 'after_section' => '

' . sprintf( + 'after_section' => $this->render_invalid_api_keys_message() . '

' . sprintf( // Translators: %s is the link to the Internet account setup. __( "To get your API key and secret, please visit the Internet Archive and create a new 'S3 access key' (this is a type of credential used by Archive.org).", 'wpcomsp_wayback_link_fixer' ), esc_url( 'https://archive.org/account/s3.php' ) @@ -460,7 +461,10 @@ function () { __( 'Archive.org Access Key', 'wpcomsp_wayback_link_fixer' ), array( $this, 'render_archive_api_access_key' ), self::PAGE_SLUG, - self::GROUP_IA_SETTINGS + self::GROUP_IA_SETTINGS, + array( + 'class' => Settings::has_valid_archive_api_credentials() ? '' : 'wlf_toggle_setting__invalid_api_keys', + ) ); add_settings_field( @@ -468,7 +472,10 @@ function () { __( 'Archive.org Secret Key', 'wpcomsp_wayback_link_fixer' ), array( $this, 'render_archive_api_secret_key' ), self::PAGE_SLUG, - self::GROUP_IA_SETTINGS + self::GROUP_IA_SETTINGS, + array( + 'class' => Settings::has_valid_archive_api_credentials() ? '' : 'wlf_toggle_setting__invalid_api_keys', + ) ); add_settings_field( @@ -507,6 +514,46 @@ function () { ); } + /** + * Renders the invalid API Keys message. + * + * @since 1.3.0 + * + * @return string + */ + public function render_invalid_api_keys_message(): string { + if ( ! Settings::has_valid_archive_api_credentials() ) { + return '

' . esc_html__( 'The Archive.org API keys are invalid. Please check your settings.', 'wpcomsp_wayback_link_fixer' ) . '

'; + } + + return ''; + } + + /** + * Validate the Archive.org keys. + * + * @since 1.3.0 + * + * @return void + */ + public function validate_archive_org_keys(): void { + // If we are on this page. + if ( ! isset( $_GET['page'] ) || self::PAGE_SLUG !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, we are only reading, not using + return; + } + + // If the settings were updated. + if ( isset( $_GET['settings-updated'] ) && 'true' === $_GET['settings-updated'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, we are only reading, not using + $key = Settings::get_archive_secret_key(); + $access_key = Settings::get_archive_access_key(); + + $system_client = wpcomsp_wayback_link_fixer_get_system_client(); + $is_valid = $system_client->is_valid_user( $access_key, $key ); + + Settings::update_archive_api_credentials_validity( $is_valid ); + } + } + /** * Render the process links field. * diff --git a/src/Wayback_Machine/HTTP_Client/HTTP_Snapshot_Client.php b/src/Wayback_Machine/HTTP_Client/HTTP_Snapshot_Client.php index 64ae52b1..130bed03 100644 --- a/src/Wayback_Machine/HTTP_Client/HTTP_Snapshot_Client.php +++ b/src/Wayback_Machine/HTTP_Client/HTTP_Snapshot_Client.php @@ -66,7 +66,7 @@ private function get_headers(): array { $headers = array(); // Add the auth header if set. - if ( Settings::is_archive_api_configured() ) { + if ( Settings::is_archive_api_configured() && Settings::has_valid_archive_api_credentials() ) { $headers['Authorization'] = sprintf( 'LOW %s:%s', Settings::get_archive_access_key(), Settings::get_archive_secret_key() ); } diff --git a/src/Wayback_Machine/HTTP_Client/HTTP_System_Client.php b/src/Wayback_Machine/HTTP_Client/HTTP_System_Client.php new file mode 100644 index 00000000..228f0676 --- /dev/null +++ b/src/Wayback_Machine/HTTP_Client/HTTP_System_Client.php @@ -0,0 +1,121 @@ +get_user_stats( $access_key, $secret_key ); + } catch ( Throwable $e ) { + return false; + } + + return is_array( $stats ); + } + + /** + * Get a user's account stats. + * + * @since 1.3.0 + * + * @param string $access_key The access key to check. + * @param string $secret_key The secret key to check. + * + * @return array{available:int, daily_captures:int, daily_captures_limit:int, processing:int}|null + * @throws Service_Offline_Exception If the service is offline. + * @throws Invalid_Response_Exception If the response is invalid. + */ + public function get_user_stats( string $access_key, string $secret_key ): ?array { + + // Compile the headers. + $headers = array(); + $headers['Authorization'] = sprintf( + 'LOW %s:%s', + sanitize_text_field( $access_key ), + sanitize_text_field( $secret_key ) + ); + $headers['Accept'] = 'application/json'; + + $url = 'http://web.archive.org/save/status/user?_t=' . time(); + + try { + $response = wp_remote_get( $url, array( 'headers' => $headers ) ); + } catch ( \Exception $e ) { + throw Invalid_Response_Exception::create( esc_html( $e->getMessage() ) ); + } + if ( is_wp_error( $response ) ) { + throw Invalid_Response_Exception::create( esc_html( $response->get_error_message() ) ); + } + + // If we dont have a 200 response, return null. + if ( ! isset( $response['response']['code'] ) || 200 !== (int) $response['response']['code'] ) { + return null; + } + + $body = wp_remote_retrieve_body( $response ); + + // If we dont have a valid body, return null. + if ( ! is_string( $body ) ) { + return null; + } + // Decode the body. + $body = json_decode( $body, true ); + + // If we dont have a valid body, return null. + if ( ! is_array( $body ) ) { + return null; + } + + return array( + 'available' => isset( $body['available'] ) ? (int) $body['available'] : 0, + 'daily_captures' => isset( $body['daily_captures'] ) ? (int) $body['daily_captures'] : 0, + 'daily_captures_limit' => isset( $body['daily_captures_limit'] ) ? (int) $body['daily_captures_limit'] : 0, + 'processing' => isset( $body['processing'] ) ? (int) $body['processing'] : 0, + ); + } +} diff --git a/src/Wayback_Machine/System_Client.php b/src/Wayback_Machine/System_Client.php new file mode 100644 index 00000000..54590a29 --- /dev/null +++ b/src/Wayback_Machine/System_Client.php @@ -0,0 +1,55 @@ +snapshot_client = wpcomsp_wayback_link_fixer_get_snapshot_client(); $this->link_checker_client = wpcomsp_wayback_link_fixer_get_link_checker_client(); + $this->system_client = wpcomsp_wayback_link_fixer_get_system_client(); } /** @@ -159,4 +169,40 @@ public function is_online(): array { 'link_checker' => $this->link_checker_client->is_online(), ); } + + /** + * Check a users account details. + * + * @since 1.3.0 + * + * @param string $access_key The access key to check. + * @param string $secret_key The secret key to check. + * + * @return boolean + */ + public function is_valid_user( string $access_key, string $secret_key ): bool { + try { + return $this->system_client->is_valid_user( $access_key, $secret_key ); + } catch ( Throwable $e ) { + return false; // If the service is offline, we cannot validate the user. + } + } + + /** + * Get a users account stats. + * + * @since 1.3.0 + * + * @param string $access_key The access key to check. + * @param string $secret_key The secret key to check. + * + * @return array{available:int, daily_captures:int, daily_captures_limit:int, processing:int}|null + */ + public function get_user_stats( string $access_key, string $secret_key ): ?array { + try { + return $this->system_client->get_user_stats( $access_key, $secret_key ); + } catch ( Throwable $e ) { + return null; // If the service is offline or the response is invalid. + } + } } diff --git a/templates/admin/wizard/step-1.php b/templates/admin/wizard/step-1.php index ed6d63f3..29a12e35 100644 --- a/templates/admin/wizard/step-1.php +++ b/templates/admin/wizard/step-1.php @@ -13,8 +13,17 @@ */ // Sets the type for api keys based on the current environment. -$wlf_access_type = '' === $settings->get_archive_access_key() ? 'text' : 'password'; -$wlf_secret_type = '' === $settings->get_archive_secret_key() ? 'text' : 'password'; +$wlf_invalid_keys = isset( $_POST['wlf_wizard_invalid_keys'] ); // phpcs:ignore +$wlf_access_type = '' === $settings->get_archive_access_key() || $wlf_invalid_keys + ? 'text' + : 'password'; +$wlf_secret_type = '' === $settings->get_archive_secret_key() || $wlf_invalid_keys + ? 'text' + : 'password'; + +// Get any temp values from the POST request. +$wlf_existing_access_key = isset( $_POST['wlf_wizard_archive_access_key_temp'] ) ? sanitize_text_field( wp_unslash( $_POST['wlf_wizard_archive_access_key_temp'] ) ) : $settings->get_archive_access_key(); // phpcs:ignore +$wlf_existing_secret_key = isset( $_POST['wlf_wizard_archive_secret_key_temp'] ) ? sanitize_text_field( wp_unslash( $_POST['wlf_wizard_archive_secret_key_temp'] ) ) : $settings->get_archive_secret_key(); // phpcs:ignore ?> @@ -24,7 +33,7 @@
-

+


@@ -32,13 +41,13 @@ - + />
- + />
diff --git a/tests/Wayback_Machine/Test_HTTP_System_Client.php b/tests/Wayback_Machine/Test_HTTP_System_Client.php new file mode 100644 index 00000000..65f5f8fc --- /dev/null +++ b/tests/Wayback_Machine/Test_HTTP_System_Client.php @@ -0,0 +1,268 @@ + 1000, + 'daily_captures' => 10, + 'daily_captures_limit' => 100, + 'processing' => 5, + ); + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 200 ), + 'body' => json_encode( $mock_response ), + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + // Call the method with valid credentials. + $stats = $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + // Assert that the returned stats match the mock response. + $this->assertIsArray( $stats ); + } + + /** + * @testdox If some fields are missing from the responce of user stats, they should be defaulted. + */ + public function test_get_user_stats_with_missing_fields(): void { + $mock_response = array( + 'foo' => 1000, + 'bar' => 10, + ); + + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 200 ), + 'body' => json_encode( $mock_response ), + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with valid credentials. + $stats = $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + + // Assert that the returned stats have default values for missing fields. + $this->assertIsArray( $stats ); + $this->assertArrayHasKey( 'available', $stats ); + $this->assertArrayHasKey( 'daily_captures', $stats ); + $this->assertArrayHasKey( 'daily_captures_limit', $stats ); + $this->assertArrayHasKey( 'processing', $stats ); + $this->assertEquals( 0, $stats['available'] ); + $this->assertEquals( 0, $stats['daily_captures'] ); + $this->assertEquals( 0, $stats['daily_captures_limit'] ); + $this->assertEquals( 0, $stats['processing'] ); + } + + /** + * @testdox If we get a 401 when attempted to get a users creds, it should return null. + */ + public function test_get_user_stats_with_invalid_creds(): void { + // Mock the HTTP response to simulate a 401 Unauthorized error. + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 401 ), + 'body' => json_encode( array( 'error' => 'Unauthorized' ) ), + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with invalid credentials. + $stats = $client->get_user_stats( 'invalid_access_key', 'invalid_secret_key' ); + + // Assert that the returned stats are null. + $this->assertNull( $stats ); + } + + /** + * @testdox An exception should be thrown if an exception is generated doing the get call. + */ + public function test_get_user_stats_with_exception(): void { + // Mock the HTTP response to simulate an exception. + $this->mock_wp_http_response( new \WP_Error( 'http_error', 'An error occurred.' ) ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Assert that an exception is thrown when calling get_user_stats. + $this->expectException( Invalid_Response_Exception::class ); + $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + } + + /** + * @testdox an Invalid_Response_Exception should be thrown if the response is a wp_eror + */ + public function test_get_user_stats_with_invalid_json_response(): void { + // Mock the HTTP response to simulate an invalid JSON response. + $this->mock_wp_http_response( + new \WP_Error( 'http_request_failed', 'Error' ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Assert that an Invalid_Response_Exception is thrown when calling get_user_stats. + $this->expectException( Invalid_Response_Exception::class ); + $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + } + + /** + * @testdox If the response is not a proper string, null should be returned when checking user creds. + */ + public function test_get_user_stats_with_invalid_response_body(): void { + // Mock the HTTP response to simulate an invalid response body. + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 200 ), + 'body' => 12345, // Not a valid JSON string. + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with valid credentials. + $stats = $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + + // Assert that the returned stats are null due to invalid response body. + $this->assertNull( $stats ); + } + + /** + * @testdox If the response is not a valid JSON String, null should be returned when checking user creds. + */ + public function test_get_user_stats_with_invalid_json_string(): void { + // Mock the HTTP response to simulate an invalid JSON string. + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 200 ), + 'body' => 'This is not a valid JSON string', // Invalid JSON. + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with valid credentials. + $stats = $client->get_user_stats( 'valid_access_key', 'valid_secret_key' ); + + // Assert that the returned stats are null due to invalid JSON string. + $this->assertNull( $stats ); + } + + /** + * @testdox When checking if a user is valid, it should return true if the service is online and the credentials are valid. + */ + public function test_is_valid_user_with_valid_creds(): void { + // Mock the HTTP response to simulate a successful API call. + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 200 ), + 'body' => json_encode( array( 'processing' => 1 ) ), + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with valid credentials. + $is_valid = $client->is_valid_user( 'valid_access_key', 'valid_secret_key' ); + + // Assert that the user is valid. + $this->assertTrue( $is_valid ); + } + + /** + * @testdox When checking if a user is valid, it should return false if the service is offline. + */ + public function test_is_valid_user_with_service_offline(): void { + // Mock the HTTP response to simulate an offline service. + $this->mock_wp_http_response( + array( + 'response' => array( 'code' => 401 ), + 'body' => json_encode( array( 'fail' => 1 ) ), + ) + ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + // Call the method with valid credentials. + $is_valid = $client->is_valid_user( 'valid_access_key', 'valid_secret_key' ); + + // Assert that the user is not valid due to service being offline. + $this->assertFalse( $is_valid ); + } + + /** + * @testdox When checking if a user is valid, it should return false if an exception is thrown during the request. + */ + public function test_is_valid_user_with_exception(): void { + // Mock the HTTP response to simulate an exception. + $this->mock_wp_http_response( new \WP_Error( 'http_error', 'An error occurred.' ) ); + + // Create an instance of the HTTP_System_Client. + $client = new HTTP_System_Client(); + + $resp = $client->is_valid_user( 'valid_access_key', 'valid_secret_key' ); + // Assert that the user is not valid due to an exception being thrown. + $this->assertFalse( $resp ); + } +}