plugin installs
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"version": "23.0",
|
||||
"name": "yoast-seo/siblings",
|
||||
"title": "Yoast Siblings",
|
||||
"description": "Adds a list of internal links to sibling pages which share the same parent.",
|
||||
"category": "yoast-internal-linking-blocks",
|
||||
"icon": "editor-ul",
|
||||
"keywords": [
|
||||
"SEO",
|
||||
"siblings",
|
||||
"siblings pages",
|
||||
"internal linking",
|
||||
"site structure"
|
||||
],
|
||||
"textdomain": "wordpress-seo-premium",
|
||||
"attributes": {},
|
||||
"example": {
|
||||
"attributes": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"version": "23.0",
|
||||
"name": "yoast-seo/subpages",
|
||||
"title": "Yoast Subpages",
|
||||
"description": "Adds a list of internal links to subpages of this page.",
|
||||
"category": "yoast-internal-linking-blocks",
|
||||
"icon": "editor-ul",
|
||||
"keywords": [
|
||||
"SEO",
|
||||
"subpages",
|
||||
"childpages",
|
||||
"children",
|
||||
"internal linking",
|
||||
"site structure"
|
||||
],
|
||||
"textdomain": "wordpress-seo-premium",
|
||||
"attributes": {},
|
||||
"example": {
|
||||
"attributes": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.yst-root button.yst-replacevar__use-ai-button{align-items:center;background-color:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;box-shadow:inset 0 -2px 0 0 #0000001a;box-sizing:border-box;color:#303030;cursor:pointer;display:flex;min-height:32px;padding:0 .5em;transition:var(--yoast-transition-default)}.yst-root button.yst-replacevar__use-ai-button:hover{background-color:#fff;border-color:var(--yoast-color-border--default);color:#000}.yst-root .yst-logo-icon{background-color:var(--yoast-color-primary);display:inline-block;mask-image:var(--yoast-svg-icon-yoast);-webkit-mask-image:var(--yoast-svg-icon-yoast);mask-size:100% 100%;-webkit-mask-size:100% 100%}.yst-root .yst-ai-mode .yst-radio-group__options{flex-direction:row;gap:1.5rem}.yst-root .yst-length-progress-bar.yst-score-good .yst-progress-bar__progress{background-color:#7ad03a}.yst-root .yst-length-progress-bar.yst-score-ok .yst-progress-bar__progress{background-color:#ee7c1b}.yst-root .yst-length-progress-bar.yst-score-bad .yst-progress-bar__progress{background-color:#dc3232}.yst-root .yst-suggestions-radio-group .yst-radio-group__options{gap:0}.yst-root .yst-suggestions-radio-group .yst-radio-group__options>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(-1px*var(--tw-space-y-reverse));margin-top:calc(-1px*(1 - var(--tw-space-y-reverse)))}.yst-root .yst-introduction-modal .yst-modal__close-button{--tw-text-opacity:1;background-color:initial;color:rgb(107 114 128/var(--tw-text-opacity))}.yst-root .yst-introduction-modal .yst-modal__close-button:focus{--tw-ring-offset-width:0px;outline:2px solid #0000;outline-offset:2px}.yst-root .yst-ai-modal .yst-modal__panel{overflow:visible}.yst-root .yst-revoke-button .yst-animate-spin{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity))}.yst-root #facebookPreview,.yst-root #twitterPreview,.yst-root #yoast-snippet-preview-container{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));margin-right:auto;margin-left:auto}.yst-root #yoast-snippet-preview-container{border-bottom:1px hidden #fff;border-radius:8px;box-shadow:0 1px 6px #20212447}.yst-root .desktop #yoast-snippet-preview-container{padding:12px 16px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-ai-generator-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-ai-generator-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.yst-root button.yst-replacevar__use-ai-button{align-items:center;background-color:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;box-shadow:inset 0 -2px 0 0 #0000001a;box-sizing:border-box;color:#303030;cursor:pointer;display:flex;min-height:32px;padding:0 .5em;transition:var(--yoast-transition-default)}.yst-root button.yst-replacevar__use-ai-button:hover{background-color:#fff;border-color:var(--yoast-color-border--default);color:#000}.yst-root .yst-logo-icon{background-color:var(--yoast-color-primary);display:inline-block;mask-image:var(--yoast-svg-icon-yoast);-webkit-mask-image:var(--yoast-svg-icon-yoast);mask-size:100% 100%;-webkit-mask-size:100% 100%}.yst-root .yst-ai-mode .yst-radio-group__options{flex-direction:row;gap:1.5rem}.yst-root .yst-length-progress-bar.yst-score-good .yst-progress-bar__progress{background-color:#7ad03a}.yst-root .yst-length-progress-bar.yst-score-ok .yst-progress-bar__progress{background-color:#ee7c1b}.yst-root .yst-length-progress-bar.yst-score-bad .yst-progress-bar__progress{background-color:#dc3232}.yst-root .yst-suggestions-radio-group .yst-radio-group__options{gap:0}.yst-root .yst-suggestions-radio-group .yst-radio-group__options>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(-1px*var(--tw-space-y-reverse));margin-top:calc(-1px*(1 - var(--tw-space-y-reverse)))}.yst-root .yst-introduction-modal .yst-modal__close-button{--tw-text-opacity:1;background-color:initial;color:rgb(107 114 128/var(--tw-text-opacity))}.yst-root .yst-introduction-modal .yst-modal__close-button:focus{--tw-ring-offset-width:0px;outline:2px solid #0000;outline-offset:2px}.yst-root .yst-ai-modal .yst-modal__panel{overflow:visible}.yst-root .yst-revoke-button .yst-animate-spin{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity))}.yst-root #facebookPreview,.yst-root #twitterPreview,.yst-root #yoast-snippet-preview-container{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));margin-left:auto;margin-right:auto}.yst-root #yoast-snippet-preview-container{border-bottom:1px hidden #fff;border-radius:8px;box-shadow:0 1px 6px #20212447}.yst-root .desktop #yoast-snippet-preview-container{padding:12px 16px}
|
||||
@@ -0,0 +1 @@
|
||||
.emoji-select-button{align-items:center;background-color:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;box-shadow:inset 0 -2px 0 0 #0000001a;box-sizing:border-box;color:#303030;cursor:pointer;display:flex;min-height:32px;padding:0 .5em;transition:var(--yoast-transition-default);width:32px}.emoji-select-button svg{width:100%}.emoji-select-button-pressed,.emoji-select-button:hover{background-color:#fff;border-color:var(--yoast-color-border--default)}.yst-replacevar{position:relative}.yst-replacevar .emoji-select-popover{font-size:15px;margin-top:0;left:0}.rtl .yst-replacevar .emoji-select-popover{right:0;left:unset}.yst-replacevar .emoji-select-popover h3{font-size:1em;margin:0 0 .3em}.yst-replacevar .emoji-select-popover li{margin-bottom:unset}.yst-replacevar .emoji-select-popover .single-emoji{height:37.5px;width:37.5px}.yst-replacevar .emoji-select-popover .single-emoji span{font-size:20px!important}.yst-replacevar{color:#303030}.yst-replacevar .emoji-select-popover-nav{width:auto}.yst-replacevar .emoji-suggestions{padding:6px 0}.rtl .yst-replacevar .emoji-suggestions{left:0}.yst-replacevar .emoji-suggestion-item-wrapper{align-items:center;display:flex;padding:6px 10px}.yst-replacevar .emoji-suggestion-item-wrapper span{font-size:18px;line-height:1}.yst-replacevar .emoji-suggestion-item-wrapper .emoji-suggestion-item-text{font-size:13px}
|
||||
@@ -0,0 +1 @@
|
||||
.emoji-select-button{align-items:center;background-color:#f7f7f7;border:1px solid #dbdbdb;border-radius:4px;box-shadow:inset 0 -2px 0 0 #0000001a;box-sizing:border-box;color:#303030;cursor:pointer;display:flex;min-height:32px;padding:0 .5em;transition:var(--yoast-transition-default);width:32px}.emoji-select-button svg{width:100%}.emoji-select-button-pressed,.emoji-select-button:hover{background-color:#fff;border-color:var(--yoast-color-border--default)}.yst-replacevar{position:relative}.yst-replacevar .emoji-select-popover{font-size:15px;margin-top:0;right:0}.rtl .yst-replacevar .emoji-select-popover{left:0;right:unset}.yst-replacevar .emoji-select-popover h3{font-size:1em;margin:0 0 .3em}.yst-replacevar .emoji-select-popover li{margin-bottom:unset}.yst-replacevar .emoji-select-popover .single-emoji{height:37.5px;width:37.5px}.yst-replacevar .emoji-select-popover .single-emoji span{font-size:20px!important}.yst-replacevar{color:#303030}.yst-replacevar .emoji-select-popover-nav{width:auto}.yst-replacevar .emoji-suggestions{padding:6px 0}.rtl .yst-replacevar .emoji-suggestions{right:0}.yst-replacevar .emoji-suggestion-item-wrapper{align-items:center;display:flex;padding:6px 10px}.yst-replacevar .emoji-suggestion-item-wrapper span{font-size:18px;line-height:1}.yst-replacevar .emoji-suggestion-item-wrapper .emoji-suggestion-item-text{font-size:13px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-elementor-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-elementor-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.elementor-panel .yoast-link-suggestions a{color:var(--yoast-color-link)}.yoast-link-suggestion__wrapper .yoast-link-suggestion__container{max-width:100%;padding-left:6px}.yoast-link-suggestion__wrapper .yoast-link-suggestion__container>a{width:-moz-fit-content;width:fit-content}.yoast-link-suggestion__wrapper .yoast-link-suggestion__copy{margin-right:auto}.yoast .notice{border:1px solid #ccd0d4;border-right-width:4px;box-shadow:0 1px 1px #0000000a;padding:1px 12px}.yoast .notice-warning{border-right-color:#ffb900}.yoast .notice-warning.notice-alt{background-color:#fff8e5}.yoast .notice p,.yoast .notice-title{margin:.5em 0;padding:2px}.yoast-links-suggestions-notice .button{background-color:var(--yoast-color-secondary);border:1px solid #0003;border-radius:4px;box-shadow:inset 0 -2px 0 #0000001a;color:var(--yoast-color-dark);cursor:pointer;font-size:14px;line-height:1.2;padding:10px 12px 12px;transition:background-color .15s ease-out 0s}.yoast-links-suggestions-notice .button:focus{box-shadow:var(--yoast-color-focus);outline:none}p.yoast-redirect-notification-modal-url{margin-bottom:0}.yoast .yoast-data-model{color:var(--yoast-elementor-color-paragraph)}@media(hover:hover){.yoast .yoast-links-suggestions-notice .button:active,.yoast-links-suggestions-notice .button:not(:disabled):hover{background-color:var(--yoast-color-secondary-darker);border:1px solid #0003}}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-elementor-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-elementor-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.elementor-panel .yoast-link-suggestions a{color:var(--yoast-color-link)}.yoast-link-suggestion__wrapper .yoast-link-suggestion__container{max-width:100%;padding-right:6px}.yoast-link-suggestion__wrapper .yoast-link-suggestion__container>a{width:-moz-fit-content;width:fit-content}.yoast-link-suggestion__wrapper .yoast-link-suggestion__copy{margin-left:auto}.yoast .notice{border:1px solid #ccd0d4;border-left-width:4px;box-shadow:0 1px 1px #0000000a;padding:1px 12px}.yoast .notice-warning{border-left-color:#ffb900}.yoast .notice-warning.notice-alt{background-color:#fff8e5}.yoast .notice p,.yoast .notice-title{margin:.5em 0;padding:2px}.yoast-links-suggestions-notice .button{background-color:var(--yoast-color-secondary);border:1px solid #0003;border-radius:4px;box-shadow:inset 0 -2px 0 #0000001a;color:var(--yoast-color-dark);cursor:pointer;font-size:14px;line-height:1.2;padding:10px 12px 12px;transition:background-color .15s ease-out 0s}.yoast-links-suggestions-notice .button:focus{box-shadow:var(--yoast-color-focus);outline:none}p.yoast-redirect-notification-modal-url{margin-bottom:0}.yoast .yoast-data-model{color:var(--yoast-elementor-color-paragraph)}@media(hover:hover){.yoast .yoast-links-suggestions-notice .button:active,.yoast-links-suggestions-notice .button:not(:disabled):hover{background-color:var(--yoast-color-secondary-darker);border:1px solid #0003}}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-metabox-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-metabox-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.yoast-link-suggestion-icon.yoast-link-suggestion-icon{box-sizing:border-box}.yoast-link-suggestions--loading{display:block;height:75px;margin:0 auto;padding:3em 0;width:75px}#yoast_internal_linking .inside{margin-top:0;padding-bottom:25px;padding-top:10px}.yoast-links-suggestions-notice{margin:5px 0 2px}body.is-dark-theme .wp-block .yoast-links-suggestions-notice{color:#000}.yoast-redirect-notification-modal-url{margin:0}.yoast-redirect-notification-modal-buttons{align-items:center;display:flex;margin-top:16px}.yoast-redirect-notification-modal-buttons button{margin-left:16px}#wpseosynonyms{margin-bottom:2em}.yoast-section{background-color:#fff;box-shadow:0 1px 2px #0003;padding:0 20px 15px;position:relative}.yoast-section__heading{color:#555;font-family:Open Sans,sans-serif;font-size:.9rem;font-weight:300;margin:0 -20px 15px;padding:8px 20px}.yoast-section__heading-icon{background-position:right 20px top .6em;background-repeat:no-repeat;background-size:16px;padding-right:44px}.yoast-section,.yoast-section *,.yoast-section :after,.yoast-section :before,.yoast-section:after,.yoast-section:before{box-sizing:border-box}.editable-preview h3:first-child{margin-top:0}.wpseo-form .snippet-editor__label{display:inline-block;margin-top:24px;width:auto}.wpseo-form .snippet-editor__label:first-child{margin-top:0}.wpseo-form .yoast_help.yoast-help-button{margin-top:20px}.wpseo-form .snippet-editor__label:first-child+.yoast-help-button{margin-top:-4px}.remove-keyword{background:#0000;border:none;cursor:pointer;display:inline-block;margin:0 -3px 0 0;padding:5px 6px 6px}.remove-keyword span{border:1px solid #0000;border-radius:100%;color:#686868;display:inline-block;font-size:12px;font-weight:400;height:16px;line-height:15px;padding:0;text-align:center;vertical-align:top;width:16px}.remove-keyword:focus{box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px #1e8cbecc;outline:none}.remove-keyword:focus span,.remove-keyword:hover span{background-color:#dc3232;color:#fff}.wrap .wpseo-notice-breakout-inside{margin:0 -12px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-metabox-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-metabox-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.yoast-link-suggestion-icon.yoast-link-suggestion-icon{box-sizing:border-box}.yoast-link-suggestions--loading{display:block;height:75px;margin:0 auto;padding:3em 0;width:75px}#yoast_internal_linking .inside{margin-top:0;padding-bottom:25px;padding-top:10px}.yoast-links-suggestions-notice{margin:5px 0 2px}body.is-dark-theme .wp-block .yoast-links-suggestions-notice{color:#000}.yoast-redirect-notification-modal-url{margin:0}.yoast-redirect-notification-modal-buttons{align-items:center;display:flex;margin-top:16px}.yoast-redirect-notification-modal-buttons button{margin-right:16px}#wpseosynonyms{margin-bottom:2em}.yoast-section{background-color:#fff;box-shadow:0 1px 2px #0003;padding:0 20px 15px;position:relative}.yoast-section__heading{color:#555;font-family:Open Sans,sans-serif;font-size:.9rem;font-weight:300;margin:0 -20px 15px;padding:8px 20px}.yoast-section__heading-icon{background-position:left 20px top .6em;background-repeat:no-repeat;background-size:16px;padding-left:44px}.yoast-section,.yoast-section *,.yoast-section :after,.yoast-section :before,.yoast-section:after,.yoast-section:before{box-sizing:border-box}.editable-preview h3:first-child{margin-top:0}.wpseo-form .snippet-editor__label{display:inline-block;margin-top:24px;width:auto}.wpseo-form .snippet-editor__label:first-child{margin-top:0}.wpseo-form .yoast_help.yoast-help-button{margin-top:20px}.wpseo-form .snippet-editor__label:first-child+.yoast-help-button{margin-top:-4px}.remove-keyword{background:#0000;border:none;cursor:pointer;display:inline-block;margin:0 0 0 -3px;padding:5px 6px 6px}.remove-keyword span{border:1px solid #0000;border-radius:100%;color:#686868;display:inline-block;font-size:12px;font-weight:400;height:16px;line-height:15px;padding:0;text-align:center;vertical-align:top;width:16px}.remove-keyword:focus{box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px #1e8cbecc;outline:none}.remove-keyword:focus span,.remove-keyword:hover span{background-color:#dc3232;color:#fff}.wrap .wpseo-notice-breakout-inside{margin:0 -12px}
|
||||
@@ -0,0 +1 @@
|
||||
.fixed th.column-wpseo-cornerstone,.fixed th.column-wpseo-inclusive-language,.fixed th.column-wpseo-linked{padding:0;width:3em}th.column-wpseo-cornerstone .yoast-tooltip,th.column-wpseo-inclusive-language .yoast-tooltip{display:inline-block;overflow:visible;padding:8px 0;vertical-align:middle}th.column-wpseo-cornerstone .yoast-tooltip:after,th.column-wpseo-inclusive-language .yoast-tooltip:after{max-width:60px;white-space:break-spaces}td.column-wpseo-cornerstone,td.column-wpseo-inclusive-language{padding:8px 0 8px 10px}.manage-column .yoast-column-cornerstone:before{background:#0000 url(../../../assets/images/cornerstone-icon.svg) no-repeat 100% 3px;background-size:20px}.manage-column .yoast-column-inclusive-language:before{background:#0000 url(../../../assets/images/inclusive-language-icon.svg) no-repeat 100% 3px;background-size:20px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-post-overview-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-post-overview-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.fixed th.column-wpseo-cornerstone,.fixed th.column-wpseo-inclusive-language,.fixed th.column-wpseo-linked{padding:0;width:3em}th.column-wpseo-cornerstone .yoast-tooltip,th.column-wpseo-inclusive-language .yoast-tooltip{display:inline-block;overflow:visible;padding:8px 0;vertical-align:middle}th.column-wpseo-cornerstone .yoast-tooltip:after,th.column-wpseo-inclusive-language .yoast-tooltip:after{max-width:60px;white-space:break-spaces}td.column-wpseo-cornerstone,td.column-wpseo-inclusive-language{padding:8px 10px 8px 0}.manage-column .yoast-column-cornerstone:before{background:#0000 url(../../../assets/images/cornerstone-icon.svg) no-repeat 0 3px;background-size:20px}.manage-column .yoast-column-inclusive-language:before{background:#0000 url(../../../assets/images/inclusive-language-icon.svg) no-repeat 0 3px;background-size:20px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-redirects-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-redirects-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.redirect_form_row.field_error label{color:#dc3232}.redirect_form_row.field_error input{border-color:#dc3232}tbody .column-old .val:after{color:#d3d3d3;content:"/"}tbody .column-new .has-trailing-slash:after,tbody .column-new .val:before,tbody .column-old .val:before{content:"/"}tbody .column-new .remove-slashes:after,tbody .column-new .remove-slashes:before,tbody .column-old .remove-slashes:after,tbody .column-old .remove-slashes:before{content:none}.wpseo-redirect-clear{clear:both}.redirect_htaccess_disabled{opacity:.5}#inline-edit td{padding:0 20px}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-redirects-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-redirects-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.redirect_form_row.field_error label{color:#dc3232}.redirect_form_row.field_error input{border-color:#dc3232}tbody .column-old .val:after{color:#d3d3d3;content:"/"}tbody .column-new .has-trailing-slash:after,tbody .column-new .val:before,tbody .column-old .val:before{content:"/"}tbody .column-new .remove-slashes:after,tbody .column-new .remove-slashes:before,tbody .column-old .remove-slashes:after,tbody .column-old .remove-slashes:before{content:none}.wpseo-redirect-clear{clear:both}.redirect_htaccess_disabled{opacity:.5}#inline-edit td{padding:0 20px}
|
||||
@@ -0,0 +1 @@
|
||||
.wp-block-yoast-job-employment-type .components-input-control__container{width:50%}.yoast-job-block__salary .wp-block-yoast-job-salary-range{max-width:25%}.yoast-job-block__salary .wp-block-yoast-job-salary-range input[type=number]{-moz-appearance:textfield}.editor-styles-wrapper .wp-block-yoast-job-salary [data-block]{margin-bottom:0;margin-top:0}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-schema-blocks-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-schema-blocks-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.wp-block-yoast-job-employment-type .components-input-control__container{width:50%}.yoast-job-block__salary .wp-block-yoast-job-salary-range{max-width:25%}.yoast-job-block__salary .wp-block-yoast-job-salary-range input[type=number]{-moz-appearance:textfield}.editor-styles-wrapper .wp-block-yoast-job-salary [data-block]{margin-bottom:0;margin-top:0}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-settings-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-settings-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#yoast-seo-settings .emoji-select-button{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(203 213 225/var(--tw-border-opacity));border-radius:.375rem;border-width:1px;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);color:rgb(30 41 59/var(--tw-text-opacity));cursor:pointer;display:inline-flex;font-size:.8125rem;font-weight:500;line-height:1rem;min-height:34px;text-align:center;-webkit-text-decoration-line:none;text-decoration-line:none;width:34px}#yoast-seo-settings .emoji-select-button:hover{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity));color:rgb(30 41 59/var(--tw-text-opacity))}#yoast-seo-settings .emoji-select-button:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-opacity:1;--tw-ring-color:rgb(166 30 105/var(--tw-ring-opacity));--tw-ring-offset-width:2px;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000;outline-offset:2px}#yoast-seo-settings .emoji-select-button>svg{height:1.25rem;margin-right:auto;margin-left:auto;width:1.25rem}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-settings-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-settings-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#yoast-seo-settings .emoji-select-button{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:rgb(255 255 255/var(--tw-bg-opacity));border-color:rgb(203 213 225/var(--tw-border-opacity));border-radius:.375rem;border-width:1px;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);color:rgb(30 41 59/var(--tw-text-opacity));cursor:pointer;display:inline-flex;font-size:.8125rem;font-weight:500;line-height:1rem;min-height:34px;text-align:center;-webkit-text-decoration-line:none;text-decoration-line:none;width:34px}#yoast-seo-settings .emoji-select-button:hover{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity));color:rgb(30 41 59/var(--tw-text-opacity))}#yoast-seo-settings .emoji-select-button:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-opacity:1;--tw-ring-color:rgb(166 30 105/var(--tw-ring-opacity));--tw-ring-offset-width:2px;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000;outline-offset:2px}#yoast-seo-settings .emoji-select-button>svg{height:1.25rem;margin-left:auto;margin-right:auto;width:1.25rem}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-tailwind-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-tailwind-2290-rtl.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-tailwind-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-tailwind-2290.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-thank-you-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-thank-you-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.admin_page_wpseo_installation_successful{background:#fff}.admin_page_wpseo_installation_successful h1{color:#a4286a;font-size:40px;font-weight:400;margin-top:0;text-align:center}.admin_page_wpseo_installation_successful .yoast-image{display:block;margin:100px auto 24px;max-width:200px}.admin_page_wpseo_installation_successful #wpcontent{padding-left:20px}@media screen and (max-width:782px){.admin_page_wpseo_installation_successful #wpcontent{padding-left:10px}}.yoast-whats-next{font-size:16px;text-align:center}.yoast-grid{grid-gap:32px;display:grid;grid-template-columns:1fr 1fr 1fr 1fr;margin:48px auto 0;max-width:1120px;text-align:center;width:100%}@media screen and (max-width:1250px){.yoast-grid{grid-template-columns:1fr 1fr;max-width:768px}}@media screen and (max-width:600px){.yoast-grid{grid-template-columns:1fr;max-width:480px}}.yoast-grid div{display:flex;flex-direction:column}.yoast-grid h3{color:#a4286a;font-size:19px;font-weight:400;line-height:26px;margin-top:0}.yoast-grid .yoast-button{margin-top:auto;width:calc(100% - 26px)}.yoast-grid img{margin-bottom:16px;max-width:100%}.yoast-grid p{margin:16px 0}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-thank-you-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-thank-you-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.admin_page_wpseo_installation_successful{background:#fff}.admin_page_wpseo_installation_successful h1{color:#a4286a;font-size:40px;font-weight:400;margin-top:0;text-align:center}.admin_page_wpseo_installation_successful .yoast-image{display:block;margin:100px auto 24px;max-width:200px}.admin_page_wpseo_installation_successful #wpcontent{padding-right:20px}@media screen and (max-width:782px){.admin_page_wpseo_installation_successful #wpcontent{padding-right:10px}}.yoast-whats-next{font-size:16px;text-align:center}.yoast-grid{grid-gap:32px;display:grid;grid-template-columns:1fr 1fr 1fr 1fr;margin:48px auto 0;max-width:1120px;text-align:center;width:100%}@media screen and (max-width:1250px){.yoast-grid{grid-template-columns:1fr 1fr;max-width:768px}}@media screen and (max-width:600px){.yoast-grid{grid-template-columns:1fr;max-width:480px}}.yoast-grid div{display:flex;flex-direction:column}.yoast-grid h3{color:#a4286a;font-size:19px;font-weight:400;line-height:26px;margin-top:0}.yoast-grid .yoast-button{margin-top:auto;width:calc(100% - 26px)}.yoast-grid img{margin-bottom:16px;max-width:100%}.yoast-grid p{margin:16px 0}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-workouts-2290-rtl.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-workouts-2290-rtl.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#wpseo-workouts-container h1,#wpseo-workouts-container h3{color:#a4286a;font-weight:500}#wpseo-workouts-container h2{font-size:12px;text-transform:uppercase}.workflow tr.cornerstone{font-weight:700}#wpseo-workouts-container hr{margin-bottom:24px}#wpseo-workouts-container progress{margin:16px 0 8px}#wpseo-workouts-container div.card{border:0;border-radius:8px;box-shadow:0 1px 3px 0 #0000001a,0 1px 2px 0 #0000000f;max-width:720px;padding:24px;width:100%}#wpseo-workouts-container div.card>h2{margin:0 0 1em}#wpseo-workouts-container div.card.card-small{display:flex;flex-direction:column;max-width:320px}#wpseo-workouts-container div.card.card-small>span{margin-top:auto}#wpseo-workouts-container table button{margin:2px}.workflow{counter-reset:line-number;list-style:none;margin-right:48px}.workflow li{counter-increment:line-number;padding-bottom:16px;position:relative}.workflow>li:before{background:#a4286a;bottom:-20px;content:"";right:-33px;position:absolute;top:0;width:2px}.workflow>li:last-of-type:before{display:none}.workflow>li:after{background:#fff;border:2px solid #a4286a;border-radius:100%;color:#a4286a;content:counter(line-number);display:block;height:28px;right:-48px;line-height:28px;position:absolute;text-align:center;top:-8px;width:28px}.workflow li.finished:after{background:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' fill='none' stroke='%23FFF' height='24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 13 4 4L19 7'/%3E%3C/svg%3E") #a4286a;background-position:50%;background-repeat:no-repeat;background-size:20px 20px;content:""}.workflow li.finished p,.workflow li.finished table{opacity:.5}.workflow li img{max-width:100%}.workflow li img.workflow__image{max-height:100px;max-width:100px}.workflow li #react-select-2-input{box-shadow:none!important}.workflows__index__grid{display:grid;gap:16px;grid-template-columns:320px 320px}.workflow__grid{display:grid;gap:8px;grid-template-columns:auto 100px}.workflow__grid>div:last-of-type{display:flex;flex-wrap:wrap;justify-content:flex-end}table.yoast_help.yoast_link_suggestions thead td{padding:16px 8px}table.yoast_help.yoast_link_suggestions td{vertical-align:middle}table.yoast_help th.divider{text-align:center}.workflow table.yoast_help td{vertical-align:middle}.workflow table.yoast_help.yoast_link_suggestions td div{display:inline-block}.workflow table.yoast_help.yoast_link_suggestions td strong{display:inline-block;margin-left:8px}.components-modal__header{height:72px;padding:0 24px}.components-modal__header .components-modal__header-heading{color:#a4286a;font-size:20px;font-weight:400;line-height:1.2;margin:0}.components-modal__header .yoast-icon{background-color:var(--yoast-color-primary);display:inline-block;height:20px;margin-left:8px;mask-image:var(--yoast-svg-icon-yoast);-webkit-mask-image:var(--yoast-svg-icon-yoast);mask-size:100% 100%;-webkit-mask-size:100% 100%;width:20px}.components-modal__content{padding:0 24px 24px}.components-modal__content input[type=text]{max-width:400px;width:100%}.components-modal__frame.yoast__workout{max-width:720px}.yoast__redirect-suggestions{line-height:2}
|
||||
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-workouts-2290.css
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/css/dist/premium-workouts-2290.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#wpseo-workouts-container h1,#wpseo-workouts-container h3{color:#a4286a;font-weight:500}#wpseo-workouts-container h2{font-size:12px;text-transform:uppercase}.workflow tr.cornerstone{font-weight:700}#wpseo-workouts-container hr{margin-bottom:24px}#wpseo-workouts-container progress{margin:16px 0 8px}#wpseo-workouts-container div.card{border:0;border-radius:8px;box-shadow:0 1px 3px 0 #0000001a,0 1px 2px 0 #0000000f;max-width:720px;padding:24px;width:100%}#wpseo-workouts-container div.card>h2{margin:0 0 1em}#wpseo-workouts-container div.card.card-small{display:flex;flex-direction:column;max-width:320px}#wpseo-workouts-container div.card.card-small>span{margin-top:auto}#wpseo-workouts-container table button{margin:2px}.workflow{counter-reset:line-number;list-style:none;margin-left:48px}.workflow li{counter-increment:line-number;padding-bottom:16px;position:relative}.workflow>li:before{background:#a4286a;bottom:-20px;content:"";left:-33px;position:absolute;top:0;width:2px}.workflow>li:last-of-type:before{display:none}.workflow>li:after{background:#fff;border:2px solid #a4286a;border-radius:100%;color:#a4286a;content:counter(line-number);display:block;height:28px;left:-48px;line-height:28px;position:absolute;text-align:center;top:-8px;width:28px}.workflow li.finished:after{background:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' fill='none' stroke='%23FFF' height='24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 13 4 4L19 7'/%3E%3C/svg%3E") #a4286a;background-position:50%;background-repeat:no-repeat;background-size:20px 20px;content:""}.workflow li.finished p,.workflow li.finished table{opacity:.5}.workflow li img{max-width:100%}.workflow li img.workflow__image{max-height:100px;max-width:100px}.workflow li #react-select-2-input{box-shadow:none!important}.workflows__index__grid{display:grid;gap:16px;grid-template-columns:320px 320px}.workflow__grid{display:grid;gap:8px;grid-template-columns:auto 100px}.workflow__grid>div:last-of-type{display:flex;flex-wrap:wrap;justify-content:flex-end}table.yoast_help.yoast_link_suggestions thead td{padding:16px 8px}table.yoast_help.yoast_link_suggestions td{vertical-align:middle}table.yoast_help th.divider{text-align:center}.workflow table.yoast_help td{vertical-align:middle}.workflow table.yoast_help.yoast_link_suggestions td div{display:inline-block}.workflow table.yoast_help.yoast_link_suggestions td strong{display:inline-block;margin-right:8px}.components-modal__header{height:72px;padding:0 24px}.components-modal__header .components-modal__header-heading{color:#a4286a;font-size:20px;font-weight:400;line-height:1.2;margin:0}.components-modal__header .yoast-icon{background-color:var(--yoast-color-primary);display:inline-block;height:20px;margin-right:8px;mask-image:var(--yoast-svg-icon-yoast);-webkit-mask-image:var(--yoast-svg-icon-yoast);mask-size:100% 100%;-webkit-mask-size:100% 100%;width:20px}.components-modal__content{padding:0 24px 24px}.components-modal__content input[type=text]{max-width:400px;width:100%}.components-modal__frame.yoast__workout{max-width:720px}.yoast__redirect-suggestions{line-height:2}
|
||||
@@ -0,0 +1 @@
|
||||
<svg role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.12 19.5"><g fill="#444"><path d="M13.78.98a1.94 1.94 0 0 0-3.38 0L6.75 7.3h10.68L13.78.98ZM8.85 6.26l2.68-4.63c.12-.2.33-.33.56-.33s.45.12.56.33l2.68 4.63H8.85ZM6.55 7.95l-3.69 6.5h.01l-.02.04h8.49V7.95H6.55Zm-1.59 5.47 2.42-4.41h2.71v4.4H4.97ZM12.73 7.95v6.54l8.61-.04-3.69-6.5h-4.92Zm1.26 5.46v-4.4h2.84l2.42 4.41H14ZM2.44 15.33 0 19.5h24.12l-2.26-4.17H2.44Zm18.53 1.09 1.02 1.97-19.87.06 1.1-1.97 17.75-.06Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 527 B |
@@ -0,0 +1 @@
|
||||
<svg role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="#444" d="M17 .75H3C1.48.75.25 1.98.25 3.5v8c0 1.52 1.23 2.75 2.75 2.75h4.69l4.78 4.78c.14.14.34.22.53.22a.753.753 0 0 0 .75-.75v-4.25H17c1.52 0 2.75-1.23 2.75-2.75v-8c0-1.52-1.23-2.75-2.75-2.75Zm0 1.5c.69 0 1.25.56 1.25 1.25v8c0 .69-.56 1.25-1.25 1.25h-4c-.41 0-.75.34-.75.75v3.19l-3.72-3.72a.75.75 0 0 0-.53-.22H3c-.69 0-1.25-.56-1.25-1.25v-8c0-.69.56-1.25 1.25-1.25"/><path fill="#444" fill-rule="evenodd" d="M6.29 6.35c0-1.02.87-1.85 1.93-1.85.8 0 1.48.46 1.78 1.13.29-.66.98-1.13 1.78-1.13 1.07 0 1.93.83 1.93 1.85 0 2.98-3.71 4.95-3.71 4.95S6.29 9.33 6.29 6.35Z"/></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
70
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/ai-generator-2290.min.js
vendored
Normal file
70
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/ai-generator-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
64
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/blocks-2290.min.js
vendored
Normal file
64
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/blocks-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/dynamic-blocks-2290.min.js
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/dynamic-blocks-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/frontend-inspector-2290.min.js
vendored
Normal file
1
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/frontend-inspector-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/introductions-2290.min.js
vendored
Normal file
6
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/introductions-2290.min.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var a in s)e.o(s,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:s[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.domReady;var s=e.n(t);const a=window.wp.hooks,r=window.lodash,i=window.yoast.reduxJsToolkit,n="adminUrl",o=(0,i.createSlice)({name:n,initialState:"",reducers:{setAdminUrl:(e,{payload:t})=>t}}),l=(o.getInitialState,{selectAdminUrl:e=>(0,r.get)(e,n,"")});l.selectAdminLink=(0,i.createSelector)([l.selectAdminUrl,(e,t)=>t],((e,t)=>{try{return new URL(t,e).href}catch(t){return e}})),o.actions,o.reducer;const c=window.wp.apiFetch;var m=e.n(c);const d="hasConsent",u=`${d}/storeConsent`,y=(0,i.createSlice)({name:d,initialState:!1,reducers:{giveAiGeneratorConsent:(e,{payload:t})=>t}}),p=y.getInitialState,w={selectHasAiGeneratorConsent:e=>(0,r.get)(e,d,!1)},g={...y.actions,storeAiGeneratorConsent:function*(e){try{yield{type:u,payload:e}}catch(e){return!1}return yield{type:`${d}/giveAiGeneratorConsent`,payload:e},!0}},h={[u]:({payload:e})=>m()({path:"yoast/v1/ai_generator/consent",method:"POST",data:{consent:e},parse:!1})},v=y.reducer,f="pluginUrl",b=(0,i.createSlice)({name:f,initialState:"",reducers:{setPluginUrl:(e,{payload:t})=>t}}),E=b.getInitialState,k={selectPluginUrl:e=>(0,r.get)(e,f,"")};k.selectImageLink=(0,i.createSelector)([k.selectPluginUrl,(e,t,s="assets/images")=>s,(e,t)=>t],((e,t,s)=>[(0,r.trimEnd)(e,"/"),(0,r.trim)(t,"/"),(0,r.trimStart)(s,"/")].join("/")));const x=b.actions,S=b.reducer,P=window.wp.url,_="linkParams",N=(0,i.createSlice)({name:_,initialState:{},reducers:{setLinkParams:(e,{payload:t})=>t}}),C=(N.getInitialState,{selectLinkParam:(e,t,s={})=>(0,r.get)(e,`${_}.${t}`,s),selectLinkParams:e=>(0,r.get)(e,_,{})});C.selectLink=(0,i.createSelector)([C.selectLinkParams,(e,t)=>t],((e,t)=>(0,P.addQueryArgs)(t,e))),N.actions,N.reducer;const R="loading",L="showPlay",I="askPermission",q="isPlaying",A="wistiaEmbedPermission",$=(0,i.createSlice)({name:A,initialState:{value:!1,status:"idle",error:{}},reducers:{setWistiaEmbedPermissionValue:(e,{payload:t})=>{e.value=Boolean(t)}},extraReducers:e=>{e.addCase(`${A}/request`,(e=>{e.status=R})),e.addCase(`${A}/success`,((e,{payload:t})=>{e.status="success",e.value=Boolean(t&&t.value)})),e.addCase(`${A}/error`,((e,{payload:t})=>{e.status="error",e.value=Boolean(t&&t.value),e.error={code:(0,r.get)(t,"error.code",500),message:(0,r.get)(t,"error.message","Unknown")}}))}}),O=($.getInitialState,$.actions,$.reducer,window.React),U=window.wp.data,B=window.wp.element,G=O.forwardRef((function(e,t){return O.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",fill:"currentColor","aria-hidden":"true",ref:t},e),O.createElement("path",{fillRule:"evenodd",d:"M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z",clipRule:"evenodd"}))})),T=window.wp.i18n,M=window.yoast.uiLibrary,j=window.yoast.propTypes;var W=e.n(j);const Y=({onGiveConsent:e,learnMoreLink:t,privacyPolicyLink:s,termsOfServiceLink:a,thumbnail:r,wistiaEmbedPermission:i})=>{const{onClose:n,initialFocus:o}=(0,M.useModalContext)(),[l,c]=(0,M.useToggleState)(!1),m=(0,B.useCallback)((()=>{n(),e(!0)}),[e,n]),d=(0,B.createInterpolateElement)((0,T.sprintf)(/* translators: %1$s and %2$s are a set of anchor tags and %3$s and %4$s are a set of anchor tags. */ /* translators: %1$s and %2$s are a set of anchor tags and %3$s and %4$s are a set of anchor tags. */
|
||||
(0,T.__)("I approve the %1$sTerms of Service%2$s & %3$sPrivacy Policy%4$s of the Yoast AI service. This includes consenting to the collection and use of data to improve user experience.","wordpress-seo-premium"),"<a1>","</a1>","<a2>","</a2>"),{a1:(0,O.createElement)(F,{href:a}),a2:(0,O.createElement)(F,{href:s})});return(0,O.createElement)("div",{className:"yst-flex yst-flex-col yst-items-center yst-p-10"},(0,O.createElement)("div",{className:"yst-relative yst-w-full"},(0,O.createElement)(H,{videoId:"vmrahpfjxp",thumbnail:r,wistiaEmbedPermission:i}),(0,O.createElement)(M.Badge,{className:"yst-absolute yst-top-0 yst-right-2 yst-mt-2 yst-ml-2",variant:"info"},"Beta")),(0,O.createElement)("div",{className:"yst-mt-6 yst-text-xs yst-font-medium"},(0,O.createElement)("span",{className:"yst-introduction-modal-uppercase"},(0,T.sprintf)(/* translators: %1$s expands to Yoast SEO Premium. */ /* translators: %1$s expands to Yoast SEO Premium. */
|
||||
(0,T.__)("New to %1$s","wordpress-seo-premium"),"Yoast SEO Premium"))," ",(0,O.createElement)("span",{className:"yst-uppercase yst-text-slate-700"},"21.0")),(0,O.createElement)("div",{className:"yst-mt-4 yst-mx-1.5 yst-text-center"},(0,O.createElement)("h3",{className:"yst-text-slate-900 yst-text-lg yst-font-medium"},(0,T.__)("Generate titles & descriptions with Yoast AI!","wordpress-seo-premium")),(0,O.createElement)("div",{className:"yst-mt-2 yst-text-slate-600 yst-text-sm"},(0,B.createInterpolateElement)((0,T.sprintf)(/* translators: %1$s and %2$s are anchor tag; %3$s is the arrow icon. */ /* translators: %1$s and %2$s are anchor tag; %3$s is the arrow icon. */
|
||||
(0,T.__)("Speed up your workflow with generative AI. Get high-quality title and description suggestions for your search and social appearance. %1$sLearn more%2$s%3$s","wordpress-seo-premium"),"<a>","<ArrowNarrowRightIcon />","</a>"),{a:(0,O.createElement)(F,{href:t,className:"yst-inline-flex yst-items-center yst-gap-1 yst-no-underline yst-font-medium",variant:"primary"}),ArrowNarrowRightIcon:(0,O.createElement)(G,{className:"yst-w-4 yst-h-4 rtl:yst-rotate-180"})}))),(0,O.createElement)("div",{className:"yst-flex yst-w-full yst-mt-6"},(0,O.createElement)("hr",{className:"yst-w-full yst-text-gray-200"})),(0,O.createElement)("div",{className:"yst-flex yst-items-start yst-mt-4"},(0,O.createElement)("input",{type:"checkbox",id:"yst-ai-consent-checkbox",name:"yst-ai-consent-checkbox",checked:l,value:l?"true":"false",onChange:c,className:"yst-checkbox__input",ref:o}),(0,O.createElement)("label",{htmlFor:"yst-ai-consent-checkbox",className:"yst-label yst-checkbox__label yst-text-xs yst-font-normal yst-text-slate-500"},d)),(0,O.createElement)("div",{className:"yst-w-full yst-flex yst-mt-4"},(0,O.createElement)(M.Button,{as:"button",className:"yst-grow",size:"large",disabled:!l,onClick:m},(0,T.__)("Start generating","wordpress-seo-premium"))),(0,O.createElement)(M.Button,{as:"button",className:"yst-mt-4",variant:"tertiary",onClick:n},(0,T.__)("Close","wordpress-seo-premium")))};Y.propTypes={onGiveConsent:W().func.isRequired,learnMoreLink:W().string.isRequired,privacyPolicyLink:W().string.isRequired,termsOfServiceLink:W().string.isRequired,thumbnail:W().shape({src:W().string.isRequired,width:W().string,height:W().string}).isRequired,wistiaEmbedPermission:W().shape({value:W().bool.isRequired,status:W().string.isRequired,set:W().func.isRequired}).isRequired};const F=({href:e,children:t,...s})=>(0,O.createElement)(M.Link,{target:"_blank",rel:"noopener noreferrer",...s,href:e},t,(0,O.createElement)("span",{className:"yst-sr-only"},/* translators: Hidden accessibility text. */ /* translators: Hidden accessibility text. */
|
||||
(0,T.__)("(Opens in a new browser tab)","wordpress-seo-premium")));F.propTypes={href:W().string.isRequired,children:W().node},F.defaultProps={children:null};const z=window.yoast.reactHelmet,H=({videoId:e,thumbnail:t,wistiaEmbedPermission:s})=>{const[a,r]=(0,B.useState)(s.value?q:L),i=(0,B.useCallback)((()=>r(q)),[r]),n=(0,B.useCallback)((()=>{s.value?i():r(I)}),[s.value,i,r]),o=(0,B.useCallback)((()=>r(L)),[r]),l=(0,B.useCallback)((()=>{s.set(!0),i()}),[s.set,i]);return(0,O.createElement)(B.Fragment,null,s.value&&(0,O.createElement)(z.Helmet,null,(0,O.createElement)("script",{src:"https://fast.wistia.com/assets/external/E-v1.js",async:!0})),(0,O.createElement)("div",{className:"yst-relative yst-w-full yst-h-0 yst-pt-[56.25%] yst-overflow-hidden yst-rounded-md yst-drop-shadow-md yst-bg-white"},a===L&&(0,O.createElement)("button",{className:"yst-absolute yst-inset-0 yst-button yst-p-0 yst-border-none yst-bg-white yst-transition-opacity yst-duration-1000 yst-opacity-100",onClick:n},(0,O.createElement)("img",{className:"yst-w-full yst-h-auto",alt:"",loading:"lazy",decoding:"async",...t})),a===I&&(0,O.createElement)("div",{className:"yst-absolute yst-inset-0 yst-flex yst-flex-col yst-items-center yst-justify-center yst-bg-white"},(0,O.createElement)("p",{className:"yst-max-w-xs yst-mx-auto yst-text-center"},s.status===R&&(0,O.createElement)(M.Spinner,null),s.status!==R&&(0,T.sprintf)(/* translators: %1$s expands to Yoast SEO. %2$s expands to Wistia. */ /* translators: %1$s expands to Yoast SEO. %2$s expands to Wistia. */
|
||||
(0,T.__)("To see this video, you need to allow %1$s to load embedded videos from %2$s.","wordpress-seo-premium"),"Yoast SEO","Wistia")),(0,O.createElement)("div",{className:"yst-flex yst-mt-6 yst-gap-x-4"},(0,O.createElement)(M.Button,{type:"button",variant:"secondary",onClick:o,disabled:s.status===R},(0,T.__)("Deny","wordpress-seo-premium")),(0,O.createElement)(M.Button,{type:"button",variant:"primary",onClick:l,disabled:s.status===R},(0,T.__)("Allow","wordpress-seo-premium")))),s.value&&a===q&&(0,O.createElement)("div",{className:"yst-absolute yst-w-full yst-h-full yst-top-0 yst-left-0"},null===e&&(0,O.createElement)(M.Spinner,{className:"yst-h-full yst-mx-auto"}),null!==e&&(0,O.createElement)("div",{className:`wistia_embed wistia_async_${e} videoFoam=true`}))))};H.propTypes={videoId:W().string.isRequired,thumbnail:W().shape({src:W().string.isRequired,width:W().string,height:W().string}).isRequired,wistiaEmbedPermission:W().shape({value:W().bool.isRequired,status:W().string.isRequired,set:W().func.isRequired}).isRequired};const D="yoast-seo/introductions",V="yoast-seo-premium/introductions",J=()=>{const e=(0,U.useSelect)((e=>e(D).selectLink("https://yoa.st/ai-generator-learn-more")),[]),t=(0,U.useSelect)((e=>e(D).selectLink("https://yoa.st/ai-generator-privacy-policy")),[]),s=(0,U.useSelect)((e=>e(D).selectLink("https://yoa.st/ai-generator-terms-of-service")),[]),{storeAiGeneratorConsent:a}=(0,U.useDispatch)(V),r=(0,U.useSelect)((e=>e(D).selectImageLink("ai-generator-preview.png")),[]),i=(0,B.useMemo)((()=>({src:r,width:"432",height:"244"})),[r]),n=(0,U.useSelect)((e=>e(D).selectWistiaEmbedPermissionValue()),[]),o=(0,U.useSelect)((e=>e(D).selectWistiaEmbedPermissionStatus()),[]),{setWistiaEmbedPermission:l}=(0,U.useDispatch)(D),c=(0,B.useMemo)((()=>({value:n,status:o,set:l})),[n,o,l]);return(0,O.createElement)(Y,{onGiveConsent:a,learnMoreLink:e,privacyPolicyLink:t,termsOfServiceLink:s,thumbnail:i,wistiaEmbedPermission:c})},Q=()=>{const e=(0,r.get)(window,"YoastSEO._registerIntroductionComponent",null);null!==e?(((e={})=>{(0,U.register)((e=>(0,U.createReduxStore)(V,{actions:{...g,...x},selectors:{...w,...k},initialState:(0,r.merge)({},{[d]:p(),[f]:E()},e),reducer:(0,U.combineReducers)({[d]:v,[f]:S}),controls:{...h}}))(e))})({[f]:(0,r.get)(window,"wpseoPremiumIntroductions.pluginUrl","")}),e("ai-generate-titles-and-descriptions",J)):console.error("Warning: Premium introductions expected window.YoastSEO._registerIntroductionComponent to exist.")};s()((()=>{(0,a.didAction)("yoast.introductions.ready")?Q():(0,a.addAction)("yoast.introductions.ready","yoast/yoast-seo-premium/initializeIntroductions",Q)}))})();
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";const e=window.yoast.analysis,{readability:s}=e.assessments,{WordComplexityAssessment:t}=s,i={de:e.languageProcessing.languageHelpers.wordComplexityHelperGerman,en:e.languageProcessing.languageHelpers.wordComplexityHelperEnglish,es:e.languageProcessing.languageHelpers.wordComplexityHelperSpanish,fr:e.languageProcessing.languageHelpers.wordComplexityHelperFrench},n={de:e.languageProcessing.languageConfigs.wordComplexityConfigGerman,en:e.languageProcessing.languageConfigs.wordComplexityConfigEnglish,es:e.languageProcessing.languageConfigs.wordComplexityConfigSpanish,fr:e.languageProcessing.languageConfigs.wordComplexityConfigFrench},{seo:r}=e.assessments,{KeyphraseDistributionAssessment:g}=r,{readability:o,seo:a}=e.assessments,l="YoastSEOPremium";(new class{constructor(){this._worker=analysisWorker}register(){this._worker.registerMessageHandler("initialize",this.initialize.bind(this),l)}initialize({options:s}){if(s.isTitleAssessmentAvailable){const{TextTitleAssessment:e}=a,s=new e;this._worker.registerAssessment("TextTitleAssessment",s,l,"seo")}!function(s,t){const i=new g;s.registerResearch("keyphraseDistribution",e.languageProcessing.researches.keyphraseDistribution),s.registerAssessment("keyphraseDistributionAssessment",i,t,"seo")}(this._worker,l);const r=s.language;(function(e){return(0,window.yoast.analysis.helpers.getLanguagesWithWordComplexity)().includes(e)})(r)&&function(s,r,g){const o=n[r],a=i[r],l=new t,m=new t({scores:{acceptableAmount:3}});s.registerResearcherConfig("wordComplexity",o),s.registerHelper("checkIfWordIsComplex",a),s.registerResearch("wordComplexity",e.languageProcessing.researches.wordComplexity),s.registerAssessment("wordComplexity",l,g,"readability"),s.registerAssessment("wordComplexity",m,g,"cornerstoneReadability")}(this._worker,r,l),s.isTextAlignmentAssessmentAvailable&&this.registerTextAlignmentAssessment()}registerTextAlignmentAssessment(){const{TextAlignmentAssessment:s}=o;this._textAlignmentAssessment=new s,this._worker.registerResearch("getLongCenterAlignedTexts",e.languageProcessing.researches.getLongCenterAlignedTexts),this._worker.registerAssessment("textAlignment",this._textAlignmentAssessment,l,"readability"),this._worker.registerAssessment("textAlignment",this._textAlignmentAssessment,l,"cornerstoneReadability")}}).register()})();
|
||||
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";const e=window.yoast.analysis,{getSentences:r,getWords:n}=e.languageProcessing,o=["I","you","me","my","mine","your","yours","myself","yourself","yourselves"],t=["we","they","their","theirs","themselves","us","our","ours","ourselves","it","its","itself"];function s(e,r){return e.filter((e=>r.includes(e)))}analysisWorker.registerResearch("textFormality",(function(e,l){const{charsPerSentence:a,wordsPerSentence:i,averageFormalPronouns:m,averageInformalPronouns:u}=function(e,l){const a=e.getText(),i=l.getHelper("memoizedTokenizer"),m=r(a,i),u=n(a);var g;return{charsPerSentence:(0===(g=m).length?0:g.map((e=>e.length)).reduce(((e,r)=>e+r)))/m.length,wordsPerSentence:u.length/m.length,averageFormalPronouns:s(u,t).length/u.length,averageInformalPronouns:s(u,o).length/u.length}}(e,l);return u<=.01805790513753891?a<=90.87325286865234?a<=73.79549026489258?"informal":a<=86.5999984741211?"formal":"informal":a<=172.26373291015625&&m<=.05696944147348404?"formal":"informal":u<=.04157066158950329?i<=24.832167625427246?"informal":m<=.0007042253273539245?"formal":"informal":u<=.06274875998497009?u<=.06258514896035194?"informal":"formal":"informal"}))})();
|
||||
22
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/workouts-2290.min.js
vendored
Normal file
22
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/workouts-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(()=>{var t;(t=function(){YoastSEO.app.registerPlugin("YoastCustomFieldsPlugin",{status:"loading"}),this.customFields={},this.updateCustomFields(),this.declareReady()}).prototype.declareReady=function(){YoastSEO.app.pluginReady("YoastCustomFieldsPlugin"),YoastSEO.app.registerModification("content",this.addCustomFields.bind(this),"YoastCustomFieldsPlugin")},t.prototype.declareReloaded=function(){YoastSEO.app.pluginReloaded("YoastCustomFieldsPlugin")},t.prototype.addCustomFields=function(t){return Object.values(this.customFields).reduce(((t,e)=>t+" "+e),t)},t.prototype.updateCustomFields=function(){var t={};jQuery("#the-list > tr:visible").each((function(e,s){var o=jQuery("#"+s.id+"-key").val();-1!==YoastCustomFieldsPluginL10.custom_field_names.indexOf(o)&&(t[o]=jQuery("#"+s.id+"-value").val())})),this.customFields=t,this.declareReloaded(),this.bindCustomFields()},t.prototype.bindCustomFields=function(){var t=_.debounce(this.updateCustomFields.bind(this),500,!0);jQuery("#the-list .button + .update_meta").off("click.wpseoCustomFields").on("click.wpseoCustomFields",t),jQuery("#the-list").off("wpListDelEnd.wpseoCustomFields").on("wpListDelEnd.wpseoCustomFields",t),jQuery("#the-list").off("wpListAddEnd.wpseoCustomFields").on("wpListAddEnd.wpseoCustomFields",t),jQuery("#the-list textarea").off("input.wpseoCustomFields").on("input.wpseoCustomFields",t)},"undefined"!=typeof YoastSEO&&void 0!==YoastSEO.app?new t:jQuery(window).on("YoastSEO:ready",(function(){new t}))})();
|
||||
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e={n:t=>{var o=t&&t.__esModule?()=>t.default:()=>t;return e.d(o,{a:o}),o},d:(t,o)=>{for(var i in o)e.o(o,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:o[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,o=window.wp.components,i=window.wp.element,s=window.wp.hooks,n=window.yoast.propTypes;var a=e.n(n);const m=({pluginList:e,fieldId:s})=>{if(window.yoast.draftJsEmoji){window.yoast.draftJsEmoji.defaultTheme.customClassesSet||(window.yoast.draftJsEmoji.defaultTheme.emojiSelectButton="emoji-select-button",window.yoast.draftJsEmoji.defaultTheme.emojiSelectButtonPressed="emoji-select-button emoji-select-button-pressed",window.yoast.draftJsEmoji.defaultTheme.emojiSelectPopover+=" emoji-select-popover",window.yoast.draftJsEmoji.defaultTheme.emojiSelectPopoverNav+=" emoji-select-popover-nav",window.yoast.draftJsEmoji.defaultTheme.emojiSuggestionsEntry+=" emoji-suggestion-item-wrapper",window.yoast.draftJsEmoji.defaultTheme.emojiSuggestionsEntryFocused+=" emoji-suggestion-item-wrapper",window.yoast.draftJsEmoji.defaultTheme.emojiSuggestionsEntryText+=" emoji-suggestion-item-text",window.yoast.draftJsEmoji.defaultTheme.emojiSelectPopoverGroupItem+=" single-emoji",window.yoast.draftJsEmoji.defaultTheme.emojiSuggestions+=" emoji-suggestions",window.yoast.draftJsEmoji.defaultTheme.customClassesSet=!0);const{EmojiSelect:n,EmojiSuggestions:a}=e.emojiPlugin;return(0,t.createElement)(i.Fragment,null,(0,t.createElement)(o.Fill,{name:`PluginComponent-${s}`},(0,t.createElement)(n,{closeOnEmojiSelect:!0})),(0,t.createElement)(a,null))}return(0,t.createElement)(i.Fragment,null)};m.propTypes={pluginList:a().object.isRequired,fieldId:a().string.isRequired};const r=()=>(0,t.createElement)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"1.5"},(0,t.createElement)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}));(0,s.addFilter)("yoast.replacementVariableEditor.additionalPlugins","yoast/yoast-seo-premium/EmojiSelect",((e,o,i)=>(0,t.createElement)(m,{key:`${i}-emoji`,pluginList:o,fieldId:i})),Number.MAX_SAFE_INTEGER),(0,s.addFilter)("yoast.replacementVariableEditor.pluginList","yoast/yoast-seo-premium/EmojiSelectLoad",(e=>(window.yoast.draftJsEmoji&&!e.emojiPlugin&&(e.emojiPlugin=window.yoast.draftJsEmoji.createEmojiPlugin({selectButtonContent:(0,t.createElement)(r,null),useNativeArt:!0,theme:window.yoast.draftJsEmoji.defaultTheme})),e)),Number.MAX_SAFE_INTEGER)})();
|
||||
90
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/wp-seo-premium-elementor-2290.min.js
vendored
Normal file
90
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/wp-seo-premium-elementor-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
87
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/wp-seo-premium-metabox-2290.min.js
vendored
Normal file
87
wp/wp-content/plugins/wordpress-seo-premium/assets/js/dist/wp-seo-premium-metabox-2290.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(()=>{var e={368:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ALLOW_EMPTY_TARGET:()=>i,wpseoCreateRedirect:()=>u,wpseoDeleteRedirect:()=>a,wpseoRemoveNotification:()=>s,wpseoUndoRedirect:()=>c,wpseoUndoRedirectByObjectId:()=>p,wpseoUndoRedirectForObject:()=>d});var o=r(455),n=r.n(o);const i=[410,451];function a(e,t,r,o="plain"){return n()({method:"POST",url:wpApiSettings.root+"yoast/v1/redirects/delete",headers:{"X-WP-Nonce":wpApiSettings.nonce},data:{origin:e,target:t,type:r,format:o}})}function d(e,t){return n()({method:"POST",url:wpApiSettings.root+"yoast/v1/redirects/undo-for-object",headers:{"X-WP-Nonce":wpApiSettings.nonce},data:{obj_id:e,obj_type:t}})}function s(e){jQuery(e).closest(".yoast-notification").fadeOut("slow")}function c(e,t,r,o,n){a(e,t,r).then((e=>{!0===e.success&&s(n)}))}function p(e,t,r){d(e,t).then((e=>{!0===e.success&&s(r)}))}function u(e,t,r,o){var n="";410===parseInt(t,10)||""!==(n=window.prompt(wpseoPremiumStrings.enter_new_url.replace("%s",e)))?jQuery.post(ajaxurl,{action:"wpseo_add_redirect_plain",ajax_nonce:r,redirect:{origin:e,target:n,type:t}},(function(e){var r=jQuery(o).closest(".yoast-notification");if(jQuery(r).removeClass("updated").removeClass("error"),jQuery(r).find(".redirect_error").remove(),e.error)jQuery(r).addClass("error").prepend('<p class="redirect_error">'+e.error.message+"</p>");else{var n="";n=(n=410===parseInt(t,10)?_.escape(wpseoPremiumStrings.redirect_saved_no_target):wpseoPremiumStrings.redirect_saved.replace("%2$s","<code>"+_.escape(e.target)+"</code>")).replace("%1$s","<code>"+_.escape(e.origin)+"</code>"),jQuery(r).addClass("updated").html("<p>"+n+"</p>")}}),"json"):window.alert(wpseoPremiumStrings.error_new_url)}},455:e=>{"use strict";e.exports=window.wp.apiFetch}},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,r),i.exports}r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e=r(368),t=0;function o(){jQuery.post(ajaxurl,{action:"yoast_get_notifications"},(function(e){if(""!==e){var r=jQuery(".wrap").children().eq(0);jQuery(e).insertAfter(r),t=0}t<20&&""===e&&(t++,setTimeout(o,500))}))}function n(){return jQuery(location).attr("pathname").split("/").pop()}function i(e){return"edit-tags.php"===(e=e||n())?"slug":"post_name"}function a(){return jQuery("tr.inline-editor")}function d(){var e=a();return 0===e.length||""===e?"":e.attr("id").replace("edit-","")}function s(){var e=d(),t=i();return jQuery("#inline_"+e).find("."+t).html()}function c(){var e=a(),t=i();return s()!==e.find("input[name="+t+"]").val()}function p(e){13===e.which&&c()&&o()}function u(e){"save-order"!==jQuery(e.target).attr("id")&&c()&&o()}window.wpseoShowNotification=o,window.wpseoGetCurrentPage=n,window.wpseoGetActiveEditor=a,window.wpseoGetItemId=d,window.wpseoGetCurrentSlug=s,window.wpseoSlugChanged=c,window.wpseoHandleKeyEvents=p,window.wpseoHandleButtonEvents=u,window.wpseoUndoRedirect=e.wpseoUndoRedirect,window.wpseoUndoRedirectByObjectId=e.wpseoUndoRedirectByObjectId,window.wpseoCreateRedirect=e.wpseoCreateRedirect,window.wpseoRemoveNotification=e.wpseoRemoveNotification,jQuery((function(){var e=n();["edit.php","edit-tags.php"].includes(e)&&(jQuery("#inline-edit input").on("keydown",(function(e){p(e)})),jQuery(".button-primary").on("click",(function(e){u(e)}))),"edit-tags.php"===e&&jQuery(document).on("ajaxComplete",(function(e,t,r){"string"==typeof r.data&&r.data.indexOf("action=delete-tag")>-1&&o()}))}))})()})();
|
||||
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e={n:r=>{var t=r&&r.__esModule?()=>r.default:()=>r;return e.d(t,{a:t}),t},d:(r,t)=>{for(var o in t)e.o(t,o)&&!e.o(r,o)&&Object.defineProperty(r,o,{enumerable:!0,get:t[o]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r)};const r=window.wp.apiFetch;var t=e.n(r);function o(e,r,o,n="plain"){return t()({method:"POST",url:wpApiSettings.root+"yoast/v1/redirects/delete",headers:{"X-WP-Nonce":wpApiSettings.nonce},data:{origin:e,target:r,type:o,format:n}})}function n(e){jQuery(e).closest(".yoast-notification").fadeOut("slow")}window.wpseoUndoRedirect=function(e,r,t,i,s){o(e,r,t).then((e=>{!0===e.success&&n(s)}))},window.wpseoUndoRedirectByObjectId=function(e,r,o){(function(e,r){return t()({method:"POST",url:wpApiSettings.root+"yoast/v1/redirects/undo-for-object",headers:{"X-WP-Nonce":wpApiSettings.nonce},data:{obj_id:e,obj_type:r}})})(e,r).then((e=>{!0===e.success&&n(o)}))},window.wpseoCreateRedirect=function(e,r,t,o){var n="";410===parseInt(r,10)||""!==(n=window.prompt(wpseoPremiumStrings.enter_new_url.replace("%s",e)))?jQuery.post(ajaxurl,{action:"wpseo_add_redirect_plain",ajax_nonce:t,redirect:{origin:e,target:n,type:r}},(function(e){var t=jQuery(o).closest(".yoast-notification");if(jQuery(t).removeClass("updated").removeClass("error"),jQuery(t).find(".redirect_error").remove(),e.error)jQuery(t).addClass("error").prepend('<p class="redirect_error">'+e.error.message+"</p>");else{var n="";n=(n=410===parseInt(r,10)?_.escape(wpseoPremiumStrings.redirect_saved_no_target):wpseoPremiumStrings.redirect_saved.replace("%2$s","<code>"+_.escape(e.target)+"</code>")).replace("%1$s","<code>"+_.escape(e.origin)+"</code>"),jQuery(t).addClass("updated").html("<p>"+n+"</p>")}}),"json"):window.alert(wpseoPremiumStrings.error_new_url)},window.wpseoDeleteRedirect=o,window.wpseoRemoveNotification=n})();
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";const e=window.lodash,n=window.yoast.analysis,t=class{static create(e){return new window.yoast.Researcher.default(e)}},o="22.9",{doAjaxRequest:i}=window.yoast.editorModules.helpers.ajaxHelper;let r=!1;function s(){return r}function a(e,o){const r=[];return e.forEach((e=>{const i=function(e,n){const o=t.create(e),i=s();return i&&o.addResearchData("morphology",i),function(e){const n={};return e.forEach((function(e){n[e.getStem()]=e.getOccurrences()})),n}(o.getResearch("getProminentWordsForInternalLinking").prominentWords.slice(0,void 0))}(new n.Paper(e.content,{keyword:e.meta.primary_focus_keyword||"",synonyms:e.meta.keyphrase_synonyms||"",title:e.meta.title||"",description:e.meta.description||"",locale:o.locale||"en_US"}));r.push({object_id:e.object_id,object_type:e.object_type,prominent_words:i})})),i("POST",`${o.restApi.root}${o.prominentWords.endpoint}`,o.restApi.nonce,{data:r})}async function c(n){let t=s();if(t||!n.morphologySupported)return Promise.resolve();const a=function(e){const n={en:"v5",de:"v10",es:"v10",fr:"v11",it:"v10",nl:"v9",ru:"v10",id:"v9",pt:"v9",pl:"v9",ar:"v9",sv:"v1",he:"v1",hu:"v2",nb:"v1",tr:"v1",cs:"v1",sk:"v1",el:"v1",ja:"v1"};return!!Object.keys(n).includes(e)&&`https://my.yoast.com/api/downloads/file/morphology-${e}-${n[e]}`}(n.language||"en");if(!1===a)return Promise.resolve();const c=(0,e.get)(window,"wpseoPremiumIndexationData.licensedURL","");return t=await i("GET",a,null,{plugin_version:o,site:c}),function(e){r=e}(t),Promise.resolve(t)}jQuery((()=>{window.yoast=window.yoast||{},window.yoast.indexing=window.yoast.indexing||{},"function"==typeof window.yoast.indexing.registerPreIndexingAction&&window.yoast.indexing.registerPreIndexingAction("get_content",c),"function"==typeof window.yoast.indexing.registerIndexingAction&&window.yoast.indexing.registerIndexingAction("get_content",a)}))})();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
*** Yoast SEO premium Changelog ***
|
||||
|
||||
|
||||
2024-06-18 - version 22.9
|
||||
* add - Converts the _Siblings_ and _Subpages_ blocks to the V3 API.
|
||||
* add - Converts the _Siblings_ and _Subpages_ blocks to the V3 API.
|
||||
* fix - Fixes a bug where a fatal error would occur when clicking `Click here to remove the redirect` after reverting the trashing of a post.
|
||||
* update - Bumps the minimum required version of Yoast SEO to 22.9.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presenters\Url_List_Presenter;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Siblings block class
|
||||
*/
|
||||
class Siblings_Block extends Dynamic_Block_V3 {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'siblings';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Siblings_Block constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $indexable_repository ) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
|
||||
$this->base_path = \WPSEO_PREMIUM_PATH . 'assets/blocks/dynamic-blocks/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes ) {
|
||||
$post_parent_id = \wp_get_post_parent_id( null );
|
||||
if ( $post_parent_id === false || $post_parent_id === 0 ) {
|
||||
return '';
|
||||
}
|
||||
$indexables = $this->indexable_repository->get_subpages_by_post_parent(
|
||||
$post_parent_id,
|
||||
[ \get_the_ID() ]
|
||||
);
|
||||
|
||||
$links = \array_map(
|
||||
static function ( Indexable $indexable ) {
|
||||
return [
|
||||
'title' => $indexable->breadcrumb_title,
|
||||
'permalink' => $indexable->permalink,
|
||||
];
|
||||
},
|
||||
$indexables
|
||||
);
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$class_name = 'yoast-url-list';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class_name .= ' ' . \esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
$presenter = new Url_List_Presenter( $links, $class_name );
|
||||
|
||||
return $presenter->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Yoast\WP\SEO\Integrations\Blocks;
|
||||
|
||||
use Yoast\WP\SEO\Models\Indexable;
|
||||
use Yoast\WP\SEO\Presenters\Url_List_Presenter;
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Subpages block class
|
||||
*/
|
||||
class Subpages_Block extends Dynamic_Block_V3 {
|
||||
|
||||
/**
|
||||
* The name of the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'subpages';
|
||||
|
||||
/**
|
||||
* The editor script for the block.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $script = 'wp-seo-premium-dynamic-blocks';
|
||||
|
||||
/**
|
||||
* The indexable repository.
|
||||
*
|
||||
* @var Indexable_Repository
|
||||
*/
|
||||
private $indexable_repository;
|
||||
|
||||
/**
|
||||
* Subpages_Block constructor.
|
||||
*
|
||||
* @param Indexable_Repository $indexable_repository The indexable repository.
|
||||
*/
|
||||
public function __construct( Indexable_Repository $indexable_repository ) {
|
||||
$this->indexable_repository = $indexable_repository;
|
||||
|
||||
$this->base_path = \WPSEO_PREMIUM_PATH . 'assets/blocks/dynamic-blocks/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the block output.
|
||||
*
|
||||
* @param array $attributes The block attributes.
|
||||
*
|
||||
* @return string The block output.
|
||||
*/
|
||||
public function present( $attributes ) {
|
||||
$indexables = $this->indexable_repository->get_subpages_by_post_parent( \get_the_ID() );
|
||||
|
||||
$links = \array_map(
|
||||
static function ( Indexable $indexable ) {
|
||||
return [
|
||||
'title' => $indexable->breadcrumb_title,
|
||||
'permalink' => $indexable->permalink,
|
||||
];
|
||||
},
|
||||
$indexables
|
||||
);
|
||||
|
||||
if ( empty( $links ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$class_name = 'yoast-url-list';
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$class_name .= ' ' . \esc_attr( $attributes['className'] );
|
||||
}
|
||||
|
||||
$presenter = new Url_List_Presenter( $links, $class_name );
|
||||
|
||||
return $presenter->present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enqueues a JavaScript plugin for YoastSEO.js that adds custom fields to the content that were defined in the titles
|
||||
* and meta's section of the Yoast SEO settings when those fields are available.
|
||||
*/
|
||||
class WPSEO_Custom_Fields_Plugin implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Initialize the AJAX hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! WPSEO_Metabox::is_post_edit( $pagenow ) && ! WPSEO_Metabox::is_post_overview( $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the needed JS scripts.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on WordPress functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue() {
|
||||
wp_enqueue_script( 'wp-seo-premium-custom-fields-plugin' );
|
||||
wp_localize_script( 'wp-seo-premium-custom-fields-plugin', 'YoastCustomFieldsPluginL10', $this->localize_script() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the custom fields translations.
|
||||
*
|
||||
* @return array The fields to localize.
|
||||
*/
|
||||
public function localize_script() {
|
||||
return [
|
||||
'custom_field_names' => $this->get_custom_field_names(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all custom field names set in SEO ->
|
||||
*
|
||||
* @return array The custom field names.
|
||||
*/
|
||||
protected function get_custom_field_names() {
|
||||
$custom_field_names = [];
|
||||
|
||||
$post = $this->get_post();
|
||||
|
||||
if ( ! is_object( $post ) ) {
|
||||
return $custom_field_names;
|
||||
}
|
||||
|
||||
$options = $this->get_titles_from_options();
|
||||
$target_option = 'page-analyse-extra-' . $post->post_type;
|
||||
|
||||
if ( array_key_exists( $target_option, $options ) ) {
|
||||
$custom_field_names = explode( ',', $options[ $target_option ] );
|
||||
}
|
||||
|
||||
return $custom_field_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves post data given a post ID or the global.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on dependencies.
|
||||
*
|
||||
* @return WP_Post|array|null Returns a post if found, otherwise returns an empty array.
|
||||
*/
|
||||
protected function get_post() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
if ( isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting the unsafe value to an integer.
|
||||
$post_id = (int) wp_unslash( $_GET['post'] );
|
||||
if ( $post_id > 0 ) {
|
||||
return get_post( $post_id );
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( isset( $GLOBALS['post'] ) ) {
|
||||
return $GLOBALS['post'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the WPSEO_Titles option.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on the options.
|
||||
*
|
||||
* @return array The value from WPSEO_Titles option.
|
||||
*/
|
||||
protected function get_titles_from_options() {
|
||||
$option_name = WPSEO_Options::get_option_instance( 'wpseo_titles' )->option_name;
|
||||
|
||||
return get_option( $option_name, [] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_CSV
|
||||
*
|
||||
* Exports data as returned by WPSEO_Export_Keywords_Presenter to CSV.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_CSV {
|
||||
|
||||
/**
|
||||
* The columns that should be presented.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* Data to be exported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = '';
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_CSV constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns An array of columns that should be presented.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes the CSV headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function print_headers() {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput -- Correctly escaped in get_headers() method below.
|
||||
echo $this->get_headers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes a formatted row.
|
||||
*
|
||||
* @param array $row Row to add to the export.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function print_row( $row ) {
|
||||
echo $this->format( $row );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSV headers based on the queried columns.
|
||||
*
|
||||
* @return string The headers in CSV format.
|
||||
*/
|
||||
protected function get_headers() {
|
||||
$header_columns = [
|
||||
'title' => esc_html__( 'title', 'wordpress-seo-premium' ),
|
||||
'url' => esc_html__( 'url', 'wordpress-seo-premium' ),
|
||||
'readability_score' => esc_html__( 'readability score', 'wordpress-seo-premium' ),
|
||||
'keywords' => esc_html__( 'keyphrase', 'wordpress-seo-premium' ),
|
||||
'keywords_score' => esc_html__( 'keyphrase score', 'wordpress-seo-premium' ),
|
||||
'seo_title' => esc_html__( 'seo title', 'wordpress-seo-premium' ),
|
||||
'meta_description' => esc_html__( 'meta description', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
$csv = $this->sanitize_csv_column( esc_html__( 'ID', 'wordpress-seo-premium' ) );
|
||||
$csv .= ',' . $this->sanitize_csv_column( esc_html_x( 'type', 'post_type of a post or the taxonomy of a term', 'wordpress-seo-premium' ) );
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
if ( array_key_exists( $column, $header_columns ) ) {
|
||||
$csv .= ',' . $this->sanitize_csv_column( $header_columns[ $column ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $csv . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a WPSEO_Export_Keywords_Query result as a CSV line.
|
||||
* In case of multiple keywords it will return multiple lines.
|
||||
*
|
||||
* @param array $result A result as returned from WPSEO_Export_Keywords_Query::get_data.
|
||||
*
|
||||
* @return string A line of CSV, beginning with EOL.
|
||||
*/
|
||||
protected function format( array $result ) {
|
||||
// If our input is malformed return an empty string.
|
||||
if ( ! array_key_exists( 'ID', $result ) || ! array_key_exists( 'type', $result ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Ensure we have arrays in the keywords.
|
||||
$result['keywords'] = $this->get_array_from_result( $result, 'keywords' );
|
||||
$result['keywords_score'] = $this->get_array_from_result( $result, 'keywords_score' );
|
||||
|
||||
$csv = '';
|
||||
|
||||
// Add at least one row plus additional ones if we have more keywords.
|
||||
$keywords = max( 1, count( $result['keywords'] ) );
|
||||
for ( $keywords_index = 0; $keywords_index < $keywords; $keywords_index++ ) {
|
||||
// Add static columns.
|
||||
$csv .= $this->sanitize_csv_column( $result['ID'] );
|
||||
$csv .= ',' . $this->sanitize_csv_column( $result['type'] );
|
||||
|
||||
// Add dynamic columns.
|
||||
foreach ( $this->columns as $column ) {
|
||||
$csv .= $this->get_csv_column_from_result( $result, $column, $keywords_index );
|
||||
}
|
||||
|
||||
$csv .= PHP_EOL;
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object based on the specified key
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key of the value to get the CSV column for.
|
||||
* @param int $keywords_index The number keyword to output.
|
||||
*
|
||||
* @return string CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_column_from_result( array $result, $key, $keywords_index ) {
|
||||
if ( in_array( $key, [ 'title', 'url', 'seo_title', 'meta_description', 'readability_score' ], true ) ) {
|
||||
return $this->get_csv_string_column_from_result( $result, $key );
|
||||
}
|
||||
|
||||
if ( in_array( $key, [ 'keywords', 'keywords_score' ], true ) ) {
|
||||
return $this->get_csv_array_column_from_result( $result, $key, $keywords_index );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array from the result object.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key of the array to retrieve.
|
||||
*
|
||||
* @return array Contents of the key in the object.
|
||||
*/
|
||||
protected function get_array_from_result( array $result, $key ) {
|
||||
if ( array_key_exists( $key, $result ) && is_array( $result[ $key ] ) ) {
|
||||
return $result[ $key ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object by the specified key.
|
||||
* Expects the value to be a string.
|
||||
*
|
||||
* @param array $result The result object to get the CSV column from.
|
||||
* @param string $key The key of the value to get the CSV column for.
|
||||
*
|
||||
* @return string A CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_string_column_from_result( array $result, $key ) {
|
||||
if ( array_key_exists( $key, $result ) ) {
|
||||
return ',' . $this->sanitize_csv_column( $result[ $key ] );
|
||||
}
|
||||
|
||||
return ',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a CSV column, including comma, from the result object by the specified key.
|
||||
* Expects the value to be inside an array.
|
||||
*
|
||||
* @param array $result The result object to get the CSV column from.
|
||||
* @param string $key The key of the array to get the CSV column for.
|
||||
* @param int $index The index of the value in the array.
|
||||
*
|
||||
* @return string A CSV formatted column.
|
||||
*/
|
||||
protected function get_csv_array_column_from_result( array $result, $key, $index ) {
|
||||
// If the array has an element at $index.
|
||||
if ( $index < count( $result[ $key ] ) ) {
|
||||
return ',' . $this->sanitize_csv_column( $result[ $key ][ $index ] );
|
||||
}
|
||||
|
||||
return ',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a value to be output as a CSV value.
|
||||
*
|
||||
* @param string $value The value to sanitize.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
protected function sanitize_csv_column( $value ) {
|
||||
// Return an empty string if value is null.
|
||||
if ( $value === null ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert non-string values to strings.
|
||||
if ( ! is_string( $value ) ) {
|
||||
$value = var_export( $value, true );
|
||||
}
|
||||
|
||||
// Replace all whitespace with spaces because Excel can't deal with newlines and tabs even if escaped.
|
||||
$value = preg_replace( '/\s/', ' ', $value );
|
||||
|
||||
// Escape double quotes.
|
||||
$value = str_replace( '"', '""', $value );
|
||||
|
||||
// Return the value enclosed in double quotes.
|
||||
return '"' . $value . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Post_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Post_Query for exporting.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Post_Presenter implements WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Post_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a presentable result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result or an empty array if the result is considered invalid.
|
||||
*/
|
||||
public function present( array $result ) {
|
||||
if ( ! $this->validate_result( $result ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
$result = $this->prepare_column_result( $result, $column );
|
||||
}
|
||||
|
||||
$result['type'] = $result['post_type'];
|
||||
unset( $result['post_type'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the passed result to make it more presentable.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
* @param string $column The requested column.
|
||||
*
|
||||
* @return array The prepared result.
|
||||
*/
|
||||
protected function prepare_column_result( array $result, $column ) {
|
||||
switch ( $column ) {
|
||||
case 'title':
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Using WP native filter.
|
||||
$result['title'] = apply_filters( 'the_title', $result['post_title'], $result['ID'] );
|
||||
unset( $result['post_title'] );
|
||||
break;
|
||||
case 'url':
|
||||
$result['url'] = get_permalink( $result['ID'] );
|
||||
break;
|
||||
case 'readability_score':
|
||||
$result['readability_score'] = WPSEO_Rank::from_numeric_score( (int) $result['readability_score'] )->get_label();
|
||||
break;
|
||||
case 'keywords':
|
||||
$result = $this->convert_result_keywords( $result );
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a result to present is a valid result.
|
||||
*
|
||||
* @param array $result The result to validate.
|
||||
*
|
||||
* @return bool True for a value valid result.
|
||||
*/
|
||||
protected function validate_result( array $result ) {
|
||||
// If there is no ID then it's not valid.
|
||||
if ( ! array_key_exists( 'ID', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a title is requested but not present then it's not valid.
|
||||
if ( $this->column_is_present( 'title' ) && $this->has_title( $result ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the result contains a valid title.
|
||||
*
|
||||
* @param array $result The result array to check for a title.
|
||||
*
|
||||
* @return bool Whether or not a title is valid.
|
||||
*/
|
||||
protected function has_title( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'post_title', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['post_title'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the wanted column exists within the $this->columns class variable.
|
||||
*
|
||||
* @param string $column The column to search for.
|
||||
*
|
||||
* @return bool Whether or not the column exists.
|
||||
*/
|
||||
protected function column_is_present( $column ) {
|
||||
if ( ! is_string( $column ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( $column, $this->columns, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the results of the query from strings and JSON string to keyword arrays.
|
||||
*
|
||||
* @param array $result The result to convert.
|
||||
*
|
||||
* @return array The converted result.
|
||||
*/
|
||||
protected function convert_result_keywords( array $result ) {
|
||||
$result['keywords'] = [];
|
||||
|
||||
if ( $this->column_is_present( 'keywords_score' ) ) {
|
||||
$result['keywords_score'] = [];
|
||||
}
|
||||
|
||||
if ( $this->has_primary_keyword( $result ) ) {
|
||||
$result['keywords'][] = $result['primary_keyword'];
|
||||
|
||||
// Convert multiple keywords from the Premium plugin from json to string arrays.
|
||||
$keywords = $this->parse_result_keywords_json( $result, 'other_keywords' );
|
||||
|
||||
$other_keywords = wp_list_pluck( $keywords, 'keyword' );
|
||||
$result['keywords'] = array_merge( $result['keywords'], $other_keywords );
|
||||
|
||||
if ( $this->column_is_present( 'keywords_score' ) ) {
|
||||
$result['keywords_score'] = $this->get_result_keywords_scores( $result, $keywords );
|
||||
}
|
||||
}
|
||||
|
||||
// Unset all old variables, if they do not exist nothing will happen.
|
||||
unset( $result['primary_keyword'], $result['primary_keyword_score'], $result['other_keywords'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether there's a valid primary keyword present in the result array.
|
||||
*
|
||||
* @param array $result The result array to check for the primary_keyword key.
|
||||
*
|
||||
* @return bool Whether or not a valid primary keyword is present.
|
||||
*/
|
||||
protected function has_primary_keyword( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'primary_keyword', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['primary_keyword'] ) && ! empty( $result['primary_keyword'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses then keywords JSON string in the result object for the specified key.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param string $key The key containing the JSON.
|
||||
*
|
||||
* @return array The parsed keywords.
|
||||
*/
|
||||
protected function parse_result_keywords_json( array $result, $key ) {
|
||||
if ( empty( $result[ $key ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parsed = json_decode( $result[ $key ], true );
|
||||
|
||||
if ( ! $parsed ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all scores from the result object and the parsed keywords JSON.
|
||||
*
|
||||
* @param array $result The result object.
|
||||
* @param array $keywords The parsed keywords.
|
||||
*
|
||||
* @return array The keyword scores.
|
||||
*/
|
||||
protected function get_result_keywords_scores( array $result, $keywords ) {
|
||||
$scores = [];
|
||||
|
||||
$rank = WPSEO_Rank::from_numeric_score( (int) $result['primary_keyword_score'] );
|
||||
$scores[] = $rank->get_label();
|
||||
|
||||
foreach ( $keywords as $keyword ) {
|
||||
if ( isset( $keyword['score'] ) ) {
|
||||
$rank = new WPSEO_Rank( $keyword['score'] );
|
||||
$scores[] = $rank->get_label();
|
||||
}
|
||||
}
|
||||
|
||||
return $scores;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Post_Query
|
||||
*
|
||||
* Creates an SQL query to gather all post data for a keywords export.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Post_Query implements WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The database columns to select in the query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selects;
|
||||
|
||||
/**
|
||||
* The database tables to join in the query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $joins = [];
|
||||
|
||||
/**
|
||||
* Number of items to fetch per page.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $page_size;
|
||||
|
||||
/**
|
||||
* Escaped list of post types.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $escaped_post_types;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Query constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns List of columns that need to be retrieved.
|
||||
* @param int $page_size Number of items to retrieve.
|
||||
*/
|
||||
public function __construct( array $columns, $page_size = 1000 ) {
|
||||
$this->page_size = max( 1, (int) $page_size );
|
||||
|
||||
$this->set_columns( $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was
|
||||
* constructed with. Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( $this->columns === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
if ( empty( $post_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Pages have a starting index of 1, we need to convert to a 0 based offset.
|
||||
$offset_multiplier = max( 0, ( $page - 1 ) );
|
||||
|
||||
$replacements = [];
|
||||
$replacements[] = $wpdb->posts;
|
||||
$replacements[] = 'post_status';
|
||||
$replacements[] = 'post_type';
|
||||
$replacements = array_merge( $replacements, $post_types );
|
||||
$replacements[] = $this->page_size;
|
||||
$replacements[] = ( $offset_multiplier * $this->page_size );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Already prepared, and no cache applicable.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'SELECT ' . implode( ', ', $this->selects )
|
||||
. ' FROM %i AS posts '
|
||||
. implode( ' ', $this->joins )
|
||||
. ' WHERE posts.%i = "publish" AND posts.%i IN ('
|
||||
. implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ')'
|
||||
. ' LIMIT %d OFFSET %d',
|
||||
$replacements
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_columns( array $columns ) {
|
||||
$this->columns = $columns;
|
||||
|
||||
$this->joins = [];
|
||||
$this->selects = [ 'posts.ID', 'posts.post_type' ];
|
||||
|
||||
if ( in_array( 'title', $this->columns, true ) ) {
|
||||
$this->selects[] = 'posts.post_title';
|
||||
}
|
||||
|
||||
// If we're selecting keywords_score then we always want the keywords as well.
|
||||
if ( in_array( 'keywords', $this->columns, true ) || in_array( 'keywords_score', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'primary_keyword', WPSEO_Meta::$meta_prefix . 'focuskw' );
|
||||
$this->add_meta_join( 'other_keywords', WPSEO_Meta::$meta_prefix . 'focuskeywords' );
|
||||
}
|
||||
|
||||
if ( in_array( 'readability_score', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'readability_score', WPSEO_Meta::$meta_prefix . 'content_score' );
|
||||
}
|
||||
|
||||
if ( in_array( 'keywords_score', $this->columns, true ) ) {
|
||||
// Score for other keywords is already in the other_keywords select so only join for the primary_keyword_score.
|
||||
$this->add_meta_join( 'primary_keyword_score', WPSEO_Meta::$meta_prefix . 'linkdex' );
|
||||
}
|
||||
|
||||
if ( in_array( 'seo_title', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'seo_title', WPSEO_Meta::$meta_prefix . 'title' );
|
||||
}
|
||||
|
||||
if ( in_array( 'meta_description', $this->columns, true ) ) {
|
||||
$this->add_meta_join( 'meta_description', WPSEO_Meta::$meta_prefix . 'metadesc' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size() {
|
||||
return $this->page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an aliased join to the $wpdb->postmeta table so that multiple meta values can be selected in a single row.
|
||||
*
|
||||
* While this function should never be used with user input,
|
||||
* all non-word non-digit characters are removed from both params for increased robustness.
|
||||
*
|
||||
* @param string $alias The alias to use in our query output.
|
||||
* @param string $key The meta_key to select.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_meta_join( $alias, $key ) {
|
||||
global $wpdb;
|
||||
$alias = preg_replace( '/[^\w\d]/', '', $alias );
|
||||
$key = preg_replace( '/[^\w\d]/', '', $key );
|
||||
|
||||
$this->selects[] = $alias . '_join.meta_value AS ' . $alias;
|
||||
$this->joins[] = 'LEFT OUTER JOIN ' . $wpdb->prefix . 'postmeta AS ' . $alias . '_join '
|
||||
. 'ON ' . $alias . '_join.post_id = posts.ID '
|
||||
. 'AND ' . $alias . '_join.meta_key = "' . $key . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface WPSEO_Export_Keywords_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Query for exporting.
|
||||
*/
|
||||
interface WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns );
|
||||
|
||||
/**
|
||||
* Updates a result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result.
|
||||
*/
|
||||
public function present( array $result );
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface WPSEO_Export_Keywords_Query
|
||||
*
|
||||
* Creates a SQL query to gather all data for a keywords export.
|
||||
*/
|
||||
interface WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size();
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was constructed with.
|
||||
* Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 );
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_columns( array $columns );
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Term_Presenter
|
||||
*
|
||||
* Readies data as returned by WPSEO_Export_Keywords_Term_Query for exporting.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Term_Presenter implements WPSEO_Export_Keywords_Presenter {
|
||||
|
||||
/**
|
||||
* The columns to query for.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Term_Presenter constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*/
|
||||
public function __construct( array $columns ) {
|
||||
$this->columns = array_filter( $columns, 'is_string' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a presentable result by modifying and adding the requested fields.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
*
|
||||
* @return array The modified result or an empty array if the result is considered invalid.
|
||||
*/
|
||||
public function present( array $result ) {
|
||||
if ( ! $this->validate_result( $result ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result['ID'] = (int) $result['term_id'];
|
||||
unset( $result['term_id'] );
|
||||
|
||||
foreach ( $this->columns as $column ) {
|
||||
$result = $this->prepare_column_result( $result, $column );
|
||||
}
|
||||
|
||||
$result['type'] = $result['taxonomy'];
|
||||
unset( $result['taxonomy'] );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the passed result to make it more presentable.
|
||||
*
|
||||
* @param array $result The result to modify.
|
||||
* @param string $column The requested column.
|
||||
*
|
||||
* @return array The prepared result.
|
||||
*/
|
||||
protected function prepare_column_result( array $result, $column ) {
|
||||
switch ( $column ) {
|
||||
case 'keywords':
|
||||
$result['keywords'] = $this->get_result_keywords( $result );
|
||||
break;
|
||||
case 'keywords_score':
|
||||
$result['keywords_score'] = $this->get_result_keywords_score( $result );
|
||||
break;
|
||||
case 'url':
|
||||
$result['url'] = get_term_link( $result['ID'], $result['taxonomy'] );
|
||||
break;
|
||||
case 'title':
|
||||
$result['title'] = $result['name'];
|
||||
unset( $result['name'] );
|
||||
break;
|
||||
case 'seo_title':
|
||||
$result['seo_title'] = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'title' );
|
||||
break;
|
||||
case 'meta_description':
|
||||
$result['meta_description'] = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'desc' );
|
||||
break;
|
||||
case 'readability_score':
|
||||
$content_score = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'content_score' );
|
||||
$result['readability_score'] = WPSEO_Rank::from_numeric_score( (int) $content_score )->get_label();
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a result to present is a valid result.
|
||||
*
|
||||
* @param array $result The result to validate.
|
||||
*
|
||||
* @return bool True if the result is validated.
|
||||
*/
|
||||
protected function validate_result( array $result ) {
|
||||
// If there is no ID then it's not valid.
|
||||
if ( ! array_key_exists( 'term_id', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a title is requested but not present then it's not valid.
|
||||
if ( $this->column_is_present( 'title' ) && $this->has_title( $result ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the result contains a valid title.
|
||||
*
|
||||
* @param array $result The result array to check for a title.
|
||||
*
|
||||
* @return bool Whether or not a title is valid.
|
||||
*/
|
||||
protected function has_title( $result ) {
|
||||
if ( ! is_array( $result ) || ! array_key_exists( 'name', $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_string( $result['name'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the wanted column exists within the $this->columns class variable.
|
||||
*
|
||||
* @param string $column The column to search for.
|
||||
*
|
||||
* @return bool Whether or not the column exists.
|
||||
*/
|
||||
protected function column_is_present( $column ) {
|
||||
if ( ! is_string( $column ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array( $column, $this->columns, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result keywords from WPSEO_Taxonomy_Meta.
|
||||
*
|
||||
* @param array $result The result to get the keywords for.
|
||||
*
|
||||
* @return array The keywords.
|
||||
*/
|
||||
protected function get_result_keywords( array $result ) {
|
||||
$keyword = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'focuskw' );
|
||||
|
||||
if ( $keyword === false || empty( $keyword ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [ (string) $keyword ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result keyword scores from WPSEO_Taxonomy_Meta.
|
||||
*
|
||||
* @param array $result The result to get the keyword scores for.
|
||||
*
|
||||
* @return array The keyword scores.
|
||||
*/
|
||||
protected function get_result_keywords_score( array $result ) {
|
||||
$keyword_score = WPSEO_Taxonomy_Meta::get_term_meta( $result['ID'], $result['taxonomy'], 'linkdex' );
|
||||
|
||||
if ( $keyword_score === false || empty( $keyword_score ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keyword_score_label = WPSEO_Rank::from_numeric_score( (int) $keyword_score )->get_label();
|
||||
|
||||
return [ $keyword_score_label ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Export
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Term_Query
|
||||
*
|
||||
* Creates an SQL query to gather all term data for a keywords export.
|
||||
*/
|
||||
class WPSEO_Export_Keywords_Term_Query implements WPSEO_Export_Keywords_Query {
|
||||
|
||||
/**
|
||||
* The columns to query for, an array of strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columns;
|
||||
|
||||
/**
|
||||
* The database columns to select in the query, an array of strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selects;
|
||||
|
||||
/**
|
||||
* Number of items to fetch per page.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $page_size;
|
||||
|
||||
/**
|
||||
* WPSEO_Export_Keywords_Query constructor.
|
||||
*
|
||||
* Supported values for columns are 'title', 'url', 'keywords', 'readability_score' and 'keywords_score'.
|
||||
* Requesting 'keywords_score' will always also return 'keywords'.
|
||||
*
|
||||
* @param array $columns List of columns that need to be retrieved.
|
||||
* @param int $page_size Number of items to retrieve.
|
||||
*/
|
||||
public function __construct( array $columns, $page_size = 1000 ) {
|
||||
$this->page_size = max( 1, (int) $page_size );
|
||||
$this->set_columns( $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page size for the query.
|
||||
*
|
||||
* @return int Page size that is being used.
|
||||
*/
|
||||
public function get_page_size() {
|
||||
return $this->page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the query and executes it, returning an array of objects containing the columns this object was constructed with.
|
||||
* Every object will always contain the ID column.
|
||||
*
|
||||
* @param int $page Paginated page to retrieve.
|
||||
*
|
||||
* @return array An array of associative arrays containing the keys as requested in the constructor.
|
||||
*/
|
||||
public function get_data( $page = 1 ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( $this->columns === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$taxonomies = get_taxonomies(
|
||||
[
|
||||
'public' => true,
|
||||
'show_ui' => true,
|
||||
],
|
||||
'names'
|
||||
);
|
||||
|
||||
if ( empty( $taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Pages have a starting index of 1, we need to convert to a 0 based offset.
|
||||
$offset_multiplier = max( 0, ( $page - 1 ) );
|
||||
|
||||
$replacements = [];
|
||||
$replacements[] = $wpdb->terms;
|
||||
$replacements[] = $wpdb->term_taxonomy;
|
||||
$replacements[] = 'term_id';
|
||||
$replacements[] = 'term_id';
|
||||
$replacements[] = 'taxonomy';
|
||||
$replacements = array_merge( $replacements, $taxonomies );
|
||||
$replacements[] = $this->page_size;
|
||||
$replacements[] = ( $offset_multiplier * $this->page_size );
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Already prepared, and no cache applicable.
|
||||
return $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'SELECT ' . implode( ', ', $this->selects )
|
||||
. ' FROM %i AS terms'
|
||||
. ' INNER JOIN %i AS taxonomies'
|
||||
. ' ON terms.%i = taxonomies.%i AND taxonomies.%i IN ('
|
||||
. implode( ',', array_fill( 0, count( $taxonomies ), '%s' ) ) . ')'
|
||||
. ' LIMIT %d OFFSET %d',
|
||||
$replacements
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the necessary selects and joins to get all data in a single query.
|
||||
*
|
||||
* @param array $columns The columns we want our query to return.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_columns( array $columns ) {
|
||||
$this->columns = $columns;
|
||||
|
||||
$this->selects = [ 'terms.term_id', 'taxonomies.taxonomy' ];
|
||||
|
||||
if ( in_array( 'title', $this->columns, true ) ) {
|
||||
$this->selects[] = 'terms.name';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the class for adding the link suggestions metabox for each post type.
|
||||
*/
|
||||
class WPSEO_Metabox_Link_Suggestions implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Sets the hooks for adding the metaboxes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a meta for each public post type.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_meta_boxes() {
|
||||
/*
|
||||
* Since the link suggestions are already added in the Yoast sidebar.
|
||||
* Do not add them to the metabox when in the block editor.
|
||||
*/
|
||||
if ( WP_Screen::get()->is_block_editor() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_types = $this->get_post_types();
|
||||
|
||||
array_map( [ $this, 'add_meta_box' ], $post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the link suggestions are available for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type for which to check if the link suggestions are available.
|
||||
*
|
||||
* @return bool Whether the link suggestions are available for the given post type.
|
||||
*/
|
||||
public function is_available( $post_type ) {
|
||||
$allowed_post_types = $this->get_post_types();
|
||||
|
||||
return in_array( $post_type, $allowed_post_types, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the content for the metabox. We leave this empty because we render with React.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_metabox_content() {
|
||||
echo '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the public post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
|
||||
return $prominent_words_support->get_supported_post_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the Link Suggestions are enabled.
|
||||
*
|
||||
* @return bool Whether or not the link suggestions are enabled.
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a meta box for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type to add a meta box for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function add_meta_box( $post_type ) {
|
||||
if ( ! $this->is_available( $post_type ) || ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! WPSEO_Premium_Metabox::are_content_endpoints_available() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_meta_box(
|
||||
'yoast_internal_linking',
|
||||
sprintf(
|
||||
/* translators: %s expands to Yoast */
|
||||
__( '%s internal linking', 'wordpress-seo-premium' ),
|
||||
'Yoast'
|
||||
),
|
||||
[ $this, 'render_metabox_content' ],
|
||||
$post_type,
|
||||
'side',
|
||||
'low',
|
||||
[
|
||||
'__block_editor_compatible_meta_box' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements multi keyword in the admin.
|
||||
*/
|
||||
class WPSEO_Multi_Keyword implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Sets WordPress hooks.
|
||||
*
|
||||
* @codeCoverageIgnore It relies on dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_focus_keywords_input' ] );
|
||||
add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_keyword_synonyms_input' ] );
|
||||
|
||||
add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_focus_keywords_taxonomy_input' ] );
|
||||
add_filter( 'wpseo_taxonomy_content_fields', [ $this, 'add_keyword_synonyms_taxonomy_input' ] );
|
||||
add_filter( 'wpseo_add_extra_taxmeta_term_defaults', [ $this, 'register_taxonomy_metafields' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field in which we can save multiple keywords.
|
||||
*
|
||||
* @param array $field_defs The current fields definitions.
|
||||
*
|
||||
* @return array Field definitions with our added field.
|
||||
*/
|
||||
public function add_focus_keywords_input( $field_defs ) {
|
||||
if ( is_array( $field_defs ) ) {
|
||||
$field_defs['focuskeywords'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'focuskeywords',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field in which we can save multiple keyword synonyms.
|
||||
*
|
||||
* @param array $field_defs The current fields definitions.
|
||||
*
|
||||
* @return array Field definitions with our added field.
|
||||
*/
|
||||
public function add_keyword_synonyms_input( $field_defs ) {
|
||||
if ( is_array( $field_defs ) ) {
|
||||
$field_defs['keywordsynonyms'] = [
|
||||
'type' => 'hidden',
|
||||
'title' => 'keywordsynonyms',
|
||||
];
|
||||
}
|
||||
|
||||
return $field_defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field to the taxonomy metabox in which we can save multiple keywords.
|
||||
*
|
||||
* @param array $fields The current fields.
|
||||
*
|
||||
* @return array Fields including our added field.
|
||||
*/
|
||||
public function add_focus_keywords_taxonomy_input( $fields ) {
|
||||
if ( is_array( $fields ) ) {
|
||||
$fields['focuskeywords'] = [
|
||||
'label' => '',
|
||||
'description' => '',
|
||||
'type' => 'hidden',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field in which we can save multiple keyword synonyms.
|
||||
*
|
||||
* @param array $fields The current fields.
|
||||
*
|
||||
* @return array Fields including our added field.
|
||||
*/
|
||||
public function add_keyword_synonyms_taxonomy_input( $fields ) {
|
||||
if ( is_array( $fields ) ) {
|
||||
$fields['keywordsynonyms'] = [
|
||||
'label' => '',
|
||||
'description' => '',
|
||||
'type' => 'hidden',
|
||||
'options' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the taxonomy defaults.
|
||||
*
|
||||
* @param array $defaults The defaults to extend.
|
||||
*
|
||||
* @return array The extended defaults.
|
||||
*/
|
||||
public function register_taxonomy_metafields( $defaults ) {
|
||||
$defaults['wpseo_focuskeywords'] = '';
|
||||
$defaults['wpseo_keywordsynonyms'] = '';
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Post_Watcher.
|
||||
*/
|
||||
class WPSEO_Post_Watcher extends WPSEO_Watcher implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Type of watcher, will be used for the filters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $watch_type = 'post';
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method used WordPress functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
global $pagenow;
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'page_scripts' ] );
|
||||
|
||||
// Only set the hooks for the page where they are needed.
|
||||
if ( ! $this->is_rest_request() && ! $this->post_redirect_can_be_made( $pagenow ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect a post slug change.
|
||||
add_action( 'post_updated', [ $this, 'detect_slug_change' ], 12, 3 );
|
||||
|
||||
// Detect a post trash.
|
||||
add_action( 'wp_trash_post', [ $this, 'detect_post_trash' ] );
|
||||
|
||||
// Detect a post untrash.
|
||||
add_action( 'untrashed_post', [ $this, 'detect_post_untrash' ] );
|
||||
|
||||
// Detect a post delete.
|
||||
add_action( 'before_delete_post', [ $this, 'detect_post_delete' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the page scripts.
|
||||
*
|
||||
* @codeCoverageIgnore Method used WordPress functions.
|
||||
*
|
||||
* @param string $current_page The page that is opened at the moment.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function page_scripts( $current_page ) {
|
||||
// Register the scripts.
|
||||
parent::page_scripts( $current_page );
|
||||
|
||||
/**
|
||||
* If in Gutenberg, always load these scripts.
|
||||
*/
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) && wp_script_is( 'wp-editor', 'enqueued' ) ) {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\show_post_redirect_slug_change_notification' - Allows to suppress the block editor notification
|
||||
* about automatic redirect creation when the post slug is changed.
|
||||
*
|
||||
* The middleware used to intercept the redirect creation and trigger the notice can interfere with the API
|
||||
* call since it passes the full response on instead of its content. Using this filter, it can be disabled.
|
||||
* Notice that this doesn't prevent the actual redirect from being created.
|
||||
*
|
||||
* @since 21.9
|
||||
*
|
||||
* @param bool $show_notification Determines if the notification should be displayed.
|
||||
*/
|
||||
$show_notification = apply_filters( 'Yoast\WP\SEO\show_post_redirect_slug_change_notification', true );
|
||||
|
||||
if ( $show_notification ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications' );
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications-gutenberg' );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->post_redirect_can_be_made( $current_page ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_overview( $current_page ) ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-quickedit-notification' );
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) ) {
|
||||
wp_enqueue_script( 'wp-seo-premium-redirect-notifications' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if the slug changed, hooked into 'post_updated'.
|
||||
*
|
||||
* @param int $post_id The ID of the post.
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function detect_slug_change( $post_id, $post, $post_before ) {
|
||||
// Bail if this is a multisite installation and the site has been switched.
|
||||
if ( is_multisite() && ms_is_switched() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->is_redirect_relevant( $post, $post_before ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->remove_colliding_redirect( $post, $post_before );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\post_redirect_slug_change' - Check if a redirect should be created
|
||||
* on post slug change.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param bool $create_redirect Determines if a redirect should be created for this post slug change.
|
||||
* @param int $post_id The ID of the post.
|
||||
* @param WP_Post $post The current post object.
|
||||
* @param WP_Post $previous_post The previous post object.
|
||||
*/
|
||||
$create_redirect = apply_filters( 'Yoast\WP\SEO\post_redirect_slug_change', false, $post_id, $post, $post_before );
|
||||
|
||||
if ( $create_redirect === true ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$old_url = $this->get_target_url( $post_before );
|
||||
if ( ! $old_url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the post URL wasn't public before, or isn't public now, don't even check if we have to redirect.
|
||||
if ( ! $this->check_public_post_status( $post_before ) || ! $this->check_public_post_status( $post ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the new URL.
|
||||
$new_url = $this->get_target_url( $post_id );
|
||||
|
||||
// Maybe we can undo the created redirect.
|
||||
$created_redirect = $this->notify_undo_slug_redirect( $old_url, $new_url, $post_id, 'post' );
|
||||
|
||||
if ( $created_redirect ) {
|
||||
$redirect_info = [
|
||||
'origin' => $created_redirect->get_origin(),
|
||||
'target' => $created_redirect->get_target(),
|
||||
'type' => $created_redirect->get_type(),
|
||||
'format' => $created_redirect->get_format(),
|
||||
];
|
||||
update_post_meta( $post_id, '_yoast_post_redirect_info', $redirect_info );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a colliding redirect if it is found.
|
||||
*
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_colliding_redirect( $post, $post_before ) {
|
||||
$redirect = $this->get_redirect_manager()->get_redirect( $this->get_target_url( $post ) );
|
||||
if ( $redirect === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $redirect->get_target() !== trim( $this->get_target_url( $post_before ), '/' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_redirect_manager()->delete_redirects( [ $redirect ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if redirect is relevant for the provided post.
|
||||
*
|
||||
* @param WP_Post $post The post with the new values.
|
||||
* @param WP_Post $post_before The post with the previous values.
|
||||
*
|
||||
* @return bool True if a redirect might be relevant.
|
||||
*/
|
||||
protected function is_redirect_relevant( $post, $post_before ) {
|
||||
// Check if the post type is enabled for redirects.
|
||||
$post_type = get_post_type( $post );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\redirect_post_type' - Check if a redirect should be created
|
||||
* on post slug change for specified post type.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param bool $create_redirect Determines if a redirect should be created for this post type.
|
||||
* @param string $post_type The post type that is being checked for.
|
||||
*/
|
||||
$post_type_accessible = apply_filters( 'Yoast\WP\SEO\redirect_post_type', WPSEO_Post_Type::is_post_type_accessible( $post_type ), $post_type );
|
||||
|
||||
if ( ! $post_type_accessible ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If post is a revision do not create redirect.
|
||||
if ( wp_is_post_revision( $post_before ) !== false && wp_is_post_revision( $post ) !== false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no slug change.
|
||||
if ( $this->get_target_url( $post ) === $this->get_target_url( $post_before ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given post is public or not.
|
||||
*
|
||||
* @param int|WP_Post $post Post ID or post object.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function check_public_post_status( $post ) {
|
||||
$public_post_statuses = [
|
||||
'publish',
|
||||
'static',
|
||||
'private',
|
||||
];
|
||||
|
||||
// Need to set $post_id for backward compatibility with the filter, as $post can also be an object now.
|
||||
if ( is_int( $post ) ) {
|
||||
$post_id = $post;
|
||||
}
|
||||
else {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\public_post_statuses' - Allow changing the statuses that are expected
|
||||
* to have caused a URL to be public.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param array $published_post_statuses The statuses that'll be treated as published.
|
||||
* @param object $post The post object we're doing the published check for.
|
||||
*/
|
||||
$public_post_statuses = apply_filters( 'Yoast\WP\SEO\public_post_statuses', $public_post_statuses, $post_id );
|
||||
|
||||
return ( in_array( get_post_status( $post ), $public_post_statuses, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get trashed.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detect_post_trash( $post_id ) {
|
||||
|
||||
$url = $this->check_if_redirect_needed( $post_id );
|
||||
if ( ! empty( $url ) ) {
|
||||
|
||||
$id = 'wpseo_redirect_' . md5( $url );
|
||||
|
||||
// Format the message.
|
||||
$message = sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: List with actions, %3$s: <a href=''>, %4$s: </a>, %5$s: Slug to post */
|
||||
__( '%1$s detected that you moved a post (%5$s) to the trash. You can either: %2$s Don\'t know what to do? %3$sRead this post%4$s.', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium',
|
||||
$this->get_delete_action_list( $url, $id ),
|
||||
'<a target="_blank" href="' . WPSEO_Shortlinker::get( 'https://yoa.st/2jd' ) . '">',
|
||||
'</a>',
|
||||
'<code>' . $url . '</code>'
|
||||
);
|
||||
|
||||
$this->create_notification( $message, 'trash' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get restored from the trash.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detect_post_untrash( $post_id ) {
|
||||
$redirect = $this->check_if_redirect_needed( $post_id, true );
|
||||
|
||||
if ( $redirect ) {
|
||||
// Format the message.
|
||||
$message = sprintf(
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: <a href='{undo_redirect_url}'>, %3$s: </a>, %4$s: Slug to post */
|
||||
__( '%1$s detected that you restored a post (%4$s) from the trash, for which a redirect was created. %2$sClick here to remove the redirect%3$s', 'wordpress-seo-premium' ),
|
||||
'Yoast SEO Premium',
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '" class="button">',
|
||||
'</a>',
|
||||
'<code>' . $redirect->get_origin() . '</code>'
|
||||
);
|
||||
|
||||
$this->create_notification( $message, 'untrash' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer to create a redirect from the post that is about to get deleted.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function detect_post_delete( $post_id ) {
|
||||
|
||||
// We don't want to redirect menu items.
|
||||
if ( is_nav_menu_item( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the post comes from the trash or if the post is a revision then skip further execution.
|
||||
if ( get_post_status( $post_id ) === 'trash' || wp_is_post_revision( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is a redirect needed.
|
||||
$url = $this->check_if_redirect_needed( $post_id );
|
||||
if ( ! empty( $url ) ) {
|
||||
$this->set_delete_notification( $url );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up if URL does exists in the current redirects.
|
||||
*
|
||||
* @param string $url URL to search for.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function get_redirect( $url ) {
|
||||
return $this->get_redirect_manager()->get_redirect( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if a redirect is needed.
|
||||
*
|
||||
* This method will check if URL as redirect already exists.
|
||||
*
|
||||
* @param int $post_id The current post ID.
|
||||
* @param bool $should_exist Boolean to determine if the URL should be exist as a redirect.
|
||||
*
|
||||
* @return WPSEO_Redirect|string|bool
|
||||
*/
|
||||
protected function check_if_redirect_needed( $post_id, $should_exist = false ) {
|
||||
// If the post type is not public, don't redirect.
|
||||
$post_type = get_post_type_object( get_post_type( $post_id ) );
|
||||
|
||||
if ( ! $post_type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $post_type->name, $this->get_included_automatic_redirection_post_types(), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The post types should be a public one.
|
||||
if ( $this->check_public_post_status( $post_id ) ) {
|
||||
// Get the right URL.
|
||||
$url = $this->get_target_url( $post_id );
|
||||
|
||||
// If $url is not a single /, there may be the option to create a redirect.
|
||||
if ( $url !== '/' ) {
|
||||
// Message should only be shown if there isn't already a redirect.
|
||||
$redirect = $this->get_redirect( $url );
|
||||
|
||||
if ( is_a( $redirect, 'WPSEO_Redirect' ) && $should_exist ) {
|
||||
return $redirect;
|
||||
}
|
||||
if ( ! is_a( $redirect, 'WPSEO_Redirect' ) && ! $should_exist ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post types to create automatic redirects for.
|
||||
*
|
||||
* @return array<string> Post types to include to create automatic redirects for.
|
||||
*/
|
||||
protected function get_included_automatic_redirection_post_types() {
|
||||
$post_types = WPSEO_Post_Type::get_accessible_post_types();
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\automatic_redirection_post_types' - Post types to create
|
||||
* automatic redirects for.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param array $included_post_types Array with the post type names to include to automatic redirection.
|
||||
*/
|
||||
$included_post_types = apply_filters( 'Yoast\WP\SEO\automatic_redirection_post_types', $post_types );
|
||||
|
||||
if ( ! is_array( $included_post_types ) ) {
|
||||
$included_post_types = [];
|
||||
}
|
||||
|
||||
return $included_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the path of the URL for the supplied post.
|
||||
*
|
||||
* @param int|WP_Post $post The current post ID.
|
||||
*
|
||||
* @return string The URL for the supplied post.
|
||||
*/
|
||||
protected function get_target_url( $post ) {
|
||||
// Use the correct URL path.
|
||||
$url = wp_parse_url( get_permalink( $post ) );
|
||||
if ( is_array( $url ) && isset( $url['path'] ) ) {
|
||||
return $url['path'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old URL.
|
||||
*
|
||||
* @param object $post The post object with the new values.
|
||||
* @param object $post_before The post object with the old values.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_old_url( $post, $post_before ) {
|
||||
$wpseo_old_post_url = $this->get_post_old_post_url();
|
||||
|
||||
if ( ! empty( $wpseo_old_post_url ) ) {
|
||||
return $wpseo_old_post_url;
|
||||
}
|
||||
|
||||
// Check if request is inline action and new slug is not old slug, if so set wpseo_post_old_url.
|
||||
$action = $this->get_post_action();
|
||||
|
||||
$url_before = $this->get_target_url( $post_before );
|
||||
if ( ! empty( $action ) && $action === 'inline-save' && $this->get_target_url( $post ) !== $url_before ) {
|
||||
return $url_before;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we're dealing with a REST request or not.
|
||||
*
|
||||
* @return bool Whether or not the current request is a REST request.
|
||||
*/
|
||||
private function is_rest_request() {
|
||||
return defined( 'REST_REQUEST' ) && REST_REQUEST === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the undo message for the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_undo_slug_notification() {
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s and %3$s expand to a link to the admin page. */
|
||||
return __(
|
||||
'%1$s created a %2$sredirect%3$s from the old post URL to the new post URL.',
|
||||
'wordpress-seo-premium'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the delete message for the post.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_delete_notification() {
|
||||
/* translators: %1$s: Yoast SEO Premium, %2$s: List with actions, %3$s: <a href='{post_with_explaination.}'>, %4$s: </a>, %5%s: The removed url. */
|
||||
return __(
|
||||
'%1$s detected that you deleted a post (%5$s). You can either: %2$s Don\'t know what to do? %3$sRead this post %4$s.',
|
||||
'wordpress-seo-premium'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current page valid to make a redirect from.
|
||||
*
|
||||
* @param string $current_page The currently opened page.
|
||||
*
|
||||
* @return bool True when a redirect can be made on this page.
|
||||
*/
|
||||
protected function post_redirect_can_be_made( $current_page ) {
|
||||
return $this->is_post_page( $current_page ) || $this->is_action_inline_save() || $this->is_nested_pages( $current_page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current page related to a post (edit/overview).
|
||||
*
|
||||
* @param string $current_page The current opened page.
|
||||
*
|
||||
* @return bool True when page is a post edit/overview page.
|
||||
*/
|
||||
protected function is_post_page( $current_page ) {
|
||||
return ( in_array( $current_page, [ 'edit.php', 'post.php' ], true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the page in an AJAX-request and is the action "inline save".
|
||||
*
|
||||
* @return bool True when in an AJAX-request and the action is inline-save.
|
||||
*/
|
||||
protected function is_action_inline_save() {
|
||||
return ( wp_doing_ajax() && $this->get_post_action() === 'inline-save' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current page is loaded by nested pages.
|
||||
*
|
||||
* @param string $current_page The current page.
|
||||
*
|
||||
* @return bool True when the current page is nested pages.
|
||||
*/
|
||||
protected function is_nested_pages( $current_page ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only.
|
||||
return ( $current_page === 'admin.php' && isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'nestedpages' );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves wpseo_old_post_url field from the post.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function get_post_old_post_url() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Reason: Seems to be only used in tests.
|
||||
if ( isset( $_POST['wpseo_old_post_url'] ) && is_string( $_POST['wpseo_old_post_url'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['wpseo_old_post_url'] ) );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves action field from the post.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
protected function get_post_action() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Reason: We are not controlling the request.
|
||||
if ( isset( $_POST['action'] ) && is_string( $_POST['action'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['action'] ) );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the undo redirect notification
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The old URL to the post.
|
||||
* @param int $object_id The post or term ID.
|
||||
* @param string $object_type The object type: post or term.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_undo_slug_notification( WPSEO_Redirect $redirect, $object_id, $object_type ) {
|
||||
|
||||
if ( ! $this->is_rest_request() && ! wp_doing_ajax() ) {
|
||||
parent::set_undo_slug_notification( $redirect, $object_id, $object_type );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header( 'X-Yoast-Redirect-Created: 1; origin=' . $redirect->get_origin() . '; target=' . $redirect->get_target() . '; type=' . $redirect->get_type() . '; objectId=' . $object_id . '; objectType=' . $object_type );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Localizes JavaScript files.
|
||||
*/
|
||||
final class WPSEO_Premium_Asset_JS_L10n {
|
||||
|
||||
/**
|
||||
* Localizes the given script with the JavaScript translations.
|
||||
*
|
||||
* @param string $script_handle The script handle to localize for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function localize_script( $script_handle ) {
|
||||
$translations = [
|
||||
'wordpress-seo-premium' => $this->get_translations( 'wordpress-seo-premiumjs' ),
|
||||
];
|
||||
wp_localize_script( $script_handle, 'wpseoPremiumJSL10n', $translations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns translations necessary for JS files.
|
||||
*
|
||||
* @param string $component The component to retrieve the translations for.
|
||||
* @return object|null The translations in a Jed format for JS files or null
|
||||
* if the translation file could not be found.
|
||||
*/
|
||||
protected function get_translations( $component ) {
|
||||
$locale = get_user_locale();
|
||||
|
||||
$file = plugin_dir_path( WPSEO_PREMIUM_FILE ) . '/languages/' . $component . '-' . $locale . '.json';
|
||||
if ( file_exists( $file ) ) {
|
||||
$file = file_get_contents( $file );
|
||||
if ( is_string( $file ) && $file !== '' ) {
|
||||
return json_decode( $file, true );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Loads the Premium assets.
|
||||
*/
|
||||
class WPSEO_Premium_Assets implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore Method relies on a WordPress function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_init', [ $this, 'register_assets' ] );
|
||||
add_action( 'init', [ $this, 'register_frontend_assets' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the assets for premium.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_assets() {
|
||||
$version = $this->get_version();
|
||||
$scripts = $this->get_scripts( $version );
|
||||
$styles = $this->get_styles( $version );
|
||||
|
||||
array_walk( $scripts, [ $this, 'register_script' ] );
|
||||
array_walk( $styles, [ $this, 'register_style' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the assets for premium.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_frontend_assets() {
|
||||
$version = $this->get_version();
|
||||
$scripts = $this->get_frontend_scripts( $version );
|
||||
|
||||
array_walk( $scripts, [ $this, 'register_script' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a flatten version.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses a dependency.
|
||||
*
|
||||
* @return string The flatten version.
|
||||
*/
|
||||
protected function get_version() {
|
||||
$asset_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
return $asset_manager->flatten_version( WPSEO_PREMIUM_VERSION );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of script to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The scripts.
|
||||
*/
|
||||
protected function get_frontend_scripts( $version ) {
|
||||
return [
|
||||
[
|
||||
'name' => 'yoast-seo-premium-frontend-inspector',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'frontend-inspector-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'react',
|
||||
'react-dom',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'frontend-inspector-resources',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'prop-types-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'style-guide',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
'in_footer' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of script to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The scripts.
|
||||
*/
|
||||
protected function get_scripts( $version ) {
|
||||
return [
|
||||
[
|
||||
'name' => 'yoast-seo-premium-metabox',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-metabox-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'jquery',
|
||||
'regenerator-runtime',
|
||||
'underscore',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'wp-util',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'help-scout-beacon',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-draft-js-plugins',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-draft-js-plugins-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-premium-workouts',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'workouts-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'lodash',
|
||||
'regenerator-runtime',
|
||||
'wp-api-fetch',
|
||||
'wp-a11y',
|
||||
'wp-components',
|
||||
'wp-compose',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'admin-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-select',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-seo-social-metadata-previews-package',
|
||||
'path' => 'assets/js/dist/yoast/',
|
||||
'filename' => 'social-metadata-previews-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-a11y',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'draft-js',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'helpers',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'replacement-variable-editor',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'style-guide',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'styled-components',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-social-metadata-previews',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'yoast-premium-social-metadata-previews-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'in_footer' => true,
|
||||
'dependencies' => [
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-plugins',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-custom-fields-plugin',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-custom-fields-plugin-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-quickedit-notification',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-quickedit-notification-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
'wp-api',
|
||||
'wp-api-fetch',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-redirect-notifications',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-redirect-notifications-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'jquery',
|
||||
'wp-api',
|
||||
'wp-api-fetch',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-redirect-notifications-gutenberg',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-redirect-notifications-gutenberg-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'wp-plugins',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-dynamic-blocks',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'dynamic-blocks-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'wp-blocks',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-hooks',
|
||||
'wp-server-side-render',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-blocks',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'blocks-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'wp-block-editor',
|
||||
'wp-blocks',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-i18n',
|
||||
'yoast-seo-premium-metabox',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'yoast-premium-prominent-words-indexation',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'yoast-premium-prominent-words-indexation-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'regenerator-runtime',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'indexation',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'elementor-premium',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'wp-seo-premium-elementor-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'clipboard',
|
||||
'jquery',
|
||||
'regenerator-runtime',
|
||||
'underscore',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
'wp-util',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'help-scout-beacon',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'search-metadata-previews',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-forms',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'social-metadata-previews-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'yoast-components',
|
||||
],
|
||||
'footer' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-ai-generator',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'ai-generator-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'regenerator-runtime',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'analysis',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'editor-modules',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-manage-ai-consent-button',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'manage-ai-consent-button-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'regenerator-runtime',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'wp-seo-premium-introductions',
|
||||
'path' => 'assets/js/dist/',
|
||||
'filename' => 'introductions-' . $version . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
'dependencies' => [
|
||||
'lodash',
|
||||
'regenerator-runtime',
|
||||
'wp-api-fetch',
|
||||
'wp-components',
|
||||
'wp-data',
|
||||
'wp-dom-ready',
|
||||
'wp-element',
|
||||
'wp-hooks',
|
||||
'wp-i18n',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'introductions',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'ui-library-package',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'react-helmet-package',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of styles to register.
|
||||
*
|
||||
* @codeCoverageIgnore Returns a simple dataset.
|
||||
*
|
||||
* @param string $version Current version number.
|
||||
*
|
||||
* @return array The styles.
|
||||
*/
|
||||
protected function get_styles( $version ) {
|
||||
$rtl_suffix = ( is_rtl() ) ? '-rtl' : '';
|
||||
|
||||
return [
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox',
|
||||
'source' => 'assets/css/dist/premium-metabox-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-workouts',
|
||||
'source' => 'assets/css/dist/premium-workouts-' . $version . '.css',
|
||||
'dependencies' => [
|
||||
'wp-components',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'elementor-premium',
|
||||
'source' => 'assets/css/dist/premium-elementor-' . $version . '.css',
|
||||
'dependencies' => [
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-draft-js-plugins',
|
||||
'source' => 'assets/css/dist/premium-draft-js-plugins-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-thank-you',
|
||||
'source' => 'assets/css/dist/premium-thank-you-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-settings',
|
||||
'source' => 'assets/css/dist/premium-settings-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-post-overview',
|
||||
'source' => 'assets/css/dist/premium-post-overview-' . $version . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-tailwind',
|
||||
'source' => 'assets/css/dist/premium-tailwind-' . $version . $rtl_suffix . '.css',
|
||||
'dependencies' => [],
|
||||
],
|
||||
[
|
||||
'name' => WPSEO_Admin_Asset_Manager::PREFIX . 'premium-ai-generator',
|
||||
'source' => 'assets/css/dist/premium-ai-generator-' . $version . $rtl_suffix . '.css',
|
||||
'dependencies' => [
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'premium-tailwind',
|
||||
WPSEO_Admin_Asset_Manager::PREFIX . 'monorepo',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given script to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method calls a WordPress function.
|
||||
*
|
||||
* @param array $script The script to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function register_script( $script ) {
|
||||
$url = plugin_dir_url( WPSEO_PREMIUM_FILE ) . $script['path'] . $script['filename'];
|
||||
|
||||
if ( defined( 'YOAST_SEO_PREMIUM_DEV_SERVER' ) && YOAST_SEO_PREMIUM_DEV_SERVER ) {
|
||||
$url = 'http://localhost:8081/' . $script['filename'];
|
||||
}
|
||||
|
||||
$in_footer = ( $script['in_footer'] ?? false );
|
||||
|
||||
wp_register_script(
|
||||
$script['name'],
|
||||
$url,
|
||||
$script['dependencies'],
|
||||
WPSEO_PREMIUM_VERSION,
|
||||
$in_footer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given style to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method calls a WordPress function.
|
||||
*
|
||||
* @param array $style The style to register.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function register_style( $style ) {
|
||||
wp_register_style(
|
||||
$style['name'],
|
||||
plugin_dir_url( WPSEO_PREMIUM_FILE ) . $style['source'],
|
||||
$style['dependencies'],
|
||||
WPSEO_PREMIUM_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exposes shortlinks to wpseoAdminL10n.
|
||||
*/
|
||||
class WPSEO_Premium_Expose_Shortlinks implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'wpseo_admin_l10n', [ $this, 'expose_shortlinks' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that adds the keyword synonyms shortlink to the localization object.
|
||||
*
|
||||
* @param array $input Admin localization object.
|
||||
*
|
||||
* @return array Admin localization object.
|
||||
*/
|
||||
public function expose_shortlinks( $input ) {
|
||||
$input['shortlinks.keyword_synonyms_info'] = WPSEO_Shortlinker::get( 'https://yoa.st/kd1' );
|
||||
$input['shortlinks.site_structure_metabox'] = WPSEO_Shortlinker::get( 'https://yoa.st/site-structure-metabox' );
|
||||
$input['shortlinks.notification_internal_link'] = WPSEO_Shortlinker::get( 'https://yoa.st/notification-internal-link' );
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Import_Manager
|
||||
*/
|
||||
class WPSEO_Premium_Import_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Holds the import object.
|
||||
*
|
||||
* @var stdClass
|
||||
*/
|
||||
protected $import;
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Handle premium imports.
|
||||
add_filter( 'wpseo_handle_import', [ $this, 'do_premium_imports' ] );
|
||||
|
||||
// Add htaccess import block.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_redirect_import_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'redirects_import_header' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports redirects from specified file or location.
|
||||
*
|
||||
* @param stdClass|bool $import The import object.
|
||||
*
|
||||
* @return stdClass The import status object.
|
||||
*/
|
||||
public function do_premium_imports( $import ) {
|
||||
if ( ! $import ) {
|
||||
$import = (object) [
|
||||
'msg' => '',
|
||||
'success' => false,
|
||||
'status' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$this->import = $import;
|
||||
$this->htaccess_import();
|
||||
$this->do_plugin_imports();
|
||||
$this->do_csv_imports();
|
||||
|
||||
return $this->import;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the htaccess import block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirects_import_header() {
|
||||
/* translators: %s: '.htaccess' file name */
|
||||
echo '<a class="nav-tab" id="import-htaccess-tab" href="#top#import-htaccess">' . esc_html__( 'Import redirects', 'wordpress-seo-premium' ) . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding the import block for redirects.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_redirect_import_block() {
|
||||
$import = $this->import;
|
||||
|
||||
// Display the forms.
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/import-redirects.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Do .htaccess file import.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function htaccess_import() {
|
||||
$htaccess = $this->get_posted_htaccess();
|
||||
|
||||
if ( ! $htaccess || $htaccess === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader = new WPSEO_Redirect_HTAccess_Loader( $htaccess );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles plugin imports.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_plugin_imports() {
|
||||
$import_plugin = $this->get_posted_import_plugin();
|
||||
|
||||
if ( ! $import_plugin ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$loader = $this->get_plugin_loader( $import_plugin );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a CSV import.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function do_csv_imports() {
|
||||
$redirects_csv_file = $this->get_posted_csv_file();
|
||||
|
||||
if ( ! $redirects_csv_file ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validate_uploaded_csv_file( $redirects_csv_file );
|
||||
|
||||
// Load the redirects from the uploaded file.
|
||||
$loader = new WPSEO_Redirect_CSV_Loader( $redirects_csv_file['tmp_name'] );
|
||||
$result = $this->import_redirects_from_loader( $loader );
|
||||
|
||||
$this->set_import_success( $result );
|
||||
}
|
||||
catch ( WPSEO_Redirect_Import_Exception $e ) {
|
||||
$this->set_import_message( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the import message.
|
||||
*
|
||||
* @param string $import_message The message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_import_message( $import_message ) {
|
||||
$this->import->msg .= $import_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the import success state to true.
|
||||
*
|
||||
* @param array $result The import result.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_import_success( array $result ) {
|
||||
$this->import->success = true;
|
||||
|
||||
$this->set_import_message(
|
||||
$this->get_success_message( $result['total_imported'], $result['total_redirects'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the success message when import has been successful.
|
||||
*
|
||||
* @param int $total_imported The number of imported redirects.
|
||||
* @param int $total_redirects The total amount of redirects.
|
||||
*
|
||||
* @return string The generated message.
|
||||
*/
|
||||
protected function get_success_message( $total_imported, $total_redirects ) {
|
||||
if ( $total_imported === $total_redirects ) {
|
||||
return sprintf(
|
||||
/* translators: 1: link to redirects overview, 2: closing link tag */
|
||||
__( 'All redirects have been imported successfully. Go to the %1$sredirects overview%2$s to see the imported redirects.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $total_imported === 0 ) {
|
||||
return sprintf(
|
||||
/* translators: 1: link to redirects overview, 2: closing link tag */
|
||||
__( 'No redirects have been imported. Probably they already exist as a redirect. Go to the %1$sredirects overview%2$s to see the existing redirects.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: 1: amount of imported redirects, 2: total amount of redirects, 3: link to redirects overview, 4: closing link tag */
|
||||
_n(
|
||||
'Imported %1$s/%2$s redirects successfully. Go to the %3$sredirects overview%4$s to see the imported redirect.',
|
||||
'Imported %1$s/%2$s redirects successfully. Go to the %3$sredirects overview%4$s to see the imported redirects.',
|
||||
$total_imported,
|
||||
'wordpress-seo-premium'
|
||||
),
|
||||
$total_imported,
|
||||
$total_redirects,
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_redirects' ) ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a loader for the given plugin.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $plugin_name The plugin we want to load redirects from.
|
||||
*
|
||||
* @return bool|WPSEO_Redirect_Abstract_Loader The redirect loader.
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When the plugin is not installed or activated.
|
||||
*/
|
||||
protected function get_plugin_loader( $plugin_name ) {
|
||||
global $wpdb;
|
||||
|
||||
switch ( $plugin_name ) {
|
||||
case 'redirection':
|
||||
// Only do import if Redirections is active.
|
||||
if ( ! defined( 'REDIRECTION_VERSION' ) ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: the Redirection plugin is not installed or activated.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
return new WPSEO_Redirect_Redirection_Loader( $wpdb );
|
||||
case 'safe_redirect_manager':
|
||||
return new WPSEO_Redirect_Safe_Redirect_Loader();
|
||||
case 'simple-301-redirects':
|
||||
return new WPSEO_Redirect_Simple_301_Redirect_Loader();
|
||||
default:
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: the selected redirect plugin is not installed or activated.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an uploaded CSV file.
|
||||
*
|
||||
* @param array $csv_file The file to upload, from the $_FILES object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When the given file is invalid.
|
||||
*/
|
||||
protected function validate_uploaded_csv_file( $csv_file ) {
|
||||
|
||||
// If no file is selected.
|
||||
if ( array_key_exists( 'name', $csv_file ) && $csv_file['name'] === '' ) {
|
||||
$error_message = __( 'CSV import failed: No file selected.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If the file upload failed for any other reason.
|
||||
if ( array_key_exists( 'error', $csv_file ) && $csv_file['error'] !== UPLOAD_ERR_OK ) {
|
||||
$error_message = __( 'CSV import failed: the provided file could not be parsed using a CSV parser.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If somehow the file is larger than it should be.
|
||||
if ( $csv_file['size'] > wp_max_upload_size() ) {
|
||||
$max_size_formatted = size_format( wp_max_upload_size() );
|
||||
/* translators: 1: The maximum file size */
|
||||
$error_message = sprintf( __( 'CSV import failed: the provided file is larger than %1$s.', 'wordpress-seo-premium' ), $max_size_formatted );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
|
||||
// If it's not a CSV file (send the csv mimetype along for multisite installations).
|
||||
$filetype = wp_check_filetype( $csv_file['name'], [ 'csv' => 'text/csv' ] );
|
||||
if ( strtolower( $filetype['ext'] ) !== 'csv' ) {
|
||||
$error_message = __( 'CSV import failed: the provided file is not a CSV file.', 'wordpress-seo-premium' );
|
||||
throw new WPSEO_Redirect_Import_Exception( $error_message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all redirects from the loader.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param WPSEO_Redirect_Loader $loader The loader to import redirects from.
|
||||
*
|
||||
* @return array The result of the import.
|
||||
*
|
||||
* @throws WPSEO_Redirect_Import_Exception When there is no loader given or when there are no redirects.
|
||||
*/
|
||||
protected function import_redirects_from_loader( WPSEO_Redirect_Loader $loader ) {
|
||||
if ( ! $loader ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: we can\'t recognize this type of import.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
|
||||
$redirects = $loader->load();
|
||||
|
||||
if ( count( $redirects ) === 0 ) {
|
||||
throw new WPSEO_Redirect_Import_Exception(
|
||||
__( 'Redirect import failed: no redirects found.', 'wordpress-seo-premium' )
|
||||
);
|
||||
}
|
||||
|
||||
$importer = new WPSEO_Redirect_Importer();
|
||||
return $importer->import( $redirects );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted htaccess.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The posted htaccess.
|
||||
*/
|
||||
protected function get_posted_htaccess() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are validating a nonce here.
|
||||
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wpseo-import' )
|
||||
&& isset( $_POST['htaccess'] ) && is_string( $_POST['htaccess'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['htaccess'] ) );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted import plugin.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string|null The posted import plugin.
|
||||
*/
|
||||
protected function get_posted_import_plugin() {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are validating a nonce here.
|
||||
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'wpseo-import' )
|
||||
&& isset( $_POST['wpseo'] ) && is_array( $_POST['wpseo'] )
|
||||
&& isset( $_POST['wpseo']['import_plugin'] ) && is_string( $_POST['wpseo']['import_plugin'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_POST['wpseo']['import_plugin'] ) );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the posted CSV file.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return array|null The posted CSV file.
|
||||
*/
|
||||
protected function get_posted_csv_file() {
|
||||
if ( ! isset( $_FILES['redirects_csv_file'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $_FILES['redirects_csv_file'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Javascript_Strings.
|
||||
*/
|
||||
class WPSEO_Premium_Javascript_Strings {
|
||||
|
||||
/**
|
||||
* List containing the localized JavaScript translations.
|
||||
*
|
||||
* @var string[]|null
|
||||
*/
|
||||
private static $strings = null;
|
||||
|
||||
/**
|
||||
* Fill the value of self::$strings with translated strings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function fill() {
|
||||
self::$strings = [
|
||||
'error_circular' => __( 'You can\'t redirect a URL to itself.', 'wordpress-seo-premium' ),
|
||||
'error_old_url' => __( 'The old URL field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_regex' => __( 'The Regular Expression field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_new_url' => __( 'The new URL field can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'error_saving_redirect' => __( 'Error while saving this redirect', 'wordpress-seo-premium' ),
|
||||
'error_new_type' => __( 'New type can\'t be empty.', 'wordpress-seo-premium' ),
|
||||
'unsaved_redirects' => __( 'You have unsaved redirects, are you sure you want to leave?', 'wordpress-seo-premium' ),
|
||||
|
||||
/* translators: %s is replaced with the URL that will be deleted. */
|
||||
'enter_new_url' => __( 'Please enter the new URL for %s', 'wordpress-seo-premium' ),
|
||||
/* translators: variables will be replaced with from and to URLs. */
|
||||
'redirect_saved' => __( 'Redirect created from %1$s to %2$s!', 'wordpress-seo-premium' ),
|
||||
/* translators: %1$s will be replaced with the from URL. */
|
||||
'redirect_saved_no_target' => __( '410 Redirect created from %1$s!', 'wordpress-seo-premium' ),
|
||||
|
||||
'redirect_added' => [
|
||||
'title' => __( 'Redirect added.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was added successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
'redirect_updated' => [
|
||||
'title' => __( 'Redirect updated.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was updated successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
'redirect_deleted' => [
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
],
|
||||
|
||||
'button_ok' => __( 'OK', 'wordpress-seo-premium' ),
|
||||
'button_cancel' => __( 'Cancel', 'wordpress-seo-premium' ),
|
||||
'button_save' => __( 'Save', 'wordpress-seo-premium' ),
|
||||
'button_save_anyway' => __( 'Save anyway', 'wordpress-seo-premium' ),
|
||||
|
||||
'edit_redirect' => __( 'Edit redirect', 'wordpress-seo-premium' ),
|
||||
'editing_redirect' => __( 'You are already editing a redirect, please finish this one first', 'wordpress-seo-premium' ),
|
||||
|
||||
'editAction' => __( 'Edit', 'wordpress-seo-premium' ),
|
||||
'deleteAction' => __( 'Delete', 'wordpress-seo-premium' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the translated strings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function strings() {
|
||||
if ( self::$strings === null ) {
|
||||
self::fill();
|
||||
}
|
||||
|
||||
return self::$strings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this is an old premium file.
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Export_Keywords_Manager.
|
||||
*
|
||||
* Manages exporting keywords.
|
||||
*/
|
||||
class WPSEO_Premium_Keyword_Export_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Hook into the request in case of CSV download and return our generated CSV instead.
|
||||
add_action( 'admin_init', [ $this, 'keywords_csv_export' ] );
|
||||
|
||||
// Add htaccess import block.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_keyword_export_tab_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'keywords_export_tab_header' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the CSV export block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function keywords_export_tab_header() {
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
echo '<a class="nav-tab" id="keywords-export-tab" href="#top#keywords-export">'
|
||||
. esc_html__( 'Export keyphrases', 'wordpress-seo-premium' )
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the export block for CSV. Makes it able to export redirects to CSV.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_keyword_export_tab_block() {
|
||||
// Display the forms.
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
$yform = Yoast_Form::get_instance();
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/export-keywords.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into the request and returns a CSV file if we're on the right page with the right method and the right capabilities.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function keywords_csv_export() {
|
||||
|
||||
if ( ! $this->is_valid_csv_export_request() || ! current_user_can( 'export' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have a valid nonce.
|
||||
check_admin_referer( 'wpseo-export' );
|
||||
|
||||
// Clean any content that has been already outputted, for example by other plugins or faulty PHP files.
|
||||
if ( ob_get_contents() ) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
// Make sure we don't time out during the collection of items.
|
||||
set_time_limit( 0 );
|
||||
|
||||
// Set CSV headers and content.
|
||||
$this->set_csv_headers();
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is controlled output.
|
||||
echo $this->get_csv_contents();
|
||||
|
||||
// And exit so we don't start appending HTML to our CSV file.
|
||||
// NOTE: this makes this entire class untestable as it will exit all tests but WordPress seems to have no elegant way of handling this.
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a POST request for a CSV export of posts and keywords.
|
||||
*
|
||||
* @return bool True if this is a valid CSV export request.
|
||||
*/
|
||||
protected function is_valid_csv_export_request() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- Reason: Nonce is checked in export.
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only or ignoring the value.
|
||||
return ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'wpseo_tools' )
|
||||
&& ( isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) && wp_unslash( $_GET['tool'] ) === 'import-export' )
|
||||
&& ( isset( $_POST['export-posts'] ) && ! empty( $_POST['export-posts'] ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers to trigger a CSV download in the browser.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_csv_headers() {
|
||||
header( 'Content-type: text/csv' );
|
||||
header( 'Content-Disposition: attachment; filename=' . gmdate( 'Y-m-d' ) . '-yoast-seo-keywords.csv' );
|
||||
header( 'Pragma: no-cache' );
|
||||
header( 'Expires: 0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CSV to be exported.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function get_csv_contents() {
|
||||
$columns = [ 'keywords' ];
|
||||
|
||||
$post_wpseo = filter_input( INPUT_POST, 'wpseo', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
|
||||
|
||||
if ( is_array( $post_wpseo ) ) {
|
||||
$columns = array_merge( $columns, $this->get_export_columns( $post_wpseo ) );
|
||||
}
|
||||
|
||||
$builder = new WPSEO_Export_Keywords_CSV( $columns );
|
||||
$builder->print_headers();
|
||||
$this->prepare_export( $builder, $columns );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the requested columns.
|
||||
*
|
||||
* @param array $post_object An associative array with the post data.
|
||||
*
|
||||
* @return array List of export columns.
|
||||
*/
|
||||
protected function get_export_columns( array $post_object ) {
|
||||
$exportable_columns = [
|
||||
'export-keywords-score' => 'keywords_score',
|
||||
'export-url' => 'url',
|
||||
'export-title' => 'title',
|
||||
'export-seo-title' => 'seo_title',
|
||||
'export-meta-description' => 'meta_description',
|
||||
'export-readability-score' => 'readability_score',
|
||||
];
|
||||
|
||||
// Need to call array_values to ensure that we get a numerical key back.
|
||||
return array_values( array_intersect_key( $exportable_columns, $post_object ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds post and term items to the CSV builder.
|
||||
*
|
||||
* @param WPSEO_Export_Keywords_CSV $builder The builder to use.
|
||||
* @param array $columns The columns that need to be exported.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepare_export( WPSEO_Export_Keywords_CSV $builder, array $columns ) {
|
||||
$this->feed_to_builder(
|
||||
$builder,
|
||||
new WPSEO_Export_Keywords_Post_Query( $columns, 1000 ),
|
||||
new WPSEO_Export_Keywords_Post_Presenter( $columns )
|
||||
);
|
||||
|
||||
$this->feed_to_builder(
|
||||
$builder,
|
||||
new WPSEO_Export_Keywords_Term_Query( $columns, 1000 ),
|
||||
new WPSEO_Export_Keywords_Term_Presenter( $columns )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the items and feeds them to the builder.
|
||||
*
|
||||
* @param WPSEO_Export_Keywords_CSV $builder Builder to feed the items to.
|
||||
* @param WPSEO_Export_Keywords_Query $export_query Query to use to get the items.
|
||||
* @param WPSEO_Export_Keywords_Presenter $presenter Presenter to present the items in the builder format.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function feed_to_builder( WPSEO_Export_Keywords_CSV $builder, WPSEO_Export_Keywords_Query $export_query, WPSEO_Export_Keywords_Presenter $presenter ) {
|
||||
$page_size = $export_query->get_page_size();
|
||||
|
||||
$page = 1;
|
||||
do {
|
||||
$results = $export_query->get_data( $page );
|
||||
|
||||
if ( ! is_array( $results ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$result_count = count( $results );
|
||||
|
||||
// Present the result.
|
||||
$presented = array_map( [ $presenter, 'present' ], $results );
|
||||
|
||||
// Feed presented item to the builder.
|
||||
array_walk( $presented, [ $builder, 'print_row' ] );
|
||||
|
||||
++$page;
|
||||
|
||||
// If we have the number of items per page, there will be more items ahead.
|
||||
} while ( $result_count === $page_size );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium|Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Premium\Helpers\Current_Page_Helper;
|
||||
use Yoast\WP\SEO\Premium\Helpers\Prominent_Words_Helper;
|
||||
use Yoast\WP\SEO\Premium\Integrations\Admin\Prominent_Words\Indexing_Integration;
|
||||
|
||||
/**
|
||||
* The metabox for premium.
|
||||
*/
|
||||
class WPSEO_Premium_Metabox implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Metabox_Link_Suggestions class.
|
||||
*
|
||||
* @var WPSEO_Metabox_Link_Suggestions
|
||||
*/
|
||||
protected $link_suggestions;
|
||||
|
||||
/**
|
||||
* The prominent words helper.
|
||||
*
|
||||
* @var Prominent_Words_Helper
|
||||
*/
|
||||
protected $prominent_words_helper;
|
||||
|
||||
/**
|
||||
* Holds the Current_Page_Helper instance.
|
||||
*
|
||||
* @var Current_Page_Helper
|
||||
*/
|
||||
private $current_page_helper;
|
||||
|
||||
/**
|
||||
* Creates the meta box class.
|
||||
*
|
||||
* @param Prominent_Words_Helper $prominent_words_helper The prominent words helper.
|
||||
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
|
||||
* @param WPSEO_Metabox_Link_Suggestions|null $link_suggestions The link suggestions meta box.
|
||||
*/
|
||||
public function __construct(
|
||||
Prominent_Words_Helper $prominent_words_helper,
|
||||
Current_Page_Helper $current_page_helper,
|
||||
?WPSEO_Metabox_Link_Suggestions $link_suggestions = null
|
||||
) {
|
||||
if ( $link_suggestions === null ) {
|
||||
$link_suggestions = new WPSEO_Metabox_Link_Suggestions();
|
||||
}
|
||||
|
||||
$this->prominent_words_helper = $prominent_words_helper;
|
||||
$this->current_page_helper = $current_page_helper;
|
||||
$this->link_suggestions = $link_suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers relevant hooks to WordPress.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
||||
add_action( 'admin_init', [ $this, 'initialize' ] );
|
||||
|
||||
$this->link_suggestions->register_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content endpoints are available.
|
||||
*
|
||||
* @return bool Returns true if the content endpoints are available
|
||||
*/
|
||||
public static function are_content_endpoints_available() {
|
||||
if ( function_exists( 'rest_get_server' ) ) {
|
||||
$namespaces = rest_get_server()->get_namespaces();
|
||||
|
||||
return in_array( 'wp/v2', $namespaces, true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the metabox by loading the register_hooks for the dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize() {
|
||||
if ( ! $this->load_metabox( $this->get_current_page() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $this->get_metabox_integrations() as $integration ) {
|
||||
$integration->register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues assets when relevant.
|
||||
*
|
||||
* @codeCoverageIgnore Method uses dependencies.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
if ( ! $this->load_metabox( $this->get_current_page() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
wp_enqueue_style( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
|
||||
$premium_localization = new WPSEO_Premium_Asset_JS_L10n();
|
||||
$premium_localization->localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'premium-metabox' );
|
||||
|
||||
$this->send_data_to_assets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to assets by using wp_localize_script.
|
||||
* Also localizes the Table of Contents heading title to the wp-seo-premium-blocks asset.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function send_data_to_assets() {
|
||||
$analysis_seo = new WPSEO_Metabox_Analysis_SEO();
|
||||
$content_analysis = new WPSEO_Metabox_Analysis_Readability();
|
||||
$assets_manager = new WPSEO_Admin_Asset_Manager();
|
||||
|
||||
/**
|
||||
* Filters the parameter to disable Table of Content block.
|
||||
*
|
||||
* Note: Used to prevent auto-generation of HTML anchors for headings when TOC block is registered.
|
||||
*
|
||||
* @since 21.5
|
||||
*
|
||||
* @param bool $disable_table_of_content The value of the `autoload` parameter. Default: false.
|
||||
*
|
||||
* @return bool The filtered value of the `disable_table_of_content` parameter.
|
||||
*/
|
||||
$disable_table_of_content = apply_filters( 'Yoast\WP\SEO\disable_table_of_content_block', false );
|
||||
|
||||
$data = [
|
||||
'restApi' => $this->get_rest_api_config(),
|
||||
'seoAnalysisEnabled' => $analysis_seo->is_enabled(),
|
||||
'contentAnalysisEnabled' => $content_analysis->is_enabled(),
|
||||
'licensedURL' => WPSEO_Utils::get_home_url(),
|
||||
'settingsPageUrl' => admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo-enable_link_suggestions' ),
|
||||
'integrationsTabURL' => admin_url( 'admin.php?page=wpseo_integrations' ),
|
||||
'premiumAssessmentsScriptUrl' => plugins_url(
|
||||
'assets/js/dist/register-premium-assessments-' . $assets_manager->flatten_version( WPSEO_PREMIUM_VERSION ) . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
WPSEO_PREMIUM_FILE
|
||||
),
|
||||
'pluginUrl' => plugins_url( '', WPSEO_PREMIUM_FILE ),
|
||||
];
|
||||
|
||||
if ( defined( 'YOAST_SEO_TEXT_FORMALITY' ) && YOAST_SEO_TEXT_FORMALITY === true ) {
|
||||
$data['textFormalityScriptUrl'] = plugins_url(
|
||||
'assets/js/dist/register-text-formality-' . $assets_manager->flatten_version( WPSEO_PREMIUM_VERSION ) . WPSEO_CSSJS_SUFFIX . '.js',
|
||||
WPSEO_PREMIUM_FILE
|
||||
);
|
||||
}
|
||||
|
||||
if ( WPSEO_Metabox::is_post_edit( $this->get_current_page() ) ) {
|
||||
$data = array_merge( $data, $this->get_post_metabox_config() );
|
||||
}
|
||||
elseif ( WPSEO_Taxonomy::is_term_edit( $this->get_current_page() ) ) {
|
||||
$data = array_merge( $data, $this->get_term_metabox_config() );
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||
$data['workoutsUrl'] = admin_url( 'admin.php?page=wpseo_workouts' );
|
||||
}
|
||||
|
||||
// Use an extra level in the array to preserve booleans. WordPress sanitizes scalar values in the first level of the array.
|
||||
wp_localize_script( 'yoast-seo-premium-metabox', 'wpseoPremiumMetaboxData', [ 'data' => $data ] );
|
||||
|
||||
// Localize the title of the Table of Contents block: the translation needs to be based on the site language instead of the user language.
|
||||
wp_localize_script(
|
||||
'wp-seo-premium-blocks',
|
||||
'wpseoTOCData',
|
||||
[
|
||||
'data' => [
|
||||
'TOCTitle' => __( 'Table of contents', 'wordpress-seo-premium' ),
|
||||
'disableTableOfContents' => $disable_table_of_content,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox config for a post.
|
||||
*
|
||||
* @return array The config.
|
||||
*/
|
||||
protected function get_post_metabox_config() {
|
||||
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
|
||||
$post = $this->get_post();
|
||||
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
$is_prominent_words_available = $prominent_words_support->is_post_type_supported( $post->post_type );
|
||||
|
||||
$site_locale = get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
return [
|
||||
'currentObjectId' => $this->get_post_ID(),
|
||||
'currentObjectType' => 'post',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $is_prominent_words_available,
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'isProminentWordsAvailable' => $is_prominent_words_available,
|
||||
'isTitleAssessmentAvailable' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox config for a term.
|
||||
*
|
||||
* @return array The config.
|
||||
*/
|
||||
protected function get_term_metabox_config() {
|
||||
$term = null;
|
||||
if ( isset( $GLOBALS['tag_ID'], $GLOBALS['taxonomy'] ) ) {
|
||||
$term = get_term( $GLOBALS['tag_ID'], $GLOBALS['taxonomy'] );
|
||||
}
|
||||
|
||||
if ( $term === null || is_wp_error( $term ) ) {
|
||||
return [
|
||||
'insightsEnabled' => 'disabled',
|
||||
'linkSuggestionsEnabled' => 'disabled',
|
||||
'linkSuggestionsAvailable' => false,
|
||||
'linkSuggestionsUnindexed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$link_suggestions_enabled = WPSEO_Options::get( 'enable_link_suggestions', false );
|
||||
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
$is_prominent_words_available = $prominent_words_support->is_taxonomy_supported( $term->taxonomy );
|
||||
|
||||
$site_locale = get_locale();
|
||||
$language = WPSEO_Language_Utils::get_language( $site_locale );
|
||||
|
||||
return [
|
||||
'currentObjectId' => $term->term_id,
|
||||
'currentObjectType' => 'term',
|
||||
'linkSuggestionsEnabled' => ( $link_suggestions_enabled ) ? 'enabled' : 'disabled',
|
||||
'linkSuggestionsAvailable' => $is_prominent_words_available,
|
||||
'linkSuggestionsUnindexed' => ! $this->is_prominent_words_indexing_completed() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ),
|
||||
'perIndexableLimit' => $this->per_indexable_limit( $language ),
|
||||
'isProminentWordsAvailable' => $is_prominent_words_available,
|
||||
'isTitleAssessmentAvailable' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the REST API configuration.
|
||||
*
|
||||
* @return array The configuration.
|
||||
*/
|
||||
protected function get_rest_api_config() {
|
||||
return [
|
||||
'available' => WPSEO_Utils::is_api_available(),
|
||||
'contentEndpointsAvailable' => self::are_content_endpoints_available(),
|
||||
'root' => esc_url_raw( rest_url() ),
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post for the current admin page.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WP_Post The post for the current admin page.
|
||||
*/
|
||||
protected function get_post() {
|
||||
return get_post( $this->get_post_ID() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post ID from the globals.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return int The post ID.
|
||||
*/
|
||||
protected function get_post_ID() {
|
||||
if ( ! isset( $GLOBALS['post_ID'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $GLOBALS['post_ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the metabox specific integrations.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return WPSEO_WordPress_Integration[] The metabox integrations.
|
||||
*/
|
||||
protected function get_metabox_integrations() {
|
||||
return [
|
||||
'social-previews' => new WPSEO_Social_Previews(),
|
||||
|
||||
// Add custom fields plugin to post and page edit pages.
|
||||
'premium-custom-fields' => new WPSEO_Custom_Fields_Plugin(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the metabox related scripts should be loaded.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $current_page The page we are on.
|
||||
*
|
||||
* @return bool True when it should be loaded.
|
||||
*/
|
||||
protected function load_metabox( $current_page ) {
|
||||
// When the current page is a term related one.
|
||||
if ( WPSEO_Taxonomy::is_term_edit( $current_page ) || WPSEO_Taxonomy::is_term_overview( $current_page ) ) {
|
||||
return WPSEO_Options::get( 'display-metabox-tax-' . $this->current_page_helper->get_current_taxonomy() );
|
||||
}
|
||||
|
||||
// When the current page isn't a post related one.
|
||||
if ( WPSEO_Metabox::is_post_edit( $current_page ) || WPSEO_Metabox::is_post_overview( $current_page ) ) {
|
||||
return WPSEO_Post_Type::has_metabox_enabled( $this->current_page_helper->get_current_post_type() );
|
||||
}
|
||||
|
||||
// Make sure ajax integrations are loaded.
|
||||
return wp_doing_ajax();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the pagenow variable.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return string The value of pagenow.
|
||||
*/
|
||||
private function get_current_page() {
|
||||
global $pagenow;
|
||||
|
||||
return $pagenow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not we need to index more posts for correct link suggestion functionality.
|
||||
*
|
||||
* @return bool Whether or not we need to index more posts.
|
||||
*/
|
||||
protected function is_prominent_words_indexing_completed() {
|
||||
$is_indexing_completed = $this->prominent_words_helper->is_indexing_completed();
|
||||
if ( $is_indexing_completed === null ) {
|
||||
$indexation_integration = YoastSEOPremium()->classes->get( Indexing_Integration::class );
|
||||
$is_indexing_completed = $indexation_integration->get_unindexed_count( 0 ) === 0;
|
||||
|
||||
$this->prominent_words_helper->set_indexing_completed( $is_indexing_completed );
|
||||
}
|
||||
|
||||
return $is_indexing_completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of prominent words to store for content written in the given language.
|
||||
*
|
||||
* @param string $language The current language.
|
||||
*
|
||||
* @return int The number of words to store.
|
||||
*/
|
||||
protected function per_indexable_limit( $language ) {
|
||||
if ( YoastSEO()->helpers->language->has_function_word_support( $language ) ) {
|
||||
return Indexing_Integration::PER_INDEXABLE_LIMIT;
|
||||
}
|
||||
|
||||
return Indexing_Integration::PER_INDEXABLE_LIMIT_NO_FUNCTION_WORD_SUPPORT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the premium option.
|
||||
*/
|
||||
class WPSEO_Premium_Option extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_premium';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* {@internal Shouldn't be requested directly, use $this->get_defaults();}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Form fields.
|
||||
'prominent_words_indexing_completed' => null,
|
||||
'workouts' => [ 'cornerstone' => [ 'finishedSteps' => [] ] ],
|
||||
'should_redirect_after_install' => false,
|
||||
'activation_redirect_timestamp' => 0,
|
||||
'dismiss_update_premium_notification' => '0',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option to the WPSEO Options framework.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_option() {
|
||||
WPSEO_Options::register_option( static::get_instance() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return static Returns instance of itself.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( static::$instance instanceof static ) ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* All concrete classes must contain a validate_option() method which validates all
|
||||
* values within the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array The clean option value.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'prominent_words_indexing_completed':
|
||||
if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== null ) {
|
||||
$clean[ $key ] = WPSEO_Utils::validate_bool( $dirty[ $key ] );
|
||||
}
|
||||
|
||||
break;
|
||||
case 'workouts':
|
||||
if ( isset( $dirty[ $key ] ) && is_array( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'should_redirect_after_install':
|
||||
if ( isset( $dirty[ $key ] ) && is_bool( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'activation_redirect_timestamp':
|
||||
if ( isset( $dirty[ $key ] ) && is_int( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
case 'dismiss_update_premium_notification':
|
||||
if ( isset( $dirty[ $key ] ) && is_string( $dirty[ $key ] ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the functionality for the orphaned content support.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Content_Support {
|
||||
|
||||
/**
|
||||
* Returns an array with the supported post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
public function get_supported_post_types() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\orphaned_post_types' - Allows changes for the accessible post types.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param array $post_types The accessible post types.
|
||||
*/
|
||||
$orphaned_post_types = apply_filters( 'Yoast\WP\SEO\orphaned_post_types', WPSEO_Post_Type::get_accessible_post_types() );
|
||||
|
||||
if ( ! is_array( $orphaned_post_types ) || empty( $orphaned_post_types ) ) {
|
||||
$orphaned_post_types = [];
|
||||
}
|
||||
|
||||
return $orphaned_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is supported.
|
||||
*
|
||||
* @param string $post_type The post type to look up.
|
||||
*
|
||||
* @return bool True when post type is supported.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
return in_array( $post_type, $this->get_supported_post_types(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action;
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
|
||||
/**
|
||||
* Represents some util helpers for the orphaned posts.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Content_Utils {
|
||||
|
||||
/**
|
||||
* Checks if the orphaned content feature is enabled.
|
||||
*
|
||||
* @return bool True when the text link counter is enabled.
|
||||
*/
|
||||
public static function is_feature_enabled() {
|
||||
if ( ! YoastSEO()->classes->get( Migration_Status::class )->is_version( 'free', WPSEO_VERSION ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPSEO_Options::get( 'enable_text_link_counter', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are unprocessed objects.
|
||||
*
|
||||
* @return bool True when there are unprocessed objects.
|
||||
*/
|
||||
public static function has_unprocessed_content() {
|
||||
static $has_unprocessed_posts;
|
||||
|
||||
if ( $has_unprocessed_posts === null ) {
|
||||
$post_link_action = YoastSEO()->classes->get( Post_Link_Indexing_Action::class );
|
||||
$has_unprocessed_posts = $post_link_action->get_total_unindexed();
|
||||
}
|
||||
|
||||
return $has_unprocessed_posts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Config\Migration_Status;
|
||||
|
||||
/**
|
||||
* Registers the filter for filtering posts by orphaned content.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Post_Filter extends WPSEO_Abstract_Post_Filter {
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
public function get_query_val() {
|
||||
return 'orphaned';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the hooks when the link feature is enabled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
if ( ! YoastSEO()->classes->get( Migration_Status::class )->is_version( 'free', WPSEO_VERSION ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::is_feature_enabled() ) {
|
||||
parent::register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter.
|
||||
*
|
||||
* @return string|null The explanation or null if the current post stype is unknown.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
$post_type_object = get_post_type_object( $this->get_current_post_type() );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$unprocessed = WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content();
|
||||
$can_recalculate = WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
|
||||
|
||||
$learn_more = sprintf(
|
||||
/* translators: %1$s expands to the link to an article to read more about orphaned content, %2$s expands to </a> */
|
||||
__( '%1$sLearn more about orphaned content%2$s.', 'wordpress-seo-premium' ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1ja' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
|
||||
if ( $unprocessed && ! $can_recalculate ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s: plural form of the current post type, %2$s: a Learn more about link */
|
||||
__( 'Ask your SEO Manager or Site Administrator to count links in all texts, so we can identify orphaned %1$s. %2$s', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
if ( $unprocessed ) {
|
||||
return sprintf(
|
||||
/* translators: %1$s expands to link to the recalculation option, %2$s: anchor closing, %3$s: plural form of the current post type, %4$s: a Learn more about link */
|
||||
__( '%1$sClick here%2$s to index your links, so we can identify orphaned %3$s. %4$s', 'wordpress-seo-premium' ),
|
||||
'<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_tools&reIndexLinks=1' ) ) . '">',
|
||||
'</a>',
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s: plural form of the current post type, %2$s: a Learn more about link */
|
||||
__( '\'Orphaned content\' refers to %1$s that have no inbound links, consider adding links towards these %1$s. %2$s', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
$learn_more
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query based on the seo_filter variable in $_GET.
|
||||
*
|
||||
* @param string $where Query variables.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
public function filter_posts( $where ) {
|
||||
if ( $this->is_filter_active() ) {
|
||||
$where .= $this->get_where_filter();
|
||||
$where .= $this->filter_published_posts();
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the where clause to use.
|
||||
*
|
||||
* @return string The where clause.
|
||||
*/
|
||||
protected function get_where_filter() {
|
||||
global $wpdb;
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content() ) {
|
||||
// Hide all posts, because we cannot tell anything for certain.
|
||||
return 'AND 1 = 0';
|
||||
}
|
||||
|
||||
$subquery = WPSEO_Premium_Orphaned_Post_Query::get_orphaned_content_query();
|
||||
|
||||
return ' AND ' . $wpdb->posts . '.ID IN ( ' . $subquery . ' ) ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a published posts filter so we don't show unpublished posts in the orphaned pages results.
|
||||
*
|
||||
* @return string A published posts filter.
|
||||
*/
|
||||
protected function filter_published_posts() {
|
||||
global $wpdb;
|
||||
|
||||
return " AND {$wpdb->posts}.post_status = 'publish' AND {$wpdb->posts}.post_password = ''";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
protected function get_label() {
|
||||
static $label;
|
||||
|
||||
if ( $label === null ) {
|
||||
$label = __( 'Orphaned content', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of articles that are orphaned content.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_post_total() {
|
||||
global $wpdb;
|
||||
$post_type = $this->get_current_post_type();
|
||||
$cache_key = 'orphaned_count_' . $post_type;
|
||||
$count = wp_cache_get( $cache_key, 'orphaned_counts' );
|
||||
|
||||
if ( WPSEO_Premium_Orphaned_Content_Utils::has_unprocessed_content() ) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder -- Reason: Will be supported in the next WPcs version.
|
||||
if ( $count === false ) {
|
||||
$subquery = WPSEO_Premium_Orphaned_Post_Query::get_orphaned_content_query();
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(ID)
|
||||
FROM %i
|
||||
WHERE ID IN ( $subquery )
|
||||
AND %i = 'publish'
|
||||
AND %i = ''
|
||||
AND %i = %s",
|
||||
$wpdb->posts,
|
||||
'post_status',
|
||||
'post_password',
|
||||
'post_type',
|
||||
$post_type
|
||||
)
|
||||
);
|
||||
|
||||
$count = (int) $count;
|
||||
|
||||
$expiry = ( wp_using_ext_object_cache() && wp_cache_supports( 'flush_group' ) ) ? DAY_IN_SECONDS : MINUTE_IN_SECONDS;
|
||||
wp_cache_set( $cache_key, $count, 'orphaned_counts', $expiry );
|
||||
}
|
||||
|
||||
// phpcs:enable
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$orphaned_content_support = new WPSEO_Premium_Orphaned_Content_Support();
|
||||
|
||||
return $orphaned_content_support->get_supported_post_types();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
use Yoast\WP\SEO\Repositories\Indexable_Repository;
|
||||
|
||||
/**
|
||||
* Represents the orphaned post query methods.
|
||||
*/
|
||||
class WPSEO_Premium_Orphaned_Post_Query {
|
||||
|
||||
/**
|
||||
* Returns the total number of orphaned items for the given post types.
|
||||
*
|
||||
* @deprecated 21.7
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $post_types The post types to get the counts for.
|
||||
*
|
||||
* @return int[] The counts for all post types.
|
||||
*/
|
||||
public static function get_counts( array $post_types ) {
|
||||
_deprecated_function( __METHOD__, 'Yoast SEO 21.7', 'WPSEO_Premium_Orphaned_Post_Filter::get_post_total' );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query to retrieve the object ids from the records with an incoming link count of 0.
|
||||
*
|
||||
* @return string Query for get all objects with an incoming link count of 0 from the DB.
|
||||
*/
|
||||
public static function get_orphaned_content_query() {
|
||||
static $query;
|
||||
|
||||
if ( $query === null ) {
|
||||
$repository = YoastSEO()->classes->get( Indexable_Repository::class );
|
||||
$query = $repository->query()
|
||||
->select( 'object_id' )
|
||||
->where( 'object_type', 'post' )
|
||||
->where_any_is(
|
||||
[
|
||||
[ 'incoming_link_count' => 0 ],
|
||||
[ 'incoming_link_count' => null ],
|
||||
]
|
||||
);
|
||||
|
||||
$frontpage_id = self::get_frontpage_id();
|
||||
if ( $frontpage_id ) {
|
||||
$query = $query->where_not_equal( 'object_id', $frontpage_id );
|
||||
}
|
||||
|
||||
$query = sprintf( $query->get_sql(), '\'post\'', 0, $frontpage_id );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the object ids from the records with an incoming link count of 0.
|
||||
*
|
||||
* @return array Array with the object ids.
|
||||
*/
|
||||
public static function get_orphaned_object_ids() {
|
||||
$repository = YoastSEO()->classes->get( Indexable_Repository::class );
|
||||
$results = $repository->query()
|
||||
->select( 'object_id' )
|
||||
->where( 'object_type', 'post' )
|
||||
->where( 'incoming_link_count', 0 )
|
||||
->find_array();
|
||||
|
||||
$object_ids = wp_list_pluck( $results, 'object_id' );
|
||||
$object_ids = self::remove_frontpage_id( $object_ids );
|
||||
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the frontpage id from orphaned id's when the frontpage is a static page.
|
||||
*
|
||||
* @param array $object_ids The orphaned object ids.
|
||||
*
|
||||
* @return array The orphaned object ids, without frontpage id.
|
||||
*/
|
||||
protected static function remove_frontpage_id( $object_ids ) {
|
||||
// When the frontpage is a static page, remove it from the object ids.
|
||||
if ( get_option( 'show_on_front' ) !== 'page' ) {
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
$frontpage_id = get_option( 'page_on_front' );
|
||||
|
||||
// If the frontpage ID exists in the list, remove it.
|
||||
$object_id_key = array_search( $frontpage_id, $object_ids, true );
|
||||
if ( $object_id_key !== false ) {
|
||||
unset( $object_ids[ $object_id_key ] );
|
||||
}
|
||||
|
||||
return $object_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the frontpage id when set, otherwise null.
|
||||
*
|
||||
* @return int|null The frontpage id when set.
|
||||
*/
|
||||
protected static function get_frontpage_id() {
|
||||
if ( get_option( 'show_on_front' ) !== 'page' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$page_on_front = get_option( 'page_on_front', null );
|
||||
if ( empty( $page_on_front ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $page_on_front;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the functionality for the prominent words support.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Support {
|
||||
|
||||
/**
|
||||
* Returns an array with the supported post types.
|
||||
*
|
||||
* @return array The supported post types.
|
||||
*/
|
||||
public function get_supported_post_types() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\prominent_words_post_types' - Allows changes for the accessible post types.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param array $post_types The accessible post types.
|
||||
*/
|
||||
$prominent_words_post_types = apply_filters(
|
||||
'Yoast\WP\SEO\prominent_words_post_types',
|
||||
WPSEO_Post_Type::get_accessible_post_types()
|
||||
);
|
||||
|
||||
if ( ! is_array( $prominent_words_post_types ) || empty( $prominent_words_post_types ) ) {
|
||||
$prominent_words_post_types = [];
|
||||
}
|
||||
|
||||
$prominent_words_post_types = WPSEO_Post_Type::filter_attachment_post_type( $prominent_words_post_types );
|
||||
$prominent_words_post_types = array_filter( $prominent_words_post_types, [ 'WPSEO_Post_Type', 'has_metabox_enabled' ] );
|
||||
|
||||
/*
|
||||
* The above combination of functions results in array looking like this:
|
||||
* [
|
||||
* `post` => `post`
|
||||
* `page` => `page`
|
||||
* ]
|
||||
*
|
||||
* This can result in problems downstream when trying to array_merge this twice.
|
||||
* array_values prevents this issue by ensuring numeric keys.
|
||||
*/
|
||||
$prominent_words_post_types = array_values( $prominent_words_post_types );
|
||||
|
||||
return $prominent_words_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the post type is supported.
|
||||
*
|
||||
* @param string $post_type The post type to look up.
|
||||
*
|
||||
* @return bool True when post type is supported.
|
||||
*/
|
||||
public function is_post_type_supported( $post_type ) {
|
||||
return in_array( $post_type, $this->get_supported_post_types(), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of taxonomies that are public, viewable and have the metabox enabled.
|
||||
*
|
||||
* @return array The supported taxonomies.
|
||||
*/
|
||||
public function get_supported_taxonomies() {
|
||||
$taxonomies = get_taxonomies( [ 'public' => true ] );
|
||||
$taxonomies = array_filter( $taxonomies, 'is_taxonomy_viewable' );
|
||||
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\prominent_words_taxonomies' - Allows to filter from which taxonomies terms are eligible for generating prominent words.
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 14.7.0
|
||||
*
|
||||
* @param array $taxonomies The accessible taxonomies.
|
||||
*/
|
||||
$prominent_words_taxonomies = apply_filters(
|
||||
'Yoast\WP\SEO\prominent_words_taxonomies',
|
||||
$taxonomies
|
||||
);
|
||||
|
||||
if ( ! is_array( $prominent_words_taxonomies ) || empty( $prominent_words_taxonomies ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prominent_words_taxonomies = array_filter(
|
||||
$prominent_words_taxonomies,
|
||||
static function ( $taxonomy ) {
|
||||
return (bool) WPSEO_Options::get( 'display-metabox-tax-' . $taxonomy, true );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( $prominent_words_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the taxonomy is supported.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy to look up.
|
||||
*
|
||||
* @return bool True when taxonomy is supported.
|
||||
*/
|
||||
public function is_taxonomy_supported( $taxonomy ) {
|
||||
return in_array( $taxonomy, $this->get_supported_taxonomies(), true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the unindexed posts.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Unindexed_Post_Query {
|
||||
|
||||
/**
|
||||
* List containing unindexed posts totals per post type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $totals = [];
|
||||
|
||||
/**
|
||||
* Returns the total amount of posts.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param int $limit The offset for the query.
|
||||
*
|
||||
* @return bool True when the limit has been exceeded.
|
||||
*/
|
||||
public function exceeds_limit( $limit ) {
|
||||
$unindexed_post_ids = $this->get_unindexed_post_ids( $this->get_post_types(), ( $limit + 1 ) );
|
||||
return count( $unindexed_post_ids ) > $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total unindexed posts for given post type.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param string $post_type The posttype to fetch.
|
||||
*
|
||||
* @return int The total amount of unindexed posts.
|
||||
*/
|
||||
public function get_total( $post_type ) {
|
||||
if ( ! array_key_exists( $post_type, $this->totals ) ) {
|
||||
$totals = $this->get_totals( $this->get_post_types() );
|
||||
|
||||
foreach ( $totals as $total_post_type => $total ) {
|
||||
$this->totals[ $total_post_type ] = $total;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $post_type, $this->totals ) ) {
|
||||
$this->totals[ $post_type ] = 0;
|
||||
}
|
||||
|
||||
return $this->totals[ $post_type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the totals for each posttype by counting them.
|
||||
*
|
||||
* @since 4.6.0
|
||||
*
|
||||
* @param array $post_types The posttype to limit the resultset for.
|
||||
*
|
||||
* @return array Array with the totals for the requested posttypes.
|
||||
*/
|
||||
public function get_totals( $post_types ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( $post_types === [] ) {
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
WPSEO_Premium_Prominent_Words_Versioning::POST_META_NAME,
|
||||
WPSEO_Premium_Prominent_Words_Versioning::get_version_number(),
|
||||
];
|
||||
$replacements = array_merge( $replacements, $post_types );
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT COUNT( ID ) as total, post_type
|
||||
FROM ' . $wpdb->posts . '
|
||||
WHERE ID NOT IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value = %s )
|
||||
AND post_status IN( "future", "draft", "pending", "private", "publish" )
|
||||
AND post_type IN( ' . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' )
|
||||
GROUP BY post_type
|
||||
',
|
||||
$replacements
|
||||
)
|
||||
);
|
||||
|
||||
$totals = [];
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
$totals[ $this->determine_rest_endpoint_for_post_type( $result->post_type ) ] = (int) $result->total;
|
||||
}
|
||||
|
||||
return $totals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the REST endpoint for the given post type.
|
||||
*
|
||||
* @param string $post_type The post type to determine the endpoint for.
|
||||
*
|
||||
* @return string The endpoint. Returns empty string if post type doesn't exist.
|
||||
*/
|
||||
protected function determine_rest_endpoint_for_post_type( $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
if ( is_null( $post_type_object ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( isset( $post_type_object->rest_base ) && ! empty( $post_type_object->rest_base ) ) {
|
||||
return $post_type_object->rest_base;
|
||||
}
|
||||
|
||||
return $post_type_object->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with supported posttypes.
|
||||
*
|
||||
* @return array The supported posttypes.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
$prominent_words_support = new WPSEO_Premium_Prominent_Words_Support();
|
||||
|
||||
return array_filter( $prominent_words_support->get_supported_post_types(), [ 'WPSEO_Post_Type', 'is_rest_enabled' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Post IDs of un-indexed objects.
|
||||
*
|
||||
* @param array|string $post_types The post type(s) to fetch.
|
||||
* @param int $limit Limit the number of results.
|
||||
*
|
||||
* @return int[] Post IDs found which are un-indexed.
|
||||
*/
|
||||
public function get_unindexed_post_ids( $post_types, $limit ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( is_string( $post_types ) ) {
|
||||
$post_types = (array) $post_types;
|
||||
}
|
||||
|
||||
if ( $post_types === [] ) {
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
WPSEO_Premium_Prominent_Words_Versioning::POST_META_NAME,
|
||||
WPSEO_Premium_Prominent_Words_Versioning::get_version_number(),
|
||||
];
|
||||
$replacements = array_merge( $replacements, $post_types );
|
||||
$replacements[] = $limit;
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT ID
|
||||
FROM ' . $wpdb->posts . '
|
||||
WHERE ID NOT IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s AND meta_value = %s )
|
||||
AND post_status IN( "future", "draft", "pending", "private", "publish" )
|
||||
AND post_type IN( ' . implode( ',', array_fill( 0, count( $post_types ), '%s' ) ) . ' )
|
||||
LIMIT %d',
|
||||
$replacements
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// Make sure we return a list of IDs.
|
||||
$results = wp_list_pluck( $results, 'ID' );
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array with supported post statuses.
|
||||
*
|
||||
* @return string[] The supported post statuses.
|
||||
*/
|
||||
public function get_supported_post_statuses() {
|
||||
return [ 'future', 'draft', 'pending', 'private', 'publish' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Keeps track of the prominent words version.
|
||||
*/
|
||||
class WPSEO_Premium_Prominent_Words_Versioning {
|
||||
|
||||
// Needs to be manually updated in case of a major change.
|
||||
public const VERSION_NUMBER = 2;
|
||||
|
||||
public const POST_META_NAME = '_yst_prominent_words_version';
|
||||
|
||||
/**
|
||||
* Gets the version number.
|
||||
*
|
||||
* @return int The version number that was set in WPSEO_Premium_Prominent_Words_Versioning.
|
||||
*/
|
||||
public static function get_version_number() {
|
||||
return self::VERSION_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames the meta key for the prominent words version. It was a public meta field and it has to be private.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function upgrade_4_7() {
|
||||
global $wpdb;
|
||||
|
||||
// The meta key has to be private, so prefix it.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'UPDATE ' . $wpdb->postmeta . ' SET meta_key = %s WHERE meta_key = "yst_prominent_words_version"',
|
||||
self::POST_META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the meta key for the prominent words version for the unsupported languages that might have this value
|
||||
* set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function upgrade_4_8() {
|
||||
$supported_languages = [ 'en', 'de', 'nl', 'es', 'fr', 'it', 'pt', 'ru', 'pl', 'sv', 'id' ];
|
||||
|
||||
if ( in_array( WPSEO_Language_Utils::get_language( get_locale() ), $supported_languages, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// The remove all post metas.
|
||||
$wpdb->query(
|
||||
$wpdb->prepare(
|
||||
'DELETE FROM ' . $wpdb->postmeta . ' WHERE meta_key = %s',
|
||||
self::POST_META_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the endpoint for the redirects to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_EndPoint implements WPSEO_WordPress_Integration {
|
||||
|
||||
public const REST_NAMESPACE = 'yoast/v1';
|
||||
public const ENDPOINT_QUERY = 'redirects';
|
||||
public const ENDPOINT_UNDO = 'redirects/delete';
|
||||
|
||||
public const CAPABILITY_STORE = 'wpseo_manage_redirects';
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Premium_Redirect_Service class.
|
||||
*
|
||||
* @var WPSEO_Premium_Redirect_Service
|
||||
*/
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Sets the service to handle the request.
|
||||
*
|
||||
* @param WPSEO_Premium_Redirect_Service $service The service to handle the requests to the endpoint.
|
||||
*/
|
||||
public function __construct( WPSEO_Premium_Redirect_Service $service ) {
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'rest_api_init', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST endpoint to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$args = [
|
||||
'origin' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The origin to redirect',
|
||||
],
|
||||
'target' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'description' => 'The redirect target',
|
||||
],
|
||||
'type' => [
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'description' => 'The redirect type',
|
||||
],
|
||||
];
|
||||
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_QUERY,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => $args,
|
||||
'callback' => [
|
||||
$this->service,
|
||||
'save',
|
||||
],
|
||||
'permission_callback' => [
|
||||
$this,
|
||||
'can_save_data',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_UNDO,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => array_merge(
|
||||
$args,
|
||||
[
|
||||
'type' => [
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'description' => 'The redirect format',
|
||||
],
|
||||
]
|
||||
),
|
||||
'callback' => [
|
||||
$this->service,
|
||||
'delete',
|
||||
],
|
||||
'permission_callback' => [
|
||||
$this,
|
||||
'can_save_data',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @return bool True user is allowed to use this endpoint.
|
||||
*/
|
||||
public function can_save_data() {
|
||||
return current_user_can( self::CAPABILITY_STORE );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Premium_Redirect_Export_Manager
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Export_Manager implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
// Add export CSV block, the import and export settings are confusingly named only import.
|
||||
add_action( 'wpseo_import_tab_content', [ $this, 'add_redirect_export_block' ] );
|
||||
add_action( 'wpseo_import_tab_header', [ $this, 'redirects_export_header' ] );
|
||||
|
||||
// Hijack the request in case of CSV download and return our generated CSV instead.
|
||||
add_action( 'admin_init', [ $this, 'redirects_csv_export' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a tab header for the CSV export block.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirects_export_header() {
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
echo '<a class="nav-tab" id="export-redirects-tab" href="#top#export-redirects">'
|
||||
. esc_html__( 'Export redirects', 'wordpress-seo-premium' )
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding the export block for CSV. Makes it able to export redirects to CSV.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_redirect_export_block() {
|
||||
// Display the forms.
|
||||
if ( current_user_can( 'export' ) ) {
|
||||
require WPSEO_PREMIUM_PATH . 'classes/views/export-redirects.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hijacks the request and returns a CSV file if we're on the right page with the right method and the right capabilities.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redirects_csv_export() {
|
||||
if ( $this->is_valid_csv_export_request() && current_user_can( 'export' ) ) {
|
||||
// Check if we have a valid nonce.
|
||||
check_admin_referer( 'wpseo-export' );
|
||||
|
||||
// Clean any content that has been already outputted, for example by other plugins or faulty PHP files.
|
||||
if ( ob_get_contents() ) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
// Set CSV headers and content.
|
||||
$this->set_csv_headers();
|
||||
echo $this->get_csv_contents();
|
||||
|
||||
// And exit so we don't start appending HTML to our CSV file.
|
||||
// NOTE: this makes this entire class untestable as it will exit all tests but WordPress seems to have no elegant way of handling this.
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we on the wpseo_tools page in the import-export tool and have we received an export post request?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_valid_csv_export_request() {
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- Reason: Nonce is checked in export.
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only or ignoring the value.
|
||||
return ( isset( $_GET['page'] ) && is_string( $_GET['page'] ) && wp_unslash( $_GET['page'] ) === 'wpseo_tools' )
|
||||
&& ( isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) && wp_unslash( $_GET['tool'] ) === 'import-export' )
|
||||
&& ( isset( $_POST['export'] ) && ! empty( $_POST['export'] ) );
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the headers to trigger an CSV download in the browser.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_csv_headers() {
|
||||
header( 'Content-type: text/csv' );
|
||||
header( 'Content-Disposition: attachment; filename=wordpress-seo-redirects.csv' );
|
||||
header( 'Pragma: no-cache' );
|
||||
header( 'Expires: 0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates CSV from all redirects.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_csv_contents() {
|
||||
// Grab all our redirects.
|
||||
$redirect_manager = new WPSEO_Redirect_Manager();
|
||||
$redirects = $redirect_manager->get_all_redirects();
|
||||
|
||||
$csv_exporter = new WPSEO_Redirect_CSV_Exporter();
|
||||
return $csv_exporter->export( $redirects );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the premium redirect option
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Option extends WPSEO_Option {
|
||||
|
||||
/**
|
||||
* Option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $option_name = 'wpseo_redirect';
|
||||
|
||||
/**
|
||||
* Array of defaults for the option.
|
||||
*
|
||||
* {@internal Shouldn't be requested directly, use $this->get_defaults();}}
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
// Form fields.
|
||||
'disable_php_redirect' => 'off',
|
||||
'separate_file' => 'off',
|
||||
];
|
||||
|
||||
/**
|
||||
* Registers the option to the WPSEO Options framework.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_option() {
|
||||
WPSEO_Options::register_option( static::get_instance() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
*
|
||||
* @return static Returns instance of itself.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! ( static::$instance instanceof static ) ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* All concrete classes must contain a validate_option() method which validates all
|
||||
* values within the option.
|
||||
*
|
||||
* @param array $dirty New value for the option.
|
||||
* @param array $clean Clean value for the option, normally the defaults.
|
||||
* @param array $old Old value of the option.
|
||||
*
|
||||
* @return array The clean option with the saved value.
|
||||
*/
|
||||
protected function validate_option( $dirty, $clean, $old ) {
|
||||
|
||||
foreach ( $clean as $key => $value ) {
|
||||
switch ( $key ) {
|
||||
case 'disable_php_redirect':
|
||||
case 'separate_file':
|
||||
if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], [ 'on', 'off' ], true ) ) {
|
||||
$clean[ $key ] = $dirty[ $key ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* The service for the redirects to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Service {
|
||||
|
||||
/**
|
||||
* Saves the redirect to the redirects.
|
||||
*
|
||||
* This save function is only used in the deprecated google-search-console integration.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response to send back.
|
||||
*/
|
||||
public function save( WP_REST_Request $request ) {
|
||||
$redirect = $this->map_request_to_redirect( $request );
|
||||
|
||||
if ( $this->get_redirect_manager()->create_redirect( $redirect ) ) {
|
||||
return new WP_REST_Response( 'true' );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( 'false' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the redirect from the redirects.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WP_REST_Response The response to send back.
|
||||
*/
|
||||
public function delete( WP_REST_Request $request ) {
|
||||
$redirect = $this->map_request_to_redirect( $request );
|
||||
$redirects = [ $redirect ];
|
||||
|
||||
$redirect_format = $request->get_param( 'format' );
|
||||
if ( ! $redirect_format ) {
|
||||
$redirect_format = WPSEO_Redirect_Formats::PLAIN;
|
||||
}
|
||||
|
||||
if ( $this->get_redirect_manager( $redirect_format )->delete_redirects( $redirects ) ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
'success' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an instance of the redirect manager.
|
||||
*
|
||||
* @param string $format The redirect format.
|
||||
*
|
||||
* @return WPSEO_Redirect_Manager The redirect maanger.
|
||||
*/
|
||||
protected function get_redirect_manager( $format = WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return new WPSEO_Redirect_Manager( $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given request to an instance of the WPSEO_Redirect.
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return WPSEO_Redirect Redirect instance.
|
||||
*/
|
||||
protected function map_request_to_redirect( WP_REST_Request $request ) {
|
||||
$origin = $request->get_param( 'origin' );
|
||||
$target = $request->get_param( 'target' );
|
||||
$type = $request->get_param( 'type' );
|
||||
$format = $request->get_param( 'format' );
|
||||
|
||||
return new WPSEO_Redirect( $origin, $target, $type, $format );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin\Capabilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* Capabilities registration class.
|
||||
*/
|
||||
class WPSEO_Premium_Register_Capabilities implements WPSEO_WordPress_Integration {
|
||||
|
||||
/**
|
||||
* Registers the hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'wpseo_register_capabilities_premium', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the capabilities.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
$manager = WPSEO_Capability_Manager_Factory::get( 'premium' );
|
||||
|
||||
$manager->register( 'wpseo_manage_redirects', [ 'administrator', 'editor', 'wpseo_editor', 'wpseo_manager' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO plugin file.
|
||||
*
|
||||
* @package WPSEO\Admin
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the filter for filtering stale cornerstone content.
|
||||
*/
|
||||
class WPSEO_Premium_Stale_Cornerstone_Content_Filter extends WPSEO_Abstract_Post_Filter {
|
||||
|
||||
/**
|
||||
* Returns the query value this filter uses.
|
||||
*
|
||||
* @return string The query value this filter uses.
|
||||
*/
|
||||
public function get_query_val() {
|
||||
return 'stale-cornerstone-content';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the query based on the seo_filter variable in $_GET.
|
||||
*
|
||||
* @param string $where The where statement.
|
||||
*
|
||||
* @return string The modified query.
|
||||
*/
|
||||
public function filter_posts( $where ) {
|
||||
if ( ! $this->is_filter_active() ) {
|
||||
return $where;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$where .= sprintf(
|
||||
' AND ' . $wpdb->posts . '.ID IN( SELECT post_id FROM ' . $wpdb->postmeta . ' WHERE meta_key = "%s" AND meta_value = "1" ) AND ' . $wpdb->posts . '.post_modified < "%s" ',
|
||||
WPSEO_Meta::$meta_prefix . 'is_cornerstone',
|
||||
$this->date_threshold()
|
||||
);
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for this filter.
|
||||
*
|
||||
* @return string The label for this filter.
|
||||
*/
|
||||
protected function get_label() {
|
||||
return __( 'Stale cornerstone content', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text explaining this filter.
|
||||
*
|
||||
* @return string|null The explanation for this filter.
|
||||
*/
|
||||
protected function get_explanation() {
|
||||
$post_type_object = get_post_type_object( $this->get_current_post_type() );
|
||||
|
||||
if ( $post_type_object === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s expands to dynamic post type label, %2$s expands anchor to blog post about cornerstone content, %3$s expands to </a> */
|
||||
__( 'Stale cornerstone content refers to cornerstone content that hasn’t been updated in the last 6 months. Make sure to keep these %1$s up-to-date. %2$sLearn more about cornerstone content%3$s.', 'wordpress-seo-premium' ),
|
||||
strtolower( $post_type_object->labels->name ),
|
||||
'<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/1i9' ) . '" target="_blank">',
|
||||
'</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of stale cornerstone content.
|
||||
*
|
||||
* @return int The total amount of stale cornerstone content.
|
||||
*/
|
||||
protected function get_post_total() {
|
||||
global $wpdb;
|
||||
|
||||
$post_type = $this->get_current_post_type();
|
||||
$cache_key = 'stale_cornerstone_count_' . $post_type;
|
||||
$count = wp_cache_get( $cache_key, 'stale_cornerstone_counts' );
|
||||
|
||||
if ( $count === false ) {
|
||||
$count = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
'
|
||||
SELECT COUNT( 1 )
|
||||
FROM ' . $wpdb->postmeta . '
|
||||
WHERE post_id IN( SELECT ID FROM ' . $wpdb->posts . ' WHERE post_type = %s && post_modified < %s ) &&
|
||||
meta_value = "1" AND meta_key = %s
|
||||
',
|
||||
$post_type,
|
||||
$this->date_threshold(),
|
||||
WPSEO_Meta::$meta_prefix . 'is_cornerstone'
|
||||
)
|
||||
);
|
||||
wp_cache_set( $cache_key, $count, 'stale_cornerstone_counts', DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the post types to which this filter should be added.
|
||||
*
|
||||
* @return array<string> The post types to which this filter should be added.
|
||||
*/
|
||||
protected function get_post_types() {
|
||||
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using YoastSEO hook.
|
||||
$post_types = apply_filters( 'wpseo_cornerstone_post_types', parent::get_post_types() );
|
||||
if ( ! is_array( $post_types ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date 6 months ago.
|
||||
*
|
||||
* @return string The formatted date.
|
||||
*/
|
||||
protected function date_threshold() {
|
||||
return gmdate( 'Y-m-d', strtotime( '-6months' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes
|
||||
*/
|
||||
|
||||
if ( class_exists( 'Yoast_Product' ) && ! class_exists( 'WPSEO_Product_Premium', false ) ) {
|
||||
|
||||
/**
|
||||
* Class WPSEO_Product_Premium
|
||||
*/
|
||||
class WPSEO_Product_Premium extends Yoast_Product {
|
||||
|
||||
/**
|
||||
* Plugin author name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PLUGIN_AUTHOR = 'Yoast';
|
||||
|
||||
/**
|
||||
* License endpoint.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const EDD_STORE_URL = 'http://my.yoast.com';
|
||||
|
||||
/**
|
||||
* Product name to use for license checks.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const EDD_PLUGIN_NAME = 'Yoast SEO Premium';
|
||||
|
||||
/**
|
||||
* Construct the Product Premium class
|
||||
*/
|
||||
public function __construct() {
|
||||
$file = plugin_basename( WPSEO_FILE );
|
||||
$slug = dirname( $file );
|
||||
|
||||
parent::__construct(
|
||||
trailingslashit( self::EDD_STORE_URL ) . 'edd-sl-api',
|
||||
self::EDD_PLUGIN_NAME,
|
||||
$slug,
|
||||
WPSEO_Premium::PLUGIN_VERSION_NAME,
|
||||
'https://yoast.com/wordpress/plugins/seo-premium/',
|
||||
'admin.php?page=wpseo_licenses#top#licenses',
|
||||
'wordpress-seo',
|
||||
self::PLUGIN_AUTHOR,
|
||||
$file
|
||||
);
|
||||
|
||||
if ( method_exists( $this, 'set_extension_url' ) ) {
|
||||
$this->set_extension_url( 'https://my.yoast.com/licenses/' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers the endpoint to delete the redirect for a Post to WordPress.
|
||||
*/
|
||||
class WPSEO_Premium_Redirect_Undo_EndPoint implements WPSEO_WordPress_Integration {
|
||||
|
||||
public const REST_NAMESPACE = 'yoast/v1';
|
||||
public const ENDPOINT_UNDO = 'redirects/undo-for-object';
|
||||
|
||||
/**
|
||||
* Instance of the WPSEO_Redirect_Manager class.
|
||||
*
|
||||
* @var WPSEO_Redirect_Manager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Sets the service to handle the request.
|
||||
*
|
||||
* @param WPSEO_Redirect_Manager $manager The manager for working with redirects.
|
||||
*/
|
||||
public function __construct( WPSEO_Redirect_Manager $manager ) {
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all hooks to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_action( 'rest_api_init', [ $this, 'register' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the REST endpoint to WordPress.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register() {
|
||||
register_rest_route(
|
||||
self::REST_NAMESPACE,
|
||||
self::ENDPOINT_UNDO,
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'args' => [
|
||||
'obj_id' => [
|
||||
'required' => true,
|
||||
'type' => 'int',
|
||||
'description' => 'The id of the post or term',
|
||||
],
|
||||
'obj_type' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'description' => 'The object type: post or term',
|
||||
],
|
||||
],
|
||||
'callback' => [ $this, 'undo_redirect' ],
|
||||
'permission_callback' => [ $this, 'can_save_data' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the latest redirect to the post or term referenced in the request.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
*
|
||||
* @return WP_REST_Response The response.
|
||||
*/
|
||||
public function undo_redirect( WP_REST_Request $request ) {
|
||||
$object_id = $request->get_param( 'obj_id' );
|
||||
$object_type = $request->get_param( 'obj_type' );
|
||||
|
||||
$redirect_info = $this->retrieve_post_or_term_redirect_info( $object_type, $object_id );
|
||||
$redirect = $this->map_redirect_info_to_redirect( $redirect_info );
|
||||
|
||||
if ( ! $redirect->get_origin() ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->manager->delete_redirects( [ $redirect ] ) ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'The redirect was deleted successfully.', 'wordpress-seo-premium' ),
|
||||
'success' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'title' => __( 'Redirect not deleted.', 'wordpress-seo-premium' ),
|
||||
'message' => __( 'Something went wrong when deleting this redirect.', 'wordpress-seo-premium' ),
|
||||
'success' => false,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the given redirect info to an instance of the WPSEO_Redirect.
|
||||
*
|
||||
* @param array $redirect_info The redirect info array.
|
||||
*
|
||||
* @return WPSEO_Redirect Redirect instance.
|
||||
*/
|
||||
protected function map_redirect_info_to_redirect( $redirect_info ) {
|
||||
$origin = ( $redirect_info['origin'] ?? null );
|
||||
$target = ( $redirect_info['target'] ?? null );
|
||||
$type = ( $redirect_info['type'] ?? null );
|
||||
$format = ( $redirect_info['format'] ?? null );
|
||||
|
||||
return new WPSEO_Redirect( $origin, $target, $type, $format );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the redirect info from the meta for the specified object and id.
|
||||
*
|
||||
* @param string $object_type The type of object: post or term.
|
||||
* @param int $object_id The post or term ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function retrieve_post_or_term_redirect_info( $object_type, $object_id ) {
|
||||
if ( $object_type === 'post' ) {
|
||||
$redirect_info = get_post_meta( $object_id, '_yoast_post_redirect_info', true );
|
||||
delete_post_meta( $object_id, '_yoast_post_redirect_info' );
|
||||
return $redirect_info;
|
||||
}
|
||||
|
||||
if ( $object_type === 'term' ) {
|
||||
$redirect_info = get_term_meta( $object_id, '_yoast_term_redirect_info', true );
|
||||
delete_term_meta( $object_id, '_yoast_term_redirect_info' );
|
||||
return $redirect_info;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is allowed to use this endpoint.
|
||||
*
|
||||
* @param WP_REST_Request $request The request.
|
||||
*
|
||||
* @return bool True user is allowed to use this endpoint.
|
||||
*/
|
||||
public function can_save_data( WP_REST_Request $request ) {
|
||||
$object_id = $request->get_param( 'obj_id' );
|
||||
$object_type = $request->get_param( 'obj_type' );
|
||||
|
||||
if ( $object_type === 'post' ) {
|
||||
return current_user_can( 'edit_post', $object_id );
|
||||
}
|
||||
|
||||
if ( $object_type === 'term' ) {
|
||||
return current_user_can( 'edit_term', $object_id );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirects
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents an executable redirect.
|
||||
*/
|
||||
final class WPSEO_Executable_Redirect {
|
||||
|
||||
/**
|
||||
* Redirect origin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $origin;
|
||||
|
||||
/**
|
||||
* Redirect target.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $target;
|
||||
|
||||
/**
|
||||
* A HTTP code determining the redirect type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* A string determining the redirect format (plain or regex).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $origin The origin of the redirect.
|
||||
* @param string $target The target of the redirect.
|
||||
* @param int $type The type of the redirect.
|
||||
* @param string $format The format of the redirect.
|
||||
*/
|
||||
public function __construct( $origin, $target, $type, $format ) {
|
||||
$this->origin = $origin;
|
||||
$this->target = $target;
|
||||
$this->type = $type;
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance based on the given data.
|
||||
*
|
||||
* @param array $data The redirect data.
|
||||
*
|
||||
* @return WPSEO_Executable_Redirect The created object.
|
||||
*/
|
||||
public static function from_array( $data ) {
|
||||
return new self( $data['origin'], $data['target'], $data['type'], $data['format'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the origin.
|
||||
*
|
||||
* @return string The origin.
|
||||
*/
|
||||
public function get_origin() {
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the target.
|
||||
*
|
||||
* @return string The target.
|
||||
*/
|
||||
public function get_target() {
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type.
|
||||
*
|
||||
* @return int The redirect type.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the redirect format.
|
||||
*
|
||||
* @return string The redirect format.
|
||||
*/
|
||||
public function get_format() {
|
||||
return $this->format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exporter class will format the redirects for apache files.
|
||||
*/
|
||||
class WPSEO_Redirect_Apache_Exporter extends WPSEO_Redirect_File_Exporter {
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the new URL
|
||||
* %3$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = 'Redirect %3$s "%1$s" "%2$s"';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_non_target_format = 'Redirect %2$s "%1$s"';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the new URL
|
||||
* %3$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = 'RedirectMatch %3$s %1$s %2$s';
|
||||
|
||||
/**
|
||||
* %1$s is the old URL
|
||||
* %2$s is the redirect type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_non_target_format = 'RedirectMatch %2$s %1$s';
|
||||
|
||||
/**
|
||||
* Overrides the parent method. This method will in case of URL redirects add slashes to the URL.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
|
||||
// 4xx redirects don't have a target.
|
||||
$redirect_type = intval( $redirect->get_type() );
|
||||
if ( $redirect_type >= 400 && $redirect_type < 500 ) {
|
||||
return $this->format_non_target( $redirect );
|
||||
}
|
||||
|
||||
$origin = $redirect->get_origin();
|
||||
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
$origin = $this->format_url( $redirect->get_origin() );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$origin,
|
||||
$this->format_url( $redirect->get_target() ),
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the URL before it is added to the redirects.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string Formatted URL.
|
||||
*/
|
||||
protected function format_url( $url ) {
|
||||
return $this->add_url_slash( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the redirect output for non-target status codes (4xx)
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format_non_target( WPSEO_Redirect $redirect ) {
|
||||
|
||||
$target = $redirect->get_origin();
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
$target = $this->add_url_slash( $target );
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->get_non_target_format( $redirect->get_format() ),
|
||||
$target,
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format the redirect needs to output
|
||||
*
|
||||
* @param string $redirect_format The format of the redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_non_target_format( $redirect_format ) {
|
||||
if ( $redirect_format === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return $this->url_non_target_format;
|
||||
}
|
||||
|
||||
return $this->regex_non_target_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if first character is a slash, adds a slash if it ain't so
|
||||
*
|
||||
* @param string $url The URL add the slashes to.
|
||||
*
|
||||
* @return string mixed
|
||||
*/
|
||||
private function add_url_slash( $url ) {
|
||||
$scheme = wp_parse_url( $url, PHP_URL_SCHEME );
|
||||
if ( substr( $url, 0, 1 ) !== '/' && empty( $scheme ) ) {
|
||||
$url = '/' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* This exporter class will format the redirects for csv files.
|
||||
*
|
||||
* Does not implement WPSEO_Redirect_File_Exporter as the CSV is intended to be streamed, not saved.
|
||||
*/
|
||||
class WPSEO_Redirect_CSV_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* Exports an array of redirects to a CSV string.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return string CSV string of all exported redirects with headers.
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
$csv = $this->get_headers();
|
||||
|
||||
if ( ! empty( $redirects ) ) {
|
||||
foreach ( $redirects as $redirect ) {
|
||||
if ( $redirect instanceof WPSEO_Redirect ) {
|
||||
$csv .= PHP_EOL . $this->format( $redirect );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export, returns a line of CSV.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string CSV line of the redirect for format.
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
$target = $redirect->get_target();
|
||||
if ( WPSEO_Redirect_Util::is_relative_url( $target ) ) {
|
||||
$target = '/' . $target;
|
||||
}
|
||||
if ( WPSEO_Redirect_Util::requires_trailing_slash( $target ) ) {
|
||||
$target = trailingslashit( $target );
|
||||
}
|
||||
|
||||
$origin = $redirect->get_origin();
|
||||
if ( $redirect->get_format() === WPSEO_Redirect_Formats::PLAIN && WPSEO_Redirect_Util::is_relative_url( $origin ) ) {
|
||||
$origin = '/' . $origin;
|
||||
}
|
||||
|
||||
$redirect_details = [
|
||||
$this->format_csv_column( $origin ),
|
||||
$this->format_csv_column( $target ),
|
||||
$this->format_csv_column( $redirect->get_type() ),
|
||||
$this->format_csv_column( $redirect->get_format() ),
|
||||
];
|
||||
|
||||
return implode( ',', $redirect_details );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers to add to the first line of the generated CSV.
|
||||
*
|
||||
* @return string CSV line of the headers.
|
||||
*/
|
||||
protected function get_headers() {
|
||||
$headers = [
|
||||
__( 'Origin', 'wordpress-seo-premium' ),
|
||||
__( 'Target', 'wordpress-seo-premium' ),
|
||||
__( 'Type', 'wordpress-seo-premium' ),
|
||||
__( 'Format', 'wordpress-seo-premium' ),
|
||||
];
|
||||
|
||||
return implode( ',', $headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Surrounds a value with double quotes and escapes existing double quotes.
|
||||
*
|
||||
* @param string $value The value to sanitize.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
protected function format_csv_column( $value ) {
|
||||
return '"' . str_replace( '"', '""', (string) $value ) . '"';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a redirect export
|
||||
*/
|
||||
interface WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* Exports an array of redirects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*/
|
||||
public function export( $redirects );
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect );
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_File.
|
||||
*/
|
||||
abstract class WPSEO_Redirect_File_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* The URL format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = '';
|
||||
|
||||
/**
|
||||
* The regex format.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = '';
|
||||
|
||||
/**
|
||||
* Exports an array of redirects.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
|
||||
$file_content = '';
|
||||
if ( ! empty( $redirects ) ) {
|
||||
foreach ( $redirects as $redirect ) {
|
||||
$file_content .= $this->format( $redirect ) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
// Check if the file content isset.
|
||||
return $this->save( $file_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$redirect->get_origin(),
|
||||
$redirect->get_target(),
|
||||
$redirect->get_type()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the needed format for the redirect.
|
||||
*
|
||||
* @param string $redirect_format The format of the redirect.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_format( $redirect_format ) {
|
||||
if ( $redirect_format === WPSEO_Redirect_Formats::PLAIN ) {
|
||||
return $this->url_format;
|
||||
}
|
||||
|
||||
return $this->regex_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the redirect file.
|
||||
*
|
||||
* @param string $file_content The file content that will be saved.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save( $file_content ) {
|
||||
// Save the actual file.
|
||||
if ( is_writable( WPSEO_Redirect_File_Util::get_file_path() ) ) {
|
||||
WPSEO_Redirect_File_Util::write_file( WPSEO_Redirect_File_Util::get_file_path(), $file_content );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Htaccess_Redirect_File
|
||||
*/
|
||||
class WPSEO_Redirect_Htaccess_Exporter extends WPSEO_Redirect_Apache_Exporter {
|
||||
|
||||
/**
|
||||
* Save the redirect file
|
||||
*
|
||||
* @param string $file_content The file content that will be saved.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function save( $file_content ) {
|
||||
$file_path = WPSEO_Redirect_Htaccess_Util::get_htaccess_file_path();
|
||||
|
||||
// Update the .htaccess file.
|
||||
if ( is_writable( $file_path ) ) {
|
||||
$htaccess = $this->get_htaccess_content( $file_path, $file_content );
|
||||
$return = (bool) WPSEO_Redirect_File_Util::write_file( $file_path, $htaccess );
|
||||
|
||||
// Make sure defines are created.
|
||||
WP_Filesystem();
|
||||
chmod( $file_path, FS_CHMOD_FILE );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the content from current .htaccess
|
||||
*
|
||||
* @param string $file_path The location of the htaccess file.
|
||||
* @param string $file_content THe content to save in the htaccess file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_htaccess_content( $file_path, $file_content ) {
|
||||
// Read current htaccess.
|
||||
$htaccess = '';
|
||||
if ( file_exists( $file_path ) ) {
|
||||
$htaccess = file_get_contents( $file_path );
|
||||
}
|
||||
|
||||
$htaccess = preg_replace( '`# BEGIN YOAST REDIRECTS.*# END YOAST REDIRECTS' . PHP_EOL . '`is', '', $htaccess );
|
||||
|
||||
// Only add redirect code when redirects are present.
|
||||
if ( ! empty( $file_content ) ) {
|
||||
$file_content = '# BEGIN YOAST REDIRECTS' . PHP_EOL . '<IfModule mod_rewrite.c>' . PHP_EOL . 'RewriteEngine On' . PHP_EOL . $file_content . '</IfModule>' . PHP_EOL . '# END YOAST REDIRECTS' . PHP_EOL;
|
||||
|
||||
// Prepend our redirects to htaccess file.
|
||||
$htaccess = $file_content . $htaccess;
|
||||
}
|
||||
|
||||
return $htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters in the URL that will cause problems in .htaccess.
|
||||
*
|
||||
* Overrides WPSEO_Redirect_Apache_Exporter::format_url.
|
||||
*
|
||||
* @param string $url The URL.
|
||||
*
|
||||
* @return string The escaped URL.
|
||||
*/
|
||||
protected function format_url( $url ) {
|
||||
$url = parent::format_url( $url );
|
||||
|
||||
return $this->sanitize( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters that will cause problems in .htaccess.
|
||||
*
|
||||
* @param string $unsanitized The unsanitized value.
|
||||
*
|
||||
* @return string The sanitized value.
|
||||
*/
|
||||
private function sanitize( $unsanitized ) {
|
||||
return str_replace(
|
||||
[
|
||||
'\\',
|
||||
'"',
|
||||
],
|
||||
[
|
||||
'/',
|
||||
'\"',
|
||||
],
|
||||
$unsanitized
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exporter for Nginx, only declares the two formats
|
||||
*/
|
||||
class WPSEO_Redirect_Nginx_Exporter extends WPSEO_Redirect_File_Exporter {
|
||||
|
||||
/**
|
||||
* %1$s is the origin
|
||||
* %2$s is the target
|
||||
* %3$s is the redirect type
|
||||
* %4$s is the optional x-redirect-by filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url_format = 'location /%1$s { %4$s return %3$s %2$s; }';
|
||||
|
||||
/**
|
||||
* %1$s is the origin
|
||||
* %2$s is the target
|
||||
* %3$s is the redirect type
|
||||
* %4$s is the optional x-redirect-by filter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regex_format = 'location ~ %1$s { %4$s return %3$s %2$s; }';
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return sprintf(
|
||||
$this->get_format( $redirect->get_format() ),
|
||||
$redirect->get_origin(),
|
||||
$redirect->get_target(),
|
||||
$redirect->get_type(),
|
||||
$this->add_x_redirect_header()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an X-Redirect-By header if allowed by the filter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function add_x_redirect_header() {
|
||||
/**
|
||||
* Filter: 'Yoast\WP\SEO\add_x_redirect' - can be used to remove the X-Redirect-By header
|
||||
* Yoast SEO Premium creates (defaults to true, which is adding it)
|
||||
*
|
||||
* Note: This is a Premium plugin-only hook.
|
||||
*
|
||||
* @since 12.9.0
|
||||
*
|
||||
* @param bool $add_xredirect
|
||||
*/
|
||||
if ( apply_filters( 'Yoast\WP\SEO\add_x_redirect', true ) === true ) {
|
||||
return 'add_header X-Redirect-By "Yoast SEO Premium";';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirects\Redirect\Exporters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Saving the redirects from a single file into two smaller options files.
|
||||
*/
|
||||
class WPSEO_Redirect_Option_Exporter implements WPSEO_Redirect_Exporter {
|
||||
|
||||
/**
|
||||
* This method will split the redirects in separate arrays and store them in an option.
|
||||
*
|
||||
* @param WPSEO_Redirect[] $redirects The redirects to export.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function export( $redirects ) {
|
||||
$formatted_redirects = [
|
||||
WPSEO_Redirect_Formats::PLAIN => [],
|
||||
WPSEO_Redirect_Formats::REGEX => [],
|
||||
];
|
||||
|
||||
foreach ( $redirects as $redirect ) {
|
||||
$formatted_redirects[ $redirect->get_format() ][ $redirect->get_origin() ] = $this->format( $redirect );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the parameter to save the redirect options as autoloaded.
|
||||
*
|
||||
* Note that the `autoload` value in the database will change only if the option value changes (i.e. a redirect is added, edited or deleted).
|
||||
* Otherwise you will need to change the `autoload` value directly in the DB.
|
||||
*
|
||||
* @since 20.13
|
||||
*
|
||||
* @param bool $autoload The value of the `autoload` parameter. Default: true.
|
||||
* @param string $type The type of redirects, either `plain` or `regex`.
|
||||
* @param array $formatted_redirects The redirects to be written in the options, already formatted.
|
||||
*
|
||||
* @return bool The filtered value of the `autoload` parameter.
|
||||
*/
|
||||
$autoload_options_plain = apply_filters( 'Yoast\WP\SEO\redirects_options_autoload', true, 'plain', $formatted_redirects );
|
||||
$autoload_options_regex = apply_filters( 'Yoast\WP\SEO\redirects_options_autoload', true, 'regex', $formatted_redirects );
|
||||
|
||||
update_option( WPSEO_Redirect_Option::OPTION_PLAIN, $formatted_redirects[ WPSEO_Redirect_Formats::PLAIN ], $autoload_options_plain );
|
||||
update_option( WPSEO_Redirect_Option::OPTION_REGEX, $formatted_redirects[ WPSEO_Redirect_Formats::REGEX ], $autoload_options_regex );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a redirect for use in the export.
|
||||
*
|
||||
* @param WPSEO_Redirect $redirect The redirect to format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function format( WPSEO_Redirect $redirect ) {
|
||||
return [
|
||||
'url' => $redirect->get_target(),
|
||||
'type' => $redirect->get_type(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for loading redirects from an external source and validating them.
|
||||
*/
|
||||
abstract class WPSEO_Redirect_Abstract_Loader implements WPSEO_Redirect_Loader {
|
||||
|
||||
/**
|
||||
* Validates if the given value is a http status code.
|
||||
*
|
||||
* @param string|int $status_code The status code to validate.
|
||||
*
|
||||
* @return bool Whether or not the status code is valid.
|
||||
*/
|
||||
protected function validate_status_code( $status_code ) {
|
||||
if ( is_string( $status_code ) ) {
|
||||
if ( ! preg_match( '/\A\d+\Z/', $status_code, $matches ) ) {
|
||||
return false;
|
||||
}
|
||||
$status_code = (int) $status_code;
|
||||
}
|
||||
|
||||
$status_codes = new WPSEO_Redirect_Types();
|
||||
|
||||
return $status_codes->has( $status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given value is a redirect format.
|
||||
*
|
||||
* @param string $format The format to validate.
|
||||
*
|
||||
* @return bool Whether or not the format is valid.
|
||||
*/
|
||||
protected function validate_format( $format ) {
|
||||
$redirect_formats = new WPSEO_Redirect_Formats();
|
||||
|
||||
return $redirect_formats->has( $format );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from a CSV file and validating them.
|
||||
*/
|
||||
class WPSEO_Redirect_CSV_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Path of the CSV file to load.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $csv_file;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_CSV_Loader constructor.
|
||||
*
|
||||
* @param string $csv_file Path of the CSV file to load.
|
||||
*/
|
||||
public function __construct( $csv_file ) {
|
||||
$this->csv_file = $csv_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all redirects from the CSV file.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The redirects loaded from the CSV file.
|
||||
*/
|
||||
public function load() {
|
||||
$handle = fopen( $this->csv_file, 'r' );
|
||||
|
||||
if ( ! $handle ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$redirects = [];
|
||||
while ( $item = fgetcsv( $handle, 10000 ) ) {
|
||||
if ( ! $this->validate_item( $item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item[0], $item[1], $item[2], $item[3] );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a parsed CSV row is has a valid redirect format.
|
||||
* It should have exactly 4 values.
|
||||
* The third value should be a http status code.
|
||||
* The last value should be a redirect format.
|
||||
*
|
||||
* @param array $item The parsed CSV row.
|
||||
*
|
||||
* @return bool Whether or not the parsed CSV row is valid.
|
||||
*/
|
||||
protected function validate_item( $item ) {
|
||||
if ( count( $item ) !== 4 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_status_code( $item[2] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_format( $item[3] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from .htaccess files.
|
||||
*/
|
||||
class WPSEO_Redirect_HTAccess_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* The contents of the htaccess file to import.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $htaccess;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_HTAccess_Loader constructor.
|
||||
*
|
||||
* @param string $htaccess The contents of the htaccess file to import.
|
||||
*/
|
||||
public function __construct( $htaccess ) {
|
||||
$this->htaccess = $htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the .htaccess file given to the constructor.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$redirects = [];
|
||||
|
||||
// Loop through patterns.
|
||||
foreach ( self::regex_patterns() as $regex ) {
|
||||
$matches = $this->match_redirects( $regex['pattern'] );
|
||||
|
||||
if ( is_array( $matches ) ) {
|
||||
$redirects = array_merge( $redirects, $this->convert_redirects_from_matches( $matches, $regex['type'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the string (containing redirects) for the given regex.
|
||||
*
|
||||
* @param string $pattern The regular expression to match redirects.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
protected function match_redirects( $pattern ) {
|
||||
preg_match_all( $pattern, $this->htaccess, $matches, PREG_SET_ORDER );
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts matches to WPSEO_Redirect objects.
|
||||
*
|
||||
* @param array[] $matches The redirects to save.
|
||||
* @param string $format The format for the redirects.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The redirects.
|
||||
*/
|
||||
protected function convert_redirects_from_matches( $matches, $format ) {
|
||||
$redirects = [];
|
||||
|
||||
foreach ( $matches as $match ) {
|
||||
$type = trim( $match[1] );
|
||||
$source = trim( $match[2] );
|
||||
$target = $this->parse_target( $type, $match );
|
||||
|
||||
if ( $target === false || $source === '' || ! $this->validate_status_code( $type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $source, $target, $type, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the target from a match.
|
||||
*
|
||||
* @param string $type The status code of the redirect.
|
||||
* @param string[] $matched The match.
|
||||
*
|
||||
* @return bool|string The status code, false if no status code could be parsed.
|
||||
*/
|
||||
protected function parse_target( $type, $matched ) {
|
||||
// If it's a gone status code that doesn't need a target.
|
||||
if ( $type === '410' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$target = trim( $matched[3] );
|
||||
|
||||
// There is no target, skip it.
|
||||
if ( $target === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns regex patterns to match redirects in .htaccess files.
|
||||
*
|
||||
* @return array[] The regex patterns to test against.
|
||||
*/
|
||||
protected static function regex_patterns() {
|
||||
return [
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect ([0-9]{3}) ([^"\s]+) ([a-z0-9-_+/.:%&?=#\][]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect ([0-9]{3}) "([^"]+)" ([a-z0-9-_+/.:%&?=#\][]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect (410) ([^"\s]+)`im', // Matches a redirect without a target.
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::PLAIN,
|
||||
'pattern' => '`^Redirect (410) "([^"]+)"`im', // Matches a redirect without a target.
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::REGEX,
|
||||
'pattern' => '`^RedirectMatch ([0-9]{3}) ([^"\s]+) ([^\s]+)`im',
|
||||
],
|
||||
[
|
||||
'type' => WPSEO_Redirect_Formats::REGEX,
|
||||
'pattern' => '`^RedirectMatch ([0-9]{3}) "([^"]+)" ([^\s]+)`im',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a redirect loader for external sources.
|
||||
*/
|
||||
interface WPSEO_Redirect_Loader {
|
||||
|
||||
/**
|
||||
* Loads the redirects from an external source and validates them.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Redirection plugin.
|
||||
*/
|
||||
class WPSEO_Redirect_Redirection_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* A WordPress database object.
|
||||
*
|
||||
* @var wpdb
|
||||
*/
|
||||
protected $wpdb;
|
||||
|
||||
/**
|
||||
* WPSEO_Redirect_Redirection_Loader constructor.
|
||||
*
|
||||
* @param wpdb $wpdb A WordPress database object.
|
||||
*/
|
||||
public function __construct( $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Redirection plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
// Get redirects.
|
||||
// phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnsupportedPlaceholder,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$items = $this->wpdb->get_results(
|
||||
$this->wpdb->prepare(
|
||||
"SELECT `url`, `action_data`, `regex`, `action_code`
|
||||
FROM %i
|
||||
WHERE %i = 'enabled' AND %i = 'url'",
|
||||
$this->wpdb->prefix . 'redirection_items',
|
||||
'status',
|
||||
'action_type'
|
||||
)
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
$redirects = [];
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
if ( (int) $item->regex === 1 ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
}
|
||||
|
||||
if ( ! $this->validate_status_code( $item->action_code ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item->url, $item->action_data, $item->action_code, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Safe Redirect Manager plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/safe-redirect-manager/
|
||||
*/
|
||||
class WPSEO_Redirect_Safe_Redirect_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Safe Redirect Manager plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$items = get_transient( '_srm_redirects' );
|
||||
$redirects = [];
|
||||
|
||||
if ( ! is_array( $items ) ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$item = $this->convert_wildcards( $item );
|
||||
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
if ( (int) $item['enable_regex'] === 1 ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
}
|
||||
|
||||
$status_code = $this->convert_status_code( $item['status_code'] );
|
||||
|
||||
if ( ! $this->validate_status_code( $status_code ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $item['redirect_from'], $item['redirect_to'], $status_code, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsupported 404 and 403 status codes to a 410 status code.
|
||||
* Also converts unsupported 303 status codes to a 302 status code.
|
||||
*
|
||||
* @param int $status_code The original status code.
|
||||
*
|
||||
* @return int A status code Yoast supports.
|
||||
*/
|
||||
protected function convert_status_code( $status_code ) {
|
||||
switch ( $status_code ) {
|
||||
case 303:
|
||||
return 302;
|
||||
case 403:
|
||||
case 404:
|
||||
return 410;
|
||||
default:
|
||||
return (int) $status_code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts unsupported wildcard format to supported regex format.
|
||||
*
|
||||
* @param array $item A Safe Redirect Manager redirect.
|
||||
*
|
||||
* @return array A converted redirect.
|
||||
*/
|
||||
protected function convert_wildcards( $item ) {
|
||||
if ( substr( $item['redirect_from'], -1, 1 ) === '*' ) {
|
||||
$item['redirect_from'] = preg_replace( '/(\*)$/', '.*', $item['redirect_from'] );
|
||||
$item['enable_regex'] = 1;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Loaders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for loading redirects from the Simple 301 Redirects plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/simple-301-redirects/
|
||||
*/
|
||||
class WPSEO_Redirect_Simple_301_Redirect_Loader extends WPSEO_Redirect_Abstract_Loader {
|
||||
|
||||
/**
|
||||
* Loads redirects as WPSEO_Redirects from the Simple 301 Redirects plugin.
|
||||
*
|
||||
* @return WPSEO_Redirect[] The loaded redirects.
|
||||
*/
|
||||
public function load() {
|
||||
$items = get_option( '301_redirects' );
|
||||
$uses_wildcards = get_option( '301_redirects_wildcard' );
|
||||
$redirects = [];
|
||||
|
||||
if ( ! is_array( $items ) ) {
|
||||
return $redirects;
|
||||
}
|
||||
|
||||
foreach ( $items as $origin => $target ) {
|
||||
$format = WPSEO_Redirect_Formats::PLAIN;
|
||||
|
||||
// If wildcard redirects had been used, and this is one, flip it.
|
||||
if ( $uses_wildcards && strpos( $origin, '*' ) !== false ) {
|
||||
$format = WPSEO_Redirect_Formats::REGEX;
|
||||
$origin = str_replace( '*', '(.*)', $origin );
|
||||
$target = str_replace( '*', '$1', $target );
|
||||
}
|
||||
|
||||
$redirects[] = new WPSEO_Redirect( $origin, $target, 301, $format );
|
||||
}
|
||||
|
||||
return $redirects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* The presenter for the form, this form will be used for adding and updating the redirects.
|
||||
*/
|
||||
class WPSEO_Redirect_Form_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Variables to be passed to the form view.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $view_vars;
|
||||
|
||||
/**
|
||||
* Setting up the view_vars.
|
||||
*
|
||||
* @param array $view_vars The variables to pass into the view.
|
||||
*/
|
||||
public function __construct( array $view_vars ) {
|
||||
$this->view_vars = $view_vars;
|
||||
|
||||
$this->view_vars['redirect_types'] = $this->get_redirect_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form.
|
||||
*
|
||||
* @param array $display Additional display variables.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] ) {
|
||||
$display_vars = $this->view_vars;
|
||||
if ( ! empty( $display ) ) {
|
||||
$display_vars = array_merge_recursive( $display_vars, $display );
|
||||
}
|
||||
|
||||
require WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects-form.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting array with the available redirect types.
|
||||
*
|
||||
* @return array Array with the redirect types.
|
||||
*/
|
||||
private function get_redirect_types() {
|
||||
$types = new WPSEO_Redirect_Types();
|
||||
|
||||
return $types->get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* WPSEO Premium plugin file.
|
||||
*
|
||||
* @package WPSEO\Premium\Classes\Redirect\Presenters
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPSEO_Redirect_Page_Presenter
|
||||
*/
|
||||
class WPSEO_Redirect_Page_Presenter implements WPSEO_Redirect_Presenter {
|
||||
|
||||
/**
|
||||
* Displays the redirect page.
|
||||
*
|
||||
* @param array $display Contextual display data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function display( array $display = [] ) {
|
||||
$current_tab = ! empty( $display['current_tab'] ) ? $display['current_tab'] : '';
|
||||
$tab_presenter = $this->get_tab_presenter( $current_tab );
|
||||
$redirect_tabs = $this->navigation_tabs( $current_tab );
|
||||
|
||||
include WPSEO_PREMIUM_PATH . 'classes/redirect/views/redirects.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tab presenter.
|
||||
*
|
||||
* @param string $tab_to_display The tab that will be shown.
|
||||
*
|
||||
* @return WPSEO_Redirect_Tab_Presenter|null Tab presenter instance, or null if invalid tab given.
|
||||
*/
|
||||
private function get_tab_presenter( $tab_to_display ) {
|
||||
$tab_presenter = null;
|
||||
switch ( $tab_to_display ) {
|
||||
case WPSEO_Redirect_Formats::PLAIN:
|
||||
case WPSEO_Redirect_Formats::REGEX:
|
||||
$tab_presenter = new WPSEO_Redirect_Table_Presenter( $tab_to_display );
|
||||
break;
|
||||
case 'settings':
|
||||
if ( current_user_can( 'wpseo_manage_options' ) ) {
|
||||
$tab_presenter = new WPSEO_Redirect_Settings_Presenter( $tab_to_display );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $tab_presenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returning the anchors html for the tabs
|
||||
*
|
||||
* @param string $current_tab The tab that will be active.
|
||||
*
|
||||
* @return array {
|
||||
* Associative array of navigation tabs data.
|
||||
*
|
||||
* @type array $tabs Array of $tab_slug => $tab_label pairs.
|
||||
* @type string $current_tab The currently active tab slug.
|
||||
* @type string $page_url Base URL of the current page, to append the tab slug to.
|
||||
* }
|
||||
*/
|
||||
private function navigation_tabs( $current_tab ) {
|
||||
$tabs = $this->get_redirect_formats();
|
||||
|
||||
if ( current_user_can( 'wpseo_manage_options' ) ) {
|
||||
$tabs['settings'] = __( 'Settings', 'wordpress-seo-premium' );
|
||||
}
|
||||
|
||||
return [
|
||||
'tabs' => $tabs,
|
||||
'current_tab' => $current_tab,
|
||||
'page_url' => admin_url( 'admin.php?page=wpseo_redirects&tab=' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the available redirect formats.
|
||||
*
|
||||
* @return array Redirect formats as $slug => $label pairs.
|
||||
*/
|
||||
protected function get_redirect_formats() {
|
||||
$redirect_formats = new WPSEO_Redirect_Formats();
|
||||
|
||||
return $redirect_formats->get();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user