Current File : /home/honehdyv/readbtooom.com/wp-content/plugins/autodescription/inc/classes/admin-init.class.php
<?php
/**
* @package The_SEO_Framework\Classes\Facade\Admin_Init
* @subpackage The_SEO_Framework\Admin
*/
namespace The_SEO_Framework;
\defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
/**
* The SEO Framework plugin
* Copyright (C) 2015 - 2023 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Class The_SEO_Framework\Admin_Init
*
* Initializes the plugin for the wp-admin screens.
* Enqueues CSS and Javascript.
*
* @since 2.8.0
*/
class Admin_Init extends Init {
/**
* Initializes SEO Bar tables.
*
* @since 4.0.0
* @access private
*/
public function _init_seo_bar_tables() {
if ( $this->get_option( 'display_seo_bar_tables' ) )
new Bridges\SEOBar;
}
/**
* Initializes List Edit tables.
*
* @since 4.0.0
* @access private
*/
public function _init_list_edit() {
new Bridges\ListEdit;
}
/**
* Adds post states for the post/page edit.php query.
*
* @since 4.0.0
* @access private
*
* @param array $states The current post states array
* @param \WP_Post $post The Post Object.
* @return array Adjusted $states
*/
public function _add_post_state( $states = [], $post = null ) {
$post_id = $post->ID ?? false;
if ( $post_id ) {
$search_exclude = $this->get_option( 'alter_search_query' ) && $this->get_post_meta_item( 'exclude_local_search', $post_id );
$archive_exclude = $this->get_option( 'alter_archive_query' ) && $this->get_post_meta_item( 'exclude_from_archive', $post_id );
if ( $search_exclude )
$states[] = \esc_html__( 'No Search', 'autodescription' );
if ( $archive_exclude )
$states[] = \esc_html__( 'No Archive', 'autodescription' );
}
return $states;
}
/**
* Prepares scripts in the admin area.
*
* @since 3.1.0
* @since 4.0.0 Now discerns autoloading between taxonomies and singular types.
* @since 4.1.0 Now invokes autoloading when persistent scripts are enqueued (regardless of validity).
* @since 4.1.2 Now autoenqueues on edit.php and edit-tags.php regardless of SEO Bar output (for quick/bulk-edit support).
* @since 4.1.4 Now considers headlessness.
* @access private
*/
public function _init_admin_scripts() {
if (
$this->is_seo_settings_page()
// Notices can be outputted if not entirely headless -- this very method only runs when not entirely headless.
|| $this->get_static_cache( 'persistent_notices', [] )
|| (
! $this->is_headless['meta'] && (
( $this->is_archive_admin() && $this->is_taxonomy_supported() )
|| ( $this->is_singular_admin() && $this->is_post_type_supported( $this->get_admin_post_type() ) )
)
)
) {
$this->init_admin_scripts();
}
}
/**
* Registers admin scripts and styles.
*
* @since 2.6.0
* @since 3.1.0 First parameter is now deprecated.
* @since 4.0.0 First parameter is now removed.
*
* @return void Early if already enqueued.
*/
public function init_admin_scripts() {
if ( has_run( __METHOD__ ) ) return;
Bridges\Scripts::_init();
}
/**
* Returns the title and description input guideline table, for
* (Google) search, Open Graph, and Twitter.
*
* Memoizes the output, so the return filter will run only once.
*
* NB: Some scripts have wide characters. These are recognized by Google, and have been adjusted for in the chactacter
* guidelines. German is a special Case, where we account for the Capitalization of Nouns.
*
* NB: Although the Arabic & Farsi scripts are much smaller in width, Google seems to be using the 160 & 70 char limits
* strictly... As such, we stricten the guidelines for pixels instead.
*
* @since 3.1.0
* @since 4.0.0 1. Now gives different values for various WordPress locales.
* 2. Added $locale input parameter.
* @TODO Consider splitting up search into Google, Bing, etc., as we might
* want users to set their preferred search engine. Now, these engines
* are barely any different.
* TODO move this to another object?
*
* @param string|null $locale The locale to test. If empty, it will be auto-determined.
* @return array
*/
public function get_input_guidelines( $locale = null ) {
$locale = $locale ?: \get_locale();
// Strip the "_formal" and other suffixes. 5 length: xx_YY
$locale = substr( $locale, 0, 5 );
// phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know.
if ( null !== $memo = memo( null, $locale ) ) return $memo;
// phpcs:disable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
$character_adjustments = [
'as' => 148 / 160, // Assamese (অসমীয়া)
'de_AT' => 158 / 160, // Austrian German (Österreichisch Deutsch)
'de_CH' => 158 / 160, // Swiss German (Schweiz Deutsch)
'de_DE' => 158 / 160, // German (Deutsch)
'gu' => 148 / 160, // Gujarati (ગુજરાતી)
'ml_IN' => 100 / 160, // Malayalam (മലയാളം)
'ja' => 70 / 160, // Japanese (日本語)
'ko_KR' => 82 / 160, // Korean (한국어)
'ta_IN' => 120 / 160, // Tamil (தமிழ்)
'zh_TW' => 70 / 160, // Taiwanese Mandarin (Traditional Chinese) (繁體中文)
'zh_HK' => 70 / 160, // Hong Kong (Chinese version) (香港中文版)
'zh_CN' => 70 / 160, // Mandarin (Simplified Chinese) (简体中文)
];
// phpcs:enable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
$c_adjust = $character_adjustments[ $locale ] ?? 1;
$pixel_adjustments = [
'ar' => 760 / 910, // Arabic (العربية)
'ary' => 760 / 910, // Moroccan Arabic (العربية المغربية)
'azb' => 760 / 910, // South Azerbaijani (گؤنئی آذربایجان)
'fa_IR' => 760 / 910, // Iran Farsi (فارسی)
'haz' => 760 / 910, // Hazaragi (هزاره گی)
'ckb' => 760 / 910, // Central Kurdish (كوردی)
];
$p_adjust = $pixel_adjustments[ $locale ] ?? 1;
// phpcs:disable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
/**
* @since 3.1.0
* @since 4.2.7 Added two more paramters (`$c_adjust` and `$locale`)
* @param array $guidelines The title and description guidelines.
* Don't alter the format. Only change the numeric values.
* @param array[$c_adjust,$p_adjust] The guideline calibration (Character and Pixels respectively).
* @param string $locale The current locale.
*/
return memo(
(array) \apply_filters_ref_array(
'the_seo_framework_input_guidelines',
[
[
'title' => [
'search' => [
'chars' => [
'lower' => (int) ( 25 * $c_adjust ),
'goodLower' => (int) ( 35 * $c_adjust ),
'goodUpper' => (int) ( 65 * $c_adjust ),
'upper' => (int) ( 75 * $c_adjust ),
],
'pixels' => [
'lower' => (int) ( 200 * $p_adjust ),
'goodLower' => (int) ( 280 * $p_adjust ),
'goodUpper' => (int) ( 520 * $p_adjust ),
'upper' => (int) ( 600 * $p_adjust ),
],
],
'opengraph' => [
'chars' => [
'lower' => 15,
'goodLower' => 25,
'goodUpper' => 88,
'upper' => 100,
],
'pixels' => [],
],
'twitter' => [
'chars' => [
'lower' => 15,
'goodLower' => 25,
'goodUpper' => 69,
'upper' => 70,
],
'pixels' => [],
],
],
'description' => [
'search' => [
'chars' => [
'lower' => (int) ( 45 * $c_adjust ),
'goodLower' => (int) ( 80 * $c_adjust ),
'goodUpper' => (int) ( 160 * $c_adjust ),
'upper' => (int) ( 320 * $c_adjust ),
],
'pixels' => [
'lower' => (int) ( 256 * $p_adjust ),
'goodLower' => (int) ( 455 * $p_adjust ),
'goodUpper' => (int) ( 910 * $p_adjust ),
'upper' => (int) ( 1820 * $p_adjust ),
],
],
'opengraph' => [
'chars' => [
'lower' => 45,
'goodLower' => 80,
'goodUpper' => 200,
'upper' => 300,
],
'pixels' => [],
],
'twitter' => [
'chars' => [
'lower' => 45,
'goodLower' => 80,
'goodUpper' => 200,
'upper' => 200,
],
'pixels' => [],
],
],
],
[ $c_adjust, $p_adjust ],
$locale,
]
),
$locale
);
// phpcs:enable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
}
/**
* Returns the title and description input guideline explanatory table.
*
* Already attribute-escaped.
*
* @since 3.1.0
* @since 4.0.0 Now added a short leading-dot version for ARIA labels.
* @TODO move this to another object? -> i18n/guidelines
*
* @return array
*/
public function get_input_guidelines_i18n() {
return [
'long' => [
'empty' => \esc_attr__( "There's no content.", 'autodescription' ),
'farTooShort' => \esc_attr__( "It's too short and it should have more information.", 'autodescription' ),
'tooShort' => \esc_attr__( "It's short and it could have more information.", 'autodescription' ),
'tooLong' => \esc_attr__( "It's long and it might get truncated in search.", 'autodescription' ),
'farTooLong' => \esc_attr__( "It's too long and it will get truncated in search.", 'autodescription' ),
'good' => \esc_attr__( 'Length is good.', 'autodescription' ),
],
'short' => [
'empty' => \esc_attr_x( 'Empty', 'The string is empty', 'autodescription' ),
'farTooShort' => \esc_attr__( 'Far too short', 'autodescription' ),
'tooShort' => \esc_attr__( 'Too short', 'autodescription' ),
'tooLong' => \esc_attr__( 'Too long', 'autodescription' ),
'farTooLong' => \esc_attr__( 'Far too long', 'autodescription' ),
'good' => \esc_attr__( 'Good', 'autodescription' ),
],
'shortdot' => [
'empty' => \esc_attr_x( 'Empty.', 'The string is empty', 'autodescription' ),
'farTooShort' => \esc_attr__( 'Far too short.', 'autodescription' ),
'tooShort' => \esc_attr__( 'Too short.', 'autodescription' ),
'tooLong' => \esc_attr__( 'Too long.', 'autodescription' ),
'farTooLong' => \esc_attr__( 'Far too long.', 'autodescription' ),
'good' => \esc_attr__( 'Good.', 'autodescription' ),
],
];
}
/**
* Checks ajax referred set by set_js_nonces based on capability.
*
* Performs die() on failure.
*
* @since 3.1.0 Introduced in 2.9.0, but the name changed.
* @access private
* It uses an internally and manually created prefix.
* @uses WP Core check_ajax_referer()
* @see @link https://developer.wordpress.org/reference/functions/check_ajax_referer/
*
* @param string $capability The capability that was required for the nonce check to be created.
* @return false|int False if the nonce is invalid, 1 if the nonce is valid
* and generated between 0-12 hours ago, 2 if the nonce is
* valid and generated between 12-24 hours ago.
*/
public function _check_tsf_ajax_referer( $capability ) {
return \check_ajax_referer( "tsf-ajax-$capability", 'nonce', true );
}
/**
* Redirect the user to an admin page, and add query args to the URL string
* for alerts, etc.
*
* @since 2.2.2
* @since 2.9.2 Added user-friendly exception handling.
* @since 2.9.3 1. Query arguments work again (regression 2.9.2).
* 2. Now only accepts http and https protocols.
* @since 4.2.0 Now allows query arguments with value 0|'0'.
* @TODO WP 5.2/5.4 will cause this method to never run on wp_die().
* We should further investigate the cause and remove WP's blockade. This is a corner-case, however.
*
* @param string $page Menu slug. This slug must exist, or the redirect will loop back to the current page.
* @param array $query_args Optional. Associative array of query string arguments
* (key => value). Default is an empty array.
* @return null Return early if first argument is false.
*/
public function admin_redirect( $page, $query_args = [] ) {
if ( empty( $page ) ) return;
// This can be empty... so $target will be empty. TODO test for $success and bail?
// Might cause security issues... we _must_ exit, always? Show warning?
$url = html_entity_decode( \menu_page_url( $page, false ) );
$target = \add_query_arg( array_filter( $query_args, 'strlen' ), $url );
$target = \esc_url_raw( $target, [ 'https', 'http' ] );
// Predict white screen:
$headers_sent = headers_sent();
\wp_safe_redirect( $target, 302 );
// White screen of death for non-debugging users. Let's make it friendlier.
if ( $headers_sent && $target ) {
$headers_list = headers_list();
$location = sprintf( 'Location: %s', \wp_sanitize_redirect( $target ) );
// Test if WordPress's redirect header is sent. Bail if true.
if ( \in_array( $location, $headers_list, true ) ) exit;
// phpcs:disable, WordPress.Security.EscapeOutput -- convert_markdown escapes. Added esc_url() for sanity.
printf(
'<p><strong>%s</strong></p>',
$this->convert_markdown(
sprintf(
/* translators: %s = Redirect URL markdown */
\esc_html__( 'There has been an error redirecting. Refresh the page or follow [this link](%s).', 'autodescription' ),
\esc_url( $target )
),
[ 'a' ],
[ 'a_internal' => true ]
)
);
}
exit;
}
/**
* Registers dismissible persistent notice, that'll respawn during page load until dismissed or otherwise expired.
*
* @since 4.1.0
* @since 4.1.3 Now handles timeout values below -1 gracefully, by purging the whole notification gracelessly.
* @uses $this->generate_dismissible_persistent_notice()
*
* @param string $message The notice message. Expected to be escaped if $escape is false.
* When the message contains HTML, it must start with a <p> tag,
* or it will be added for you--regardless of proper semantics.
* @param string $key The notice key. Must be unique--prevents double-registering of the notice, and allows for
* deregistering of the notice.
* @param array $args : {
* 'type' => string Optional. The notification type. Default 'updated'.
* 'icon' => bool Optional. Whether to enable icon. Default true.
* 'escape' => bool Optional. Whether to escape the $message. Default true.
* }
* @param array $conditions : {
* 'capability' => string Required. The user capability required for the notice to display. Defaults to settings capability.
* 'screens' => array Optional. The screen bases the notice may be displayed on. When left empty, it'll output on any page.
* 'excl_screens' => array Optional. The screen bases the notice may NOT be displayed on. When left empty, only `screens` applies.
* 'user' => int Optional. The user ID to display the notice for. Capability will not be ignored.
* 'count' => int Optional. The number of times the persistent notice may appear (for everyone allowed to see it).
* Set to -1 for unlimited. When -1, the notice must be removed from display manually.
* 'timeout' => int Optional. The number of seconds the notice should remain valid for display. Set to -1 to disable check.
* When the timeout is below -1, then the notification will not be outputted.
* Do not input non-integer values (such as `false`), for those might cause adverse events.
* }
*/
public function register_dismissible_persistent_notice( $message, $key, $args = [], $conditions = [] ) {
// We made this mistake ourselves. Let's test against it.
// We can't type $key to scalar, for PHP is dumb with that type.
if ( ! is_scalar( $key ) || ! \strlen( $key ) ) return;
// Sanitize the key so that HTML, JS, and PHP can communicate easily via it.
$key = \sanitize_key( $key );
$args = array_merge(
[
'type' => 'updated',
'icon' => true,
'escape' => true,
],
$args
);
$conditions = array_merge(
[
'screens' => [],
'excl_screens' => [],
'capability' => $this->get_settings_capability(),
'user' => 0,
'count' => 1,
'timeout' => -1,
],
$conditions
);
// Required key for security.
if ( ! $conditions['capability'] ) return;
// Timeout already expired. Let's not register it.
if ( $conditions['timeout'] < -1 ) return;
// Add current time to timeout, so we can compare against it later.
if ( $conditions['timeout'] > -1 )
$conditions['timeout'] += time();
$notices = $this->get_static_cache( 'persistent_notices', [] );
$notices[ $key ] = compact( 'message', 'args', 'conditions' );
$this->update_static_cache( 'persistent_notices', $notices );
}
/**
* Lowers the persistent notice display count.
* When the threshold is reached, the notice is deleted.
*
* @since 4.1.0
*
* @param string $key The notice key.
* @param int $count The number of counts the notice has left. Passed by reference.
* When -1 (permanent notice), nothing happens.
*/
public function count_down_persistent_notice( $key, &$count ) {
$_count_before = $count;
if ( $count > 0 )
--$count;
if ( ! $count ) {
$this->clear_persistent_notice( $key );
} elseif ( $_count_before !== $count ) {
$notices = $this->get_static_cache( 'persistent_notices' );
if ( isset( $notices[ $key ]['conditions']['count'] ) ) {
$notices[ $key ]['conditions']['count'] = $count;
$this->update_static_cache( 'persistent_notices', $notices );
} else {
// Notice didn't conform. Remove it.
$this->clear_persistent_notice( $key );
}
}
}
/**
* Clears a persistent notice by key.
*
* @since 4.1.0
*
* @param string $key The notice key.
* @return bool True on success, false on failure.
*/
public function clear_persistent_notice( $key ) {
// TODO We could make a oneliner using array_diff_key?: array_diff_key( cache, [ $key ] )
$notices = $this->get_static_cache( 'persistent_notices', [] );
unset( $notices[ $key ] );
return $this->update_static_cache( 'persistent_notices', $notices );
}
/**
* Clears all registered persistent notices. Useful after upgrade.
*
* @since 4.1.0
*
* @return bool True on success, false on failure.
*/
public function clear_all_persistent_notices() {
return $this->update_static_cache( 'persistent_notices', [] );
}
/**
* Returns the snaitized notice action key.
*
* @since 4.1.0
* @since 4.1.4 1. Now 'public', marked private.
* 2. Now uses underscores instead of dashes.
* @access private
*
* @param string $key The notice key.
* @return string The sanitized nonce action.
*/
public function _get_dismiss_notice_nonce_action( $key ) {
return \sanitize_key( "tsf_notice_nonce_$key" );
}
/**
* Clears persistent notice on user request (clicked Dismiss icon) via the no-JS form.
*
* @since 4.1.0
* Security check OK.
*/
public function _dismiss_notice() {
// phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
$key = \sanitize_key( $_POST['tsf-notice-submit'] ?? '' );
if ( ! $key ) return;
$notices = $this->get_static_cache( 'persistent_notices', [] );
// Notice was deleted already elsewhere, or key was faulty. Either way, ignore--should be self-resolving.
if ( empty( $notices[ $key ]['conditions']['capability'] ) ) return;
if (
! \current_user_can( $notices[ $key ]['conditions']['capability'] )
|| ! \wp_verify_nonce( $_POST['tsf_notice_nonce'] ?? '', $this->_get_dismiss_notice_nonce_action( $key ) )
) {
\wp_die( -1, 403 );
}
$this->clear_persistent_notice( $key );
}
}