--- a/squirrly-seo/classes/ActionController.php
+++ b/squirrly-seo/classes/ActionController.php
@@ -217,7 +217,6 @@
'sq_ajax_gsc_code',
'sq_ajax_ga_code',
'sq_ajax_connection_check',
- 'sq_ajax_uninstall',
),
),
'active' => '1',
--- a/squirrly-seo/classes/helpers/Cache.php
+++ b/squirrly-seo/classes/helpers/Cache.php
@@ -264,8 +264,6 @@
$this->clearTransients();
$this->cachedFiles( false );
- SQ_Classes_Helpers_Tools::emptyCache();
-
return;
}
@@ -283,7 +281,10 @@
$this->clearTransients( $type );
$this->cachedFiles( $data );
- SQ_Classes_Helpers_Tools::emptyCache();
+
+ if ( apply_filters( 'sq_clear_third_party_cache', true, $type )){
+ SQ_Classes_Helpers_Tools::emptyCache();
+ }
do_action( 'sq_invalidate_cache_after', $type );
}
--- a/squirrly-seo/classes/helpers/Tools.php
+++ b/squirrly-seo/classes/helpers/Tools.php
@@ -1343,6 +1343,7 @@
*/
public static function emptyCache()
{
+
try {
//////////////////////////////////////////////////////////////////////////////
--- a/squirrly-seo/controllers/Menu.php
+++ b/squirrly-seo/controllers/Menu.php
@@ -59,12 +59,6 @@
}
}
-
- add_action( 'current_screen', function () {
- if ( in_array( get_current_screen()->id, array( 'plugins', 'plugins-network' ) ) ) {
- SQ_Classes_ObjController::getClass( 'SQ_Controllers_Uninstall' );
- }
- } );
}
/**
--- a/squirrly-seo/controllers/SeoSettings.php
+++ b/squirrly-seo/controllers/SeoSettings.php
@@ -613,24 +613,7 @@
echo wp_json_encode( array() );
exit();
- case 'sq_ajax_uninstall':
- $reason['select'] = SQ_Classes_Helpers_Tools::getValue( 'reason_key', false );
- $reason['plugin'] = SQ_Classes_Helpers_Tools::getValue( 'reason_found_a_better_plugin', false );
- $reason['other'] = SQ_Classes_Helpers_Tools::getValue( 'reason_other', false );
- $args['action'] = 'deactivate';
- $args['value'] = json_encode( $reason );
- SQ_Classes_RemoteController::saveFeedback( $args );
-
- if ( SQ_Classes_Helpers_Tools::getValue( 'sq_disconnect', false ) ) {
- SQ_Classes_Helpers_Tools::saveOptions( 'sq_api', false );
- SQ_Classes_Helpers_Tools::saveOptions( 'sq_cloud_token', false );
- SQ_Classes_Helpers_Tools::saveOptions( 'sq_cloud_connect', false );
- }
-
- SQ_Classes_Helpers_Tools::setHeader( 'json' );
- echo wp_json_encode( array() );
- exit();
}
--- a/squirrly-seo/controllers/Uninstall.php
+++ b/squirrly-seo/controllers/Uninstall.php
@@ -1,16 +0,0 @@
-<?php
-defined( 'ABSPATH' ) || die( 'Cheatin' uh?' );
-
-/**
- * Uninstall Options
- */
-class SQ_Controllers_Uninstall extends SQ_Classes_FrontController {
-
- public function hookHead() {
- SQ_Classes_ObjController::getClass( 'SQ_Classes_DisplayController' )->loadMedia( 'uninstall' );
- }
-
- public function hookFooter() {
- $this->show_view( 'Blocks/Uninstall' );
- }
-}
--- a/squirrly-seo/models/Frontend.php
+++ b/squirrly-seo/models/Frontend.php
@@ -780,35 +780,46 @@
}
/**
- * Get the keyword fof this URL
+ * Get the keyword for this URL
*
* @param $post
*
* @return array|false|WP_Error|WP_Term
*/
- private function getTagDetails( $post ) {
- $temp = str_replace( '…', '...', single_tag_title( '', false ) );
+ private function getTagDetails( $post = null ) {
- foreach ( get_taxonomies() as $tax ) {
- if ( $tax <> 'category' ) {
+ if ( ! is_tag() ) {
+ return false;
+ }
- if ( $tag = get_term_by( 'name', $temp, $tax ) ) {
- if ( ! is_wp_error( $tag ) ) {
- return $tag;
- }
- }
+ // Best: WP already gives you the tag term
+ $term = get_queried_object();
+ if ( $term instanceof WP_Term && $term->taxonomy === 'post_tag' && ! is_wp_error( $term ) ) {
+ return $term;
+ }
+
+ // Fallback: tag ID from query var
+ $tag_id = (int) get_queried_object_id();
+ if ( $tag_id ) {
+ $term = get_term( $tag_id, 'post_tag' );
+ if ( $term && ! is_wp_error( $term ) ) {
+ return $term;
}
}
- if ( $tag = get_term_by( 'id', $post->term_id, $post->taxonomy ) ) {
- if ( ! is_wp_error( $tag ) ) {
- return $tag;
+ // Final fallback (only if you truly have it)
+ if ( $post && ! empty($post->term_id) ) {
+ $term = get_term( (int) $post->term_id, 'post_tag' );
+ if ( $term && ! is_wp_error( $term ) ) {
+ return $term;
}
}
return false;
}
+
+
/**
* Get the taxonomies details for this URL
*
@@ -816,16 +827,33 @@
*
* @return array|bool|false|mixed|null|object|string|WP_Error|WP_Term
*/
- private function getTaxonomyDetails( $post ) {
- if ( $id = get_queried_object_id() ) {
- $term = get_term( $id, '' );
- if ( ! is_wp_error( $term ) ) {
+ private function getTaxonomyDetails( $post = null ) {
+
+ if ( ! is_tax() ) {
+ return false;
+ }
+
+ // Best: WP already resolved the current term for this taxonomy archive
+ $term = get_queried_object();
+ if ( $term instanceof WP_Term && ! is_wp_error( $term ) ) {
+ return $term;
+ }
+
+ // Fallback: use query vars (taxonomy + term id)
+ $term_id = (int) get_queried_object_id();
+ $taxonomy = get_query_var( 'taxonomy' ); // e.g. 'product_cat', 'portfolio_tag', etc.
+
+ if ( $term_id && $taxonomy ) {
+ $term = get_term( $term_id, $taxonomy );
+ if ( $term && ! is_wp_error( $term ) ) {
return $term;
}
}
- if ( $term = get_term_by( 'id', $post->term_id, $post->taxonomy ) ) {
- if ( ! is_wp_error( $term ) ) {
+ // Final fallback: if you truly have it in $post
+ if ( $post && ! empty($post->term_id) && ! empty($post->taxonomy) ) {
+ $term = get_term_by( 'id', (int) $post->term_id, $post->taxonomy );
+ if ( $term && ! is_wp_error( $term ) ) {
return $term;
}
}
@@ -833,6 +861,8 @@
return false;
}
+
+
/**
* Get the category details for this URL
*
@@ -840,16 +870,31 @@
*
* @return array|false|object|WP_Error|null
*/
- private function getCategoryDetails( $post ) {
+ private function getCategoryDetails( $post = null ) {
+
+ if ( ! is_category() ) {
+ return false;
+ }
+
+ // Best: already-resolved term object for the current category archive
+ $term = get_queried_object();
+ if ( $term instanceof WP_Term && $term->taxonomy === 'category' && ! is_wp_error( $term ) ) {
+ return $term;
+ }
- if ( $term = get_category( get_query_var( 'cat' ), false ) ) {
- if ( ! is_wp_error( $term ) ) {
+ // Fallback: category ID from query var
+ $cat_id = (int) get_query_var( 'cat' );
+ if ( $cat_id ) {
+ $term = get_term( $cat_id, 'category' ); // or get_category($cat_id)
+ if ( $term && ! is_wp_error( $term ) ) {
return $term;
}
}
- if ( $tag = get_term_by( 'id', $post->term_id, $post->taxonomy ) ) {
- if ( ! is_wp_error( $tag ) ) {
+ // Final fallback (only if you truly have it)
+ if ( $post && ! empty($post->term_id) ) {
+ $term = get_term( (int) $post->term_id, 'category' );
+ if ( $term && ! is_wp_error( $term ) ) {
return $term;
}
}
@@ -857,22 +902,41 @@
return false;
}
+
+
/**
* Get the profile details for this URL
*
- * @return object
+ * @return false|object
*/
public function getAuthorDetails() {
- $author = false;
- global $authordata;
- if ( isset( $authordata->data ) ) {
- $author = $authordata->data;
- $author->description = get_the_author_meta( 'description' );
+ if ( ! is_author() ) {
+ return false;
}
- return $author;
+ // On author archives this should be the WP_User object
+ $author = get_queried_object();
+ if ( $author instanceof WP_User ) {
+ // Keep your expected "description" field
+ $author->description = get_user_meta( $author->ID, 'description', true );
+ return $author;
+ }
+
+ // Fallback: author ID from query var
+ $author_id = (int) get_query_var('author');
+ if ( $author_id ) {
+ $user = get_userdata( $author_id );
+ if ( $user ) {
+ $user->description = get_user_meta( $user->ID, 'description', true );
+ return $user;
+ }
+ }
+
+ return false;
}
+
+
/**
* Add the custom post type details to the current post
*
@@ -1048,66 +1112,87 @@
return $buffer;
}
- if ( $domain = parse_url( home_url(), PHP_URL_HOST ) ) {
- foreach ( $out[0] as $index => $link ) {
- $newlink = $link;
-
- //only for external links
- if ( isset( $out[1][ $index ] ) ) {
- //If it's not a valid link
- if ( ! $linkdomain = parse_url( $out[1][ $index ], PHP_URL_HOST ) ) {
- continue;
- }
+ $siteHost = parse_url( home_url(), PHP_URL_HOST );
+ if ( ! $siteHost ) {
+ return $buffer;
+ }
- //If it's not an external link
- if ( stripos( $linkdomain, $domain ) !== false ) {
- continue;
- }
+ // Normalize: lowercase + remove leading www.
+ $siteHostNorm = preg_replace('/^www./i', '', strtolower($siteHost));
- //If it's not an exception link
- $exceptions = SQ_Classes_Helpers_Tools::getOption( 'sq_external_exception' );
- if ( ! empty( $exceptions ) ) {
- foreach ( $exceptions as $exception ) {
- if ( $exception <> '' ) {
- if ( stripos( $exception, $linkdomain ) !== false || stripos( $linkdomain, $exception ) !== false ) {
- continue 2;
- }
- }
- }
- }
- }
+ foreach ( $out[0] as $index => $link ) {
+ $newlink = $link;
- //If nofollow rel is set
- if ( SQ_Classes_Helpers_Tools::getOption( 'sq_external_nofollow' ) ) {
+ if ( empty( $out[1][ $index ] ) ) {
+ continue;
+ }
- if ( strpos( $newlink, 'rel=' ) === false ) {
- $newlink = str_replace( '<a', '<a rel="nofollow" ', $newlink );
- } elseif ( strpos( $newlink, 'nofollow' ) === false ) {
- $newlink = preg_replace( '/(rel=['"])([^'"]+)(['"])/i', '$1nofollow $2$3', $newlink );
- }
+ $href = $out[1][ $index ];
- }
+ // Skip non-http(s), anchors, mailto, tel, relative URLs
+ $scheme = parse_url( $href, PHP_URL_SCHEME );
+ if ( $scheme && ! in_array( strtolower($scheme), array('http','https'), true ) ) {
+ continue;
+ }
+
+ $linkHost = parse_url( $href, PHP_URL_HOST );
+ if ( ! $linkHost ) {
+ // relative URL => internal
+ continue;
+ }
+
+ $linkHostNorm = preg_replace('/^www./i', '', strtolower($linkHost));
- //if force external open
- if ( SQ_Classes_Helpers_Tools::getOption( 'sq_external_blank' ) ) {
+ // sub.domain.com will be treated as external
+ if ( $linkHostNorm === $siteHostNorm ) {
+ continue;
+ }
+
+ // Exceptions (allowlist)
+ $exceptions = SQ_Classes_Helpers_Tools::getOption( 'sq_external_exception' );
+ if ( ! empty( $exceptions ) ) {
+ foreach ( $exceptions as $exception ) {
+ $exception = trim((string)$exception);
+ if ( $exception === '' ) {
+ continue;
+ }
+ $exceptionHost = parse_url( (strpos($exception, '://') === false ? 'https://' . $exception : $exception), PHP_URL_HOST );
+ $exceptionHostNorm = $exceptionHost ? preg_replace('/^www./i', '', strtolower($exceptionHost)) : preg_replace('/^www./i', '', strtolower($exception));
- if ( strpos( $newlink, 'target=' ) === false ) {
- $newlink = str_replace( '<a', '<a target="_blank" ', $newlink );
- } elseif ( strpos( $link, '_blank' ) === false &&
- (strpos( $link, '_self' ) !== false || strpos( $link, '_parent' ) !== false || strpos( $link, '_top' ) !== false ) ) {
- $newlink = preg_replace( '/(target=['"])([^'"]+)(['"])/i', '$1_blank$3', $newlink );
+ if ( $exceptionHostNorm !== '' && $linkHostNorm === $exceptionHostNorm ) {
+ continue 2; // do not modify this link
}
+ }
+ }
+ // nofollow
+ if ( SQ_Classes_Helpers_Tools::getOption( 'sq_external_nofollow' ) ) {
+ if ( strpos( $newlink, 'rel=' ) === false ) {
+ $newlink = str_replace( '<a', '<a rel="nofollow" ', $newlink );
+ } elseif ( stripos( $newlink, 'nofollow' ) === false ) {
+ $newlink = preg_replace( '/(rel=['"])([^'"]+)(['"])/i', '$1nofollow $2$3', $newlink );
}
+ }
- //Check the link and replace it
- if ( $newlink <> $link ) {
- $buffer = str_replace( $link, $newlink, $buffer );
+ // target blank
+ if ( SQ_Classes_Helpers_Tools::getOption( 'sq_external_blank' ) ) {
+ if ( strpos( $newlink, 'target=' ) === false ) {
+ $newlink = str_replace( '<a', '<a target="_blank" ', $newlink );
+ } elseif (
+ stripos( $link, '_blank' ) === false &&
+ ( stripos( $link, '_self' ) !== false || stripos( $link, '_parent' ) !== false || stripos( $link, '_top' ) !== false )
+ ) {
+ $newlink = preg_replace( '/(target=['"])([^'"]+)(['"])/i', '$1_blank$3', $newlink );
}
}
+
+ if ( $newlink !== $link ) {
+ $buffer = str_replace( $link, $newlink, $buffer );
+ }
}
return $buffer;
}
+
}
--- a/squirrly-seo/models/domain/Patterns.php
+++ b/squirrly-seo/models/domain/Patterns.php
@@ -21,8 +21,10 @@
}
public function getDate() {
- if ( $this->_currentpost->post_date ) {
- return wp_date( 'c', strtotime( $this->_currentpost->post_date ) );
+ $post = $this->currentpost;
+
+ if ( isset($post->post_date) && $post->post_date ) {
+ return wp_date( 'c', strtotime( $post->post_date ) );
} elseif ( $this->_date ) {
return wp_date( 'c', strtotime( $this->_date ) );
}
@@ -513,8 +515,10 @@
}
public function getModified() {
- if ( $this->_currentpost->post_modified ) {
- return wp_date( 'c', strtotime( $this->_currentpost->post_modified ) );
+ $post = $this->currentpost;
+
+ if ( isset( $post->post_modified ) && $post->post_modified ) {
+ return wp_date( 'c', strtotime( $post->post_modified ) );
} elseif ( $this->_modified ) {
return wp_date( 'c', strtotime( $this->_modified ) );
}
--- a/squirrly-seo/models/focuspages/Image.php
+++ b/squirrly-seo/models/focuspages/Image.php
@@ -162,11 +162,18 @@
$image = substr( $image, strrpos( $image, '/' ) + 1 );
}
+ $image = $this->normalizeFilename($image);
+
//Check if all words are present in the image URL
$allwords = true;
foreach ( $words as $word ) {
+ $word = trim($word);
+ if ($word === '') continue;
+
+ $wordNorm = $this->normalizeFilename($word);
+
//Find the string with normalization
- if ( $word <> '' && SQ_Classes_Helpers_Tools::findStr( $image, $word, true ) === false ) {
+ if ( $word <> '' && SQ_Classes_Helpers_Tools::findStr( $image, $wordNorm, true ) === false ) {
$allwords = false;
}
}
@@ -181,4 +188,37 @@
return $task;
}
+ /**
+ * @param $string
+ *
+ * @return array|string|string[]|null
+ */
+ private function normalizeFilename($string) {
+ $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
+ $string = strtolower($string);
+
+ // remove query and extension
+ $string = preg_replace('~?.*$~', '', $string);
+ $string = preg_replace('~.[a-z0-9]{2,5}$~i', '', $string);
+
+ // German folding (I would do digraphs first)
+ $string = str_replace(array('ae','oe','ue'), array('a','o','u'), $string);
+ $string = str_replace(array('ä','ö','ü'), array('a','o','u'), $string);
+ $string = str_replace('ß', 'ss', $string);
+
+ // Transliterate remaining accents
+ if (class_exists('Transliterator')) {
+ $tmp = transliterator_transliterate('Any-Latin; Latin-ASCII', $string);
+ if (is_string($tmp) && $tmp !== '') $string = $tmp;
+ } else {
+ $tmp = @iconv('UTF-8', 'ASCII//TRANSLIT', $string);
+ if (is_string($tmp) && $tmp !== '') $string = $tmp;
+ }
+
+ $string = preg_replace('~[^p{L}p{N}]+~u', ' ', $string);
+ $string = preg_replace('~s{2,}~u', ' ', trim($string));
+
+ return $string;
+ }
+
}
--- a/squirrly-seo/squirrly.php
+++ b/squirrly-seo/squirrly.php
@@ -9,7 +9,7 @@
* Description: AI Private SEO Consultant that Brings You the Full Force of SEO: All Schema Rich Results, Inner Links, Redirects, AI Research, Real-Time SEO Content, Traffic and SEO Audits, SERP Checker.
* Author: Squirrly
* Author URI: https://plugin.squirrly.co
- * Version: 12.4.14
+ * Version: 12.4.15
* License: GPLv2 or later
* License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* Text Domain: squirrly-seo
@@ -18,9 +18,9 @@
if ( ! defined( 'SQ_VERSION' ) ) {
/* SET THE CURRENT VERSION ABOVE AND BELOW */
- define( 'SQ_VERSION', '12.4.14' );
+ define( 'SQ_VERSION', '12.4.15' );
//The last stable version
- define( 'SQ_STABLE_VERSION', '12.4.12' );
+ define( 'SQ_STABLE_VERSION', '12.4.14' );
// Call config files
try {
include_once dirname( __FILE__ ) . '/config/config.php';
--- a/squirrly-seo/view/Blocks/Uninstall.php
+++ b/squirrly-seo/view/Blocks/Uninstall.php
@@ -1,86 +0,0 @@
-<?php
-defined( 'ABSPATH' ) || die( 'Cheatin' uh?' );
-if ( ! isset( $view ) ) {
- return;
-}
-
-/**
- * Uninstall Block view
- *
- * Called on plugin uninstall as a popup for feedback
- */
-?>
-<?php
-$deactivate_reasons = array(
- 'no_longer_needed' => array(
- 'title' => esc_html__( "I no longer need the plugin", "squirrly-seo" ),
- 'input_placeholder' => '',
- ),
- 'found_a_better_plugin' => array(
- 'title' => esc_html__( "I found a better plugin", "squirrly-seo" ),
- 'input_placeholder' => esc_html__( "Please share which plugin", "squirrly-seo" ),
- ),
- 'couldnt_get_the_plugin_to_work' => array(
- 'title' => esc_html__( "I couldn't get the plugin to work", "squirrly-seo" ),
- 'input_placeholder' => '',
- ),
- 'temporary_deactivation' => array(
- 'title' => esc_html__( "It's a temporary deactivation", "squirrly-seo" ),
- 'input_placeholder' => '',
- ),
- 'other' => array(
- 'title' => esc_html__( "Other", "squirrly-seo" ),
- 'input_placeholder' => esc_html__( "Please share the reason", "squirrly-seo" ),
- ),
-);
-?>
-<div id="sq_uninstall" style="display: none;">
- <div id="sq_modal_overlay"></div>
- <div id="sq_modal">
- <div id="sq_uninstall_header">
- <span id="sq_uninstall_header_title"><?php echo esc_html__( "Deactivate", "squirrly-seo" ) . ' ' . esc_html( apply_filters( 'sq_name', _SQ_MENU_NAME_ ) ); ?></span>
- </div>
- <form id="sq_uninstall_form" method="post">
- <?php SQ_Classes_Helpers_Tools::setNonce( 'sq_ajax_uninstall', 'sq_nonce' ); ?>
- <input type="hidden" name="action" value="sq_ajax_uninstall"/>
-
- <h4><?php echo esc_html__( "Please share why you are deactivating the plugin", "squirrly-seo" ); ?>:</h4>
- <div id="sq_uninstall_form_body">
- <?php foreach ( $deactivate_reasons as $reason_key => $reason ) { ?>
- <div class="sq_uninstall_feedback_input_line">
- <input id="sq_uninstall_feedback_<?php echo esc_attr( $reason_key ); ?>" class="sq_uninstall_feedback_input" type="radio" name="reason_key" value="<?php echo esc_attr( $reason_key ); ?>"/>
- <label for="sq_uninstall_feedback_<?php echo esc_attr( $reason_key ); ?>" class="sq_uninstall_feedback_input_label"><?php echo esc_html( $reason['title'] ); ?></label>
- <?php if ( ! empty( $reason['input_placeholder'] ) ) { ?>
- <label for="sq_uninstall_feedback_text"></label>
- <input id="sq_uninstall_feedback_text" class="sq_uninstall_feedback_text" type="text" name="reason_<?php echo esc_attr( $reason_key ); ?>" placeholder="<?php echo esc_attr( $reason['input_placeholder'] ); ?>"/>
- <?php } ?>
- <?php if ( ! empty( $reason['alert'] ) ) { ?>
- <div class="sq_uninstall_feedback_text"><?php echo esc_html( $reason['alert'] ); ?></div>
- <?php } ?>
- </div>
- <?php } ?>
-
-
- <div class="sq_uninstall_form_buttons_wrapper">
- <button type="button" class="sq_uninstall_form_submit sq_uninstall_form_button"><?php echo esc_html__( "Submit & Deactivate", "squirrly-seo" ); ?></button>
- <button type="button" class="sq_uninstall_form_skip sq_uninstall_form_button"><?php echo esc_html__( "Skip & Deactivate", "squirrly-seo" ); ?></button>
- </div>
-
- <?php if ( SQ_Classes_Helpers_Tools::getOption( 'sq_complete_uninstall' ) ) { ?>
- <div class="sq_uninstall_form_options_wrapper sq_uninstall_feedback_separator">
- <div class="sq_uninstall_feedback_input_line" style="color:red; font-size: 14px;">
- <?php echo sprintf( esc_html__( "You set to remove all Squirrly SEO data on uninstall. You can change this option from %s Squirrly > Technical SEO > Advanced Settings %s", "squirrly-seo" ), '<a href="' . esc_url( SQ_Classes_Helpers_Tools::getAdminUrl( 'sq_seosettings', 'tweaks#tab=advanced' ) ) . '">', '</a>' ); ?>
- </div>
- </div>
- <?php } else { ?>
- <div class="sq_uninstall_form_options_wrapper sq_uninstall_feedback_separator">
- <div class="sq_uninstall_feedback_input_line">
- <input id="sq_disconnect" class="sq_uninstall_feedback_input" type="checkbox" name="sq_disconnect" value="1"/>
- <label for="sq_disconnect" class="sq_uninstall_feedback_input_label" style="color: #D32F2F"><?php echo esc_html__( "Disconnect from Squirrly Cloud", "squirrly-seo" ); ?></label>
- </div>
- </div>
- <?php } ?>
- </div>
- </form>
- </div>
-</div>