Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions js/fewer-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,19 @@ function fewerTagsDomReady( callback ) {
* @param {Function} callback The callback to run when the element is removed.
*/
const fewerTagsObserveElementRemoval = ( elements, callback ) => {
const theList = document.getElementById( 'the-list' );
if ( ! theList ) {
return;
}
elements.forEach( ( element ) => {
let inDom = document.body.contains( element );
let inDom = theList.contains( element );
const observer = new MutationObserver( () => {
if ( ! document.body.contains( element ) && inDom ) {
if ( ! theList.contains( element ) && inDom ) {
inDom = false;
callback();
}
} );
observer.observe( document.body, { childList: true, subtree: true } );
observer.observe( theList, { childList: true, subtree: true } );
} );
};

Expand Down Expand Up @@ -136,6 +140,7 @@ async function fewerTagsGetTaxonomies( currentTaxonomy ) {
selected: ( taxonomies[ key ].slug === currentTaxonomy ),
customProperties: {
post_type: taxonomies[ key ].types,
rest_base: taxonomies[ key ].rest_base,
},
} ) );

Expand All @@ -152,18 +157,21 @@ let FTterms = [];
* Get all terms from the WordPress REST API.
*
* @param {string} taxonomy The taxonomy to get the terms for.
* @param {string} restBase Optional REST base for the taxonomy endpoint.
*
* @return {Promise} The promise of the terms.
*/
async function fewerTagsGetAllTerms( taxonomy ) {
async function fewerTagsGetAllTerms( taxonomy, restBase ) {
const retrievedTerms = [];
const perPage = 100; // Max allowed by WP REST API
let page = 1;
let hasMore = true;
const termsOutput = [];

let endpoint = '';
if ( taxonomy === 'post_tag' ) {
if ( restBase ) {
endpoint = `/wp-json/wp/v2/${ restBase }`;
} else if ( taxonomy === 'post_tag' ) {
endpoint = `/wp-json/wp/v2/tags`;
} else if ( taxonomy === 'category' ) {
endpoint = `/wp-json/wp/v2/categories`;
Expand Down Expand Up @@ -242,7 +250,7 @@ fewerTagsGetAllTerms( FTchoicesElement.dataset.taxonomy ).then( ( terms ) => {
FTterms = terms;
FTchoices = new Choices( FTchoicesElement, {
choices: FTterms,
allowHTML: true,
allowHTML: false,
position: 'bottom',
itemSelectText: '',
renderChoiceLimit: -1,
Expand Down Expand Up @@ -282,7 +290,7 @@ fewerTagsDomReady( () => {
FTtaxonomies = taxonomies;
new Choices( FTtaxonomyElement, {
choices: FTtaxonomies,
allowHTML: true,
allowHTML: false,
position: 'bottom',
itemSelectText: '',
renderChoiceLimit: -1,
Expand Down Expand Up @@ -312,10 +320,12 @@ fewerTagsDomReady( () => {

FTtaxonomyElement.addEventListener( 'change', ( event ) => {
const targetTaxonomy = event.target.value;
fewerTagsGetAllTerms( targetTaxonomy ).then( ( terms ) => {
const selectedTaxonomy = FTtaxonomies.find( ( t ) => t.value === targetTaxonomy );
const restBase = selectedTaxonomy && selectedTaxonomy.customProperties ? selectedTaxonomy.customProperties.rest_base : '';
fewerTagsGetAllTerms( targetTaxonomy, restBase ).then( ( terms ) => {
FTterms = terms;
FTchoices.setChoices( FTterms, 'value', 'label', true );
document.getElementById( 'fewer-tags-target-term-label' ).innerHTML = 'Target ' + targetTaxonomy;
document.getElementById( 'fewer-tags-target-term-label' ).textContent = 'Target ' + targetTaxonomy;
document.getElementById( 'fewer-tags-target-taxonomy-slug' ).value = targetTaxonomy;
} );
} );
Expand Down
27 changes: 23 additions & 4 deletions src/class-admin-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ public function redirect_term_on_deletion( $term, $tt_id, $taxonomy, $deleted_te
if ( ! is_array( $terms_to_redirect ) ) {
$terms_to_redirect = [];
}
// Use the permalink captured in pre_delete_term, since the term is already deleted at this point.
$permalink = $this->options->get( 'just_deleted_term_permalink' );
$terms_to_redirect[ $taxonomy ][ $deleted_term->slug ] = [
'object' => $deleted_term,
'permalink' => \get_term_link( $deleted_term, $taxonomy ),
'permalink' => $permalink,
];
$this->options->set( 'terms_to_redirect', $terms_to_redirect );
}
Expand All @@ -68,6 +70,7 @@ public function redirect_term_on_deletion( $term, $tt_id, $taxonomy, $deleted_te
public function pre_delete_term( $term_id, $taxonomy ) {
$deleted_term = \get_term( $term_id, $taxonomy );
$this->options->set( 'just_deleted_term', $deleted_term );
$this->options->set( 'just_deleted_term_permalink', \get_term_link( $deleted_term, $taxonomy ) );
}

/**
Expand All @@ -78,6 +81,10 @@ public function pre_delete_term( $term_id, $taxonomy ) {
public function get_just_deleted_term() {
\check_ajax_referer( 'fewer_tags_just_deleted_term' );

if ( ! \current_user_can( 'manage_categories' ) ) {
\wp_send_json_error( [ 'msg' => __( 'You do not have permission to do this.', 'fewer-tags' ) ] );
}

$just_deleted_term = $this->options->get( 'just_deleted_term' );
$msg = Helper::redirect_term_notice( $just_deleted_term->slug, $just_deleted_term->name, $just_deleted_term->taxonomy );

Expand All @@ -92,6 +99,10 @@ public function get_just_deleted_term() {
public function dismiss_notice() {
\check_ajax_referer( 'fewer_tags_dismiss_notice' );

if ( ! \current_user_can( 'manage_categories' ) ) {
\wp_send_json_error( [ 'msg' => __( 'You do not have permission to do this.', 'fewer-tags' ) ] );
}

if ( ! isset( $_POST['id'] ) || ! isset( $_POST['taxonomy'] ) ) {
\wp_send_json_error( [ 'msg' => __( 'Invalid data.', 'fewer-tags' ) ] );
}
Expand All @@ -112,6 +123,10 @@ public function dismiss_notice() {
public function redirect_url_action() {
\check_ajax_referer( 'fewer_tags_redirect_url' );

if ( ! \current_user_can( 'manage_categories' ) ) {
\wp_send_json_error( [ 'msg' => __( 'You do not have permission to do this.', 'fewer-tags' ) ] );
}

if ( ! isset( $_POST['slug'] ) || ! isset( $_POST['target'] ) || ! isset( $_POST['taxonomy'] ) ) {
\wp_send_json_error(
[
Expand All @@ -134,7 +149,7 @@ public function redirect_url_action() {
[
'slug' => $term->slug,
// translators: %1$s is the just redirected term name.
'msg' => sprintf( __( 'Redirect for %1$s created!', 'fewer-tags' ), '<a href="' . $term_url . '">' . $term->name . '</a>' ),
'msg' => sprintf( __( 'Redirect for %1$s created!', 'fewer-tags' ), '<a href="' . \esc_url( $term_url ) . '">' . \esc_html( $term->name ) . '</a>' ),
]
);
}
Expand All @@ -147,6 +162,10 @@ public function redirect_url_action() {
public function merge_terms() {
\check_ajax_referer( 'fewer_tags_merge_terms' );

if ( ! \current_user_can( 'manage_categories' ) ) {
\wp_send_json_error( [ 'msg' => __( 'You do not have permission to do this.', 'fewer-tags' ) ] );
}

if ( ! isset( $_POST['source_id'] ) || ! isset( $_POST['target_id'] ) || ! isset( $_POST['source_taxonomy'] ) || ! isset( $_POST['target_taxonomy'] ) ) {
\wp_send_json_error( [ 'msg' => __( 'Invalid data.', 'fewer-tags' ) ] );
}
Expand Down Expand Up @@ -180,10 +199,10 @@ public function merge_terms() {
$target_term = \get_term( $target_term_id, $target_taxonomy );

// translators: %1$s is the source tag name, %2$s is the target tag name.
$msg = sprintf( \esc_html__( '%1$s merged into %2$s!', 'fewer-tags' ), '<strong>' . $source_term->name . '</strong>', '<strong>' . $target_term->name . '</strong>' );
$msg = sprintf( \esc_html__( '%1$s merged into %2$s!', 'fewer-tags' ), '<strong>' . \esc_html( $source_term->name ) . '</strong>', '<strong>' . \esc_html( $target_term->name ) . '</strong>' );
if ( $source_taxonomy !== $target_taxonomy ) {
// translators: %1$s is the source tag name, %2$s is the target tag name, %3$s is the source taxonomy, %4$s is the target taxonomy.
$msg = sprintf( \esc_html__( '%1$s (%3$s) merged into %2$s (%4$s)!', 'fewer-tags' ), '<strong>' . $source_term->name . '</strong>', '<strong>' . $target_term->name . '</strong>', $source_taxonomy, $target_taxonomy );
$msg = sprintf( \esc_html__( '%1$s (%3$s) merged into %2$s (%4$s)!', 'fewer-tags' ), '<strong>' . \esc_html( $source_term->name ) . '</strong>', '<strong>' . \esc_html( $target_term->name ) . '</strong>', \esc_html( $source_taxonomy ), \esc_html( $target_taxonomy ) );
}
\wp_send_json_success(
[
Expand Down
58 changes: 50 additions & 8 deletions src/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

namespace FewerTags;

use FewerTags\Plugin;

/**
* FewerTags Admin Class
*/
Expand Down Expand Up @@ -41,14 +39,21 @@ public function register_settings() {
);

\add_settings_field(
Plugin::$option_name,
'fewer_tags_min_posts_count',
__( 'Tags need to have', 'fewer-tags' ),
[ $this, 'display_setting' ],
'reading',
'fewer_tags_section'
);

\register_setting( 'reading', Plugin::$option_name );
\register_setting(
'reading',
'fewer_tags',
[
'type' => 'array',
'sanitize_callback' => [ $this, 'sanitize_setting' ],
]
);
}

/**
Expand All @@ -68,8 +73,8 @@ public function display_section() {
public function display_setting() {
?>
<input
name="<?php echo \esc_attr( Plugin::$option_name ); ?>"
id="<?php echo \esc_attr( Plugin::$option_name ); ?>"
name="fewer_tags[min_posts_count]"
id="fewer_tags_min_posts_count"
type="number"
min="1"
value="<?php echo (int) Plugin::$min_posts_count; ?>"
Expand All @@ -79,6 +84,32 @@ class="small-text"
<?php
}

/**
* Sanitize the setting value.
*
* Receives the form submission for the fewer_tags option, updates only the
* min_posts_count key, and preserves all other keys in the array.
*
* @param mixed $input The input value from the form.
*
* @return array The sanitized option array.
*/
public function sanitize_setting( $input ) {
$current = \get_option( 'fewer_tags', [] );
if ( ! is_array( $current ) ) {
$current = [];
}

$min_posts_count = isset( $input['min_posts_count'] ) ? (int) $input['min_posts_count'] : 10;
if ( $min_posts_count < 1 ) {
$min_posts_count = 1;
}

$current['min_posts_count'] = $min_posts_count;

return $current;
}

/**
* Adds a new column to the tag list table to show whether a tag is active or inactive.
*
Expand Down Expand Up @@ -134,14 +165,25 @@ public function remove_view_action( $actions, $tag ) {
* @return void
*/
public function redirect_tool_notice() {
$screen = \get_current_screen();
if ( ! \is_object( $screen ) || $screen->base !== 'edit-tags' ) {
return;
}

if ( Helper::determine_redirect_tool() !== false ) {
return;
}
?>
<div class="error">
<p>
<strong><?php \esc_html_e( 'Warning:', 'fewer-tags' ); ?></strong>
<?php \esc_html_e( 'Fewer Tags requires either the Redirection plugin by John Godley or the Yoast SEO Premium plugin to be installed and activated to be able to merge tags, categories and other terms.', 'fewer-tags' ); ?>
<?php
printf(
/* translators: %s: link to Redirection plugin */
\esc_html__( 'Fewer Tags requires either the %s plugin by John Godley or the Yoast SEO Premium plugin to be installed and activated to be able to merge tags, categories and other terms.', 'fewer-tags' ),
'<a href="https://wordpress.org/plugins/redirection/" target="_blank" rel="noopener noreferrer">Redirection</a>'
);
?>
</p>
<p>
<?php
Expand Down Expand Up @@ -178,7 +220,7 @@ public function redirect_tool_notice() {
: \esc_html__( 'Install Redirection', 'fewer-tags' );
?>
</a>
<a href="https://yoast.com/wordpress/plugins/seo/"><?php \esc_html_e( 'Get Yoast SEO Premium', 'fewer-tags' ); ?></a></p>
<a href="https://yoast.com/wordpress/plugins/seo/" target="_blank" rel="noopener noreferrer"><?php \esc_html_e( 'Get Yoast SEO Premium', 'fewer-tags' ); ?></a></p>
</div>
<?php
}
Expand Down
8 changes: 5 additions & 3 deletions src/class-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ class Helper {
* @return string The redirect notice HTML.
*/
public static function redirect_term_notice( $slug, $name, $taxonomy ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Slugs are never user input.
$slug = \esc_attr( $slug );
$taxonomy = \esc_attr( $taxonomy );

$notice = '<div id="fewer-tags-redirect-' . $slug . '" class="notice notice-error is-dismissible fewer-tags-redirect-notice" data-slug="' . $slug . '">';
$notice .= '<p>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in function.
Expand Down Expand Up @@ -80,8 +82,8 @@ public static function get_redirect_message( $slug, $taxonomy ) {
}

$msg .= '<ul>';
$msg .= '<li style="list-style-type: disc; margin-left: 15px;"><a href="javascript:fewerTagsRedirectToUrl(\'' . $slug . '\',\'' . $taxonomy . '\',\'/\',\'' . \wp_create_nonce( 'fewer_tags_redirect_url' ) . '\')">' . \esc_html__( 'Redirect to homepage', 'fewer-tags' ) . '</a></li>';
$msg .= '<li style="list-style-type: disc; margin-left: 15px;"><a href="javascript:fewerTagsRedirectToUrl(\'' . $slug . '\',\'' . $taxonomy . '\',prompt(\'' . \esc_html__( 'Where should the page redirect to?', 'fewer-tags' ) . '\'),\'' . \wp_create_nonce( 'fewer_tags_redirect_url' ) . '\')">' . \esc_html__( 'Redirect to another URL', 'fewer-tags' ) . '</a></li>';
$msg .= '<li style="list-style-type: disc; margin-left: 15px;"><a href="javascript:fewerTagsRedirectToUrl(\'' . \esc_js( $slug ) . '\',\'' . \esc_js( $taxonomy ) . '\',\'/\',\'' . \wp_create_nonce( 'fewer_tags_redirect_url' ) . '\')">' . \esc_html__( 'Redirect to homepage', 'fewer-tags' ) . '</a></li>';
$msg .= '<li style="list-style-type: disc; margin-left: 15px;"><a href="javascript:fewerTagsRedirectToUrl(\'' . \esc_js( $slug ) . '\',\'' . \esc_js( $taxonomy ) . '\',prompt(\'' . \esc_js( __( 'Where should the page redirect to?', 'fewer-tags' ) ) . '\'),\'' . \wp_create_nonce( 'fewer_tags_redirect_url' ) . '\')">' . \esc_html__( 'Redirect to another URL', 'fewer-tags' ) . '</a></li>';
$msg .= '</ul>';

return $msg;
Expand Down
2 changes: 1 addition & 1 deletion src/class-option.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Option {
*
* @var string
*/
private $option_name = 'fewer_tags_pro';
private $option_name = 'fewer_tags';

/**
* Holds the plugin options.
Expand Down
Loading
Loading