Initial commit: WordPress wp-content (themes, plugins, languages)

- Theme: momentry (custom theme with REST API routes)
- Plugins: code-snippets (contains all API proxies)
- Languages: zh_TW translations
- Excludes: cache, backups, uploads, logs
This commit is contained in:
OpenCode
2026-05-29 19:07:56 +08:00
commit 09ef1f000f
6521 changed files with 867163 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
<?php
/**
* This file handles rendering the settings fields
*
* @since 2.0.0
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
/**
* Represents a single setting field
*
* @property-read string $desc Field description.
* @property-read string $label Field label.
* @property-read string $type Field type.
* @property-read string $name Setting name.
*
* @property-read int $min Minimum value (for numerical inputs).
* @property-read int $max Maximum value(for numerical inputs).
* @property-read array<string, string> $options List of options for a select or checkboxes field.
* @property-read callable $render_callback Custom function to use when rendering a callback field.
* @property-read callable $sanitize_callback Custom function to use when sanitize the setting value.
* @property-read mixed $default Default setting value.
*
* @property-read string $input_name Value of `name` HTML attribute on an input element.
* @property-read string $element_id
*/
class Setting_Field {
/**
* Input field identifier.
*
* @var string
*/
private string $field_id;
/**
* Settings section identifier.
*
* @var string
*/
private string $section;
/**
* List of possible arguments.
*
* @var array<string, mixed>
*/
private array $args = array(
'desc' => '',
'label' => '',
'min' => null,
'max' => null,
'options' => [],
);
/**
* Class constructor.
*
* @param string $section_id Settings section identifier.
* @param string $field_id Setting field identifier.
* @param array<string, mixed> $args The setting field attributes.
*/
public function __construct( string $section_id, string $field_id, array $args ) {
$this->field_id = $field_id;
$this->section = $section_id;
$this->args = array_merge( $this->args, $args );
}
/**
* Retrieve a single setting attribute.
*
* @param string $argument Attribute name.
*
* @return mixed Attribute value.
*/
public function __get( string $argument ) {
if ( 'input_name' === $argument ) {
return sprintf( '%s[%s][%s]', OPTION_NAME, $this->section, $this->field_id );
}
return $this->args[ $argument ];
}
/**
* Retrieve the saved value for this setting.
*
* @return mixed
*/
private function get_saved_value() {
return get_setting( $this->section, $this->field_id );
}
/**
* Render the setting field
*/
public function render() {
$method_name = 'render_' . $this->type . '_field';
if ( method_exists( $this, $method_name ) ) {
call_user_func( array( $this, $method_name ) );
} else {
// Error message, not necessary to translate.
printf( 'Cannot render a %s field.', esc_html( $this->type ) );
return;
}
if ( $this->desc ) {
echo '<p class="description">', wp_kses_post( $this->desc ), '</p>';
}
}
/**
* Render a callback field.
*/
public function render_callback_field() {
if ( ! is_callable( $this->render_callback ) ) {
return;
}
call_user_func( $this->render_callback, $this->args );
}
/**
* Render a single checkbox field.
*
* @param string $input_name Input name.
* @param string $label Input label.
* @param boolean $checked Whether the checkbox should be checked.
*/
private static function render_checkbox( string $input_name, string $label, bool $checked ) {
$checkbox = sprintf(
'<input type="checkbox" name="%s"%s>',
esc_attr( $input_name ),
checked( $checked, true, false )
);
$kses = [
'input' => [
'type' => [],
'name' => [],
'checked' => [],
],
];
if ( $label ) {
printf(
'<label>%s %s</label>',
wp_kses( $checkbox, $kses ),
wp_kses_post( $label )
);
} else {
echo wp_kses( $checkbox, $kses );
}
}
/**
* Render a checkbox field for a setting
*
* @return void
* @since 2.0.0
*/
public function render_checkbox_field() {
$this->render_checkbox( $this->input_name, $this->label, $this->get_saved_value() ?? false );
}
/**
* Render a checkbox field for a setting
*
* @return void
* @since 2.0.0
*/
public function render_checkboxes_field() {
$saved_value = $this->get_saved_value();
$saved_value = is_array( $saved_value ) ? $saved_value : [];
echo '<fieldset>';
printf( '<legend class="screen-reader-text"><span>%s</span></legend>', esc_html( $this->name ) );
foreach ( $this->options as $option => $label ) {
$this->render_checkbox( $this->input_name . "[$option]", $label, in_array( $option, $saved_value, true ) );
echo '<br>';
}
echo '</fieldset>';
}
/**
* Render a basic text field for an editor setting.
*
* @return void
*/
private function render_text_field() {
printf(
'<input id="%s" type="text" name="%s" value="%s" class="regular-text %s">',
esc_attr( $this->element_id ),
esc_attr( $this->input_name ),
esc_attr( $this->get_saved_value() ),
esc_attr( $this->element_id )
);
if ( $this->label ) {
echo ' ' . wp_kses_post( $this->label );
}
}
/**
* Render a number select field for an editor setting
*
* @since 2.0.0
*/
private function render_number_field() {
printf(
'<input type="number" name="%s" value="%s"',
esc_attr( $this->input_name ),
esc_attr( $this->get_saved_value() )
);
if ( is_numeric( $this->min ) ) {
printf( ' min="%d"', intval( $this->min ) );
}
if ( is_numeric( $this->max ) ) {
printf( ' max="%d"', intval( $this->max ) );
}
echo '>';
if ( $this->label ) {
echo ' ' . wp_kses_post( $this->label );
}
}
/**
* Render a number select field for an editor setting.
*
* @since 3.0.0
*/
private function render_select_field() {
$saved_value = $this->get_saved_value();
printf( '<select name="%s">', esc_attr( $this->input_name ) );
foreach ( $this->options as $option => $option_label ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $option ),
selected( $option, $saved_value, false ),
esc_html( $option_label )
);
}
echo '</select>';
}
/**
* Render a button link.
*
* @since 3.5.1
*/
private function render_action_field() {
printf(
'<button type="submit" name="%s" class="button">%s</button>',
esc_attr( $this->input_name ),
esc_html( $this->label ? $this->label : $this->name )
);
}
}

View File

@@ -0,0 +1,362 @@
<?php
/**
* Class-based version switching functionality for the Code Snippets plugin.
*
* Converted from procedural `version-switch.php` to an OO class `Version_Switch`.
*
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
// Configuration constants for version switching
const VERSION_CACHE_KEY = 'code_snippets_available_versions';
const PROGRESS_KEY = 'code_snippets_version_switch_progress';
const VERSION_CACHE_DURATION = HOUR_IN_SECONDS;
const PROGRESS_TIMEOUT = 5 * MINUTE_IN_SECONDS;
const WORDPRESS_API_ENDPOINT = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets';
class Version_Switch {
/**
* Initialize hook registrations.
* Call this after the file is required.
*/
public static function init(): void {
add_action( 'wp_ajax_code_snippets_switch_version', [ __CLASS__, 'ajax_switch_version' ] );
add_action( 'wp_ajax_code_snippets_refresh_versions', [ __CLASS__, 'ajax_refresh_versions' ] );
}
public static function get_available_versions(): array {
$versions = get_transient( VERSION_CACHE_KEY );
if ( false === $versions ) {
$response = wp_remote_get( WORDPRESS_API_ENDPOINT );
if ( is_wp_error( $response ) ) {
return [];
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( ! $data || ! isset( $data['versions'] ) ) {
return [];
}
// Filter out 'trunk' and sort versions
$versions = [];
foreach ( $data['versions'] as $version => $download_url ) {
if ( 'trunk' !== $version ) {
$versions[] = [
'version' => $version,
'url' => $download_url,
];
}
}
// Sort versions in descending order
usort( $versions, function( $a, $b ) {
return version_compare( $b['version'], $a['version'] );
});
// Cache for configured duration
set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION );
}
return $versions;
}
public static function get_current_version(): string {
return defined( 'CODE_SNIPPETS_VERSION' ) ? CODE_SNIPPETS_VERSION : '0.0.0';
}
public static function is_version_switch_in_progress(): bool {
return get_transient( PROGRESS_KEY ) !== false;
}
public static function clear_version_caches(): void {
delete_transient( VERSION_CACHE_KEY );
delete_transient( PROGRESS_KEY );
}
public static function validate_target_version( string $target_version, array $available_versions ): array {
if ( empty( $target_version ) ) {
return [
'success' => false,
'message' => __( 'No target version specified.', 'code-snippets' ),
'download_url' => '',
];
}
foreach ( $available_versions as $version_info ) {
if ( $version_info['version'] === $target_version ) {
return [
'success' => true,
'message' => '',
'download_url' => $version_info['url'],
];
}
}
return [
'success' => false,
'message' => __( 'Invalid version specified.', 'code-snippets' ),
'download_url' => '',
];
}
public static function create_error_response( string $message, string $technical_details = '' ): array {
if ( ! empty( $technical_details ) ) {
if ( function_exists( 'error_log' ) ) {
error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) );
}
}
return [
'success' => false,
'message' => $message,
];
}
public static function perform_version_install( string $download_url ) {
if ( ! function_exists( 'wp_update_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/update.php';
}
if ( ! function_exists( 'show_message' ) ) {
require_once ABSPATH . 'wp-admin/includes/misc.php';
}
if ( ! class_exists( 'Plugin_Upgrader' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
}
$update_handler = new \WP_Ajax_Upgrader_Skin();
$upgrader = new \Plugin_Upgrader( $update_handler );
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
$code_snippets_last_update_handler = $update_handler;
$code_snippets_last_upgrader = $upgrader;
return $upgrader->install( $download_url, [
'overwrite_package' => true,
'clear_update_cache' => true,
] );
}
public static function extract_handler_messages( $update_handler, $upgrader ): string {
$handler_messages = '';
if ( isset( $update_handler ) ) {
if ( method_exists( $update_handler, 'get_errors' ) ) {
$errs = $update_handler->get_errors();
if ( $errs instanceof \WP_Error && $errs->has_errors() ) {
$handler_messages .= implode( "\n", $errs->get_error_messages() );
}
}
if ( method_exists( $update_handler, 'get_error_messages' ) ) {
$em = $update_handler->get_error_messages();
if ( $em ) {
$handler_messages .= "\n" . $em;
}
}
if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) {
$upgrade_msgs = $update_handler->get_upgrade_messages();
if ( is_array( $upgrade_msgs ) ) {
$handler_messages .= "\n" . implode( "\n", $upgrade_msgs );
} elseif ( $upgrade_msgs ) {
$handler_messages .= "\n" . (string) $upgrade_msgs;
}
}
}
if ( empty( $handler_messages ) && isset( $upgrader->result ) ) {
if ( is_wp_error( $upgrader->result ) ) {
$handler_messages = implode( "\n", $upgrader->result->get_error_messages() );
} else {
$handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true );
}
}
return trim( $handler_messages );
}
public static function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void {
if ( function_exists( 'error_log' ) ) {
error_log( sprintf( 'Code Snippets version switch failed. target=%s, result=%s, details=%s', $target_version, var_export( $result, true ), $details ) );
}
}
public static function handle_installation_failure( string $target_version, string $download_url, $install_result ): array {
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
$handler_messages = self::extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader );
self::log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" );
$fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' );
if ( ! empty( $handler_messages ) ) {
$short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' );
$fallback_message = sprintf( '%s %s', $fallback_message, $short );
}
return [
'success' => false,
'message' => $fallback_message,
];
}
public static function handle_version_switch( string $target_version ): array {
if ( ! current_user_can( 'update_plugins' ) ) {
return self::create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) );
}
$available_versions = self::get_available_versions();
$validation = self::validate_target_version( $target_version, $available_versions );
if ( ! $validation['success'] ) {
return self::create_error_response( $validation['message'] );
}
if ( self::get_current_version() === $target_version ) {
return self::create_error_response( __( 'Already on the specified version.', 'code-snippets' ) );
}
set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT );
$install_result = self::perform_version_install( $validation['download_url'] );
delete_transient( PROGRESS_KEY );
if ( is_wp_error( $install_result ) ) {
return self::create_error_response( $install_result->get_error_message() );
}
if ( $install_result ) {
delete_transient( VERSION_CACHE_KEY );
return [
'success' => true,
'message' => sprintf( __( 'Successfully switched to version %s. Please refresh the page to see changes.', 'code-snippets' ), $target_version ),
];
}
return self::handle_installation_failure( $target_version, $validation['download_url'], $install_result );
}
public static function render_version_switch_field( array $args ): void {
$current_version = self::get_current_version();
$available_versions = self::get_available_versions();
$is_switching = self::is_version_switch_in_progress();
?>
<div class="code-snippets-version-switch">
<p>
<strong><?php esc_html_e( 'Current Version:', 'code-snippets' ); ?></strong>
<span class="current-version"><?php echo esc_html( $current_version ); ?></span>
</p>
<?php if ( $is_switching ) : ?>
<div class="notice notice-info inline">
<p><?php esc_html_e( 'Version switch in progress. Please wait...', 'code-snippets' ); ?></p>
</div>
<?php else : ?>
<p>
<label for="target_version">
<?php esc_html_e( 'Switch to Version:', 'code-snippets' ); ?>
</label>
<select id="target_version" name="target_version" <?php disabled( empty( $available_versions ) ); ?>>
<option value=""><?php esc_html_e( 'Select a version...', 'code-snippets' ); ?></option>
<?php foreach ( $available_versions as $version_info ) : ?>
<option value="<?php echo esc_attr( $version_info['version'] ); ?>"
<?php selected( $version_info['version'], $current_version ); ?>>
<?php echo esc_html( $version_info['version'] ); ?>
<?php if ( $version_info['version'] === $current_version ) : ?>
<?php esc_html_e( ' (Current)', 'code-snippets' ); ?>
<?php endif; ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<button type="button" id="switch-version-btn" class="button button-secondary" disabled
<?php disabled( empty( $available_versions ) ); ?>>
<?php esc_html_e( 'Switch Version', 'code-snippets' ); ?>
</button>
</p>
<div id="version-switch-result" class="notice" style="display: none;"></div>
<?php endif; ?>
</div><?php
}
public static function ajax_switch_version(): void {
if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'code_snippets_version_switch' ) ) {
wp_die( __( 'Security check failed.', 'code-snippets' ) );
}
if ( ! current_user_can( 'update_plugins' ) ) {
wp_send_json_error( [
'message' => __( 'You do not have permission to update plugins.', 'code-snippets' ),
] );
}
$target_version = sanitize_text_field( $_POST['target_version'] ?? '' );
if ( empty( $target_version ) ) {
wp_send_json_error( [
'message' => __( 'No target version specified.', 'code-snippets' ),
] );
}
$result = self::handle_version_switch( $target_version );
if ( $result['success'] ) {
wp_send_json_success( $result );
} else {
wp_send_json_error( $result );
}
}
public static function render_refresh_versions_field( array $args ): void {
?>
<button type="button" id="refresh-versions-btn" class="button button-secondary">
<?php esc_html_e( 'Refresh Available Versions', 'code-snippets' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'Check for the latest available plugin versions from WordPress.org.', 'code-snippets' ); ?>
</p><?php
}
public static function ajax_refresh_versions(): void {
if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'code_snippets_refresh_versions' ) ) {
wp_die( __( 'Security check failed.', 'code-snippets' ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( [
'message' => __( 'You do not have permission to manage options.', 'code-snippets' ),
] );
}
delete_transient( VERSION_CACHE_KEY );
self::get_available_versions();
wp_send_json_success( [
'message' => __( 'Available versions updated successfully.', 'code-snippets' ),
] );
}
public static function render_version_switch_warning(): void {
?>
<div id="version-switch-warning" class="notice notice-warning" style="display: none; margin-block-start: 20px;">
<p>
<strong><?php esc_html_e( 'Warning:', 'code-snippets' ); ?></strong>
<?php esc_html_e( 'Switching versions may cause compatibility issues. Always backup your site before switching versions.', 'code-snippets' ); ?>
</p>
</div>
<?php
}
}
// Initialize hooks when the file is loaded.
Version_Switch::init();

View File

@@ -0,0 +1,132 @@
<?php
/**
* This file handles the editor preview setting
*
* @since 2.0.0
* @package Code_Snippets
*/
namespace Code_Snippets\Settings;
use function Code_Snippets\code_snippets;
use function Code_Snippets\enqueue_code_editor;
use function Code_Snippets\get_editor_themes;
/**
* Load the CSS and JavaScript for the editor preview field
*/
function enqueue_editor_preview_assets() {
$plugin = code_snippets();
enqueue_code_editor( 'php' );
// Enqueue all editor themes.
$themes = get_editor_themes();
foreach ( $themes as $theme ) {
wp_enqueue_style(
'code-snippets-editor-theme-' . $theme,
plugins_url( "dist/editor-themes/$theme.css", $plugin->file ),
[ 'code-editor' ],
$plugin->version
);
}
// Enqueue the menu scripts.
wp_enqueue_script(
'code-snippets-settings-menu',
plugins_url( 'dist/settings.js', $plugin->file ),
[ 'code-snippets-code-editor' ],
$plugin->version,
true
);
wp_set_script_translations( 'code-snippets-settings-menu', 'code-snippets' );
// Extract the CodeMirror-specific editor settings.
$setting_fields = get_settings_fields();
$editor_fields = array();
foreach ( $setting_fields['editor'] as $name => $field ) {
if ( empty( $field['codemirror'] ) ) {
continue;
}
$editor_fields[] = array(
'name' => $name,
'type' => $field['type'],
'codemirror' => addslashes( $field['codemirror'] ),
);
}
// Pass the saved options to the external JavaScript file.
$inline_script = 'var code_snippets_editor_settings = ' . wp_json_encode( $editor_fields ) . ';';
wp_add_inline_script( 'code-snippets-settings-menu', $inline_script, 'before' );
// Provide configuration and simple i18n for the version switch JS module.
$version_switch = array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce_switch' => wp_create_nonce( 'code_snippets_version_switch' ),
'nonce_refresh' => wp_create_nonce( 'code_snippets_refresh_versions' ),
);
$strings = array(
'selectDifferent' => esc_html__( 'Please select a different version to switch to.', 'code-snippets' ),
'switching' => esc_html__( 'Switching...', 'code-snippets' ),
'processing' => esc_html__( 'Processing version switch. Please wait...', 'code-snippets' ),
'error' => esc_html__( 'An error occurred.', 'code-snippets' ),
'errorSwitch' => esc_html__( 'An error occurred while switching versions. Please try again.', 'code-snippets' ),
'refreshing' => esc_html__( 'Refreshing...', 'code-snippets' ),
'refreshed' => esc_html__( 'Refreshed!', 'code-snippets' ),
);
wp_add_inline_script( 'code-snippets-settings-menu', 'var code_snippets_version_switch = ' . wp_json_encode( $version_switch ) . '; var __code_snippets_i18n = ' . wp_json_encode( $strings ) . ';', 'before' );
}
/**
* Retrieve the list of code editor themes.
*
* @return array<string, string> List of editor themes.
*/
function get_editor_theme_list(): array {
$themes = [
'default' => __( 'Default', 'code-snippets' ),
];
foreach ( get_editor_themes() as $theme ) {
// Skip mobile themes.
if ( '-mobile' === substr( $theme, -7 ) ) {
continue;
}
$themes[ $theme ] = ucwords( str_replace( '-', ' ', $theme ) );
}
return $themes;
}
/**
* Render the editor preview setting
*/
function render_editor_preview() {
$settings = get_settings_values();
$settings = $settings['editor'];
$indent_unit = absint( $settings['indent_unit'] );
$tab_size = absint( $settings['tab_size'] );
$n_tabs = $settings['indent_with_tabs'] ? floor( $indent_unit / $tab_size ) : 0;
$n_spaces = $settings['indent_with_tabs'] ? $indent_unit % $tab_size : $indent_unit;
$indent = str_repeat( "\t", $n_tabs ) . str_repeat( ' ', $n_spaces );
$code = "add_filter( 'admin_footer_text', function ( \$text ) {\n\n" .
$indent . "\$site_name = get_bloginfo( 'name' );\n\n" .
$indent . '$text = "Thank you for visiting $site_name.";' . "\n" .
$indent . 'return $text;' . "\n" .
"} );\n";
echo '<textarea id="code_snippets_editor_preview">', esc_textarea( $code ), '</textarea>';
}

View File

@@ -0,0 +1,263 @@
<?php
/**
* Manages the settings field definitions.
*
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
use function Code_Snippets\code_snippets;
/**
* Retrieve the default setting values
*
* @return array<string, array<string, mixed>>
*/
function get_default_settings(): array {
static $defaults;
if ( isset( $defaults ) ) {
return $defaults;
}
$defaults = [
'general' => [
'activate_by_default' => true,
'enable_tags' => true,
'enable_description' => true,
'visual_editor_rows' => 5,
'list_order' => 'priority-asc',
'disable_prism' => false,
'hide_upgrade_menu' => false,
'complete_uninstall' => false,
'enable_flat_files' => false,
],
'editor' => [
'indent_with_tabs' => true,
'tab_size' => 4,
'indent_unit' => 4,
'font_size' => 14,
'wrap_lines' => true,
'code_folding' => true,
'line_numbers' => true,
'auto_close_brackets' => true,
'highlight_selection_matches' => true,
'highlight_active_line' => true,
'keymap' => 'default',
'theme' => 'default',
],
'version-switch' => [
'selected_version' => '',
],
'debug' => [
'enable_version_change' => false,
],
];
$defaults = apply_filters( 'code_snippets_settings_defaults', $defaults );
return $defaults;
}
/**
* Retrieve the settings fields
*
* @return array<string, array<string, array>>
*/
function get_settings_fields(): array {
static $fields;
if ( isset( $fields ) ) {
return $fields;
}
$fields = [];
$fields['debug'] = [
'database_update' => [
'name' => __( 'Database Table Upgrade', 'code-snippets' ),
'type' => 'action',
'label' => __( 'Upgrade Database Table', 'code-snippets' ),
'desc' => __( 'Use this button to manually upgrade the Code Snippets database table. This action will only affect the snippets table and should be used only when necessary.', 'code-snippets' ),
],
'reset_caches' => [
'name' => __( 'Reset Caches', 'code-snippets' ),
'type' => 'action',
'desc' => __( 'Use this button to manually clear snippets caches.', 'code-snippets' ),
],
'enable_version_change' => [
'name' => __( 'Version Change', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Enable the ability to switch or rollback versions of the Code Snippets core plugin.', 'code-snippets' ),
],
];
$fields['version-switch'] = [
'version_switcher' => [
'name' => __( 'Switch Version', 'code-snippets' ),
'type' => 'callback',
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_field' ],
],
'refresh_versions' => [
'name' => __( 'Refresh Versions', 'code-snippets' ),
'type' => 'callback',
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_refresh_versions_field' ],
],
'version_warning' => [
'name' => '',
'type' => 'callback',
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_warning' ],
],
];
$fields['general'] = [
'activate_by_default' => [
'name' => __( 'Activate by Default', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( "Make the 'Save and Activate' button the default action when saving a snippet.", 'code-snippets' ),
],
'enable_tags' => [
'name' => __( 'Enable Snippet Tags', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show snippet tags on admin pages.', 'code-snippets' ),
],
'enable_description' => [
'name' => __( 'Enable Snippet Descriptions', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show snippet descriptions on admin pages.', 'code-snippets' ),
],
'visual_editor_rows' => [
'name' => __( 'Description Editor Height', 'code-snippets' ),
'type' => 'number',
'label' => _x( 'rows', 'unit', 'code-snippets' ),
'min' => 0,
],
'list_order' => [
'name' => __( 'Snippets List Order', 'code-snippets' ),
'type' => 'select',
'desc' => __( 'Default way to order snippets on the All Snippets admin menu.', 'code-snippets' ),
'options' => [
'priority-asc' => __( 'Priority', 'code-snippets' ),
'name-asc' => __( 'Name (A-Z)', 'code-snippets' ),
'name-desc' => __( 'Name (Z-A)', 'code-snippets' ),
'modified-desc' => __( 'Modified (latest first)', 'code-snippets' ),
'modified-asc' => __( 'Modified (oldest first)', 'code-snippets' ),
],
],
'disable_prism' => [
'name' => __( 'Disable Syntax Highlighter', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Disable syntax highlighting when displaying snippet code on the front-end.', 'code-snippets' ),
],
];
if ( ! code_snippets()->licensing->is_licensed() ) {
$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() ) {
$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' ),
];
}
$fields['editor'] = [
'indent_with_tabs' => [
'name' => __( 'Indent With Tabs', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Use hard tabs instead of spaces for indentation.', 'code-snippets' ),
'codemirror' => 'indentWithTabs',
],
'tab_size' => [
'name' => __( 'Tab Size', 'code-snippets' ),
'type' => 'number',
'desc' => __( 'The width of a tab character.', 'code-snippets' ),
'label' => _x( 'spaces', 'unit', 'code-snippets' ),
'codemirror' => 'tabSize',
'min' => 0,
],
'indent_unit' => [
'name' => __( 'Indent Unit', 'code-snippets' ),
'type' => 'number',
'desc' => __( 'The number of spaces to indent a block.', 'code-snippets' ),
'label' => _x( 'spaces', 'unit', 'code-snippets' ),
'codemirror' => 'indentUnit',
'min' => 0,
],
'font_size' => [
'name' => __( 'Font Size', 'code-snippets' ),
'type' => 'number',
'label' => _x( 'px', 'unit', 'code-snippets' ),
'codemirror' => 'fontSize',
'min' => 8,
'max' => 28,
],
'wrap_lines' => [
'name' => __( 'Wrap Lines', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Soft-wrap long lines of code instead of horizontally scrolling.', 'code-snippets' ),
'codemirror' => 'lineWrapping',
],
'code_folding' => [
'name' => __( 'Code Folding', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Allow folding functions or other blocks into a single line.', 'code-snippets' ),
'codemirror' => 'foldGutter',
],
'line_numbers' => [
'name' => __( 'Line Numbers', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show line numbers to the left of the editor.', 'code-snippets' ),
'codemirror' => 'lineNumbers',
],
'auto_close_brackets' => [
'name' => __( 'Auto Close Brackets', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Auto-close brackets and quotes when typed.', 'code-snippets' ),
'codemirror' => 'autoCloseBrackets',
],
'highlight_selection_matches' => [
'name' => __( 'Highlight Selection Matches', 'code-snippets' ),
'label' => __( 'Highlight all instances of a currently selected word.', 'code-snippets' ),
'type' => 'checkbox',
'codemirror' => 'highlightSelectionMatches',
],
'highlight_active_line' => [
'name' => __( 'Highlight Active Line', 'code-snippets' ),
'label' => __( 'Highlight the line that is currently being edited.', 'code-snippets' ),
'type' => 'checkbox',
'codemirror' => 'styleActiveLine',
],
'keymap' => [
'name' => __( 'Keymap', 'code-snippets' ),
'type' => 'select',
'desc' => __( 'The set of keyboard shortcuts to use in the code editor.', 'code-snippets' ),
'options' => [
'default' => __( 'Default', 'code-snippets' ),
'vim' => __( 'Vim', 'code-snippets' ),
'emacs' => __( 'Emacs', 'code-snippets' ),
'sublime' => __( 'Sublime Text', 'code-snippets' ),
],
'codemirror' => 'keyMap',
],
'theme' => [
'name' => __( 'Theme', 'code-snippets' ),
'type' => 'select',
'options' => get_editor_theme_list(),
'codemirror' => 'theme',
],
];
$fields = apply_filters( 'code_snippets_settings_fields', $fields );
return $fields;
}

View File

@@ -0,0 +1,351 @@
<?php
/**
* This file registers the settings
*
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
use Code_Snippets\Welcome_API;
use function Code_Snippets\clean_snippets_cache;
use function Code_Snippets\code_snippets;
use const Code_Snippets\CACHE_GROUP;
const CACHE_KEY = 'code_snippets_settings';
const OPTION_GROUP = 'code-snippets';
const OPTION_NAME = 'code_snippets_settings';
/**
* Add a new option for either the current site or the current network
*
* @param bool $network Whether to add a network-wide option.
* @param string $option Name of option to add. Expected to not be SQL-escaped.
* @param mixed $value Option value, can be anything. Expected to not be SQL-escaped.
*
* @return bool False if the option was not added. True if the option was added.
*/
function add_self_option( bool $network, string $option, $value ): bool {
return $network ? add_site_option( $option, $value ) : add_option( $option, $value );
}
/**
* Retrieves an option value based on an option name from either the current site or the current network
*
* @param bool $network Whether to get a network-wide option.
* @param string $option Name of option to retrieve. Expected to not be SQL-escaped.
* @param mixed $default_value Optional value to return if option doesn't exist. Default false.
*
* @return mixed Value set for the option.
*/
function get_self_option( bool $network, string $option, $default_value = false ) {
return $network ? get_site_option( $option, $default_value ) : get_option( $option, $default_value );
}
/**
* Update the value of an option that was already added on the current site or the current network
*
* @param bool $network Whether to update a network-wide option.
* @param string $option Name of option. Expected to not be SQL-escaped.
* @param mixed $value Option value. Expected to not be SQL-escaped.
*
* @return bool False if value was not updated. True if value was updated.
*/
function update_self_option( bool $network, string $option, $value ): bool {
return $network ? update_site_option( $option, $value ) : update_option( $option, $value );
}
/**
* Returns 'true' if plugin settings are unified on a multisite installation
* under the Network Admin settings menu
*
* This option is controlled by the "Enable administration menus" setting on the Network Settings menu
*
* @return bool
*/
function are_settings_unified(): bool {
if ( ! is_multisite() ) {
return false;
}
$menu_perms = get_site_option( 'menu_items', array() );
return empty( $menu_perms['snippets_settings'] );
}
/**
* Retrieve the setting values from the database.
* If a setting does not exist in the database, the default value will be returned.
*
* @return array<string, array<string, mixed>>
*/
function get_settings_values(): array {
$settings = wp_cache_get( CACHE_KEY, CACHE_GROUP );
if ( $settings ) {
return $settings;
}
$settings = get_default_settings();
$saved = get_self_option( are_settings_unified(), OPTION_NAME, array() );
foreach ( $settings as $section => $fields ) {
if ( isset( $saved[ $section ] ) ) {
$settings[ $section ] = array_replace( $fields, $saved[ $section ] );
}
}
wp_cache_set( CACHE_KEY, $settings, CACHE_GROUP );
return $settings;
}
/**
* Retrieve an individual setting field value
*
* @param string $section ID of the section the setting belongs to.
* @param string $field ID of the setting field.
*
* @return mixed
*/
function get_setting( string $section, string $field ) {
$settings = get_settings_values();
return $settings[ $section ][ $field ] ?? null;
}
/**
* Update a single setting to a new value.
*
* @param string $section ID of the section the setting belongs to.
* @param string $field ID of the setting field.
* @param mixed $new_value Setting value. Expected to not be SQL-escaped.
*
* @return bool False if value was not updated. True if value was updated.
*/
function update_setting( string $section, string $field, $new_value ): bool {
$settings = get_settings_values();
$settings[ $section ][ $field ] = $new_value;
wp_cache_set( CACHE_KEY, $settings, CACHE_GROUP );
return update_self_option( are_settings_unified(), OPTION_NAME, $settings );
}
/**
* Retrieve the settings sections
*
* @return array<string, string> Settings sections.
*/
function get_settings_sections(): array {
$sections = array(
'general' => __( 'General', 'code-snippets' ),
'editor' => __( 'Code Editor', 'code-snippets' ),
'debug' => __( 'Debug', 'code-snippets' ),
);
// Only show the Version section when the debug setting to enable version changes is enabled.
$enable_version = get_setting( 'debug', 'enable_version_change' );
if ( $enable_version ) {
$sections['version-switch'] = __( 'Version', 'code-snippets' );
}
return apply_filters( 'code_snippets_settings_sections', $sections );
}
/**
* Register settings sections, fields, etc
*/
function register_plugin_settings() {
if ( are_settings_unified() ) {
if ( ! get_site_option( OPTION_NAME ) ) {
add_site_option( OPTION_NAME, get_default_settings() );
}
} elseif ( ! get_option( OPTION_NAME ) ) {
add_option( OPTION_NAME, get_default_settings() );
}
// Register the setting.
register_setting(
OPTION_GROUP,
OPTION_NAME,
[ 'sanitize_callback' => __NAMESPACE__ . '\\sanitize_settings' ]
);
// Register settings sections.
foreach ( get_settings_sections() as $section_id => $section_name ) {
add_settings_section( $section_id, $section_name, '__return_empty_string', 'code-snippets' );
}
// Register settings fields. Only register fields for sections that exist (some sections may be gated by settings).
$registered_sections = get_settings_sections();
foreach ( get_settings_fields() as $section_id => $fields ) {
if ( ! isset( $registered_sections[ $section_id ] ) ) {
continue;
}
foreach ( $fields as $field_id => $field ) {
$field_object = new Setting_Field( $section_id, $field_id, $field );
add_settings_field( $field_id, $field['name'], [ $field_object, 'render' ], 'code-snippets', $section_id );
}
}
// Add editor preview as a field.
add_settings_field(
'editor_preview',
__( 'Editor Preview', 'code-snippets' ),
__NAMESPACE__ . '\\render_editor_preview',
'code-snippets',
'editor'
);
}
add_action( 'admin_init', __NAMESPACE__ . '\\register_plugin_settings' );
/**
* Sanitize a single setting value.
*
* @param array<string, mixed> $field Setting field information.
* @param mixed $input_value User input setting value, or null if missing.
*
* @return mixed Sanitized setting value, or null if unset.
*/
function sanitize_setting_value( array $field, $input_value ) {
switch ( $field['type'] ) {
case 'checkbox':
return 'on' === $input_value;
case 'number':
return intval( $input_value );
case 'select':
$select_options = array_map( 'strval', array_keys( $field['options'] ) );
return in_array( strval( $input_value ), $select_options, true ) ? $input_value : null;
case 'checkboxes':
$results = [];
if ( ! empty( $input_value ) ) {
foreach ( $field['options'] as $option_id => $option_label ) {
if ( isset( $input_value[ $option_id ] ) && 'on' === $input_value[ $option_id ] ) {
$results[] = $option_id;
}
}
}
return $results;
case 'text':
case 'hidden':
return trim( sanitize_text_field( $input_value ) );
case 'callback':
return isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) ?
call_user_func( $field['sanitize_callback'], $input_value ) :
null;
default:
return null;
}
}
/**
* Process settings actions.
*
* @param array $input Provided settings input.
*
* @return array|null New $input value to return, or null to continue with settings update process.
*/
function process_settings_actions( array $input ): ?array {
if ( isset( $input['reset_settings'] ) ) {
add_settings_error(
OPTION_NAME,
'settings_reset',
__( 'All settings have been reset to their defaults.', 'code-snippets' ),
'updated'
);
delete_option( 'code_snippets_cloud_settings' );
return [];
}
if ( isset( $input['debug']['database_update'] ) ) {
code_snippets()->db->create_or_upgrade_tables();
add_settings_error(
OPTION_NAME,
'database_update_done',
__( 'Successfully performed database table upgrade.', 'code-snippets' ),
'updated'
);
}
if ( isset( $input['debug']['reset_caches'] ) ) {
Welcome_API::clear_cache();
clean_snippets_cache( code_snippets()->db->get_table_name( false ) );
if ( is_multisite() ) {
clean_snippets_cache( code_snippets()->db->get_table_name( true ) );
}
add_settings_error(
OPTION_NAME,
'snippet_caches_reset',
__( 'Successfully reset snippets caches.', 'code-snippets' ),
'updated'
);
}
return null;
}
/**
* Validate the settings
*
* @param array<string, array<string, mixed>> $input The received settings.
*
* @return array<string, array<string, mixed>> The validated settings.
*/
function sanitize_settings( array $input ): array {
wp_cache_delete( CACHE_KEY, CACHE_GROUP );
$result = process_settings_actions( $input );
if ( ! is_null( $result ) ) {
return $result;
}
$settings = get_settings_values();
$updated = false;
// Don't directly loop through $input as it does not include as deselected checkboxes.
foreach ( get_settings_fields() as $section_id => $fields ) {
foreach ( $fields as $field_id => $field ) {
// Fetch the corresponding input value from the posted data.
$input_value = $input[ $section_id ][ $field_id ] ?? null;
// Attempt to sanitize the setting value.
$sanitized_value = sanitize_setting_value( $field, $input_value );
$current_value = $settings[ $section_id ][ $field_id ] ?? null;
if ( ! is_null( $sanitized_value ) && $current_value !== $sanitized_value ) {
$settings[ $section_id ][ $field_id ] = $sanitized_value;
$updated = true;
}
}
}
// Add an updated message.
if ( $updated ) {
add_settings_error(
OPTION_NAME,
'settings-saved',
__( 'Settings saved.', 'code-snippets' ),
'updated'
);
do_action( 'code_snippets/settings_updated', $settings, $input );
}
return $settings;
}