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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
snippet running under file-based execution.
* Fixed the snippet editor reporting a save as failed and undelivered when no response was received, which it cannot
determine.
* Fixed translations being loaded too early, with a notice from WordPress, when posts were queried while plugins were
still loading.

## [3.10.2] (2026-09-01)

Expand Down
102 changes: 26 additions & 76 deletions src/php/Settings/Settings_Fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,75 +14,19 @@
*/
class Settings_Fields {

/**
* Instance of this class.
*
* @var Settings_Fields
*/
private static Settings_Fields $instance;

/**
* The settings fields definitions.
*
* @var array<string, array<string, array>>
*/
private array $fields;

/**
* The default settings values.
*
* @var array<string, array<string, mixed>>
*/
private array $defaults;

/**
* Constructor.
*
* Initializes the settings fields and default values.
*/
public function __construct() {
$this->init_fields();
$this->init_defaults();
}

/**
* Retrieve the instance of this class.
*
* @return Settings_Fields
*/
private static function get_instance(): Settings_Fields {
if ( ! isset( self::$instance ) ) {
self::$instance = new self();
}

return self::$instance;
}

/**
* Retrieve the default setting values
*
* @return array<string, array<string, mixed>>
*/
public static function get_default_values(): array {
return self::get_instance()->defaults;
}
static $defaults = [];

/**
* Retrieve the settings fields.
*
* @return array<string, array<string, array>>
*/
public static function get_field_definitions(): array {
return self::get_instance()->fields;
}
if ( ! empty( $defaults ) ) {
return $defaults;
}

/**
* Initialize default settings values.
*
* @return void
*/
private function init_defaults() {
$this->defaults = [
$defaults = [
'general' => [
'activate_by_default' => true,
'enable_tags' => true,
Expand Down Expand Up @@ -116,18 +60,23 @@ private function init_defaults() {
],
];

$this->defaults = apply_filters( 'code_snippets_settings_defaults', $this->defaults );
$defaults = apply_filters( 'code_snippets_settings_defaults', $defaults );
return $defaults;
}

/**
* Initialize the settings fields values.
* Retrieve the settings fields.
*
* @return void
* @return array<string, array<string, array>>
*/
private function init_fields() {
$this->fields = [];
public static function get_field_definitions(): array {
static $fields = [];

if ( ! empty( $fields ) ) {
return $fields;
}

$this->fields['debug'] = [
$fields['debug'] = [
'database_update' => [
'name' => __( 'Database Table Upgrade', 'code-snippets' ),
'type' => 'action',
Expand All @@ -141,7 +90,7 @@ private function init_fields() {
],
];

$this->fields['version-switch'] = [
$fields['version-switch'] = [
'version_switcher' => [
'name' => __( 'Switch Version', 'code-snippets' ),
'type' => 'callback',
Expand All @@ -159,7 +108,7 @@ private function init_fields() {
],
];

$this->fields['general'] = [
$fields['general'] = [
'activate_by_default' => [
'name' => __( 'Activate by Default', 'code-snippets' ),
'type' => 'checkbox',
Expand Down Expand Up @@ -201,28 +150,28 @@ private function init_fields() {
];

if ( ! code_snippets()->licensing->is_licensed() ) {
$this->fields['general']['hide_upgrade_menu'] = [
$fields['general']['hide_upgrade_menu'] = [
'name' => __( 'Hide Upgrade Notices', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Hide notices inviting you to upgrade to Code Snippets Pro.', 'code-snippets' ),
];
}

if ( ! is_multisite() || is_main_site() ) {
$this->fields['general']['complete_uninstall'] = [
$fields['general']['complete_uninstall'] = [
'name' => __( 'Complete Uninstall', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'When the plugin is deleted from the Plugins menu, also delete all snippets and plugin settings.', 'code-snippets' ),
];
}

$this->fields['general']['enable_admin_bar'] = [
$fields['general']['enable_admin_bar'] = [
'name' => __( 'Enable Admin Bar Menu', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show a Snippets menu in the admin bar for quick access to snippets.', 'code-snippets' ),
];

$this->fields['general']['admin_bar_snippet_limit'] = [
$fields['general']['admin_bar_snippet_limit'] = [
'name' => __( 'Admin Bar Snippets Per Page', 'code-snippets' ),
'type' => 'number',
'desc' => __( 'Number of snippets to show in the admin bar Active/Inactive menus before paginating.', 'code-snippets' ),
Expand All @@ -236,14 +185,14 @@ private function init_fields() {
],
];

$this->fields['general']['enable_feedback_reporter'] = [
$fields['general']['enable_feedback_reporter'] = [
'name' => __( 'Enable Feedback Reporter', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show a button on Code Snippets pages for reporting bugs, requesting features and sending feedback.', 'code-snippets' ),
'desc' => __( 'Reports include your site address, contact details and a list of active plugins, so that the team can reproduce the problem.', 'code-snippets' ),
];

$this->fields['editor'] = [
$fields['editor'] = [
'indent_with_tabs' => [
'name' => __( 'Indent With Tabs', 'code-snippets' ),
'type' => 'checkbox',
Expand Down Expand Up @@ -330,6 +279,7 @@ private function init_fields() {
],
];

$this->fields = apply_filters( 'code_snippets_settings_fields', $this->fields );
$fields = apply_filters( 'code_snippets_settings_fields', $fields );
return $fields;
}
}
103 changes: 103 additions & 0 deletions tests/unit/Settings/Settings_Early_Translation_Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace Code_Snippets\Settings;

use Code_Snippets\UnitTestCase;
use WP_Query;
use const Code_Snippets\CACHE_GROUP;

/**
* Tests that reading a setting does not translate anything.
*
* Settings are read from hooks the plugin has no say over the timing of. The
* shortcode handler reads one on the_posts, which fires for any post query,
* including one another plugin or a snippet runs while plugins are still
* loading. WordPress reports a translation requested that early as an error,
* and with debug display on the notice is printed ahead of the headers.
*
* Each test runs in its own process. The field definitions are built once and
* kept, so a read later in a shared process says nothing about a cold one.
*
* @group settings
*/
class Settings_Early_Translation_Test extends UnitTestCase {

/**
* Strings translated in the plugin's text domain during a test.
*
* @var string[]
*/
private array $translated = [];

/**
* Record a translation requested in the plugin's text domain.
*
* @param string $translation Translated text.
* @param string $text Text to translate.
* @param string $domain Text domain.
*
* @return string
*/
public function record_translation( $translation, $text, $domain ) {
if ( 'code-snippets' === $domain ) {
$this->translated[] = $text;
}

return $translation;
}

/**
* Start recording, with the cached settings cleared so the read is a cold one.
*
* @return void
*/
private function start_recording(): void {
wp_cache_delete( CACHE_KEY, CACHE_GROUP );
$this->translated = [];

add_filter( 'gettext', [ $this, 'record_translation' ], 10, 3 );
}

/**
* A setting's value can be read without translating anything.
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*
* @return void
*/
public function test_reading_a_setting_translates_nothing(): void {
$this->start_recording();

get_setting( 'general', 'disable_prism' );

$this->assertSame(
[],
$this->translated,
'reading a setting should not translate anything, as settings are read before translations may load'
);
}

/**
* A post query handled by the shortcode filter translates nothing.
*
* @runInSeparateProcess
* @preserveGlobalState disabled
*
* @return void
*/
public function test_a_post_query_translates_nothing(): void {
$post_id = $this->factory()->post->create( [ 'post_content' => 'No shortcodes in here.' ] );

$this->start_recording();

$query = new WP_Query( [ 'p' => $post_id ] );

$this->assertCount( 1, $query->posts, 'the query should return a post, or the filter has nothing to handle' );
$this->assertSame(
[],
$this->translated,
'a post query should not translate anything, as one can run while plugins are still loading'
);
}
}
Loading