Файловый менеджер - Редактировать - /home/freeclou/app.optimyar.com/front-web/build/assets/fonts/iran-yekan/beflpn/php.tar
Назад
admin-menus/class-admin-menu.php 0000755 00000011166 15105501234 0012633 0 ustar 00 <?php namespace Code_Snippets; /** * Base class for a plugin admin menu. */ abstract class Admin_Menu { /** * The snippet page short name. * * @var string */ public string $name; /** * The label shown in the admin menu. * * @var string */ public string $label; /** * The text used for the page title. * * @var string */ public string $title; /** * The base slug for the top-level admin menu. * * @var string */ protected string $base_slug; /** * The slug for this admin menu. * * @var string */ protected string $slug; /** * Constructor. * * @param string $name The snippet page short name. * @param string $label The label shown in the admin menu. * @param string $title The text used for the page title. */ public function __construct( string $name, string $label, string $title ) { $this->name = $name; $this->label = $label; $this->title = $title; $this->base_slug = code_snippets()->get_menu_slug(); $this->slug = code_snippets()->get_menu_slug( $name ); } /** * Register action and filter hooks. * * @return void */ public function run() { if ( ! code_snippets()->is_compact_menu() ) { add_action( 'admin_menu', array( $this, 'register' ) ); add_action( 'network_admin_menu', array( $this, 'register' ) ); } } /** * Add a sub-menu to the Snippets menu. * * @param string $slug Menu slug. * @param string $label Label shown in admin menu. * @param string $title Page title. * * @return void */ public function add_menu( string $slug, string $label, string $title ) { $hook = add_submenu_page( $this->base_slug, $title, $label, code_snippets()->get_cap(), $slug, array( $this, 'render' ) ); add_action( 'load-' . $hook, array( $this, 'load' ) ); } /** * Register the admin menu */ public function register() { $this->add_menu( $this->slug, $this->label, $this->title ); } /** * Render the content of a vew template * * @param string $name Name of view template to render. */ protected function render_view( string $name ) { include dirname( PLUGIN_FILE ) . '/php/views/' . $name . '.php'; } /** * Render the menu */ public function render() { $this->render_view( $this->name ); } /** * Print the status and error messages */ protected function print_messages() { // None required by default. } /** * Executed when the admin page is loaded */ public function load() { // Make sure the user has permission to be here. if ( ! current_user_can( code_snippets()->get_cap() ) ) { wp_die( esc_html__( 'You are not authorized to access this page.', 'code-snippets' ) ); } // Create the snippet tables if they are missing. $db = code_snippets()->db; if ( is_multisite() ) { $db->create_missing_table( $db->ms_table ); } $db->create_missing_table( $db->table ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } /** * Enqueue scripts and stylesheets for the admin page, if necessary */ abstract public function enqueue_assets(); /** * Generate a list of page title links for passing to React. * * @param array<string> $actions List of actions to convert into links, as array values. * * @return array<string, string> Link labels keyed to link URLs. */ public function page_title_action_links( array $actions ): array { $plugin = code_snippets(); $links = []; foreach ( $actions as $action ) { if ( 'settings' === $action && ! isset( $plugin->admin->menus['settings'] ) ) { continue; } $url = $plugin->get_menu_url( $action ); if ( isset( $_GET['type'] ) && in_array( $_GET['type'], Snippet::get_types(), true ) ) { $url = add_query_arg( 'type', sanitize_key( wp_unslash( $_GET['type'] ) ), $url ); } switch ( $action ) { case 'manage': $label = _x( 'Manage', 'snippets', 'code-snippets' ); break; case 'add': $label = _x( 'Add New', 'snippet', 'code-snippets' ); break; case 'import': $label = _x( 'Import', 'snippets', 'code-snippets' ); break; case 'settings': $label = _x( 'Settings', 'snippets', 'code-snippets' ); break; default: $label = ''; } if ( $label && $url ) { $links[ $label ] = $url; } } return $links; } /** * Render a list of links to other pages in the page title * * @param array<string> $actions List of actions to render as links, as array values. */ public function render_page_title_actions( array $actions ) { foreach ( $this->page_title_action_links( $actions ) as $label => $url ) { printf( '<a href="%s" class="page-title-action">%s</a>', esc_url( $url ), esc_html( $label ) ); } } } admin-menus/class-edit-menu.php 0000755 00000013461 15105501234 0012470 0 ustar 00 <?php namespace Code_Snippets; use function Code_Snippets\Settings\get_setting; /** * This class handles the add/edit menu. */ class Edit_Menu extends Admin_Menu { /** * Handle for JavaScript asset file. */ public const JS_HANDLE = 'code-snippets-edit-menu'; /** * Handle for CSS asset file. */ public const CSS_HANDLE = 'code-snippets-edit'; /** * The snippet object currently being edited * * @var Snippet|null * @see Edit_Menu::load_snippet_data() */ protected ?Snippet $snippet = null; /** * Constructor. * * @return void */ public function __construct() { parent::__construct( 'edit', _x( 'Edit Snippet', 'menu label', 'code-snippets' ), __( 'Edit Snippet', 'code-snippets' ) ); } /** * Register action and filter hooks. * * @return void */ public function run() { parent::run(); $this->remove_debug_bar_codemirror(); } /** * Register the admin menu * * @return void */ public function register() { parent::register(); // Only preserve the edit menu if we are currently editing a snippet. if ( ! isset( $_REQUEST['page'] ) || $_REQUEST['page'] !== $this->slug ) { remove_submenu_page( $this->base_slug, $this->slug ); } // Add New Snippet menu. $this->add_menu( code_snippets()->get_menu_slug( 'add' ), _x( 'Add New', 'menu label', 'code-snippets' ), __( 'Create New Snippet', 'code-snippets' ) ); } /** * Executed when the menu is loaded. * * @return void */ public function load() { parent::load(); $this->load_snippet_data(); $this->ensure_correct_page(); $contextual_help = new Contextual_Help( 'edit' ); $contextual_help->load(); } /** * Disallow vising the Edit Snippet page without a valid ID. * * @return void */ protected function ensure_correct_page() { $screen = get_current_screen(); $edit_hook = get_plugin_page_hookname( $this->slug, $this->base_slug ); $edit_hook .= $screen->in_admin( 'network' ) ? '-network' : ''; // Disallow visiting the edit snippet page without a valid ID. if ( $screen->base === $edit_hook && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id ) && ! isset( $_REQUEST['preview'] ) ) { wp_safe_redirect( code_snippets()->get_menu_url( 'add' ) ); exit; } } /** * Render the edit menu interface. * * @return void */ public function render() { printf( '<div id="edit-snippet-form-container">%s</div>', esc_html__( 'Loading edit page…', 'code-snippets' ) ); } /** * Load the data for the snippet currently being edited. */ public function load_snippet_data() { $edit_id = isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0; $this->snippet = get_snippet( $edit_id ); if ( 0 === $edit_id && isset( $_GET['type'] ) && sanitize_key( $_GET['type'] ) !== $this->snippet->type ) { $type = sanitize_key( $_GET['type'] ); $default_scopes = [ 'php' => 'global', 'css' => 'site-css', 'html' => 'content', 'js' => 'site-head-js', 'cond' => 'condition', ]; if ( isset( $default_scopes[ $type ] ) ) { $this->snippet->scope = $default_scopes[ $type ]; } } $this->snippet = apply_filters( 'code_snippets/admin/load_snippet_data', $this->snippet ); } /** * Enqueue assets for the edit menu * * @return void */ public function enqueue_assets() { $plugin = code_snippets(); $rtl = is_rtl() ? '-rtl' : ''; $settings = Settings\get_settings_values(); $tags_enabled = $settings['general']['enable_tags']; $desc_enabled = $settings['general']['enable_description']; enqueue_code_editor( $this->snippet->type ); wp_enqueue_style( self::CSS_HANDLE, plugins_url( "dist/edit$rtl.css", $plugin->file ), [ 'code-editor', 'wp-components', ], $plugin->version ); wp_enqueue_script( self::JS_HANDLE, plugins_url( 'dist/edit.js', $plugin->file ), [ 'code-snippets-code-editor', 'react', 'react-dom', 'wp-url', 'wp-i18n', 'wp-element', 'wp-components', ], $plugin->version, true ); wp_set_script_translations( self::JS_HANDLE, 'code-snippets' ); if ( $desc_enabled ) { remove_editor_styles(); wp_enqueue_editor(); } $plugin->localize_script( self::JS_HANDLE ); wp_localize_script( self::JS_HANDLE, 'CODE_SNIPPETS_EDIT', [ 'snippet' => $this->snippet->get_fields(), 'pageTitleActions' => $plugin->is_compact_menu() ? $this->page_title_action_links( [ 'manage', 'import', 'settings' ] ) : [], 'isPreview' => isset( $_REQUEST['preview'] ), 'activateByDefault' => get_setting( 'general', 'activate_by_default' ), 'editorTheme' => get_setting( 'editor', 'theme' ), 'enableDownloads' => apply_filters( 'code_snippets/enable_downloads', true ), 'enableDescription' => $desc_enabled, 'hideUpsell' => get_setting( 'general', 'hide_upgrade_menu' ), 'tagOptions' => apply_filters( 'code_snippets/tag_editor_options', [ 'enabled' => $tags_enabled, 'allowSpaces' => true, 'availableTags' => $tags_enabled ? get_all_snippet_tags() : [], ] ), 'descEditorOptions' => [ 'rows' => $settings['general']['visual_editor_rows'], ], ] ); } /** * Remove the old CodeMirror version used by the Debug Bar Console plugin that is messing up the snippet editor. */ public function remove_debug_bar_codemirror() { // Try to discern if we are on the single snippet page as good as we can at this early time. $is_codemirror_page = is_admin() && 'admin.php' === $GLOBALS['pagenow'] && isset( $_GET['page'] ) && ( code_snippets()->get_menu_slug( 'edit' ) === $_GET['page'] || code_snippets()->get_menu_slug( 'settings' ) === $_GET['page'] ); if ( $is_codemirror_page ) { remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' ); } } } admin-menus/class-import-menu.php 0000755 00000010560 15105501234 0013052 0 ustar 00 <?php namespace Code_Snippets; /** * This class handles the import admin menu. * * @since 2.4.0 * @package Code_Snippets */ class Import_Menu extends Admin_Menu { /** * Class constructor */ public function __construct() { parent::__construct( 'import', _x( 'Import', 'menu label', 'code-snippets' ), __( 'Import Snippets', 'code-snippets' ) ); } /** * Register action and filter hooks */ public function run() { parent::run(); add_action( 'admin_init', array( $this, 'register_importer' ) ); add_action( 'load-importer-code-snippets', array( $this, 'load' ) ); } /** * Executed when the menu is loaded */ public function load() { parent::load(); $contextual_help = new Contextual_Help( 'import' ); $contextual_help->load(); $this->process_import_files(); } /** * Process the uploaded import files */ private function process_import_files() { // Ensure the import file exists. if ( ! isset( $_FILES['code_snippets_import_files']['name'], $_FILES['code_snippets_import_files']['type'], $_FILES['code_snippets_import_files']['tmp_name'] ) ) { return; } check_admin_referer( 'import_code_snippets_file' ); // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $upload_files = $_FILES['code_snippets_import_files']['tmp_name']; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $upload_filenames = $_FILES['code_snippets_import_files']['name']; $upload_mime_types = array_map( 'sanitize_mime_type', wp_unslash( $_FILES['code_snippets_import_files']['type'] ) ); $count = 0; $network = is_network_admin(); $error = false; $dup_action = isset( $_POST['duplicate_action'] ) ? sanitize_key( $_POST['duplicate_action'] ) : 'ignore'; // Loop through the uploaded files and import the snippets. foreach ( $upload_files as $i => $import_file ) { $filename_info = pathinfo( $upload_filenames[ $i ] ); $ext = $filename_info['extension']; $mime_type = $upload_mime_types[ $i ]; $import = new Import( $import_file, $network, $dup_action ); if ( 'json' === $ext || 'application/json' === $mime_type ) { $result = $import->import_json(); } elseif ( 'xml' === $ext || 'text/xml' === $mime_type ) { $result = $import->import_xml(); } else { $result = false; } if ( false === $result ) { $error = true; } else { $count += count( $result ); } } // Send the amount of imported snippets to the page. $url = add_query_arg( $error ? array( 'error' => true ) : array( 'imported' => $count ) ); wp_safe_redirect( esc_url_raw( $url ) ); exit; } /** * Add the importer to the Tools > Import menu */ public function register_importer() { /* Only register the importer if the current user can manage snippets */ if ( ! defined( 'WP_LOAD_IMPORTERS' ) || ! code_snippets()->current_user_can() ) { return; } /* Register the Code Snippets importer with WordPress */ register_importer( 'code-snippets', __( 'Code Snippets', 'code-snippets' ), __( 'Import snippets from a code snippets export file', 'code-snippets' ), array( $this, 'render' ) ); } /** * Print the status and error messages */ protected function print_messages() { if ( ! empty( $_REQUEST['error'] ) ) { echo '<div id="message" class="error fade"><p>'; esc_html_e( 'An error occurred when processing the import files.', 'code-snippets' ); echo '</p></div>'; } if ( isset( $_REQUEST['imported'] ) ) { echo '<div id="message" class="updated fade"><p>'; $imported = intval( $_REQUEST['imported'] ); if ( 0 === $imported ) { esc_html_e( 'No snippets were imported.', 'code-snippets' ); } else { /* translators: %d: amount of snippets imported */ printf( _n( 'Successfully imported %d snippet.', 'Successfully imported %d snippets.', $imported, 'code-snippets' ), '<strong>' . number_format_i18n( $imported ) . '</strong>', ); printf( ' <a href="%s">%s</a>', esc_url( code_snippets()->get_menu_url( 'manage' ) ), esc_html__( 'Have fun!', 'code-snippets' ) ); } echo '</p></div>'; } } /** * Empty implementation for enqueue_assets. * * @return void */ public function enqueue_assets() { // none required. } } admin-menus/class-manage-menu.php 0000755 00000020534 15105501234 0012772 0 ustar 00 <?php namespace Code_Snippets; use Code_Snippets\Cloud\Cloud_Search_List_Table; use function Code_Snippets\Settings\get_setting; /** * This class handles the manage snippets menu * * @since 2.4.0 * @package Code_Snippets */ class Manage_Menu extends Admin_Menu { /** * Instance of the list table class. * * @var List_Table */ public List_Table $list_table; /** * Instance of the cloud list table class for search results. * * @var Cloud_Search_List_Table */ public Cloud_Search_List_Table $cloud_search_list_table; /** * Class constructor */ public function __construct() { parent::__construct( 'manage', _x( 'All Snippets', 'menu label', 'code-snippets' ), __( 'Snippets', 'code-snippets' ) ); } /** * Register action and filter hooks */ public function run() { parent::run(); if ( code_snippets()->is_compact_menu() ) { add_action( 'admin_menu', array( $this, 'register_compact_menu' ), 2 ); add_action( 'network_admin_menu', array( $this, 'register_compact_menu' ), 2 ); } add_action( 'admin_menu', array( $this, 'register_upgrade_menu' ), 500 ); add_filter( 'set-screen-option', array( $this, 'save_screen_option' ), 10, 3 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_menu_css' ] ); add_action( 'wp_ajax_update_code_snippet', array( $this, 'ajax_callback' ) ); } /** * Register the top-level 'Snippets' menu and associated 'Manage' subpage */ public function register() { add_menu_page( __( 'Snippets', 'code-snippets' ), _x( 'Snippets', 'top-level menu label', 'code-snippets' ), code_snippets()->get_cap(), code_snippets()->get_menu_slug(), array( $this, 'render' ), 'none', // Added through CSS as a mask to prevent loading 'blinking'. apply_filters( 'code_snippets/admin/menu_position', is_network_admin() ? 21 : 67 ) ); // Register the sub-menu. parent::register(); } /** * Register the 'upgrade' menu item. * * @return void */ public function register_upgrade_menu() { if ( code_snippets()->licensing->is_licensed() || get_setting( 'general', 'hide_upgrade_menu' ) ) { return; } $menu_title = sprintf( '<span class="button button-primary code-snippets-upgrade-button">%s %s</span>', _x( 'Go Pro', 'top-level menu label', 'code-snippets' ), '<span class="dashicons dashicons-external"></span>' ); $hook = add_submenu_page( code_snippets()->get_menu_slug(), __( 'Upgrade to Pro', 'code-snippets' ), $menu_title, code_snippets()->get_cap(), 'code_snippets_upgrade', '__return_empty_string', 100 ); add_action( "load-$hook", [ $this, 'load_upgrade_menu' ] ); } /** * Print CSS required for the admin menu icon. * * @return void */ public function enqueue_menu_css() { wp_enqueue_style( 'code-snippets-menu', plugins_url( 'dist/menu.css', PLUGIN_FILE ), [], PLUGIN_VERSION ); } /** * Redirect the user upon opening the upgrade menu. * * @return void */ public function load_upgrade_menu() { wp_safe_redirect( 'https://snipco.de/JE2f' ); exit; } /** * Add menu pages for the compact menu */ public function register_compact_menu() { if ( ! code_snippets()->is_compact_menu() ) { return; } $sub = code_snippets()->get_menu_slug( isset( $_GET['sub'] ) ? sanitize_key( $_GET['sub'] ) : 'snippets' ); $classmap = array( 'snippets' => 'manage', 'add-snippet' => 'edit', 'edit-snippet' => 'edit', 'import-code-snippets' => 'import', 'snippets-settings' => 'settings', ); $menus = code_snippets()->admin->menus; $class = isset( $classmap[ $sub ], $menus[ $classmap[ $sub ] ] ) ? $menus[ $classmap[ $sub ] ] : $this; /* Add a submenu to the Tools menu */ $hook = add_submenu_page( 'tools.php', __( 'Snippets', 'code-snippets' ), _x( 'Snippets', 'tools submenu label', 'code-snippets' ), code_snippets()->get_cap(), code_snippets()->get_menu_slug(), array( $class, 'render' ) ); add_action( 'load-' . $hook, array( $class, 'load' ) ); } /** * Executed when the admin page is loaded */ public function load() { parent::load(); $contextual_help = new Contextual_Help( 'manage' ); $contextual_help->load(); $this->cloud_search_list_table = new Cloud_Search_List_Table(); $this->cloud_search_list_table->prepare_items(); $this->list_table = new List_Table(); $this->list_table->prepare_items(); } /** * Enqueue scripts and stylesheets for the admin page. */ public function enqueue_assets() { $plugin = code_snippets(); $rtl = is_rtl() ? '-rtl' : ''; wp_enqueue_style( 'code-snippets-manage', plugins_url( "dist/manage$rtl.css", $plugin->file ), [], $plugin->version ); wp_enqueue_script( 'code-snippets-manage-js', plugins_url( 'dist/manage.js', $plugin->file ), [ 'wp-i18n' ], $plugin->version, true ); wp_set_script_translations( 'code-snippets-manage-js', 'code-snippets' ); if ( 'cloud' === $this->get_current_type() || 'cloud_search' === $this->get_current_type() ) { Front_End::enqueue_all_prism_themes(); } } /** * Get the currently displayed snippet type. * * @return string */ protected function get_current_type(): string { $types = Plugin::get_types(); $current_type = isset( $_GET['type'] ) ? sanitize_key( wp_unslash( $_GET['type'] ) ) : 'all'; return isset( $types[ $current_type ] ) ? $current_type : 'all'; } /** * Print the status and error messages * * @return void */ protected function print_messages() { $this->render_view( 'partials/list-table-notices' ); } /** * Handles saving the user's snippets per page preference * * @param mixed $status Current screen option status. * @param string $option The screen option name. * @param mixed $value Screen option value. * * @return mixed */ public function save_screen_option( $status, string $option, $value ) { return 'snippets_per_page' === $option ? $value : $status; } /** * Update the priority value for a snippet. * * @param Snippet $snippet Snippet to update. * * @return void */ private function update_snippet_priority( Snippet $snippet ) { global $wpdb; $table = code_snippets()->db->get_table_name( $snippet->network ); $wpdb->update( $table, array( 'priority' => $snippet->priority ), array( 'id' => $snippet->id ), array( '%d' ), array( '%d' ) ); clean_snippets_cache( $table ); } /** * Handle AJAX requests */ public function ajax_callback() { check_ajax_referer( 'code_snippets_manage_ajax' ); if ( ! isset( $_POST['field'], $_POST['snippet'] ) ) { wp_send_json_error( array( 'type' => 'param_error', 'message' => 'incomplete request', ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $snippet_data = array_map( 'sanitize_text_field', json_decode( wp_unslash( $_POST['snippet'] ), true ) ); $snippet = new Snippet( $snippet_data ); $field = sanitize_key( $_POST['field'] ); if ( 'priority' === $field ) { if ( ! isset( $snippet_data['priority'] ) || ! is_numeric( $snippet_data['priority'] ) ) { wp_send_json_error( array( 'type' => 'param_error', 'message' => 'missing snippet priority data', ) ); } $this->update_snippet_priority( $snippet ); } elseif ( 'active' === $field ) { if ( ! isset( $snippet_data['active'] ) ) { wp_send_json_error( array( 'type' => 'param_error', 'message' => 'missing snippet active data', ) ); } if ( $snippet->shared_network ) { $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); if ( in_array( $snippet->id, $active_shared_snippets, true ) !== $snippet->active ) { $active_shared_snippets = $snippet->active ? array_merge( $active_shared_snippets, array( $snippet->id ) ) : array_diff( $active_shared_snippets, array( $snippet->id ) ); update_option( 'active_shared_network_snippets', $active_shared_snippets ); clean_active_snippets_cache( code_snippets()->db->ms_table ); } } elseif ( $snippet->active ) { $result = activate_snippet( $snippet->id, $snippet->network ); if ( is_string( $result ) ) { wp_send_json_error( array( 'type' => 'action_error', 'message' => $result, ) ); } } else { deactivate_snippet( $snippet->id, $snippet->network ); } } wp_send_json_success(); } } admin-menus/class-settings-menu.php 0000755 00000013646 15105501234 0013410 0 ustar 00 <?php namespace Code_Snippets; use const Code_Snippets\Settings\CACHE_KEY; use const Code_Snippets\Settings\OPTION_GROUP; use const Code_Snippets\Settings\OPTION_NAME; /** * This class handles the settings admin menu * * @since 2.4.0 * @package Code_Snippets */ class Settings_Menu extends Admin_Menu { /** * Settings page name as registered with the Settings API. */ public const SETTINGS_PAGE = 'code-snippets'; /** * Constructor */ public function __construct() { parent::__construct( 'settings', _x( 'Settings', 'menu label', 'code-snippets' ), __( 'Snippets Settings', 'code-snippets' ) ); } /** * Executed when the admin page is loaded */ public function load() { parent::load(); if ( is_network_admin() ) { if ( Settings\are_settings_unified() ) { $this->update_network_options(); } else { wp_safe_redirect( code_snippets()->get_menu_url( 'settings', 'admin' ) ); exit; } } } /** * Enqueue the stylesheet for the settings menu */ public function enqueue_assets() { $plugin = code_snippets(); Settings\enqueue_editor_preview_assets(); wp_enqueue_style( 'code-snippets-settings', plugins_url( 'dist/settings.css', $plugin->file ), [ 'code-editor' ], $plugin->version ); } /** * Retrieve the list of settings sections. * * @return array<string, array<string, mixed>> */ private function get_sections(): array { global $wp_settings_sections; if ( ! isset( $wp_settings_sections[ self::SETTINGS_PAGE ] ) ) { return array(); } return (array) $wp_settings_sections[ self::SETTINGS_PAGE ]; } /** * Retrieve the name of the settings section currently being viewed. * * @param string $default_section Name of the default tab displayed. * * @return string */ public function get_current_section( string $default_section = 'general' ): string { $sections = $this->get_sections(); if ( ! $sections ) { return $default_section; } $active_tab = isset( $_REQUEST['section'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['section'] ) ) : $default_section; return isset( $sections[ $active_tab ] ) ? $active_tab : $default_section; } /** * Render the admin screen */ public function render() { $update_url = is_network_admin() ? add_query_arg( 'update_site_option', true ) : admin_url( 'options.php' ); $current_section = $this->get_current_section(); ?> <div class="code-snippets-settings wrap" data-active-tab="<?php echo esc_attr( $current_section ); ?>"> <h1> <?php esc_html_e( 'Settings', 'code-snippets' ); if ( code_snippets()->is_compact_menu() ) { $actions = [ _x( 'Manage', 'snippets', 'code-snippets' ) => code_snippets()->get_menu_url(), _x( 'Add New', 'snippet', 'code-snippets' ) => code_snippets()->get_menu_url( 'add' ), _X( 'Import', 'snippets', 'code-snippets' ) => code_snippets()->get_menu_url( 'import' ), ]; foreach ( $actions as $label => $url ) { printf( '<a href="%s" class="page-title-action">%s</a>', esc_url( $url ), esc_html( $label ) ); } } ?> </h1> <?php settings_errors( OPTION_NAME ); ?> <form action="<?php echo esc_url( $update_url ); ?>" method="post"> <input type="hidden" name="section" value="<?php echo esc_attr( $current_section ); ?>"> <?php settings_fields( OPTION_GROUP ); $this->do_settings_tabs(); ?> <p class="submit"> <?php submit_button( null, 'primary', 'submit', false ); submit_button( __( 'Reset to Default', 'code-snippets' ), 'secondary', sprintf( '%s[%s]', OPTION_NAME, 'reset_settings' ), false ); ?> </p> </form> </div> <?php } /** * Output snippet settings in tabs */ protected function do_settings_tabs() { $sections = $this->get_sections(); $active_tab = $this->get_current_section(); echo '<h2 class="nav-tab-wrapper" id="settings-sections-tabs">'; foreach ( $sections as $section ) { printf( '<a class="nav-tab%s" data-section="%s" href="%s">%s</a>', esc_attr( $active_tab ) === $section['id'] ? ' nav-tab-active' : '', esc_attr( $section['id'] ), esc_url( add_query_arg( 'section', $section['id'] ) ), esc_html( $section['title'] ) ); } echo '</h2>'; foreach ( $sections as $section ) { if ( 'license' === $section['id'] ) { continue; } if ( $section['title'] ) { printf( '<h2 id="%s-settings" class="settings-section-title">%s</h2>' . "\n", esc_attr( $section['id'] ), esc_html( $section['title'] ) ); } if ( $section['callback'] ) { call_user_func( $section['callback'], $section ); } printf( '<div class="settings-section %s-settings"><table class="form-table">', esc_attr( $section['id'] ) ); do_settings_fields( self::SETTINGS_PAGE, $section['id'] ); echo '</table></div>'; } } /** * Fill in for the Settings API in the Network Admin */ public function update_network_options() { // Ensure the settings have been saved. if ( empty( $_GET['update_site_option'] ) || empty( $_POST[ OPTION_NAME ] ) ) { return; } check_admin_referer( 'code-snippets-options' ); // Retrieve the saved options and save them to the database. $value = map_deep( wp_unslash( $_POST[ OPTION_NAME ] ), 'sanitize_key' ); update_site_option( OPTION_NAME, $value ); wp_cache_delete( CACHE_KEY ); // Add an updated notice. if ( ! count( get_settings_errors() ) ) { add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'code-snippets' ), 'updated' ); } set_transient( 'settings_errors', get_settings_errors(), 30 ); // Redirect back to the settings menu. $redirect = add_query_arg( 'settings-updated', 'true', remove_query_arg( 'update_site_option', wp_get_referer() ) ); wp_safe_redirect( esc_url_raw( $redirect ) ); exit; } /** * Empty implementation for print_messages. * * @return void */ protected function print_messages() { // none required. } } admin-menus/class-welcome-menu.php 0000755 00000003531 15105501234 0013173 0 ustar 00 <?php namespace Code_Snippets; /** * This class handles the welcome menu. * * @since 3.7.0 * @package Code_Snippets */ class Welcome_Menu extends Admin_Menu { /** * Instance of Welcome_API class. * * @var Welcome_API */ protected Welcome_API $api; /** * Class constructor * * @param Welcome_API $api Instance of API class. */ public function __construct( $api ) { parent::__construct( 'welcome', _x( "What's New", 'menu label', 'code-snippets' ), __( 'Welcome to Code Snippets', 'code-snippets' ) ); $this->api = $api; } /** * Enqueue assets necessary for the welcome menu. * * @return void */ public function enqueue_assets() { wp_enqueue_style( 'code-snippets-welcome', plugins_url( 'dist/welcome.css', PLUGIN_FILE ), [], PLUGIN_VERSION ); } /** * Retrieve a list of links to display in the page header. * * @return array<string, array{url: string, icon: string, label: string}> */ protected function get_header_links(): array { $links = [ 'cloud' => [ 'url' => 'https://codesnippets.cloud', 'icon' => 'cloud', 'label' => __( 'Cloud', 'code-snippets' ), ], 'resources' => [ 'url' => 'https://help.codesnippets.pro/', 'icon' => 'sos', 'label' => __( 'Support', 'code-snippets' ), ], 'facebook' => [ 'url' => 'https://www.facebook.com/groups/282962095661875/', 'icon' => 'facebook', 'label' => __( 'Community', 'code-snippets' ), ], 'discord' => [ 'url' => 'https://snipco.de/discord', 'icon' => 'discord', 'label' => __( 'Discord', 'code-snippets' ), ], ]; if ( ! code_snippets()->licensing->is_licensed() ) { $links['pro'] = [ 'url' => 'https://codesnippets.pro/pricing/', 'icon' => 'cart', 'label' => __( 'Upgrade to Pro', 'code-snippets' ), ]; } return $links; } } cloud/class-cloud-api.php 0000755 00000033445 15105501234 0011353 0 ustar 00 <?php namespace Code_Snippets\Cloud; use Code_Snippets\Snippet; use WP_Error; use function Code_Snippets\get_snippet_by_cloud_id; use function Code_Snippets\get_snippets; use function Code_Snippets\save_snippet; use function Code_Snippets\update_snippet_fields; /** * Functions used to manage cloud synchronisation. * * @package Code_Snippets */ class Cloud_API { /** * Key used to access the local-to-cloud map transient data. */ private const CLOUD_MAP_TRANSIENT_KEY = 'cs_local_to_cloud_map'; /** * Days to cache data retrieved from API. */ private const DAYS_TO_STORE_CS = 1; /** * Token used for public API access. * * @var string */ private const CLOUD_SEARCH_API_TOKEN = 'csc-1a2b3c4d5e6f7g8h9i0j'; /** * Cached list of cloud links. * * @var Cloud_Link[]|null */ private ?array $cached_cloud_links = null; /** * 'Private' status code. */ public const STATUS_PRIVATE = 3; /** * 'Public' status code. */ public const STATUS_PUBLIC = 4; /** * 'Public' status code. */ public const STATUS_UNVERIFIED = 5; /** * 'AI Verified' status code. */ public const STATUS_AI_VERIFIED = 6; /** * 'Pro Verified' status code. */ public const STATUS_PRO_VERIFIED = 8; /** * Retrieve the Cloud URL from wp-config or fallback to default. * * @return string * * @noinspection PhpUndefinedConstantInspection */ public static function get_cloud_url(): string { return defined( 'CS_CLOUD_URL' ) ? CS_CLOUD_URL : 'https://codesnippets.cloud/'; } /** * Retrieve the Cloud API URL from wp-config or fallback to default. * * @return string * * @noinspection PhpUndefinedConstantInspection */ public static function get_cloud_api_url(): string { return defined( 'CS_CLOUD_API_URL' ) ? CS_CLOUD_API_URL : self::get_cloud_url() . 'api/v1/'; } /** * Retrieve the cloud local token. * * @return string */ public static function get_local_token(): string { return self::CLOUD_SEARCH_API_TOKEN; } /** * Check that the cloud key is valid and verified. * * @return boolean */ public static function is_cloud_key_verified(): bool { return false; } /** * Check if the API key is set and verified. * * @return boolean */ public static function is_cloud_connection_available(): bool { return false; } /** * Create local-to-cloud map to keep track of local snippets that have been synced to the cloud. * * @return Cloud_Link[] */ private function get_cloud_links(): ?array { // Return the cached data if available. if ( is_array( $this->cached_cloud_links ) ) { return $this->cached_cloud_links; } // Fetch data from the stored transient, if available. $transient_data = get_transient( self::CLOUD_MAP_TRANSIENT_KEY ); if ( is_array( $transient_data ) ) { $this->cached_cloud_links = $transient_data; return $this->cached_cloud_links; } // Otherwise, regenerate the local-to-cloud-map. $this->cached_cloud_links = []; // Fetch and iterate through all local snippets to create the map. foreach ( get_snippets() as $local_snippet ) { // Skip snippets that are only stored locally. if ( ! $local_snippet->cloud_id ) { continue; } $link = new Cloud_Link(); $cloud_id_owner = $this->get_cloud_id_and_ownership( $local_snippet->cloud_id ); $cloud_id_int = intval( $cloud_id_owner['cloud_id'] ); $link->local_id = $local_snippet->id; $link->cloud_id = $cloud_id_int; $link->is_owner = $cloud_id_owner['is_owner']; // Check if cloud id exists in cloud_id_rev array - this shows if the snippet is in the codevault. $link->in_codevault = $cloud_id_rev[ $cloud_id_int ] ?? false; // Get the cloud snippet revision if in codevault get from cloud_id_rev array otherwise get from cloud. if ( $link->in_codevault ) { $cloud_snippet_revision = $cloud_id_rev[ $cloud_id_int ] ?? $this->get_cloud_snippet_revision( $local_snippet->cloud_id ); $link->update_available = $local_snippet->revision < $cloud_snippet_revision; } $this->cached_cloud_links[] = $link; } set_transient( self::CLOUD_MAP_TRANSIENT_KEY, $this->cached_cloud_links, DAY_IN_SECONDS * self::DAYS_TO_STORE_CS ); return $this->cached_cloud_links; } /** * Get ownership and Cloud ID of a snippet. * * @param string $cloud_id Cloud ID. * * @return array<string, mixed> */ public function get_cloud_id_and_ownership( string $cloud_id ): array { $cloud_id_owner = explode( '_', $cloud_id ); return [ 'cloud_id' => (int) $cloud_id_owner[0] ?? '', 'is_owner' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1], 'is_owner_string' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1] ? '1' : '0', ]; } /** * Unpack JSON data from a request response. * * @param array|WP_Error $response Response from wp_request_*. * * @return array<string, mixed>|null Associative array of JSON data on success, null on failure. */ private static function unpack_request_json( $response ): ?array { $body = wp_remote_retrieve_body( $response ); return $body ? json_decode( $body, true ) : null; } /** * Search Code Snippets Cloud -> Static Function * * @param string $search_method Search by name of codevault or keyword(s). * @param string $search Search query. * @param integer $page Search result page to retrieve. Defaults to '0'. * * @return Cloud_Snippets Result of search query. */ public static function fetch_search_results( string $search_method, string $search, int $page = 0 ): Cloud_Snippets { $api_url = add_query_arg( [ 's_method' => $search_method, 's' => $search, 'page' => $page, 'site_token' => self::get_local_token(), 'site_host' => wp_parse_url( get_site_url(), PHP_URL_HOST ), ], self::get_cloud_api_url() . 'public/search' ); $results = self::unpack_request_json( wp_remote_get( $api_url ) ); $results = new Cloud_Snippets( $results ); $results->page = $page; return $results; } /** * Add a new link item to the local-to-cloud map. * * @param Cloud_Link $link Link to add. * * @return void */ public function add_cloud_link( Cloud_Link $link ) { $local_to_cloud_map = get_transient( self::CLOUD_MAP_TRANSIENT_KEY ); $local_to_cloud_map[] = $link; set_transient( self::CLOUD_MAP_TRANSIENT_KEY, $local_to_cloud_map, DAY_IN_SECONDS * self::DAYS_TO_STORE_CS ); } /** * Delete a snippet from local-to-cloud map. * * @param int $snippet_id Local snippet ID. * * @return void */ public function delete_snippet_from_transient_data( int $snippet_id ) { if ( ! $this->cached_cloud_links ) { $this->get_cloud_links(); } foreach ( $this->cached_cloud_links as $link ) { if ( $link->local_id === $snippet_id ) { // Remove the link from the local_to_cloud_map. $index = array_search( $link, $this->cached_cloud_links, true ); unset( $this->cached_cloud_links[ $index ] ); // Update the transient data. set_transient( self::CLOUD_MAP_TRANSIENT_KEY, $this->cached_cloud_links, DAY_IN_SECONDS * self::DAYS_TO_STORE_CS ); } } } /** * Retrieve a single cloud snippet from the API. * * @param int $cloud_id Remote cloud snippet ID. * * @return Cloud_Snippet Retrieved snippet. */ public static function get_single_snippet_from_cloud( int $cloud_id ): Cloud_Snippet { $url = self::get_cloud_api_url() . sprintf( 'public/getsnippet/%s', $cloud_id ); $response = wp_remote_get( $url ); $cloud_snippet = self::unpack_request_json( $response ); return new Cloud_Snippet( $cloud_snippet['snippet'] ); } /** * Get the current revision of a single cloud snippet. * * @param string $cloud_id Cloud snippet ID. * * @return string|null Revision number on success, null otherwise. */ public static function get_cloud_snippet_revision( string $cloud_id ): ?string { $api_url = self::get_cloud_api_url() . sprintf( 'public/getsnippetrevision/%s', $cloud_id ); $body = wp_remote_retrieve_body( wp_remote_get( $api_url ) ); if ( ! $body ) { return null; } $cloud_snippet_revision = json_decode( $body, true ); return $cloud_snippet_revision['snippet_revision'] ?? null; } /** * Download a snippet from the cloud. * * @param int|string $cloud_id The cloud ID of the snippet as string from query args. * @param string $source Unused in Core. * @param string $action The action to be performed: 'download' or 'update'. * * @return array<string, string|bool> Result of operation: an array with `success` and `error_message` keys. * * @noinspection PhpUnusedParameterInspection */ public function download_or_update_snippet( int $cloud_id, string $source, string $action ): array { $cloud_id = intval( $cloud_id ); $snippet_to_store = $this->get_single_snippet_from_cloud( $cloud_id ); switch ( $action ) { case 'download': return $this->download_snippet_from_cloud( $snippet_to_store ); case 'update': return $this->update_snippet_from_cloud( $snippet_to_store ); default: return [ 'success' => false, 'error' => __( 'Invalid action.', 'code-snippets' ), ]; } } /** * Download a snippet from the cloud. * * @param Cloud_Snippet $snippet_to_store The snippet to be downloaded. * * @return array The result of the download. */ public function download_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array { $snippet = new Snippet( $snippet_to_store ); // Set the snippet id to 0 to ensure that the snippet is saved as a new snippet. $ownership = $snippet_to_store->is_owner ? '1' : '0'; $snippet->id = 0; $snippet->active = 0; $snippet->cloud_id = $snippet_to_store->id . '_' . $ownership; $snippet->desc = $snippet_to_store->description ? $snippet_to_store->description : ''; // Save the snippet to the database. $new_snippet = save_snippet( $snippet ); $link = new Cloud_Link(); $link->local_id = $new_snippet->id; $link->cloud_id = $snippet_to_store->id; $link->is_owner = $snippet_to_store->is_owner; $link->in_codevault = false; $link->update_available = false; $this->add_cloud_link( $link ); return [ 'success' => true, 'action' => 'Single Downloaded', 'snippet_id' => $new_snippet->id, 'link_id' => $link->cloud_id, ]; } /** * Update a snippet from the cloud. * * @param Cloud_Snippet $snippet_to_store Snippet to be updated. * * @return array The result of the update. */ public function update_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array { $cloud_id = $snippet_to_store->id . '_' . ( $snippet_to_store->is_owner ? '1' : '0' ); $local_snippet = get_snippet_by_cloud_id( sanitize_key( $cloud_id ) ); // Only update the code, active and revision fields. $fields = [ 'code' => $snippet_to_store->code, 'active' => false, 'revision' => $snippet_to_store->revision, ]; update_snippet_fields( $local_snippet->id, $fields ); $this->clear_caches(); return [ 'success' => true, 'action' => __( 'Updated', 'code-snippets' ), ]; } /** * Find the cloud link for a given cloud snippet identifier. * * @param int $cloud_id Cloud ID. * * @return Cloud_Link|null */ public function get_link_for_cloud_id( int $cloud_id ): ?Cloud_Link { $cloud_links = $this->get_cloud_links(); if ( $cloud_links ) { foreach ( $cloud_links as $cloud_link ) { if ( $cloud_link->cloud_id === $cloud_id ) { return $cloud_link; } } } return null; } /** * Find the cloud link for a given cloud snippet. * * @param Cloud_Snippet $cloud_snippet Cloud snippet. * * @return Cloud_Link|null */ public function get_link_for_cloud_snippet( Cloud_Snippet $cloud_snippet ): ?Cloud_Link { return $this->get_link_for_cloud_id( $cloud_snippet->id ); } /** * Translate a snippet scope to a type. * * @param string $scope The scope of the snippet. * * @return string The type of the snippet. */ public static function get_type_from_scope( string $scope ): string { switch ( $scope ) { case 'global': return 'php'; case 'site-css': return 'css'; case 'site-footer-js': return 'js'; case 'content': return 'html'; default: return ''; } } /** * Get the label for a given cloud status. * * @param int $status Cloud status code. * * @return string The label for the status. */ public static function get_status_label( int $status ): string { $labels = [ self::STATUS_PRIVATE => __( 'Private', 'code-snippets' ), self::STATUS_PUBLIC => __( 'Public', 'code-snippets' ), self::STATUS_UNVERIFIED => __( 'Unverified', 'code-snippets' ), self::STATUS_AI_VERIFIED => __( 'AI Verified', 'code-snippets' ), self::STATUS_PRO_VERIFIED => __( 'Pro Verified', 'code-snippets' ), ]; return $labels[ $status ] ?? __( 'Unknown', 'code-snippets' ); } /** * Get the badge class for a given cloud status. * * @param int $status Cloud status code. * * @return string */ public static function get_status_badge( int $status ): string { $badge_names = [ self::STATUS_PRIVATE => 'private', self::STATUS_PUBLIC => 'public', self::STATUS_UNVERIFIED => 'failure', self::STATUS_AI_VERIFIED => 'success', self::STATUS_PRO_VERIFIED => 'info', ]; return $badge_names[ $status ] ?? 'neutral'; } /** * Renders the html for the preview thickbox popup. * * @return void */ public static function render_cloud_snippet_thickbox() { add_thickbox(); ?> <div id="show-code-preview" style="display: none;"> <h3 id="snippet-name-thickbox"></h3> <h4><?php esc_html_e( 'Snippet Code:', 'code-snippets' ); ?></h4> <pre class="thickbox-code-viewer"> <code id="snippet-code-thickbox"></code> </pre> </div> <?php } /** * Refresh the cached synced data. * * @return void */ public function clear_caches() { $this->cached_cloud_links = null; delete_transient( self::CLOUD_MAP_TRANSIENT_KEY ); } } cloud/class-cloud-link.php 0000755 00000003071 15105501234 0011527 0 ustar 00 <?php namespace Code_Snippets\Cloud; use Code_Snippets\Data_Item; /** * A connection between a local snippet and remote cloud snippet. * * @package Code_Snippets * * @property integer $local_id ID of local snippet as stored in WordPress database, if applicable. * @property integer $cloud_id ID of remote snippet on cloud platform, if applicable. * @property boolean $is_owner Ownership status of remote snippet on cloud platform. * @property boolean $in_codevault Whether the remote snippet is stored in the users' codevault. * @property boolean $update_available If synchronised, whether there is an update available on the cloud platform. */ class Cloud_Link extends Data_Item { /** * Constructor function * * @param array<string, mixed>|object $data Initial data fields. */ public function __construct( $data = null ) { parent::__construct( [ 'local_id' => 0, 'cloud_id' => 0, 'is_owner' => false, 'in_codevault' => false, 'update_available' => false, ], $data ); } /** * Prepare a value before it is stored. * * @param mixed $value Value to prepare. * @param string $field Field name. * * @return mixed Value in the correct format. */ protected function prepare_field( $value, string $field ) { switch ( $field ) { case 'local_id': case 'remote_id': return absint( $value ); case 'is_owner': case 'in_codevault': case 'update_available': return is_bool( $value ) ? $value : (bool) $value; default: return $value; } } } cloud/class-cloud-search-list-table.php 0000755 00000026644 15105501234 0014110 0 ustar 00 <?php /** * Contains the class for handling the cloud search results table * * @package Code_Snippets */ namespace Code_Snippets\Cloud; use WP_Plugin_Install_List_Table; use function Code_Snippets\code_snippets; if ( ! class_exists( 'WP_Plugin_Install_List_Table' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-plugin-install-list-table.php'; } /** * Class for handling the cloud search results table. * * @property string $_pagination Pagination HTML. * * @package Code_Snippets */ class Cloud_Search_List_Table extends WP_Plugin_Install_List_Table { /** * Instance of Cloud API class. * * @var Cloud_API */ protected Cloud_API $cloud_api; /** * Items for the cloud list table. * * @var Cloud_Snippets */ protected Cloud_Snippets $cloud_snippets; /** * Class constructor. */ public function __construct() { /** * Declare global variable due to undeclared warning. * * @noinspection PhpUnusedLocalVariableInspection */ global $tab; parent::__construct( [ 'singular' => 'cloud-snippet', 'plural' => 'cloud-snippets', 'ajax' => false, ] ); // Strip the result query arg from the URL. $_SERVER['REQUEST_URI'] = remove_query_arg( [ 'result' ] ); $this->cloud_api = code_snippets()->cloud_api; } /** * Prepare items for the table. * * @return void */ public function prepare_items() { $this->cloud_snippets = $this->fetch_snippets(); $this->items = $this->cloud_snippets->snippets; $this->process_actions(); $this->set_pagination_args( [ 'per_page' => count( $this->cloud_snippets->snippets ), 'total_items' => $this->cloud_snippets->total_snippets, 'total_pages' => $this->cloud_snippets->total_pages, ] ); } /** * Process any actions that have been submitted, such as downloading cloud snippets to the local database. * * @return void */ public function process_actions() { $_SERVER['REQUEST_URI'] = remove_query_arg( [ 'action', 'snippet', '_wpnonce', 'source', 'cloud-bundle-run', 'cloud-bundle-show', 'bundle_share_name', 'cloud_bundles' ] ); // Check request is coming form the cloud search page. if ( isset( $_REQUEST['type'] ) && 'cloud_search' === $_REQUEST['type'] ) { if ( isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) { cloud_lts_process_download_action( sanitize_key( wp_unslash( $_REQUEST['action'] ) ), sanitize_key( wp_unslash( $_REQUEST['source'] ) ), sanitize_key( wp_unslash( $_REQUEST['snippet'] ) ) ); } } } /** * Output table rows. * * @return void */ public function display_rows() { $status_descriptions = [ Cloud_API::STATUS_PUBLIC => __( 'Snippet has passed basic review.', 'code-snippets' ), Cloud_API::STATUS_AI_VERIFIED => __( 'Snippet has been tested by our AI bot.', 'code-snippets' ), Cloud_API::STATUS_UNVERIFIED => __( 'Snippet has not undergone any review yet.', 'code-snippets' ), ]; /** * The current table item. * * @var $item Cloud_Snippet */ foreach ( $this->items as $item ) { ?> <div class="plugin-card cloud-search-card plugin-card-<?php echo esc_attr( $item->id ); ?>"> <?php cloud_lts_display_column_hidden_input( 'code', $item ); cloud_lts_display_column_hidden_input( 'name', $item ); ?> <div class="plugin-card-top"> <div class="name column-name"> <h3> <?php $link = code_snippets()->cloud_api->get_link_for_cloud_snippet( $item ); if ( $link ) { printf( '<a href="%s">', esc_url( code_snippets()->get_snippet_edit_url( $link->local_id ) ) ); } else { printf( '<a href="%s" title="%s" class="cloud-snippet-preview thickbox" data-snippet="%s" data-lang="%s">', '#TB_inline?&width=700&height=500&inlineId=show-code-preview', esc_attr__( 'Preview this snippet', 'code-snippets' ), esc_attr( $item->id ), esc_attr( Cloud_API::get_type_from_scope( $item->scope ) ) ); } echo esc_html( $item->name ); // Grab first tag in array of tags. $category = count( $item->tags ) > 0 ? strtolower( esc_attr( $item->tags[0] ) ) : 'general'; printf( '<img src="%s" class="plugin-icon" alt="%s">', esc_url( "https://codesnippets.cloud/images/plugin-icons/$category-logo.png" ), esc_attr( $category ) ); echo '</a>'; ?> </h3> </div> <div class="action-links"> <ul class="plugin-action-buttons"> <?php cloud_lts_render_action_buttons( $item, 'search' ); ?> </ul> </div> <div class="desc column-description"> <p><?php echo wp_kses_post( $this->process_description( $item->description ) ); ?></p> <p class="authors"> <cite> <?php printf( '%s <a target="_blank" href="%s">%s</a>', esc_html__( 'Codevault:', 'code-snippets' ), esc_url( sprintf( 'https://codesnippets.cloud/codevault/%s', $item->codevault ) ), esc_html( $item->codevault ) ); ?> </cite> </p> </div> </div> <div class="plugin-card-bottom cloud-search-card-bottom"> <div class="vers column-rating voted-info"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="thumbs-up"> <path stroke-linecap="round" stroke-linejoin="round" d="M6.633 10.5c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75A2.25 2.25 0 0116.5 4.5c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23H5.904M14.25 9h2.25M5.904 18.75c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 01-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 10.203 4.167 9.75 5 9.75h1.053c.472 0 .745.556.5.96a8.958 8.958 0 00-1.302 4.665c0 1.194.232 2.333.654 3.375z"></path> </svg> <span class="num-ratings" aria-hidden="true"> <?php // translators: 1: number of votes. $votes_text = _nx( '%d time', '%d times', $item->vote_count, 'vote count', 'code-snippets' ); $votes_text = sprintf( $votes_text, number_format_i18n( $item->vote_count ) ); // translators: 1: number of users. $users_text = _n( '%d user', '%d users', $item->total_votes, 'code-snippets' ); $users_text = sprintf( $users_text, number_format_i18n( $item->total_votes ) ); // translators: 1: number of votes with label, 2: number of users with label. echo esc_html( sprintf( _x( '%1$s by %2$s', 'votes', 'code-snippets' ), $votes_text, $users_text ) ); ?> </span> </div> <div class="column-updated"> <strong><?php esc_html_e( 'Last Updated:', 'code-snippets' ); ?></strong> <?php // translators: %s: Human-readable time difference. echo esc_html( sprintf( __( '%s ago', 'code-snippets' ), human_time_diff( strtotime( $item->updated ) ) ) ); ?> </div> <div class="column-downloaded"> <div class="badge <?php echo esc_attr( $this->cloud_api->get_status_badge( $item->status ) ); ?>-badge tooltip tooltip-block tooltip-end"> <?php echo esc_html( $this->cloud_api->get_status_label( $item->status ) ); if ( isset( $status_descriptions[ $item->status ] ) ) { echo '<span class="dashicons dashicons-info-outline"></span>'; printf( '<div class="tooltip-content">%s</div>', esc_html( $status_descriptions[ $item->status ] ) ); } ?> </div> </div> <div class="column-compatibility"> <strong><?php esc_html_e( 'WP Compatibility:', 'code-snippets' ); ?></strong> <?php if ( empty( $wp_tested ) ) { printf( '<span class="compatibility-untested">%s</span>', esc_html__( 'Not indicated by author', 'code-snippets' ) ); } else { printf( '<span class="compatibility-compatible">%s</span>', // translators: %s: tested status. esc_html( sprintf( __( 'Author states %s', 'code-snippets' ), $wp_tested ) ) ); } ?> </div> </div> </div> <?php } } /** * Process the description text - limit to 150 characters. * * @param string|null $description Description as provided by the API. * * @return string formatted description string max 150 chars. */ protected function process_description( ?string $description ): string { $description = wp_strip_all_tags( $description ); return strlen( $description ) > 150 ? substr( $description, 0, 150 ) . '…' : $description; } /** * Text displayed when no snippet data is available. * * @return void */ public function no_items() { if ( ! empty( $_REQUEST['cloud_search'] ) && count( $this->cloud_snippets->snippets ) < 1 ) { echo '<p class="no-results">', esc_html__( 'No snippets or codevault could be found with that search term. Please try again.', 'code-snippets' ), '</p>'; } else { echo '<p>', esc_html__( 'Please enter a term to start searching code snippets in the cloud.', 'code-snippets' ), '</p>'; } } /** * Fetch the snippets used to populate the table. * * @return Cloud_Snippets */ public function fetch_snippets(): Cloud_Snippets { // Check if search term has been entered. if ( isset( $_REQUEST['type'], $_REQUEST['cloud_search'], $_REQUEST['cloud_select'] ) && 'cloud_search' === sanitize_key( wp_unslash( $_REQUEST['type'] ) ) ) { // If we have a search query, then send a search request to cloud server API search endpoint. $search_query = sanitize_text_field( wp_unslash( $_REQUEST['cloud_search'] ) ); $search_by = sanitize_text_field( wp_unslash( $_REQUEST['cloud_select'] ) ); return Cloud_API::fetch_search_results( $search_by, $search_query, $this->get_pagenum() - 1 ); } // If no search results, then return empty object. return new Cloud_Snippets(); } /** * Gets the current search result page number. * * @return integer */ public function get_pagenum(): int { $page = isset( $_REQUEST['search_page'] ) ? absint( $_REQUEST['search_page'] ) : 0; if ( isset( $this->_pagination_args['total_pages'] ) && $page > $this->_pagination_args['total_pages'] ) { $page = $this->_pagination_args['total_pages']; } return max( 1, $page ); } /** * Display the table. * * @return void */ public function display() { Cloud_API::render_cloud_snippet_thickbox(); parent::display(); } /** * Displays the pagination. * * @param string $which Context where the pagination will be displayed. * * @return void */ protected function pagination( $which ) { $total_items = $this->_pagination_args['total_items']; $total_pages = $this->_pagination_args['total_pages']; $pagenum = $this->get_pagenum(); if ( 'top' === $which && $total_pages > 1 ) { $this->screen->render_screen_reader_content( 'heading_pagination' ); } $paginate = cloud_lts_pagination( $which, 'search', $total_items, $total_pages, $pagenum ); $page_class = $paginate['page_class']; $output = $paginate['output']; $this->_pagination = "<div class='tablenav-pages$page_class'>$output</div>"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->_pagination; // echo wp_kses_post( $this->_pagination ); TODO: This removes the top input box for page number. } } cloud/class-cloud-snippet.php 0000755 00000005146 15105501234 0012261 0 ustar 00 <?php namespace Code_Snippets\Cloud; use Code_Snippets\Data_Item; use function Code_Snippets\code_snippets_build_tags_array; /** * A snippet object as retrieved from the cloud API. * * @since 3.4.0 * @package Code_Snippets * * @property int $id The remote ID. * @property string $name The snippet title. * @property string $description The formatted description. * @property string $code The executable code. * @property array<string> $tags An array of the tags. * @property string $scope The scope name. * @property string $codevault Name of user codevault. * @property string $total_votes The total number of votes. * @property string $vote_count The number of actual votes. * @property string $wp_tested Tested with WP version. * @property string $status Snippet Status ID. * @property string $created The date and time when the snippet data was first created, in ISO format. * @property string $updated When the snippet was last updated, in ISO format. * @property integer $revision The update revision number. * @property bool $is_owner If user is owner or author of snippet. */ class Cloud_Snippet extends Data_Item { /** * Constructor function. * * @param array<string, mixed>|null $initial_data Initial snippet data. */ public function __construct( ?array $initial_data = null ) { parent::__construct( [ 'id' => '', 'cloud_id' => '', 'name' => '', 'description' => '', 'code' => '', 'tags' => [], 'scope' => '', 'status' => '', 'codevault' => '', 'total_votes' => '', 'vote_count' => '', 'wp_tested' => '', 'created' => '', 'updated' => '', 'revision' => 0, 'is_owner' => false, 'shared_network' => false, ], $initial_data ); } /** * Prepare a value before it is stored. * * @param mixed $value Value to prepare. * @param string $field Field name. * * @return mixed Value in the correct format. */ protected function prepare_field( $value, string $field ) { switch ( $field ) { case 'id': case 'revision': return absint( $value ); case 'is_owner': return (bool) $value; case 'description': return ( null === $value ) ? '' : $value; case 'tags': return code_snippets_build_tags_array( $value ); default: return $value; } } } cloud/class-cloud-snippets.php 0000755 00000004227 15105501234 0012443 0 ustar 00 <?php namespace Code_Snippets\Cloud; use Code_Snippets\Data_Item; /** * A list of snippets as retrieved from the cloud API. * * @since 3.4.0 * @package Code_Snippets * * @property Cloud_Snippet[] $snippets List of snippet items for the current page. * @property integer $page Page of data that this data belongs to. * @property integer $total_pages Total number of available pages of items. * @property integer $total_snippets Total number of available snippet items. * @property array $cloud_id_rev An array of all cloud snippet IDs and their revision numbers. * @property bool $success If the request has any results. */ class Cloud_Snippets extends Data_Item { /** * Class constructor. * * @param array<string, Cloud_Snippet[]|integer> $initial_data Initial data. */ public function __construct( $initial_data = null ) { parent::__construct( [ 'snippets' => [], 'total_snippets' => 0, 'total_pages' => 0, 'page' => 0, 'cloud_id_rev' => [], ], $initial_data, [ 'items' => 'snippets', 'total_items' => 'total_snippets', 'page' => 'page', 'cloud_id_rev' => 'cloud_id_rev', ] ); } /** * Prepare a value before it is stored. * * @param mixed $value Value to prepare. * @param string $field Field name. * * @return mixed Value in the correct format. */ protected function prepare_field( $value, string $field ) { switch ( $field ) { case 'page': case 'total_pages': case 'total_snippets': return absint( $value ); default: return $value; } } /** * Prepare the `snippets` field by ensuring it is a list of Cloud_Snippets objects. * * @param mixed $snippets The field as provided. * * @return Cloud_Snippets[] The field in the correct format. */ protected function prepare_snippets( $snippets ): array { $result = []; $snippets = is_array( $snippets ) ? $snippets : [ $snippets ]; foreach ( $snippets as $snippet ) { $result[] = $snippet instanceof Cloud_Snippet ? $snippet : new Cloud_Snippet( $snippet ); } return $result; } } cloud/list-table-shared-ops.php 0000755 00000017572 15105501234 0012501 0 ustar 00 <?php /** * Functions to perform snippet operations * * @package Code_Snippets */ namespace Code_Snippets\Cloud; use function Code_Snippets\code_snippets; /** * Display a hidden input field for a certain column and snippet value. * * @param string $column_name Column name. * @param Cloud_Snippet $snippet Column item. * * @return void */ function cloud_lts_display_column_hidden_input( string $column_name, Cloud_Snippet $snippet ) { printf( '<input id="cloud-snippet-%s-%s" class="cloud-snippet-item" type="hidden" name="%s" value="%s" />', esc_attr( $column_name ), esc_attr( $snippet->id ), esc_attr( $column_name ), esc_attr( $snippet->$column_name ) ); } /** * Process the download snippet action * * @param string $action Action - 'download' or 'update'. * @param string $source Source - 'search' or 'cloud'. * @param string $snippet Snippet ID. * * @return void */ function cloud_lts_process_download_action( string $action, string $source, string $snippet ) { if ( 'download' === $action || 'update' === $action ) { $result = code_snippets()->cloud_api->download_or_update_snippet( $snippet, $source, $action ); if ( $result['success'] ) { $redirect_uri = $result['snippet_id'] ? code_snippets()->get_snippet_edit_url( (int) $result['snippet_id'] ) : add_query_arg( 'result', $result['action'] ); wp_safe_redirect( esc_url_raw( $redirect_uri ) ); exit; } } } /** * Build action links for snippet. * * @param Cloud_Snippet $cloud_snippet Snippet/Column item. * @param string $source Source - 'search' or 'codevault'. * * @return void */ function cloud_lts_render_action_buttons( Cloud_Snippet $cloud_snippet, string $source ) { $lang = Cloud_API::get_type_from_scope( $cloud_snippet->scope ); $link = code_snippets()->cloud_api->get_link_for_cloud_snippet( $cloud_snippet ); $is_licensed = code_snippets()->licensing->is_licensed(); $download = $is_licensed || ! in_array( $lang, [ 'css', 'js' ], true ); if ( $link ) { if ( $is_licensed && $link->update_available ) { $update_url = add_query_arg( [ 'action' => 'update', 'snippet' => $cloud_snippet->id, 'source' => $source, ] ); printf( '<li><a class="button button-primary" href="%s">%s</a></li>', esc_url( $update_url ), esc_html__( 'Update Available', 'code-snippets' ) ); } else { printf( '<li><a class="button" href="%s">%s</a></li>', esc_url( code_snippets()->get_snippet_edit_url( $link->local_id ) ), esc_html__( 'View', 'code-snippets' ) ); } return; } if ( $download ) { $download_url = add_query_arg( [ 'action' => 'download', 'snippet' => $cloud_snippet->id, 'source' => $source, ] ); printf( '<li><a class="button button-primary" href="%s">%s</a></li>', esc_url( $download_url ), esc_html__( 'Download', 'code-snippets' ) ); } else { printf( '<li><span class="%s">%s <span class="tooltip-content">%s</span></span></li>', 'button button-primary button-disabled tooltip tooltip-block tooltip-end', esc_html__( 'Download', 'code-snippets' ), esc_html__( 'This snippet type is only available in Code Snippets Pro', 'code-snippets' ) ); } printf( '<li><a href="%s" aria-label="%s" class="%s" data-snippet="%s" data-lang="%s">%s</a></li>', '#TB_inline?&width=700&height=500&inlineId=show-code-preview', esc_attr( $cloud_snippet->name ), 'cloud-snippet-preview thickbox', esc_attr( $cloud_snippet->id ), esc_attr( $lang ), esc_html__( 'Preview', 'code-snippets' ) ); } /** * Build the pagination functionality * * @param string $which Context where the pagination will be displayed. * @param string $source Source - 'search' or 'cloud'. * @param int $total_items Total number of items. * @param int $total_pages Total number of pages. * @param int $pagenum Current page number. * * @return array */ function cloud_lts_pagination( string $which, string $source, int $total_items, int $total_pages, int $pagenum ): array { /* translators: %s: Number of items. */ $num = sprintf( _n( '%s item', '%s items', $total_items, 'code-snippets' ), number_format_i18n( $total_items ) ); $output = '<span class="displaying-num">' . $num . '</span>'; $current = isset( $_REQUEST['cloud_page'] ) ? (int) $_REQUEST['cloud_page'] : $pagenum; $current_url = remove_query_arg( wp_removable_query_args() ) . '#' . $source; $page_links = array(); $html_current_page = ''; $total_pages_before = '<span class="paging-input">'; $total_pages_after = '</span></span>'; $disable_first = false; $disable_last = false; $disable_prev = false; $disable_next = false; if ( 1 === $current ) { $disable_first = true; $disable_prev = true; } if ( $total_pages === $current ) { $disable_last = true; $disable_next = true; } if ( $disable_first ) { $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>'; } else { $page_links[] = sprintf( '<a class="first-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">«</span></a>', esc_url( remove_query_arg( $source . '_page', $current_url ) ), esc_html__( 'First page', 'code-snippets' ) ); } if ( $disable_prev ) { $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>'; } else { $page_links[] = sprintf( '<a class="prev-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">‹</span></a>', esc_url( add_query_arg( $source . '_page', max( 1, $current - 1 ), $current_url ) ), esc_html__( 'Previous page', 'code-snippets' ) ); } if ( 'bottom' === $which ) { $html_current_page = $current; $total_pages_before = sprintf( '<span class="screen-reader-text">%s</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">', __( 'Current page', 'code-snippets' ) ); } if ( 'top' === $which ) { $html_current_page = sprintf( '<label for="current-page-selector" class="screen-reader-text">%s</label><input class="current-page-selector" id="current-page-selector" type="text" name="%s_page" value="%s" size="%d" aria-describedby="table-paging" /><span class="tablenav-paging-text">', __( 'Current page', 'code-snippets' ), $source, $current, strlen( $total_pages ) ); } $html_total_pages = sprintf( '<span class="total-pages">%s</span>', number_format_i18n( $total_pages ) ); /* translators: 1: Current page, 2: Total pages. */ $current_html = _x( '%1$s of %2$s', 'paging', 'code-snippets' ); $page_links[] = $total_pages_before . sprintf( $current_html, $html_current_page, $html_total_pages ) . $total_pages_after; if ( $disable_next ) { $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>'; } else { $page_links[] = sprintf( '<a class="next-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">%s</span></a>', esc_url( add_query_arg( $source . '_page', min( $total_pages, $current + 1 ), $current_url ) ), esc_html__( 'Next page', 'code-snippets' ), '›' ); } if ( $disable_last ) { $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">»</span>'; } else { $page_links[] = sprintf( '<a class="last-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">%s</span></a>', esc_url( add_query_arg( $source . '_page', $total_pages, $current_url ) ), esc_html__( 'Last page', 'code-snippets' ), '»' ); } $pagination_links_class = 'pagination-links'; if ( ! empty( $infinite_scroll ) ) { $pagination_links_class .= ' hide-if-js'; } $output .= "\n<span class='$pagination_links_class'>" . implode( "\n", $page_links ) . '</span>'; return [ 'output' => $output, 'page_class' => $total_pages ? ( $total_pages < 2 ? ' one-page' : '' ) : ' no-pages', ]; } evaluation/class-evaluate-content.php 0000755 00000006367 15105501234 0014020 0 ustar 00 <?php namespace Evaluation; use Code_Snippets\DB; use Code_Snippets\Snippet; use Code_Snippets\Settings; use Code_Snippets\Snippet_Files; use function Code_Snippets\code_snippets; /** * Class for evaluating content snippets. * * @package Code_Snippets */ class Evaluate_Content { /** * Database class. * * @var DB */ private DB $db; /** * Cached list of active snippets. * * @var ?Snippet[] */ private ?array $active_snippets = null; /** * Class constructor. * * @param DB $db Database class instance. */ public function __construct( DB $db ) { $this->db = $db; add_action( 'init', array( $this, 'init' ) ); } /** * Initialise class functions. */ public function init() { if ( Snippet_Files::is_active() ) { add_action( 'wp_head', [ $this, 'load_head_content_from_flat_files' ] ); add_action( 'wp_footer', [ $this, 'load_footer_content_from_flat_files' ] ); } else { add_action( 'wp_head', [ $this, 'load_head_content' ] ); add_action( 'wp_footer', [ $this, 'load_footer_content' ] ); } } /** * Print snippet code fetched from the database from a certain scope. * * @param string $scope Name of scope to print. */ private function print_content_snippets( string $scope ) { $scopes = [ 'head-content', 'footer-content' ]; if ( is_null( $this->active_snippets ) ) { $this->active_snippets = $this->db->fetch_active_snippets( $scopes ); } foreach ( $this->active_snippets as $snippet ) { if ( $scope === $snippet['scope'] ) { // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo "\n", $snippet['code'], "\n"; } } } /** * Print head content snippets. */ public function load_head_content() { $this->print_content_snippets( 'head-content' ); } /** * Print footer content snippets. */ public function load_footer_content() { $this->print_content_snippets( 'footer-content' ); } public function load_head_content_from_flat_files() { $this->load_content_snippets_from_flat_files( 'head-content' ); } public function load_footer_content_from_flat_files() { $this->load_content_snippets_from_flat_files( 'footer-content' ); } private function populate_active_snippets_from_flat_files() { $handler = code_snippets()->snippet_handler_registry->get_handler( 'html' ); $dir_name = $handler->get_dir_name(); $ext = $handler->get_file_extension(); $scopes = [ 'head-content', 'footer-content' ]; $all_snippets = Snippet_Files::get_active_snippets_from_flat_files( $scopes, $dir_name ); foreach ( $all_snippets as $snippet ) { $scope = $snippet['scope']; // Add file path information to the snippet for later use $table_name = Snippet_Files::get_hashed_table_name( $snippet['table'] ); $base_path = Snippet_Files::get_base_dir( $table_name, $dir_name ); $snippet['file_path'] = $base_path . '/' . $snippet['id'] . '.' . $ext; $this->active_snippets[ $scope ][] = $snippet; } } private function load_content_snippets_from_flat_files( string $scope ) { if ( is_null( $this->active_snippets ) ) { $this->populate_active_snippets_from_flat_files(); } if ( ! isset( $this->active_snippets[ $scope ] ) ) { return; } foreach ( $this->active_snippets[ $scope ] as $snippet ) { require_once $snippet['file_path']; } } } evaluation/class-evaluate-functions.php 0000755 00000013473 15105501234 0014352 0 ustar 00 <?php namespace Evaluation; use Code_Snippets\DB; use Code_Snippets\REST_API\Snippets_REST_Controller; use Code_Snippets\Settings; use Code_Snippets\Snippet_Files; use function Code_Snippets\clean_active_snippets_cache; use function Code_Snippets\clean_snippets_cache; use function Code_Snippets\execute_snippet; use function Code_Snippets\code_snippets; use function Code_Snippets\execute_snippet_from_flat_file; /** * Class for evaluating functions snippets. * * @package Code_Snippets */ class Evaluate_Functions { /** * Database class. * * @var DB */ private DB $db; /** * Class constructor. * * @param DB $db Database class instance. */ public function __construct( DB $db ) { $this->db = $db; add_action( 'plugins_loaded', [ $this, 'evaluate_early' ], 1 ); } /** * Retrieve details about the currently edited snippet, if any. * * @return ?array{id: int, table: string} */ private function get_currently_editing_snippet(): ?array { if ( wp_is_json_request() && ! empty( $_SERVER['REQUEST_URI'] ) ) { $url = wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); if ( isset( $url['path'] ) && false !== strpos( $url['path'], Snippets_REST_Controller::get_prefixed_base_route() ) ) { $path_parts = explode( '/', $url['path'] ); $edit_id = intval( end( $path_parts ) ); if ( ! empty( $url['query'] ) ) { wp_parse_str( $url['query'], $path_params ); $edit_table = isset( $path_params['network'] ) && rest_sanitize_boolean( $path_params['network'] ) ? $this->db->ms_table : $this->db->table; } return [ 'id' => $edit_id, 'table' => $edit_table ?? $this->db->table, ]; } } return null; } /** * Check if the plugin is running in safe mode. * * @return bool * * @noinspection PhpUndefinedConstantInspection */ public function is_safe_mode_active(): bool { return ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) || ! apply_filters( 'code_snippets/execute_snippets', true ); } /** * Quickly deactivate a snippet with minimal overhead. * * @param int $snippet_id ID of the snippet to deactivate. * @param string $table_name Name of the table where the snippet is stored. * * @return void */ private function quick_deactivate_snippet( int $snippet_id, string $table_name ) { global $wpdb; $active_shared_ids = get_option( 'active_shared_network_snippets', [] ); $active_shared_ids = is_array( $active_shared_ids ) ? array_map( 'intval', $active_shared_ids ) : []; if ( $table_name === $this->db->ms_table && in_array( $snippet_id, $active_shared_ids, true ) ) { unset( $active_shared_ids[ array_search( $snippet_id, $active_shared_ids, true ) ] ); $active_shared_ids = array_values( $active_shared_ids ); update_option( 'active_shared_network_snippets', $active_shared_ids ); clean_active_snippets_cache( $table_name ); } else { $wpdb->update( $table_name, [ 'active' => '0' ], [ 'id' => $snippet_id ], [ '%d' ], [ '%d' ] ); clean_snippets_cache( $table_name ); $network = $table_name === $this->db->ms_table; do_action( 'code_snippets/deactivate_snippet', $snippet_id, $network ); } } private function evaluate_snippet_flat_file( array $snippet, string $file_path, ?array $edit_snippet = null ) { $snippet_id = $snippet['id']; $code = $snippet['code']; $table_name = $snippet['table']; // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { $this->quick_deactivate_snippet( $snippet_id, $table_name ); } if ( ! is_null( $edit_snippet ) && $edit_snippet['id'] === $snippet_id && $edit_snippet['table'] === $table_name ) { return; } if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) ) { execute_snippet_from_flat_file( $code, $file_path, $snippet_id ); } } /** * Evaluate applicable active snippets as early as possible. * * @return bool True if snippets were evaluated, false if safe mode is active. */ public function evaluate_early(): bool { if ( $this->is_safe_mode_active() ) { return false; } if ( Snippet_Files::is_active() ) { return $this->evaluate_file_snippets(); } return $this->evaluate_db_snippets(); } public function evaluate_db_snippets(): bool { $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ]; $active_snippets = $this->db->fetch_active_snippets( $scopes ); $edit_snippet = $this->get_currently_editing_snippet(); foreach ( $active_snippets as $snippet ) { $snippet_id = $snippet['id']; $code = $snippet['code']; $table_name = $snippet['table']; // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { $this->quick_deactivate_snippet( $snippet_id, $table_name ); } if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) && ( is_null( $edit_snippet ) || $edit_snippet['id'] !== $snippet_id || $edit_snippet['table'] !== $table_name ) ) { execute_snippet( $code, $snippet_id ); } } return true; } private function evaluate_file_snippets(): bool { $type = 'php'; $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ]; $snippets = Snippet_Files::get_active_snippets_from_flat_files( $scopes, $type ); $edit_snippet = $this->get_currently_editing_snippet(); foreach ( $snippets as $snippet ) { $table_name = Snippet_Files::get_hashed_table_name( $snippet['table'] ); $base_path = Snippet_Files::get_base_dir( $table_name, $type ); $file = $base_path . '/' . $snippet['id'] . '.' . $type; $this->evaluate_snippet_flat_file( $snippet, $file, $edit_snippet ); } return true; } } export/class-export-attachment.php 0000755 00000003147 15105501234 0013354 0 ustar 00 <?php namespace Code_Snippets; /** * Handles exporting snippets from the site to a downloadable file over HTTP. * * @package Code_Snippets */ class Export_Attachment extends Export { /** * Set up the current page to act like a downloadable file instead of being shown in the browser * * @param string $language File format. Used for file extension. * @param string $mime_type File MIME type. Used for Content-Type header. */ private function do_headers( string $language, string $mime_type = 'text/plain' ) { header( 'Content-Disposition: attachment; filename=' . sanitize_file_name( $this->build_filename( $language ) ) ); header( sprintf( 'Content-Type: %s; charset=%s', sanitize_mime_type( $mime_type ), get_bloginfo( 'charset' ) ) ); } /** * Export snippets in JSON format as a downloadable file. */ public function download_snippets_json() { $this->do_headers( 'json', 'application/json' ); // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo wp_json_encode( $this->create_export_object(), apply_filters( 'code_snippets/export/json_encode_options', 0 ) ); exit; } /** * Export snippets in their code file format. */ public function download_snippets_code() { $lang = $this->snippets_list[0]->lang; $mime_types = [ 'php' => 'text/php', 'css' => 'text/css', 'js' => 'text/javascript', 'json' => 'application/json', ]; $this->do_headers( $lang, $mime_types[ $lang ] ?? 'text/plain' ); // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->export_snippets_code( $this->snippets_list[0]->type ); exit; } } export/class-export.php 0000755 00000011154 15105501234 0011223 0 ustar 00 <?php namespace Code_Snippets; /** * Handles exporting snippets from the site in various downloadable formats * * @package Code_Snippets * @since 3.0.0 */ class Export { /** * Array of snippet data fetched from the database * * @var Snippet[] */ protected array $snippets_list; /** * Class constructor * * @param array<int> $ids List of snippet IDs to export. * @param boolean|null $network Whether to fetch snippets from local or network table. */ public function __construct( array $ids, ?bool $network = null ) { $this->snippets_list = get_snippets( $ids, $network ); } /** * Build the export filename. * * @param string $format File format. Used for file extension. * * @return string */ public function build_filename( string $format ): string { if ( 1 === count( $this->snippets_list ) ) { // If there is only snippet to export, use its name instead of the site name. $title = strtolower( $this->snippets_list[0]->name ); } else { // Otherwise, use the site name as set in Settings > General. $title = strtolower( get_bloginfo( 'name' ) ); } $filename = "$title.code-snippets.$format"; return apply_filters( 'code_snippets/export/filename', $filename, $title, $this->snippets_list ); } /** * Bundle snippets together into JSON format. * * @return array<string, string|Snippet[]> Snippets as JSON object. */ public function create_export_object(): array { $snippets = array(); foreach ( $this->snippets_list as $snippet ) { $snippets[] = array_map( function ( $value ) { return is_string( $value ) ? str_replace( "\r\n", "\n", $value ) : $value; }, $snippet->get_modified_fields() ); } return array( 'generator' => 'Code Snippets v' . code_snippets()->version, 'date_created' => gmdate( 'Y-m-d H:i' ), 'snippets' => $snippets, ); } /** * Bundle a snippets into a PHP file. */ public function export_snippets_php(): string { $result = "<?php\n"; foreach ( $this->snippets_list as $snippet ) { $code = trim( $snippet->code ); if ( ( 'php' !== $snippet->type && 'html' !== $snippet->type ) || ! $code ) { continue; } $result .= "\n/**\n * $snippet->display_name\n"; if ( ! empty( $snippet->desc ) ) { // Convert description to PhpDoc. $desc = wp_strip_all_tags( str_replace( "\n", "\n * ", $snippet->desc ) ); $result .= " *\n * $desc\n"; } $result .= " */\n"; if ( 'content' === $snippet->scope ) { $shortcode_tag = apply_filters( 'code_snippets_export_shortcode_tag', "code_snippets_export_$snippet->id", $snippet ); $code = sprintf( "add_shortcode( '%s', function () {\n\tob_start();\n\t?>\n\n\t%s\n\n\t<?php\n\treturn ob_get_clean();\n} );", $shortcode_tag, str_replace( "\n", "\n\t", $code ) ); } $result .= "$code\n"; } return $result; } /** * Export conditions in JSON format. * * @return string */ public function export_conditions_json(): string { $conditions_data = []; $fields_to_copy = [ 'name', 'desc', 'tags' ]; foreach ( $this->snippets_list as $snippet ) { $condition_data = []; if ( ! $snippet->code || 'cond' !== $snippet->type ) { continue; } $rules = json_decode( $snippet->code, false ); if ( json_last_error() !== JSON_ERROR_NONE ) { continue; } foreach ( $fields_to_copy as $field ) { if ( ! empty( $snippet->$field ) ) { $condition_data[ $field ] = $snippet->$field; } } $condition_data['rules'] = $rules; $conditions_data[] = $condition_data; } return wp_json_encode( 1 === count( $conditions_data ) ? $conditions_data[0] : $conditions_data, JSON_PRETTY_PRINT ); } /** * Generate a downloadable CSS or JavaScript file from a list of snippets * * @phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped * * @param string|null $type Snippet type. Supports 'css' or 'js'. */ public function export_snippets_code( ?string $type = null ): string { $result = ''; if ( ! $type ) { $type = $this->snippets_list[0]->type; } if ( 'php' === $type || 'html' === $type ) { return $this->export_snippets_php(); } if ( 'cond' === $type ) { return $this->export_conditions_json(); } foreach ( $this->snippets_list as $snippet ) { $snippet = new Snippet( $snippet ); if ( $snippet->type !== $type ) { continue; } $result .= "\n/*\n"; if ( $snippet->name ) { $result .= wp_strip_all_tags( $snippet->name ) . "\n\n"; } if ( ! empty( $snippet->desc ) ) { $result .= wp_strip_all_tags( $snippet->desc ) . "\n"; } $result .= "*/\n\n$snippet->code\n\n"; } return $result; } } export/class-import.php 0000755 00000012324 15105501234 0011214 0 ustar 00 <?php namespace Code_Snippets; use DOMDocument; /** * Handles importing snippets from export files into the site * * @package Code_Snippets * @since 3.0.0 * * phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents * phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */ class Import { /** * Path to file to import. * * @var string */ private string $file; /** * Whether snippets should be imported into the network-wide or site-wide table. * * @var bool */ private bool $multisite; /** * Action to take if duplicate snippets are detected. Can be 'skip', 'ignore', or 'replace'. * * @var string */ private string $dup_action; /** * Class constructor. * * @param string $file The path to the file to import. * @param bool|null $network Import into network-wide table (true) or site-wide table (false). * @param string $dup_action Action to take if duplicate snippets are detected. Can be 'skip', 'ignore', or 'replace'. */ public function __construct( string $file, ?bool $network = null, string $dup_action = 'ignore' ) { $this->file = $file; $this->multisite = DB::validate_network_param( $network ); $this->dup_action = $dup_action; } /** * Imports snippets from a JSON file. * * @return array<integer>|bool An array of imported snippet IDs on success, false on failure */ public function import_json() { if ( ! file_exists( $this->file ) || ! is_file( $this->file ) ) { return false; } $raw_data = file_get_contents( $this->file ); $data = json_decode( $raw_data, true ); $snippets = array(); // Reformat the data into snippet objects. foreach ( $data['snippets'] as $snippet_data ) { $snippet = new Snippet(); $snippet->network = $this->multisite; $import_fields = [ 'name', 'desc', 'description', 'code', 'tags', 'scope', 'priority', 'shared_network', 'modified', 'cloud_id', ]; foreach ( $import_fields as $field ) { if ( isset( $snippet_data[ $field ] ) ) { $snippet->set_field( $field, $snippet_data[ $field ] ); } } $snippets[] = $snippet; } $imported = $this->save_snippets( $snippets ); do_action( 'code_snippets/import/json', $this->file, $this->multisite ); return $imported; } /** * Imports snippets from an XML file * * @return array<integer>|bool An array of imported snippet IDs on success, false on failure */ public function import_xml() { if ( ! file_exists( $this->file ) || ! is_file( $this->file ) ) { return false; } $dom = new DOMDocument( '1.0', get_bloginfo( 'charset' ) ); $dom->load( $this->file ); $snippets_xml = $dom->getElementsByTagName( 'snippet' ); $fields = array( 'name', 'description', 'desc', 'code', 'tags', 'scope' ); $snippets = array(); foreach ( $snippets_xml as $snippet_xml ) { $snippet = new Snippet(); $snippet->network = $this->multisite; // Build a snippet object by looping through the field names. foreach ( $fields as $field_name ) { // Fetch the field element from the document. $field = $snippet_xml->getElementsByTagName( $field_name )->item( 0 ); // If the field element exists, add it to the snippet object. if ( isset( $field->nodeValue ) ) { $snippet->set_field( $field_name, $field->nodeValue ); } } // Get scope from attribute. $scope = $snippet_xml->getAttribute( 'scope' ); if ( ! empty( $scope ) ) { $snippet->scope = $scope; } $snippets[] = $snippet; } $imported = $this->save_snippets( $snippets ); do_action( 'code_snippets/import/xml', $this->file, $this->multisite ); return $imported; } /** * Fetch a list of existing snippets for checking duplicates. * * @return array<string, integer> */ private function fetch_existing_snippets(): array { $existing_snippets = array(); if ( 'replace' === $this->dup_action || 'skip' === $this->dup_action ) { $all_snippets = get_snippets( array(), $this->multisite ); foreach ( $all_snippets as $snippet ) { if ( $snippet->name ) { $existing_snippets[ $snippet->name ] = $snippet->id; } } } return $existing_snippets; } /** * Save imported snippets to the database * * @access private * * @param array<Snippet> $snippets List of snippets to save. * * @return array<integer> IDs of imported snippets. */ private function save_snippets( array $snippets ): array { $existing_snippets = $this->fetch_existing_snippets(); $imported = array(); foreach ( $snippets as $snippet ) { // Check if the snippet already exists. if ( 'ignore' !== $this->dup_action && isset( $existing_snippets[ $snippet->name ] ) ) { // If so, either overwrite the existing ID, or skip this import. if ( 'replace' === $this->dup_action ) { $snippet->id = $existing_snippets[ $snippet->name ]; } elseif ( 'skip' === $this->dup_action ) { continue; } } // Save the snippet and increase the counter if successful. $saved_snippet = save_snippet( $snippet ); // Get ID of the saved snippet as save_snippet() returns complete snippet object. $snippet_id = $saved_snippet->id; if ( $snippet_id ) { $imported[] = $snippet_id; } } return $imported; } } flat-files/classes/class-config-repository.php 0000755 00000002736 15105501234 0015534 0 ustar 00 <?php namespace Code_Snippets; class Snippet_Config_Repository implements Snippet_Config_Repository_Interface { const CONFIG_FILE_NAME = 'index.php'; private File_System_Interface $fs; public function __construct( File_System_Interface $fs ) { $this->fs = $fs; } public function load( string $base_dir ): array { $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; if ( is_file( $config_file_path ) ) { if ( function_exists( 'opcache_invalidate' ) ) { opcache_invalidate( $config_file_path, true ); } return require $config_file_path; } return []; } public function save( string $base_dir, array $active_snippets ): void { $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; ksort( $active_snippets ); $file_content = "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\nreturn " . var_export( $active_snippets, true ) . ";\n"; $this->fs->put_contents( $config_file_path, $file_content, FS_CHMOD_FILE ); if ( is_file( $config_file_path ) ) { if ( function_exists( 'opcache_invalidate' ) ) { opcache_invalidate( $config_file_path, true ); } } } public function update( string $base_dir, Snippet $snippet, ?bool $remove = false ): void { $active_snippets = $this->load( $base_dir ); if ( $remove ) { unset( $active_snippets[ $snippet->id ] ); } else { $active_snippets[ $snippet->id ] = $snippet->get_fields(); } $this->save( $base_dir, $active_snippets ); } } flat-files/classes/class-file-system-adapter.php 0000755 00000002277 15105501234 0015731 0 ustar 00 <?php namespace Code_Snippets; class WordPress_File_System_Adapter implements File_System_Interface { private $fs; public function __construct() { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); global $wp_filesystem; $this->fs = $wp_filesystem; } public function put_contents( string $path, string $contents, $chmod ) { return $this->fs->put_contents( $path, $contents, $chmod ); } public function exists( string $path ): bool { return $this->fs->exists( $path ); } public function delete( $file, $recursive = false, $type = false ): bool { return $this->fs->delete( $file, $recursive, $type ); } public function is_dir( string $path ): bool { return $this->fs->is_dir( $path ); } public function mkdir( string $path, $chmod ) { return $this->fs->mkdir( $path, $chmod ); } public function rmdir( string $path, bool $recursive = false ): bool { return $this->fs->rmdir( $path, $recursive ); } public function chmod( string $path, $chmod ): bool { return $this->fs->chmod( $path, $chmod ); } public function is_writable( string $path ): bool { return $this->fs->is_writable( $path ); } } flat-files/classes/class-snippet-files.php 0000755 00000033444 15105501234 0014634 0 ustar 00 <?php namespace Code_Snippets; class Snippet_Files { /** * Flag file name that indicates flat files are enabled. */ private const ENABLED_FLAG_FILE = 'flat-files-enabled.flag'; private Snippet_Handler_Registry $handler_registry; private File_System_Interface $fs; private Snippet_Config_Repository_Interface $config_repo; public function __construct( Snippet_Handler_Registry $handler_registry, File_System_Interface $fs, Snippet_Config_Repository_Interface $config_repo ) { $this->handler_registry = $handler_registry; $this->fs = $fs; $this->config_repo = $config_repo; } /** * Check if flat files are enabled by checking for the flag file. * This avoids database calls for better performance. * * @return bool True if flat files are enabled, false otherwise. */ public static function is_active(): bool { $flag_file_path = self::get_flag_file_path(); return file_exists( $flag_file_path ); } private static function get_flag_file_path(): string { return self::get_base_dir() . '/' . self::ENABLED_FLAG_FILE; } private function handle_enabled_file_flag( bool $enabled ): void { $flag_file_path = self::get_flag_file_path(); if ( $enabled ) { $base_dir = self::get_base_dir(); $this->maybe_create_directory( $base_dir ); $this->fs->put_contents( $flag_file_path, '', FS_CHMOD_FILE ); } else { $this->delete_file( $flag_file_path ); } } public function register_hooks(): void { if ( ! $this->fs->is_writable( WP_CONTENT_DIR ) ) { return; } if ( self::is_active() ) { add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 1 ); add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); add_action( 'code_snippets/activate_snippets', [ $this, 'activate_snippets' ], 10, 2 ); add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); add_action( 'add_option', [ $this, 'sync_active_shared_network_snippets_add' ], 10, 2 ); } add_filter( 'code_snippets_settings_fields', [ $this, 'add_settings_fields' ], 10, 1 ); add_action( 'code_snippets/settings_updated', [ $this, 'create_all_flat_files' ], 10, 2 ); } public function activate_snippets( $valid_snippets, $table ): void { foreach ( $valid_snippets as $snippet ) { $snippet->active = true; $this->handle_snippet( $snippet, $table ); } } public function handle_snippet( Snippet $snippet, string $table ): void { if ( 0 === $snippet->id ) { return; } $handler = $this->handler_registry->get_handler( $snippet->type ); if ( ! $handler ) { return; } $table = self::get_hashed_table_name( $table ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $contents = $handler->wrap_code( $snippet->code ); $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); $this->config_repo->update( $base_dir, $snippet ); } public function delete_snippet( Snippet $snippet, bool $network ): void { $handler = $this->handler_registry->get_handler( $snippet->type ); if ( ! $handler ) { return; } $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $this->delete_file( $file_path ); $this->config_repo->update( $base_dir, $snippet, true ); } public function activate_snippet( Snippet $snippet ): void { $snippet = get_snippet( $snippet->id, $snippet->network ); $handler = $this->handler_registry->get_handler( $snippet->type ); if ( ! $handler ) { return; } $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $snippet->network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $contents = $handler->wrap_code( $snippet->code ); $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); $this->config_repo->update( $base_dir, $snippet ); } public function deactivate_snippet( int $snippet_id, bool $network ): void { $snippet = get_snippet( $snippet_id, $network ); $handler = $this->handler_registry->get_handler( $snippet->type ); if ( ! $handler ) { return; } $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->config_repo->update( $base_dir, $snippet ); } public static function get_base_dir( string $table = '', string $snippet_type = '' ): string { $base_dir = WP_CONTENT_DIR . '/code-snippets'; if ( ! empty( $table ) ) { $base_dir .= '/' . $table; } if ( ! empty( $snippet_type ) ) { $base_dir .= '/' . $snippet_type; } return $base_dir; } public static function get_base_url( string $table = '', string $snippet_type = '' ): string { $base_url = WP_CONTENT_URL . '/code-snippets'; if ( ! empty( $table ) ) { $base_url .= '/' . $table; } if ( ! empty( $snippet_type ) ) { $base_url .= '/' . $snippet_type; } return $base_url; } private function maybe_create_directory( string $dir ): void { if ( ! $this->fs->is_dir( $dir ) ) { $result = wp_mkdir_p( $dir ); if ( $result ) { $this->fs->chmod( $dir, FS_CHMOD_DIR ); } } } private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ): string { return trailingslashit( $base_dir ) . $snippet_id . '.' . $ext; } private function delete_file( string $file_path ): void { if ( $this->fs->exists( $file_path ) ) { $this->fs->delete( $file_path ); } } public function sync_active_shared_network_snippets( $option, $old_value, $value ): void { if ( 'active_shared_network_snippets' !== $option ) { return; } $this->create_active_shared_network_snippets_file( $value ); } public function sync_active_shared_network_snippets_add( $option, $value ): void { if ( 'active_shared_network_snippets' !== $option ) { return; } $this->create_active_shared_network_snippets_file( $value ); } private function create_active_shared_network_snippets_file( $value ): void { $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( false ) ); $base_dir = self::get_base_dir( $table ); $this->maybe_create_directory( $base_dir ); $file_path = trailingslashit( $base_dir ) . 'active-shared-network-snippets.php'; $file_content = "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\nreturn " . var_export( $value, true ) . ";\n"; $this->fs->put_contents( $file_path, $file_content, FS_CHMOD_FILE ); } public static function get_hashed_table_name( string $table ): string { return wp_hash( $table ); } public static function get_active_snippets_from_flat_files( array $scopes = [], $snippet_type = 'php' ): array { $active_snippets = []; $db = code_snippets()->db; $table = self::get_hashed_table_name( $db->get_table_name() ); $snippets = self::load_active_snippets_from_file( $table, $snippet_type, $scopes ); if ( $snippets ) { foreach ( $snippets as $snippet ) { $active_snippets[] = [ 'id' => intval( $snippet['id'] ), 'code' => $snippet['code'], 'scope' => $snippet['scope'], 'table' => $db->table, 'network' => false, 'priority' => intval( $snippet['priority'] ), 'condition_id' => intval( $snippet['condition_id'] ), ]; } } if ( is_multisite() ) { $ms_table = self::get_hashed_table_name( $db->get_table_name( true ) ); $root_base_dir = self::get_base_dir( $table ); $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; $active_shared_ids = is_file( $active_shared_ids_file_path ) ? require $active_shared_ids_file_path : []; $ms_snippets = self::load_active_snippets_from_file( $ms_table, $snippet_type, $scopes, $active_shared_ids ); if ( $ms_snippets ) { $active_shared_ids = is_array( $active_shared_ids ) ? array_map( 'intval', $active_shared_ids ) : []; foreach ( $ms_snippets as $snippet ) { $id = intval( $snippet['id'] ); if ( ! $snippet['active'] && ! in_array( $id, $active_shared_ids, true ) ) { continue; } $active_snippets[] = [ 'id' => $id, 'code' => $snippet['code'], 'scope' => $snippet['scope'], 'table' => $db->ms_table, 'network' => true, 'priority' => intval( $snippet['priority'] ), 'condition_id' => intval( $snippet['condition_id'] ), ]; } self::sort_active_snippets( $active_snippets, $db ); } } return $active_snippets; } private static function sort_active_snippets( array &$active_snippets, $db ): void { $comparisons = [ function ( array $a, array $b ) { return $a['priority'] <=> $b['priority']; }, function ( array $a, array $b ) use ( $db ) { $a_table = $a['table'] === $db->ms_table ? 0 : 1; $b_table = $b['table'] === $db->ms_table ? 0 : 1; return $a_table <=> $b_table; }, function ( array $a, array $b ) { return $a['id'] <=> $b['id']; }, ]; usort( $active_snippets, static function ( $a, $b ) use ( $comparisons ) { foreach ( $comparisons as $comparison ) { $result = $comparison( $a, $b ); if ( 0 !== $result ) { return $result; } } return 0; } ); } private static function load_active_snippets_from_file( string $table, string $snippet_type, array $scopes, ?array $active_shared_ids = null ): array { $snippets = []; $db = code_snippets()->db; $base_dir = self::get_base_dir( $table, $snippet_type ); $snippets_file_path = $base_dir . '/index.php'; if ( ! is_file( $snippets_file_path ) ) { return $snippets; } $cache_key = sprintf( 'active_snippets_%s_%s', sanitize_key( join( '_', $scopes ) ), self::get_hashed_table_name( $db->table ) === $table ? $db->table : $db->ms_table ); $cached_snippets = wp_cache_get( $cache_key, CACHE_GROUP ); if ( is_array( $cached_snippets ) ) { return $cached_snippets; } $file_snippets = require $snippets_file_path; $filtered_snippets = array_filter( $file_snippets, function ( $snippet ) use ( $scopes, $active_shared_ids ) { $is_active = $snippet['active']; if ( null !== $active_shared_ids ) { $is_active = $is_active || in_array( intval( $snippet['id'] ), $active_shared_ids, true ); } return ( $is_active || 'condition' === $snippet['scope'] ) && in_array( $snippet['scope'], $scopes, true ); } ); wp_cache_set( $cache_key, $filtered_snippets, CACHE_GROUP ); return $filtered_snippets; } public function add_settings_fields( array $fields ): array { $fields['general']['enable_flat_files'] = [ 'name' => __( 'Enable file-based execution', 'code-snippets' ), 'type' => 'checkbox', 'label' => __( 'Snippets will be executed directly from files instead of the database.', 'code-snippets' ) . ' ' . sprintf( '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc_url( 'https://codesnippets.pro/doc/file-based-execution/' ), __( 'Learn more.', 'code-snippets' ) ), ]; return $fields; } public function create_all_flat_files( array $settings, array $input ): void { if ( ! isset( $settings['general']['enable_flat_files'] ) ) { return; } $this->handle_enabled_file_flag( $settings['general']['enable_flat_files'] ); if ( ! $settings['general']['enable_flat_files'] ) { return; } $this->create_snippet_flat_files(); $this->create_active_shared_network_snippets_config_file(); } private function create_snippet_flat_files(): void { $db = code_snippets()->db; $scopes = Snippet::get_all_scopes(); $data = $db->fetch_active_snippets( $scopes ); foreach ( $data as $snippet ) { $snippet_obj = get_snippet( $snippet['id'], $db->ms_table === $snippet['table'] ); $this->handle_snippet( $snippet_obj, $snippet['table'] ); } if ( is_multisite() ) { $current_blog_id = get_current_blog_id(); $sites = get_sites( [ 'fields' => 'ids' ] ); foreach ( $sites as $site_id ) { switch_to_blog( $site_id ); $db->set_table_vars(); $site_data = $db->fetch_active_snippets( $scopes ); foreach ( $site_data as $snippet ) { $table_name = $snippet['table']; $snippet_obj = get_snippet( $snippet['id'], false ); $this->handle_snippet( $snippet_obj, $table_name ); } restore_current_blog(); } $db->set_table_vars(); } } private function create_active_shared_network_snippets_config_file(): void { if ( is_multisite() ) { $current_blog_id = get_current_blog_id(); $sites = get_sites( [ 'fields' => 'ids' ] ); $db = code_snippets()->db; foreach ( $sites as $site_id ) { switch_to_blog( $site_id ); $db->set_table_vars(); $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); if ( false !== $active_shared_network_snippets ) { $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); } restore_current_blog(); } $db->set_table_vars(); } else { $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); if ( false !== $active_shared_network_snippets ) { $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); } } } } flat-files/handlers/html-snippet-handler.php 0000755 00000000566 15105501234 0015150 0 ustar 00 <?php namespace Code_Snippets; class Html_Snippet_Handler implements Snippet_Type_Handler_Interface { public function get_file_extension(): string { return 'php'; } public function get_dir_name(): string { return 'html'; } public function wrap_code( string $code ): string { return "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\n?>\n\n" . $code; } } flat-files/handlers/php-snippet-handler.php 0000755 00000000556 15105501234 0014772 0 ustar 00 <?php namespace Code_Snippets; class Php_Snippet_Handler implements Snippet_Type_Handler_Interface { public function get_file_extension(): string { return 'php'; } public function get_dir_name(): string { return 'php'; } public function wrap_code( string $code ): string { return "<?php\n\nif ( ! defined( 'ABSPATH' ) ) { return; }\n\n" . $code; } } flat-files/interfaces/interface-config-repository.php 0000755 00000000452 15105501234 0017046 0 ustar 00 <?php namespace Code_Snippets; interface Snippet_Config_Repository_Interface { public function load( string $base_dir ): array; public function save( string $base_dir, array $active_snippets ): void; public function update( string $base_dir, Snippet $snippet, ?bool $remove = false ): void; } flat-files/interfaces/interface-file-system.php 0000755 00000001030 15105501234 0015616 0 ustar 00 <?php namespace Code_Snippets; interface File_System_Interface { public function put_contents( string $path, string $contents, $chmod ); public function exists( string $path ): bool; public function delete( $file, $recursive = false, $type = false ): bool; public function is_dir( string $path ): bool; public function mkdir( string $path, $chmod ); public function rmdir( string $path, bool $recursive = false ): bool; public function chmod( string $path, $chmod ): bool; public function is_writable( string $path ): bool; } flat-files/interfaces/interface-snippet-handler.php 0000755 00000000332 15105501234 0016456 0 ustar 00 <?php namespace Code_Snippets; interface Snippet_Type_Handler_Interface { public function get_file_extension(): string; public function get_dir_name(): string; public function wrap_code( string $code ): string; } flat-files/registry.php 0000755 00000001745 15105501234 0011161 0 ustar 00 <?php namespace Code_Snippets; class Snippet_Handler_Registry { /** * @var Snippet_Type_Handler_Interface[] */ private array $handlers = []; /** * Constructor * * @param Snippet_Type_Handler_Interface[] $handlers */ public function __construct( array $handlers ) { foreach ( $handlers as $type => $handler ) { $this->register_handler( $type, $handler ); } } /** * Registers a handler for a snippet type. * * @param string $type * @param Snippet_Type_Handler_Interface $handler * @return void */ public function register_handler( string $type, Snippet_Type_Handler_Interface $handler ): void { $this->handlers[ $type ] = $handler; } /** * Gets the handler for a snippet type. * * @param string $type * * @return Snippet_Type_Handler_Interface|null */ public function get_handler( string $type ): ?Snippet_Type_Handler_Interface { if ( ! isset( $this->handlers[ $type ] ) ) { return null; } return $this->handlers[ $type ]; } } front-end/class-front-end.php 0000755 00000034143 15105501234 0012154 0 ustar 00 <?php namespace Code_Snippets; use WP_Post; use WP_REST_Response; use WP_REST_Server; /** * This class manages the shortcodes included with the plugin, * * @package Code_Snippets */ class Front_End { /** * Name of the shortcode tag for rendering the code source */ public const SOURCE_SHORTCODE = 'code_snippet_source'; /** * Name of the shortcode tag for rendering content snippets */ public const CONTENT_SHORTCODE = 'code_snippet'; /** * Handle to use for front-end scripts and styles. */ public const PRISM_HANDLE = 'code-snippets-prism'; /** * Class constructor */ public function __construct() { add_action( 'the_posts', [ $this, 'enqueue_highlighting' ] ); add_action( 'init', [ $this, 'setup_mce_plugin' ] ); add_shortcode( self::CONTENT_SHORTCODE, [ $this, 'render_content_shortcode' ] ); add_shortcode( self::SOURCE_SHORTCODE, [ $this, 'render_source_shortcode' ] ); add_filter( 'code_snippets/render_content_shortcode', 'trim' ); } /** * Register REST API routes for use in front-end plugins. * * @return void */ public function register_rest_routes() { register_rest_route( 'v1/snippets', '/snippets-info', array( 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_snippets_info' ], 'permission_callback' => function () { return current_user_can( 'edit_posts' ); }, ) ); } /** * Fetch snippets data in response to a request. * * @return WP_REST_Response */ public function get_snippets_info(): WP_REST_Response { $snippets = get_snippets(); $data = []; foreach ( $snippets as $snippet ) { $data[] = [ 'id' => $snippet->id, 'name' => $snippet->name, 'type' => $snippet->type, 'active' => $snippet->active, ]; } return new WP_REST_Response( $data, 200 ); } /** * Perform the necessary actions to add a button to the TinyMCE editor */ public function setup_mce_plugin() { if ( ! code_snippets()->current_user_can() ) { return; } /* Register the TinyMCE plugin */ add_filter( 'mce_external_plugins', function ( $plugins ) { $plugins['code_snippets'] = plugins_url( 'dist/mce.js', PLUGIN_FILE ); return $plugins; } ); /* Add the button to the editor toolbar */ add_filter( 'mce_buttons', function ( $buttons ) { $buttons[] = 'code_snippets'; return $buttons; } ); /* Add the translation strings to the TinyMCE editor */ add_filter( 'mce_external_languages', function ( $languages ) { $languages['code_snippets'] = __DIR__ . '/mce-strings.php'; return $languages; } ); } /** * Enqueue the syntax highlighting assets if they are required for the current posts * * @param array<WP_Post|int>|null|false $posts List of currently visible posts. * * @return array<WP_Post|int>|null|false Unchanged list of posts. */ public function enqueue_highlighting( $posts ) { // Exit early if there are no posts to check or if the highlighter has been disabled. if ( empty( $posts ) || Settings\get_setting( 'general', 'disable_prism' ) ) { return $posts; } // Loop through the posts, checking for an existing shortcode, short-circuiting if possible. $found_shortcode_content = null; foreach ( $posts as $post ) { if ( false !== stripos( $post->post_content, '[' . self::SOURCE_SHORTCODE ) || false !== strpos( $post->post_content, '<!-- wp:code-snippets/source ' ) ) { $found_shortcode_content = $post->post_content; break; } } // Load assets on the appropriate hook if a matching shortcode was found. if ( null !== $found_shortcode_content ) { $this->register_prism_assets(); add_action( 'wp_enqueue_scripts', function () { wp_enqueue_style( self::PRISM_HANDLE ); wp_enqueue_script( self::PRISM_HANDLE ); }, 100 ); } return $posts; } /** * Enqueue the styles and scripts for the Prism syntax highlighter. * * @return void */ public static function register_prism_assets() { $plugin = code_snippets(); wp_register_script( self::PRISM_HANDLE, plugins_url( 'dist/prism.js', $plugin->file ), array(), $plugin->version, true ); wp_register_style( self::PRISM_HANDLE, plugins_url( 'dist/prism.css', $plugin->file ), array(), $plugin->version ); } /** * Enqueue all available Prism themes. * * @return void */ public static function enqueue_all_prism_themes() { self::register_prism_assets(); wp_enqueue_style( self::PRISM_HANDLE ); wp_enqueue_script( self::PRISM_HANDLE ); } /** * Print a message to the user if the snippet ID attribute is invalid. * * @param integer $id Snippet ID. * * @return string Warning message. */ protected function invalid_id_warning( int $id ): string { // translators: %d: snippet ID. $text = esc_html__( 'Could not load snippet with an invalid ID: %d.', 'code-snippets' ); return current_user_can( 'edit_posts' ) ? sprintf( $text, $id ) : ''; } /** * Allow boolean attributes to be provided without a value, similar to how React works. * * @param array<string|number, mixed> $atts Unfiltered shortcode attributes. * @param array<string> $boolean_flags List of attribute names with boolean values. * * @return array<string|number, mixed> Shortcode attributes with flags converted to attributes. */ protected function convert_boolean_attribute_flags( array $atts, array $boolean_flags ): array { foreach ( $atts as $key => $value ) { if ( in_array( $value, $boolean_flags, true ) && ! isset( $atts[ $value ] ) ) { $atts[ $value ] = true; unset( $atts[ $key ] ); } } return $atts; } /** * Build the file path for a snippet's flat file. * * @param string $table_name Table name for the snippet. * @param Snippet $snippet Snippet object. * * @return string Full file path for the snippet. */ private function build_snippet_flat_file_path( string $table_name, Snippet $snippet ): string { $handler = code_snippets()->snippet_handler_registry->get_handler( $snippet->get_type() ); return Snippet_Files::get_base_dir( $table_name, $handler->get_dir_name() ) . '/' . $snippet->id . '.' . $handler->get_file_extension(); } /** * Evaluate the code from a content shortcode. * * @param Snippet $snippet Snippet. * @param array<string, mixed> $atts Shortcode attributes. * * @return string Evaluated shortcode content. */ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): string { if ( empty( $atts['php'] ) ) { return $snippet->code; } if ( ! Snippet_Files::is_active() ) { return $this->evaluate_shortcode_from_db( $snippet, $atts ); } $network = DB::validate_network_param( $snippet->network ); $table_name = Snippet_Files::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $filepath = $this->build_snippet_flat_file_path( $table_name, $snippet ); return file_exists( $filepath ) ? $this->evaluate_shortcode_from_flat_file( $filepath, $atts ) : $this->evaluate_shortcode_from_db( $snippet, $atts ); } private function evaluate_shortcode_from_db( Snippet $snippet, array $atts ): string { /** * Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet * authors to use custom attributes. * * @phpcs:disable WordPress.PHP.DontExtract.extract_extract */ extract( $atts ); ob_start(); eval( "?>\n\n" . $snippet->code ); return ob_get_clean(); } private function evaluate_shortcode_from_flat_file( $filepath, array $atts ): string { ob_start(); ( function( $atts ) use ( $filepath ) { /** * Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet * authors to use custom attributes. * * @phpcs:disable WordPress.PHP.DontExtract.extract_extract */ extract( $atts ); require_once $filepath; } )( $atts ); return ob_get_clean(); } private function get_snippet( int $id, bool $network, string $snippet_type ): Snippet { if ( ! Snippet_Files::is_active() ) { return get_snippet( $id, $network ); } $validated_network = DB::validate_network_param( $network ); $table_name = Snippet_Files::get_hashed_table_name( code_snippets()->db->get_table_name( $validated_network ) ); $handler = code_snippets()->snippet_handler_registry->get_handler( $snippet_type ); $config_filepath = Snippet_Files::get_base_dir( $table_name, $handler->get_dir_name() ) . '/index.php'; if ( file_exists( $config_filepath ) ) { $config = require_once $config_filepath; $snippet_data = $config[ $id ] ?? null; if ( $snippet_data ) { $snippet = new Snippet( $snippet_data ); return apply_filters( 'code_snippets/get_snippet', $snippet, $id, $network ); } } return get_snippet( $id, $network ); } /** * Render the value of a content shortcode * * @param array<string, mixed> $atts Shortcode attributes. * * @return string Shortcode content. */ public function render_content_shortcode( array $atts ): string { $atts = $this->convert_boolean_attribute_flags( $atts, [ 'network', 'php', 'format', 'shortcodes', 'debug' ] ); $original_atts = $atts; $atts = shortcode_atts( [ 'id' => 0, 'snippet_id' => 0, 'network' => false, 'php' => false, 'format' => false, 'shortcodes' => false, 'debug' => false, ], $atts, self::CONTENT_SHORTCODE ); $id = 0 !== intval( $atts['snippet_id'] ) ? intval( $atts['snippet_id'] ) : intval( $atts['id'] ); if ( ! $id ) { return $this->invalid_id_warning( $id ); } $snippet = $this->get_snippet( $id, (bool) $atts['network'], 'html' ); // Render the source code if this is not a shortcode snippet. if ( 'content' !== $snippet->scope ) { return $snippet->id ? $this->render_snippet_source( $snippet ) : $this->invalid_id_warning( $snippet->id ); } // If the snippet is inactive, either display a message or render nothing. if ( ! $snippet->active ) { if ( ! $atts['debug'] ) { return ''; } /* translators: 1: snippet name, 2: snippet edit link */ $text = __( '%1$s is currently inactive. You can <a href="%2$s">edit this snippet</a> to activate it and make it visible. This message will not appear in the published post.', 'code-snippets' ); $snippet_name = '<strong>' . $snippet->name . '</strong>'; $edit_url = add_query_arg( 'id', $snippet->id, code_snippets()->get_menu_url( 'edit' ) ); return wp_kses( sprintf( $text, $snippet_name, $edit_url ), [ 'strong' => [], 'a' => [ 'href' => [], ], ] ); } $content = $this->evaluate_shortcode_content( $snippet, $original_atts ); if ( $atts['format'] ) { $functions = [ 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'capital_P_dangit' ]; foreach ( $functions as $function ) { $content = call_user_func( $function, $content ); } } if ( $atts['shortcodes'] ) { // Temporarily remove this shortcode from the list to prevent recursion while executing do_shortcode. remove_shortcode( self::CONTENT_SHORTCODE ); $content = do_shortcode( $atts['format'] ? shortcode_unautop( $content ) : $content ); add_shortcode( self::CONTENT_SHORTCODE, [ $this, 'render_content_shortcode' ] ); } return apply_filters( 'code_snippets/content_shortcode', $content, $snippet, $atts, $original_atts ); } /** * Converts a value and key into an HTML attribute pair. * * @param string $value Attribute value. * @param string $key Attribute name. * * @return void */ private static function create_attribute_pair( string &$value, string $key ) { $value = sprintf( '%s="%s"', sanitize_key( $key ), esc_attr( $value ) ); } /** * Render the source code of a given snippet * * @param Snippet $snippet Snippet object. * @param array<string, mixed> $atts Shortcode attributes. * * @return string Shortcode content. */ private function render_snippet_source( Snippet $snippet, array $atts = [] ): string { $atts = array_merge( array( 'line_numbers' => false, 'highlight_lines' => '', ), $atts ); $language = 'css' === $snippet->type ? 'css' : ( 'js' === $snippet->type ? 'js' : 'php' ); $pre_attributes = array( 'id' => "code-snippet-source-$snippet->id", 'class' => 'code-snippet-source', ); $code_attributes = array( 'class' => "language-$language", ); if ( $atts['line_numbers'] ) { $code_attributes['class'] .= ' line-numbers'; $pre_attributes['class'] .= ' linkable-line-numbers'; } if ( $atts['highlight_lines'] ) { $pre_attributes['data-line'] = $atts['highlight_lines']; } $pre_attributes = apply_filters( 'code_snippets/prism_pre_attributes', $pre_attributes, $snippet, $atts ); $code_attributes = apply_filters( 'code_snippets/prism_code_attributes', $code_attributes, $snippet, $atts ); array_walk( $code_attributes, array( $this, 'create_attribute_pair' ) ); array_walk( $pre_attributes, array( $this, 'create_attribute_pair' ) ); $code = 'php' === $snippet->type ? "<?php\n\n$snippet->code" : $snippet->code; $output = sprintf( '<pre %s><code %s>%s</code></pre>', implode( ' ', $pre_attributes ), implode( ' ', $code_attributes ), esc_html( $code ) ); return apply_filters( 'code_snippets/render_source_shortcode', $output, $snippet, $atts ); } /** * Render the value of a source shortcode * * @param array<string, mixed> $atts Shortcode attributes. * * @return string Shortcode content. */ public function render_source_shortcode( array $atts ): string { $atts = $this->convert_boolean_attribute_flags( $atts, [ 'network', 'line_numbers' ] ); $atts = shortcode_atts( array( 'id' => 0, 'snippet_id' => 0, 'network' => false, 'line_numbers' => false, 'highlight_lines' => '', ), $atts, self::SOURCE_SHORTCODE ); $id = 0 !== intval( $atts['snippet_id'] ) ? intval( $atts['snippet_id'] ) : intval( $atts['id'] ); if ( ! $id ) { return $this->invalid_id_warning( $id ); } $snippet = $this->get_snippet( $id, (bool) $atts['network'], 'html' ); return $this->render_snippet_source( $snippet, $atts ); } } front-end/mce-strings.php 0000755 00000003326 15105501234 0011407 0 ustar 00 <?php /** * For some reason, WordPress requires that TinyMCE translations be hosted in an external file. So that's what this is. * * @package Code_Snippets */ namespace Code_Snippets; use _WP_Editors; /** * Variable types. * * @var array<string, string|array<string, Snippet[]>> $strings */ $strings = [ 'insert_content_menu' => __( 'Content Snippet', 'code-snippets' ), 'insert_content_title' => __( 'Insert Content Snippet', 'code-snippets' ), 'snippet_label' => __( 'Snippet', 'code-snippets' ), 'php_att_label' => __( 'Run PHP code', 'code-snippets' ), 'format_att_label' => __( 'Apply formatting', 'code-snippets' ), 'shortcodes_att_label' => __( 'Enable shortcodes', 'code-snippets' ), 'insert_source_menu' => __( 'Snippet Source Code', 'code-snippets' ), 'insert_source_title' => __( 'Insert Snippet Source', 'code-snippets' ), 'show_line_numbers_label' => __( 'Show line numbers', 'code-snippets' ), ]; $strings = array_map( 'esc_js', $strings ); $snippets = get_snippets(); $strings['all_snippets'] = []; $strings['content_snippets'] = []; foreach ( $snippets as $snippet ) { if ( 'content' === $snippet->scope ) { $strings['content_snippets'][ $snippet->id ] = $snippet->display_name; } $strings['all_snippets'][ $snippet->id ] = sprintf( '%s (%s)', $snippet->display_name, strtoupper( $snippet->type ) ); } asort( $strings['all_snippets'], SORT_STRING | SORT_FLAG_CASE ); asort( $strings['content_snippets'], SORT_STRING | SORT_FLAG_CASE ); $strings = [ _WP_Editors::$mce_locale => [ 'code_snippets' => $strings ] ]; /** $strings is used by outer file. @noinspection PhpUnusedLocalVariableInspection */ $strings = 'tinyMCE.addI18n(' . wp_json_encode( $strings ) . ');'; rest-api/class-snippets-rest-controller.php 0000755 00000041235 15105501234 0015111 0 ustar 00 <?php namespace Code_Snippets\REST_API; use Code_Snippets\Export; use Code_Snippets\Snippet; use WP_Error; use WP_REST_Controller; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; use function Code_Snippets\activate_snippet; use function Code_Snippets\code_snippets; use function Code_Snippets\deactivate_snippet; use function Code_Snippets\delete_snippet; use function Code_Snippets\get_snippet; use function Code_Snippets\get_snippets; use function Code_Snippets\save_snippet; use const Code_Snippets\REST_API_NAMESPACE; /** * Allows fetching snippet data through the WordPress REST API. * * @since 3.4.0 * @package Code_Snippets */ final class Snippets_REST_Controller extends WP_REST_Controller { /** * Current API version. */ public const VERSION = 1; /** * The base of this controller's route. */ public const BASE_ROUTE = 'snippets'; /** * The namespace of this controller's route. * * @var string */ protected $namespace = REST_API_NAMESPACE . self::VERSION; /** * The base of this controller's route. * * @var string */ protected $rest_base = self::BASE_ROUTE; /** * Retrieve this controller's REST API base path, including namespace. * * @return string */ public static function get_base_route(): string { return REST_API_NAMESPACE . self::VERSION . '/' . self::BASE_ROUTE; } /** * Retrieve the full base route including the REST API prefix. * * @return string */ public static function get_prefixed_base_route(): string { return '/' . rtrim( rest_get_url_prefix(), '/\\' ) . '/' . self::get_base_route(); } /** * Register REST routes. */ public function register_routes() { $route = '/' . $this->rest_base; $id_route = $route . '/(?P<id>[\d]+)'; $network_args = array_intersect_key( $this->get_endpoint_args_for_item_schema(), [ 'network' ] ); // Allow standard collection parameters (page, per_page, etc.) on the collection route. $collection_args = array_merge( $network_args, $this->get_collection_params() ); register_rest_route( $this->namespace, $route, [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_items' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], 'args' => $collection_args, ], [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [ $this, 'create_item' ], 'permission_callback' => [ $this, 'create_item_permissions_check' ], 'args' => $this->get_endpoint_args_for_item_schema( true ), ], 'schema' => [ $this, 'get_item_schema' ], ] ); register_rest_route( $this->namespace, $id_route, [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_item' ], 'permission_callback' => [ $this, 'get_item_permissions_check' ], 'args' => $network_args, ], [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'update_item' ], 'permission_callback' => [ $this, 'update_item_permissions_check' ], 'args' => $this->get_endpoint_args_for_item_schema( false ), ], [ 'methods' => WP_REST_Server::DELETABLE, 'callback' => [ $this, 'delete_item' ], 'permission_callback' => [ $this, 'delete_item_permissions_check' ], 'args' => $network_args, ], 'schema' => [ $this, 'get_item_schema' ], ] ); register_rest_route( $this->namespace, $route . '/schema', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_public_item_schema' ], 'permission_callback' => '__return_true', ] ); register_rest_route( $this->namespace, $id_route . '/activate', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'activate_item' ], 'permission_callback' => [ $this, 'update_item_permissions_check' ], 'schema' => [ $this, 'get_item_schema' ], 'args' => $network_args, ] ); register_rest_route( $this->namespace, $id_route . '/deactivate', [ 'methods' => WP_REST_Server::EDITABLE, 'callback' => [ $this, 'deactivate_item' ], 'permission_callback' => [ $this, 'update_item_permissions_check' ], 'schema' => [ $this, 'get_item_schema' ], 'args' => $network_args, ] ); register_rest_route( $this->namespace, $id_route . '/export', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'export_item' ], 'permission_callback' => [ $this, 'get_item_permissions_check' ], 'schema' => [ $this, 'get_item_schema' ], 'args' => $network_args, ] ); register_rest_route( $this->namespace, $id_route . '/export-code', [ 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'export_item_code' ], 'permission_callback' => [ $this, 'get_item_permissions_check' ], 'schema' => [ $this, 'get_item_schema' ], 'args' => $network_args, ] ); } /** * Retrieves a collection of snippets, with pagination. * * @param WP_REST_Request $request Full details about the request. * * @return WP_REST_Response Response object on success. */ public function get_items( $request ): WP_REST_Response { $network = $request->get_param( 'network' ); $all_snippets = get_snippets( [], $network ); // Get collection params (page, per_page). $collection_params = $this->get_collection_params(); $per_page_request = (int) $request->get_param( 'per_page' ); $per_page = max( 1, $per_page_request ? $per_page_request : (int) $collection_params['per_page']['default'] ); $page_request = (int) $request->get_param( 'page' ); $page = max( 1, $page_request ? $page_request : (int) $collection_params['page']['default'] ); // Count total items $total_items = count( $all_snippets ); $total_pages = (int) ceil( $total_items / $per_page ); // Slice the full list to the requested page. $offset = ( $page - 1 ) * $per_page; $snippets = array_slice( $all_snippets, $offset, $per_page ); $snippets_data = []; foreach ( $snippets as $snippet ) { $snippet_data = $this->prepare_item_for_response( $snippet, $request ); $snippets_data[] = $this->prepare_response_for_collection( $snippet_data ); } $response = rest_ensure_response( $snippets_data ); $response->header( 'X-WP-Total', (string) $total_items ); $response->header( 'X-WP-TotalPages', (string) $total_pages ); return $response; } /** * Retrieves one item from the collection. * * @param WP_REST_Request $request Full details about the request. * * @return WP_REST_Response|WP_Error Response object on success. */ public function get_item( $request ) { $snippet_id = $request->get_param( 'id' ); $item = get_snippet( $snippet_id, $request->get_param( 'network' ) ); if ( ! $item->id && 0 !== $snippet_id && '0' !== $snippet_id ) { return new WP_Error( 'rest_cannot_get', __( 'The snippet could not be found.', 'code-snippets' ), [ 'status' => 500 ] ); } $data = $this->prepare_item_for_response( $item, $request ); return rest_ensure_response( $data ); } /** * Create one item from the collection * * @param WP_REST_Request|array $request Full data about the request. * * @return WP_REST_Response|WP_Error */ public function create_item( $request ) { $snippet = $this->prepare_item_for_database( $request ); $result = $snippet ? save_snippet( $snippet ) : null; return $result ? $this->prepare_item_for_response( $result, $request ) : new WP_Error( 'rest_cannot_create', __( 'The snippet could not be created.', 'code-snippets' ), [ 'status' => 500 ] ); } /** * Update one item from the collection * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function update_item( $request ) { $snippet_id = absint( $request->get_param( 'id' ) ); $snippet = $snippet_id ? get_snippet( $snippet_id, $request->get_param( 'network' ) ) : null; if ( ! $snippet_id || ! $snippet || ! $snippet->id ) { return new WP_Error( 'rest_cannot_update', __( 'Cannot update a snippet without a valid ID.', 'code-snippets' ), [ 'status' => 400 ] ); } $item = $this->prepare_item_for_database( $request, $snippet ); $result = save_snippet( $item ); if ( $result ) { $request->set_param( 'id', $result->id ); return $this->get_item( $request ); } return new WP_Error( 'rest_cannot_update', __( 'The snippet could not be updated.', 'code-snippets' ), [ 'status' => 500 ] ); } /** * Delete one item from the collection * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function delete_item( $request ) { $item = $this->prepare_item_for_database( $request ); $result = delete_snippet( $item->id, $item->network ); return $result ? new WP_REST_Response( null, 204 ) : new WP_Error( 'rest_cannot_delete', __( 'The snippet could not be deleted.', 'code-snippets' ), [ 'status' => 500 ] ); } /** * Activate one item in the collection. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function activate_item( WP_REST_Request $request ) { $item = $this->prepare_item_for_database( $request ); $result = activate_snippet( $item->id, $item->network ); return $result instanceof Snippet ? rest_ensure_response( $result ) : new WP_Error( 'rest_cannot_activate', $result, [ 'status' => 500 ] ); } /** * Deactivate one item in the collection. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function deactivate_item( WP_REST_Request $request ) { $item = $this->prepare_item_for_database( $request ); $result = deactivate_snippet( $item->id, $item->network ); return $result instanceof Snippet ? rest_ensure_response( $result ) : new WP_Error( 'rest_cannot_activate', __( 'The snippet could not be deactivated.', 'code-snippets' ), [ 'status' => 500 ] ); } /** * Prepare an instance of the Export class from a request. * * @param WP_REST_Request $request Full data about the request. * * @return Export */ protected function build_export( WP_REST_Request $request ): Export { $item = $this->prepare_item_for_database( $request ); return new Export( [ $item->id ], $item->network ); } /** * Retrieve one item in the collection in JSON export format. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function export_item( WP_REST_Request $request ) { $export = $this->build_export( $request ); $result = $export->create_export_object(); return rest_ensure_response( $result ); } /** * Retrieve one item in the collection in the code export format. * * @param WP_REST_Request $request Full data about the request. * * @return WP_Error|WP_REST_Response */ public function export_item_code( WP_REST_Request $request ) { $export = $this->build_export( $request ); $result = $export->export_snippets_code(); return rest_ensure_response( $result ); } /** * Prepares one item for create or update operation. * * @param WP_REST_Request $request Request object. * @param Snippet|null $item Existing item to augment. * * @return Snippet The prepared item. */ protected function prepare_item_for_database( $request, ?Snippet $item = null ): ?Snippet { if ( ! $item instanceof Snippet ) { $item = new Snippet(); } foreach ( $item->get_allowed_fields() as $field ) { if ( isset( $request[ $field ] ) ) { $item->set_field( $field, $request[ $field ] ); } } return $item; } /** * Prepare the item for the REST response. * * @param Snippet $item Snippet object. * @param WP_REST_Request $request Request object. * * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { $schema = $this->get_item_schema(); $response = []; foreach ( array_keys( $schema['properties'] ) as $property ) { $response[ $property ] = $item->$property; } return rest_ensure_response( $response ); } /** * Check if a given request has access to get items. * * @param WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_items_permissions_check( $request ): bool { return code_snippets()->current_user_can(); } /** * Check if a given request has access to get a specific item. * * @param WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_item_permissions_check( $request ): bool { return $this->get_items_permissions_check( $request ); } /** * Check if a given request has access to create items. * * @param WP_REST_Request $request Full data about the request. * * @return boolean */ public function create_item_permissions_check( $request ): bool { return code_snippets()->current_user_can(); } /** * Check if a given request has access to update a specific item. * * @param WP_REST_Request $request Full data about the request. * * @return boolean */ public function update_item_permissions_check( $request ): bool { return $this->create_item_permissions_check( $request ); } /** * Check if a given request has access to delete a specific item. * * @param WP_REST_Request $request Full data about the request. * * @return boolean */ public function delete_item_permissions_check( $request ): bool { return $this->create_item_permissions_check( $request ); } /** * Get our sample schema for a post. * * @return array<string, mixed> The sample schema for a post */ public function get_item_schema(): array { if ( $this->schema ) { return $this->schema; } $this->schema = [ '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'snippet', 'type' => 'object', 'properties' => [ 'id' => [ 'description' => esc_html__( 'Unique identifier for the snippet.', 'code-snippets' ), 'type' => 'integer', 'readonly' => true, ], 'name' => [ 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ), 'type' => 'string', ], 'desc' => [ 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ), 'type' => 'string', ], 'code' => [ 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ), 'type' => 'string', ], 'tags' => [ 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ), 'type' => 'array', 'items' => [ 'type' => 'string', ], ], 'scope' => [ 'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ), 'type' => 'string', ], 'condition_id' => [ 'description' => esc_html__( 'Identifier of condition linked to this snippet.', 'code-snippets' ), 'type' => 'integer', ], 'active' => [ 'description' => esc_html__( 'Snippet activation status.', 'code-snippets' ), 'type' => 'boolean', ], 'priority' => [ 'description' => esc_html__( 'Relative priority in which the snippet is executed.', 'code-snippets' ), 'type' => 'integer', ], 'network' => [ 'description' => esc_html__( 'Whether the snippet is network-wide instead of site-wide.', 'code-snippets' ), 'type' => [ 'boolean', 'null' ], 'default' => null, ], 'shared_network' => [ 'description' => esc_html__( 'If a network snippet, whether can be activated on discrete sites instead of network-wide.', 'code-snippets' ), 'type' => [ 'boolean', 'null' ], ], 'modified' => [ 'description' => esc_html__( 'Date and time when the snippet was last modified, in ISO format.', 'code-snippets' ), 'type' => 'string', 'format' => 'date-time', 'readonly' => true, ], 'code_error' => [ 'description' => esc_html__( 'Error message if the snippet code could not be parsed.', 'code-snippets' ), 'type' => 'string', 'readonly' => true, ], ], ]; return $this->schema; } } settings/class-setting-field.php 0000755 00000014757 15105501234 0012773 0 ustar 00 <?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 ) ); } } settings/class-version-switch.php 0000755 00000027621 15105501234 0013213 0 ustar 00 <?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-top: 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(); settings/editor-preview.php 0000755 00000007741 15105501234 0012072 0 ustar 00 <?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>'; } settings/settings-fields.php 0000755 00000021551 15105501234 0012224 0 ustar 00 <?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, ], '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; } settings/settings.php 0000755 00000023714 15105501234 0010763 0 ustar 00 <?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; 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 ); 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 ); 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 ); 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 ); $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 ); if ( ! is_null( $sanitized_value ) && $settings[ $section_id ][ $field_id ] !== $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; } views/partials/cloud-search.php 0000755 00000005003 15105501234 0012577 0 ustar 00 <?php /** * HTML for the cloud search tab * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; /** * Loaded from manage menu. * * @var Manage_Menu $this */ $search_query = isset( $_REQUEST['cloud_search'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['cloud_search'] ) ) : ''; $cloud_select = sanitize_key( wp_unslash( $_REQUEST['cloud_select'] ?? '' ) ); ?> <p class="cloud-search-info"> <?php esc_html_e( 'Use the search bar below to search cloud snippets by entering either the name of a codevault or keywords.', 'code-snippets' ); ?> <small> <?php esc_html_e( '(Note: codevault name is case and spelling sensitive and only public snippets will be shown)', 'code-snippets' ); ?> </small> </p> <form method="get" action="" id="cloud-search-form"> <?php List_Table::required_form_fields( 'search_box' ); ?> <label class="screen-reader-text" for="cloud_search"> <?php esc_html_e( 'Search cloud snippets', 'code-snippets' ); ?> </label> <?php if ( isset( $_REQUEST['type'] ) ) { printf( '<input type="hidden" name="type" value="%s">', esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) ) ); } ?> <div class="heading-box"> <p class="cloud-search-heading"> <label for="cloud-select-prepend"><?php esc_html_e( 'Search Cloud', 'code-snippets' ); ?></label> </p> </div> <div class="input-group"> <select id="cloud-select-prepend" class="select-prepend" name="cloud_select"> <option value="term"<?php selected( $cloud_select, 'term' ); ?>> <?php esc_html_e( 'Search by keyword(s)', 'code-snippets' ); ?> </option> <option value="codevault"<?php selected( $cloud_select, 'codevault' ); ?>> <?php esc_html_e( 'Name of codevault', 'code-snippets' ); ?> </option> </select> <input type="text" id="cloud_search" name="cloud_search" class="cloud_search" value="<?php echo esc_html( $search_query ); ?>" placeholder="<?php esc_attr_e( 'e.g. Remove unused javascript…', 'code-snippets' ); ?>"> <button type="submit" id="cloud-search-submit" class="button"> <?php esc_html_e( 'Search Cloud', 'code-snippets' ); ?> <span class="dashicons dashicons-search cloud-search"></span> </button> </div> </form> <form method="post" action="" id="cloud-search-results"> <input type="hidden" id="code_snippets_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'code_snippets_manage_ajax' ) ); ?>"> <?php List_Table::required_form_fields(); if ( $search_query ) { $this->cloud_search_list_table->display(); } ?> </form> views/partials/list-table-notices.php 0000755 00000004536 15105501234 0013742 0 ustar 00 <?php /** * HTML for displaying notices for the manage table. * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; /** * Loaded from the manage menu. * * @var Manage_Menu $this */ /** * Constant existence is checked with defined(). * * @noinspection PhpUndefinedConstantInspection */ if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) { ?> <div id="message" class="notice notice-error fade is-dismissible"> <p> <strong><?php esc_html_e( 'Warning:', 'code-snippets' ); ?></strong> <?php // translators: 1: constant name, 2: file name. $text = __( 'Safe mode is active and snippets will not execute! Remove the %1$s constant from %2$s file to turn off safe mode.', 'code-snippets' ); printf( esc_html( $text ), '<code>CODE_SNIPPETS_SAFE_MODE</code>', '<code>wp-config.php</code>' ); ?> <a href="https://help.codesnippets.pro/article/12-safe-mode" target="_blank"> <?php esc_html_e( 'Help', 'code-snippets' ); ?> </a> </p> </div> <?php } if ( empty( $_REQUEST['result'] ) ) { return; } $result = sanitize_key( $_REQUEST['result'] ); $result_messages = apply_filters( 'code_snippets/manage/result_messages', [ 'executed' => __( 'Snippet <strong>executed</strong>.', 'code-snippets' ), 'activated' => __( 'Snippet <strong>activated</strong>.', 'code-snippets' ), 'activated-multi' => __( 'Selected snippets <strong>activated</strong>.', 'code-snippets' ), 'deactivated' => __( 'Snippet <strong>deactivated</strong>.', 'code-snippets' ), 'deactivated-multi' => __( 'Selected snippets <strong>deactivated</strong>.', 'code-snippets' ), 'deleted' => __( 'Snippet <strong>deleted</strong>.', 'code-snippets' ), 'deleted-multi' => __( 'Selected snippets <strong>deleted</strong>.', 'code-snippets' ), 'cloned' => __( 'Snippet <strong>cloned</strong>.', 'code-snippets' ), 'cloned-multi' => __( 'Selected snippets <strong>cloned</strong>.', 'code-snippets' ), 'cloud-refreshed' => __( 'Synced cloud data has been <strong>successfully</strong> refreshed.', 'code-snippets' ), ] ); if ( isset( $result_messages[ $result ] ) ) { $result_kses = [ 'strong' => [], ]; printf( '<div id="message" class="notice notice-success fade is-dismissible"><p>%s</p></div>', wp_kses( $result_messages[ $result ], $result_kses ) ); } views/partials/list-table.php 0000755 00000001241 15105501234 0012266 0 ustar 00 <?php /** * HTML for the all snippets and codevault list table * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; /** * Loaded from the manage menu. * * @var Manage_Menu $this */ ?> <form method="get" action=""> <?php List_Table::required_form_fields( 'search_box' ); $this->list_table->search_box( __( 'Search Snippets', 'code-snippets' ), 'search_id' ); ?> </form> <form method="post" action=""> <input type="hidden" id="code_snippets_ajax_nonce" value="<?php echo esc_attr( wp_create_nonce( 'code_snippets_manage_ajax' ) ); ?>"> <?php List_Table::required_form_fields(); $this->list_table->display(); ?> </form> views/import.php 0000755 00000006646 15105501234 0007737 0 ustar 00 <?php /** * HTML for the Import Snippets page. * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; /** * Loaded from import menu. * * @var Import_Menu $this */ if ( ! defined( 'ABSPATH' ) ) { return; } $max_size_bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() ); ?> <div class="wrap"> <h1> <?php esc_html_e( 'Import Snippets', 'code-snippets' ); if ( code_snippets()->is_compact_menu() ) { $this->render_page_title_actions( [ 'manage', 'add', 'settings' ] ); } ?> </h1> <?php $this->print_messages(); ?> <div class="narrow"> <p><?php esc_html_e( 'Upload one or more Code Snippets export files and the snippets will be imported.', 'code-snippets' ); ?></p> <p> <?php /* translators: %s: link to snippets admin menu */ $text = __( 'Afterward, you will need to visit the <a href="%s" >All Snippets</a> page to activate the imported snippets.', 'code-snippets' ); $url = esc_url( code_snippets()->get_menu_url( 'manage' ) ); echo wp_kses( sprintf( $text, $url ), array( 'a' => array( 'href' => array(), 'target' => array(), ), ) ); ?> </p> <form enctype="multipart/form-data" id="import-upload-form" method="post" class="wp-upload-form" name="code_snippets_import"> <?php wp_nonce_field( 'import_code_snippets_file' ); ?> <h2><?php esc_html_e( 'Duplicate Snippets', 'code-snippets' ); ?></h2> <p class="description"> <?php esc_html_e( 'What should happen if an existing snippet is found with an identical name to an imported snippet?', 'code-snippets' ); ?> </p> <fieldset> <p> <label> <input type="radio" name="duplicate_action" value="ignore" checked="checked"> <?php esc_html_e( 'Ignore any duplicate snippets: import all snippets from the file regardless and leave all existing snippets unchanged.', 'code-snippets' ); ?> </label> </p> <p> <label> <input type="radio" name="duplicate_action" value="replace"> <?php esc_html_e( 'Replace any existing snippets with a newly imported snippet of the same name.', 'code-snippets' ); ?> </label> </p> <p> <label> <input type="radio" name="duplicate_action" value="skip"> <?php esc_html_e( 'Do not import any duplicate snippets; leave all existing snippets unchanged.', 'code-snippets' ); ?> </label> </p> </fieldset> <h2><?php esc_html_e( 'Upload Files', 'code-snippets' ); ?></h2> <p class="description"> <?php esc_html_e( 'Choose one or more Code Snippets (.xml or .json) files to upload, then click "Upload files and import".', 'code-snippets' ); ?> </p> <fieldset> <p> <label for="upload"><?php esc_html_e( 'Choose files from your computer:', 'code-snippets' ); ?></label> <?php /* translators: %s: size in bytes */ printf( esc_html__( '(Maximum size: %s)', 'code-snippets' ), esc_html( size_format( $max_size_bytes ) ) ); ?> <input type="file" id="upload" name="code_snippets_import_files[]" size="25" accept="application/json,.json,text/xml" multiple="multiple"> <input type="hidden" name="action" value="save"> <input type="hidden" name="max_file_size" value="<?php echo esc_attr( $max_size_bytes ); ?>"> </p> </fieldset> <?php do_action( 'code_snippets/admin/import_form' ); submit_button( __( 'Upload files and import', 'code-snippets' ) ); ?> </form> </div> </div> views/manage.php 0000755 00000007616 15105501234 0007653 0 ustar 00 <?php /** * HTML for the Manage Snippets page. * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; use function Code_Snippets\Settings\get_setting; /** * Loaded from the manage menu class. * * @var Manage_Menu $this */ if ( ! defined( 'ABSPATH' ) ) { return; } $types = array_merge( [ 'all' => __( 'All Snippets', 'code-snippets' ) ], Plugin::get_types() ); $current_type = $this->get_current_type(); if ( false !== strpos( code_snippets()->version, 'beta' ) ) { echo '<div class="notice beta-test-notice"><p id="beta-testing">'; echo wp_kses( __( 'Thank you for testing this <span class="highlight-yellow">beta version of Code Snippets</span>. We would love to hear your thoughts.', 'code-snippets' ), [ 'span' => [ 'class' => [ 'highlight-yellow' ] ] ] ); printf( ' <a href="%s" class="button button-secondary" target="_blank">%s</a>', esc_url( __( 'https://codesnippets.pro/beta-testing/feedback/', 'code-snippets' ) ), esc_html__( 'Share feedback', 'code-snippets' ) ); echo '</p></div>'; } ?> <div class="wrap"> <h1> <?php esc_html_e( 'Snippets', 'code-snippets' ); $this->render_page_title_actions( code_snippets()->is_compact_menu() ? [ 'add', 'import', 'settings' ] : [ 'add', 'import' ] ); $this->list_table->search_notice(); ?> </h1> <?php $this->print_messages(); ?> <h2 class="nav-tab-wrapper" id="snippet-type-tabs"> <?php Admin::render_snippet_type_tabs( $types, $current_type ); if ( ! get_setting( 'general', 'hide_upgrade_menu' ) ) { ?> <a class="button button-large nav-tab-button nav-tab-inactive" href="https://codesnippets.pro/pricing/" target="_blank" aria-label="<?php esc_attr_e( 'Find more about Pro (opens in external tab)', 'code-snippets' ); ?>"> <?php echo wp_kses( __( 'Upgrade to <span class="badge pro-badge small-badge">Pro</span>', 'code-snippets' ), [ 'span' => [ 'class' => true ] ] ); ?> <span class="dashicons dashicons-external"></span> </a> <?php } ?> </h2> <?php $type_info = [ 'php' => [ __( 'Function snippets are run on your site as if there were in a plugin or theme functions.php file.', 'code-snippets' ), __( 'Learn more about function snippets →', 'code-snippets' ), 'https://codesnippets.pro/learn-php/', ], 'html' => [ __( 'Content snippets are bits of reusable PHP and HTML content that can be inserted into posts and pages.', 'code-snippets' ), __( 'Learn more about content snippets →', 'code-snippets' ), 'https://codesnippets.pro/learn-html/', ], 'css' => [ __( 'Style snippets are written in CSS and loaded in the admin area or on the site front-end, just like the theme style.css.', 'code-snippets' ), __( 'Learn more about style snippets →', 'code-snippets' ), 'https://codesnippets.pro/learn-css/', ], 'js' => [ __( 'Script snippets are loaded on the site front-end in a JavaScript file, either in the head or body sections.', 'code-snippets' ), __( 'Learn more about javascript snippets →', 'code-snippets' ), 'https://codesnippets.pro/learn-js/', ], 'cloud' => [ __( 'See all your public and private snippets that are stored in your Code Snippet Cloud codevault.', 'code-snippets' ), __( 'Learn more about Code Snippets Cloud →', 'code-snippets' ), 'https://codesnippets.cloud/getstarted/', ], ]; if ( isset( $type_info[ $current_type ] ) ) { $info = $type_info[ $current_type ]; printf( '<p class="snippet-type-description">%s <a href="%s" target="_blank">%s</a></p>', esc_html( $info[0] ), esc_url( $info[2] ), esc_html( $info[1] ) ); } do_action( 'code_snippets/admin/manage/before_list_table' ); $this->list_table->views(); switch ( $current_type ) { case 'cloud_search': include_once 'partials/cloud-search.php'; break; default: include_once 'partials/list-table.php'; break; } do_action( 'code_snippets/admin/manage', $current_type ); ?> </div> views/welcome.php 0000755 00000015520 15105501234 0010047 0 ustar 00 <?php /** * HTML for the welcome page. * * @package Code_Snippets * @subpackage Views */ namespace Code_Snippets; /** * Loaded from the Welcome_Menu class. * * @var Welcome_Menu $this */ if ( ! defined( 'ABSPATH' ) ) { return; } $hero = $this->api->get_hero_item(); $changelog_sections = [ 'Added' => [ 'title' => __( 'New features', 'code-snippets' ), 'icon' => 'lightbulb', ], 'Improved' => [ 'title' => __( 'Improvements', 'code-snippets' ), 'icon' => 'chart-line', ], 'Fixed' => [ 'title' => __( 'Bug fixes', 'code-snippets' ), 'icon' => 'buddicons-replies', ], 'Other' => [ 'title' => __( 'Other', 'code-snippets' ), 'icon' => 'open-folder', ], ]; $plugin_types = [ 'core' => __( 'Core', 'code-snippets' ), 'pro' => __( 'Pro', 'code-snippets' ), ]; ?> <div class="csp-welcome-wrap"> <div class="csp-welcome-header"> <header> <img width="50px" src="<?php echo esc_url( plugins_url( 'assets/icon.svg', PLUGIN_FILE ) ); ?>" alt="<?php esc_attr_e( 'Code Snippets Logo', 'code-snippets' ); ?>"> <h1> <?php echo wp_kses( __( "Resources and <span>What's New</span>", 'code-snippets' ), [ 'span' => [] ] ); ?> </h1> </header> <nav> <ul> <?php foreach ( $this->get_header_links() as $link_name => $link_info ) { ?> <li> <a href="<?php echo esc_url( $link_info['url'] ); ?>" target="_blank" class="csp-link-<?php echo esc_attr( $link_name ); ?>"> <span><?php echo esc_html( $link_info['label'] ); ?></span> <?php if ( 'discord' === $link_info['icon'] ) { ?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"> <path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z" /> </svg> <?php } else { ?> <span class="dashicons dashicons-<?php echo esc_attr( $link_info['icon'] ); ?>"></span> <?php } ?> </a> </li> <?php } ?> </ul> </nav> </div> <section class="csp-section-changes"> <h1>📰 <?php esc_html_e( 'Latest news', 'code-snippets' ); ?></h1> <div class="csp-cards"> <a class="csp-card" href="<?php echo esc_url( $hero['follow_url'] ); ?>" target="_blank" title="<?php esc_attr_e( 'Read more', 'code-snippets' ); ?>"> <header> <span class="dashicons dashicons-external"></span> <h2><?php echo esc_html( $hero['name'] ); ?></h2> </header> <figure> <div id="csp-loading-spinner" class="csp-loading-spinner"></div> <img id="csp-changes-img" onload="hideLoadingAnimation()" src="<?php echo esc_url( $hero['image_url'] ); ?>" alt="<?php esc_attr_e( 'Latest news image', 'code-snippets' ); ?>);"> </figure> </a> <a class="csp-card csp-changelog-wrapper" href="https://wordpress.org/plugins/code-snippets/changelog" target="_blank" title="<?php esc_attr_e( 'Read the full changelog', 'code-snippets' ); ?>"> <header> <span class="dashicons dashicons-external"></span> <h2><?php esc_html_e( 'Latest changes', 'code-snippets' ); ?></h2> </header> <div class="csp-section-changelog"> <?php foreach ( $this->api->get_changelog() as $version => $version_changes ) { ?> <h3><?php echo esc_html( $version ); ?></h3> <article> <?php foreach ( $changelog_sections as $section_key => $section ) { if ( empty( $version_changes[ $section_key ] ) ) { continue; } ?> <h4> <span class="dashicons dashicons-<?php echo esc_attr( $section['icon'] ); ?>"></span> <?php echo esc_html( $section['title'] ); ?> </h4> <ul> <?php foreach ( $plugin_types as $plugin_type => $type_label ) { if ( empty( $version_changes[ $section_key ][ $plugin_type ] ) ) { continue; } foreach ( $version_changes[ $section_key ][ $plugin_type ] as $change ) { ?> <li> <span class="badge <?php echo esc_attr( $plugin_type ); ?>-badge"> <?php echo esc_html( $type_label ); ?> </span> <span><?php echo esc_html( $change ); ?></span> </li> <?php } } ?> </ul> <?php } ?> </article> <?php } ?> </div> </a> </div> </section> <section class="csp-section-articles csp-section-links"> <h1>🛟 <?php esc_html_e( 'Helpful articles', 'code-snippets' ); ?></h1> <div class="csp-cards"> <?php foreach ( $this->api->get_features() as $feature ) { ?> <a class="csp-card" href="<?php echo esc_url( $feature['follow_url'] ); ?>" target="_blank" title="<?php esc_attr_e( 'Read more', 'code-snippets' ); ?>"> <figure> <img src="<?php echo esc_url( $feature['image_url'] ); ?>" alt="<?php esc_attr_e( 'Feature image', 'code-snippets' ); ?>"> </figure> <header> <h2><?php echo esc_html( $feature['title'] ); ?></h2> <p class="csp-card-item-description"><?php echo esc_html( $feature['description'] ); ?></p> </header> <footer> <p class="csp-card-item-category"><?php echo esc_html( $feature['category'] ); ?></p> <span class="dashicons dashicons-external"></span> </footer> </a> <?php } ?> </div> </section> <section class="csp-section-links csp-section-partners"> <h1>🚀 <?php esc_html_e( 'Partners and apps', 'code-snippets' ); ?></h1> <div class="csp-cards"> <?php foreach ( $this->api->get_partners() as $partner ) { ?> <a class="csp-card" href="<?php echo esc_url( $partner['follow_url'] ); ?>" target="_blank" title="<?php esc_attr_e( 'Go to Partner', 'code-snippets' ); ?>"> <figure> <img src="<?php echo esc_url( $partner['image_url'] ); ?>" alt="<?php esc_attr_e( 'Partner image', 'code-snippets' ); ?>"> </figure> <header> <span class="dashicons dashicons-external"></span> <h2><?php echo esc_html( $partner['title'] ); ?></h2> </header> </a> <?php } ?> </div> </section> </div> <script type="text/javascript"> function hideLoadingAnimation() { const spinner = document.getElementById<HTMLDivElement>('csp-loading-spinner') const image = document.getElementById<HTMLDivElement>('csp-changes-img') spinner.style.display = 'none' image.style.display = 'block' } </script> class-admin.php 0000755 00000026350 15105501234 0007455 0 ustar 00 <?php namespace Code_Snippets; use DateTimeImmutable; use DateTimeZone; use Exception; /** * Functions specific to the administration interface * * @package Code_Snippets */ class Admin { /** * Admin_Menu class instances * * @var array<string, Admin_Menu> */ public array $menus = array(); /** * Welcome_API class instance. * * @var Welcome_API */ public Welcome_API $welcome_api; /** * Class constructor */ public function __construct() { if ( is_admin() ) { $this->welcome_api = new Welcome_API(); $this->run(); } } /** * Initialise classes */ public function load_classes() { $this->menus['manage'] = new Manage_Menu(); $this->menus['edit'] = new Edit_Menu(); $this->menus['import'] = new Import_Menu(); if ( is_network_admin() === Settings\are_settings_unified() ) { $this->menus['settings'] = new Settings_Menu(); } $this->menus['welcome'] = new Welcome_Menu( $this->welcome_api ); foreach ( $this->menus as $menu ) { $menu->run(); } } /** * Register action and filter hooks */ public function run() { add_action( 'init', array( $this, 'load_classes' ), 11 ); add_filter( 'mu_menu_items', array( $this, 'mu_menu_items' ) ); add_filter( 'plugin_action_links_' . plugin_basename( PLUGIN_FILE ), array( $this, 'plugin_action_links' ), 10, 2 ); add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 ); add_filter( 'debug_information', array( $this, 'debug_information' ) ); add_action( 'code_snippets/admin/manage', array( $this, 'print_notices' ) ); } /** * Allow super admins to control site admin access to * snippet admin menus * * Adds a checkbox to the *Settings > Network Settings* * network admin menu * * @param array<string, string> $menu_items Current mu menu items. * * @return array<string, string> The modified mu menu items * * @since 1.7.1 */ public function mu_menu_items( array $menu_items ): array { $menu_items['snippets'] = __( 'Snippets', 'code-snippets' ); $menu_items['snippets_settings'] = __( 'Snippets » Settings', 'code-snippets' ); return $menu_items; } /** * Modify the action links for this plugin. * * @param array<string> $actions Existing plugin action links. * @param string $plugin_file The plugin the links are for. * * @return array<string> Modified plugin action links. * @since 2.0.0 */ public function plugin_action_links( array $actions, string $plugin_file ): array { if ( plugin_basename( PLUGIN_FILE ) !== $plugin_file ) { return $actions; } $format = '<a href="%1$s" title="%2$s">%3$s</a>'; $actions = array_merge( [ sprintf( $format, esc_url( code_snippets()->get_menu_url( 'settings' ) ), esc_attr__( 'Change plugin settings', 'code-snippets' ), esc_html__( 'Settings', 'code-snippets' ) ), sprintf( $format, esc_url( code_snippets()->get_menu_url() ), esc_attr__( 'Manage your existing snippets', 'code-snippets' ), esc_html__( 'Snippets', 'code-snippets' ) ), ], $actions ); if ( ! code_snippets()->licensing->is_licensed() ) { $actions[] = sprintf( '<a href="%1$s" title="%2$s" style="color: #d46f4d; font-weight: bold;" target="_blank">%3$s</a>', 'https://snipco.de/JE2i', esc_attr__( 'Upgrade to Code Snippets Pro', 'code-snippets' ), esc_attr__( 'Upgrade to Pro', 'code-snippets' ) ); } return $actions; } /** * Adds extra links related to the plugin * * @param array<string> $plugin_meta Existing plugin info links. * @param string $plugin_file The plugin the links are for. * * @return array<string> Modified plugin info links. * @since 2.0.0 */ public function plugin_row_meta( array $plugin_meta, string $plugin_file ): array { if ( plugin_basename( PLUGIN_FILE ) !== $plugin_file ) { return $plugin_meta; } $format = '<a href="%1$s" title="%2$s" target="_blank">%3$s</a>'; return array_merge( $plugin_meta, array( sprintf( $format, 'https://help.codesnippets.pro/', esc_attr__( 'Find out how to get support with Code Snippets', 'code-snippets' ), esc_html__( 'Docs and Support', 'code-snippets' ) ), sprintf( $format, 'https://www.facebook.com/groups/codesnippetsplugin/', esc_attr__( 'Join our community on Facebook', 'code-snippets' ), esc_html__( 'Community', 'code-snippets' ) ), ) ); } /** * Add Code Snippets information to Site Health information. * * @param array<string, array<string, mixed>> $info Current Site Health information. * * @return array<string, array<string, mixed>> Updated Site Health information. * @author sc0ttkclark */ public function debug_information( array $info ): array { $fields = array(); // build the debug information from snippet data. foreach ( get_snippets() as $snippet ) { $values = [ $snippet->scope_name ]; $debug = []; if ( ! $snippet->active ) { continue; } if ( $snippet->name ) { $debug[] = 'name: ' . $snippet->name; } $debug[] = 'scope: ' . $snippet->scope; if ( $snippet->modified ) { /* translators: %s: formatted last modified date */ $values[] = sprintf( __( 'Last modified %s', 'code-snippets' ), $snippet->format_modified( false ) ); $debug[] = 'modified: ' . $snippet->modified; } if ( $snippet->tags ) { $values[] = $snippet->tags_list; $debug[] = 'tags: [' . $snippet->tags_list . ']'; } $fields[ 'snippet-' . $snippet->id ] = [ 'label' => $snippet->display_name, 'value' => implode( "\n | ", $values ), 'debug' => implode( ', ', $debug ), ]; } $snippets_info = array( 'label' => __( 'Active Snippets', 'code-snippets' ), 'show_count' => true, 'fields' => $fields, ); // attempt to insert the new section right after the Inactive Plugins section. $index = array_search( 'wp-plugins-inactive', array_keys( $info ), true ); if ( false === $index ) { $info['code-snippets'] = $snippets_info; } else { $info = array_merge( array_slice( $info, 0, $index + 1 ), [ 'code-snippets' => $snippets_info ], array_slice( $info, $index + 1 ) ); } return $info; } /** * Print any admin notices that have not been dismissed. * * @return void */ public function print_notices() { global $current_user; if ( apply_filters( 'code_snippets/hide_welcome_banner', false ) ) { return; } $meta_key = 'ignore_code_snippets_survey_message'; $dismissed = get_user_meta( $current_user->ID, $meta_key, false ); if ( isset( $_GET[ $meta_key ], $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), $meta_key ) ) { add_user_meta( $current_user->ID, $meta_key, sanitize_key( wp_unslash( $_GET[ $meta_key ] ) ) ); return; } $welcome = $this->welcome_api->get_banner(); try { $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) ); } catch ( Exception $e ) { $now = $welcome['start_datetime']; } if ( ! empty( $welcome['key'] ) && ! in_array( $welcome['key'], $dismissed, true ) && ( empty( $welcome['start_datetime'] ) || $now >= $welcome['start_datetime'] ) && ( empty( $welcome['end_datetime'] ) || $now <= $welcome['end_datetime'] ) ) { $notice = $welcome['key']; $text = $welcome['text_free']; $action_url = $welcome['action_url_free']; $action_label = $welcome['action_label_free']; } elseif ( ! in_array( 'survey', $dismissed, true ) && ! in_array( 'true', $dismissed, true ) ) { $notice = 'survey'; $action_url = 'https://codesnippets.pro/survey/'; $action_label = __( 'Take the survey now', 'code-snippets' ); $text = __( "<strong>Have feedback on Code Snippets?</strong> Please take the time to answer a short survey on how you use this plugin and what you'd like to see changed or added in the future.", 'code-snippets' ); } else { return; } printf( '<div class="notice notice-info code-snippets-notice code-snippets-%s-notice is-dismissible"><p>', esc_attr( sanitize_key( $notice ) ) ); echo wp_kses_post( $text ); printf( '<a href="%s" class="button button-secondary" target="_blank" style="margin-block: auto; margin-inline: .5em;">%s</a>', esc_url( $action_url ), esc_html( $action_label ) ); printf( '<a href="%s" class="notice-dismiss"><span class="screen-reader-text">%s</span></a>', esc_url( wp_nonce_url( add_query_arg( $meta_key, $notice ), $meta_key ) ), esc_html__( 'Dismiss', 'code-snippets' ) ); echo '</p></div>'; } /** * Render a badge for a snippet type in the nav tabs. * * @param string $type_name Identifier of the snippet type. */ private static function render_snippet_tab_badge( string $type_name ) { if ( 'all' !== $type_name ) { printf( '<span class="badge %s-badge">', esc_attr( $type_name ) ); switch ( $type_name ) { case 'cloud': echo '<span class="dashicons dashicons-cloud"></span>'; break; case 'cloud_search': echo '<span class="dashicons dashicons-search"></span>'; break; case 'bundles': echo '<span class="dashicons dashicons-screenoptions"></span>'; break; case 'ai': echo '<span class="ai-icon">', esc_html__( 'AI', 'code-snippets' ), '</span>'; break; case 'cond': echo '<span class="dashicons dashicons-randomize"></span>'; break; default: echo esc_html( $type_name ); break; } echo '</span>'; } } /** * Render a nav tab for a snippet type. * * @param array{string, string} $type_labels Associative array of snippet type identifiers and their labels. * @param string $current_type Identifier of currently-selected type. * * @return void */ public static function render_snippet_type_tabs( array $type_labels, string $current_type = '' ) { $is_licensed = code_snippets()->licensing->is_licensed(); $pro_types = [ 'css', 'js', 'cond', 'cloud', 'bundles' ]; $cloud_tabs = [ 'cloud', 'bundles' ]; foreach ( $type_labels as $type_name => $label ) { if ( ! $is_licensed && in_array( $type_name, $pro_types, true ) ) { continue; } if ( $type_name === $current_type ) { printf( '<a class="nav-tab nav-tab-active %s-tab">', esc_attr( $type_name ) ); } else { $current_url = remove_query_arg( [ 'cloud_select', 'cloud_search' ] ); $nav_tab_inactive = in_array( $type_name, $cloud_tabs, true ) && ! code_snippets()->cloud_api->is_cloud_key_verified(); printf( '<a class="%s %s-tab" href="%s">', $nav_tab_inactive ? 'nav-tab nav-tab-inactive' : 'nav-tab', esc_attr( $type_name ), esc_url( add_query_arg( 'type', $type_name, $current_url ) ) ); } printf( '<span class="%s">%s</span>', esc_attr( 'all' === $type_name ? 'all-snippets-label' : 'snippet-label' ), esc_html( $label ) ); self::render_snippet_tab_badge( $type_name ); echo '</a>'; } foreach ( $type_labels as $type_name => $label ) { if ( $is_licensed || ! in_array( $type_name, $pro_types, true ) ) { continue; } printf( '<a class="nav-tab nav-tab-inactive %s-tab" href="%s" target="_blank" aria-label="%s">%s', esc_attr( $type_name ), esc_url( 'https://codesnippets.pro/pricing/' ), esc_attr__( 'Find more about Pro (opens in external tab)', 'code-snippets' ), esc_html( $label ) ); self::render_snippet_tab_badge( $type_name ); echo '</a>'; } } } class-contextual-help.php 0000755 00000014750 15105501234 0011502 0 ustar 00 <?php namespace Code_Snippets; use WP_Screen; /** * This file holds all the content for the contextual help screens. * * @package Code_Snippets */ class Contextual_Help { /** * Current screen object * * @see get_current_screen() * * @var WP_Screen */ public WP_Screen $screen; /** * Name of current screen * * @see get_current_screen() * * @var string */ public string $screen_name; /** * Class constructor * * @param string $screen_name Name of current screen. */ public function __construct( string $screen_name ) { $this->screen_name = $screen_name; } /** * Load the contextual help */ public function load() { $this->screen = get_current_screen(); switch ( $this->screen_name ) { case 'manage': $this->load_manage_help(); break; case 'edit': $this->load_edit_help(); break; case 'import': $this->load_import_help(); break; } $this->load_help_sidebar(); } /** * Load the help sidebar */ private function load_help_sidebar() { $sidebar_links = [ 'https://wordpress.org/plugins/code-snippets' => __( 'About Plugin', 'code-snippets' ), 'https://help.codesnippets.pro/collection/3-faq' => __( 'FAQ', 'code-snippets' ), 'https://wordpress.org/support/plugin/code-snippets' => __( 'Support Forum', 'code-snippets' ), 'https://codesnippets.pro' => __( 'Plugin Website', 'code-snippets' ), ]; $kses = [ 'p' => [], 'strong' => [], 'a' => [ 'href' => [] ], ]; $contents = sprintf( "<p><strong>%s</strong></p>\n", esc_html__( 'For more information:', 'code-snippets' ) ); foreach ( $sidebar_links as $url => $label ) { $contents .= "\n" . sprintf( '<p><a href="%s">%s</a></p>', esc_url( $url ), esc_html( $label ) ); } $this->screen->set_help_sidebar( wp_kses( $contents, $kses ) ); } /** * Add a help tab to the current screen. * * @param string $id Screen ID. * @param string $title Screen title. * @param string|array<string> $paragraphs List of paragraphs to display as content. * * @return void */ private function add_help_tab( string $id, string $title, $paragraphs ) { $this->screen->add_help_tab( array( 'title' => $title, 'id' => $id, 'content' => wp_kses_post( implode( "\n", array_map( function ( $content ) { return '<p>' . $content . '</p>'; }, is_array( $paragraphs ) ? $paragraphs : [ $paragraphs ] ) ) ), ) ); } /** * Reusable introduction text * * @return string */ private function get_intro_text(): string { return __( 'Snippets are similar to plugins - they both extend and expand the functionality of WordPress. Snippets are more light-weight, just a few lines of code, and do not put as much load on your server. ', 'code-snippets' ); } /** * Register and handle the help tabs for the manage snippets admin page */ private function load_manage_help() { $this->add_help_tab( 'overview', __( 'Overview', 'code-snippets' ), $this->get_intro_text() . __( 'Here you can manage your existing snippets and perform tasks on them such as activating, deactivating, deleting and exporting.', 'code-snippets' ) ); $this->add_help_tab( 'safe-mode', __( 'Safe Mode', 'code-snippets' ), [ __( 'Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets' ), __( "If something goes wrong with a snippet, and you can't use WordPress, you can cause all snippets to stop executing by turning on <strong>safe mode</strong>.", 'code-snippets' ), /* translators: %s: URL to Code Snippets Pro Docs */ sprintf( __( 'You can find out how to enable safe mode in the <a href="%s">Code Snippets Pro Docs</a>.', 'code-snippets' ), 'https://help.codesnippets.pro/article/12-safe-mode' ) ] ); } /** * Register and handle the help tabs for the single snippet admin page */ private function load_edit_help() { $this->add_help_tab( 'overview', __( 'Overview', 'code-snippets' ), [ $this->get_intro_text() . __( 'Here you can add a new snippet, or edit an existing one.', 'code-snippets' ), /* translators: %s: URL to Code Snippets Pro Docs */ sprintf( __( "If you're not sure about the types of snippets you can add, take a look at the <a href=\"%s\">Code Snippets Pro Docs</a> for inspiration.", 'code-snippets' ), 'https://help.codesnippets.pro/collection/2-adding-snippets' ), ] ); $this->add_help_tab( 'adding', __( 'Adding Snippets', 'code-snippets' ), [ __( 'You need to fill out the name and code fields for your snippet to be added. While the description field will add more information about how your snippet works, what is does and where you found it, it is completely optional.', 'code-snippets' ), __( 'Please be sure to check that your snippet is valid PHP code and will not produce errors before adding it through this page. While doing so will not become active straight away, it will help to minimize the chance of a faulty snippet becoming active on your site.', 'code-snippets' ), ] ); } /** * Register and handle the help tabs for the import snippets admin page */ private function load_import_help() { $manage_url = code_snippets()->get_menu_url( 'manage' ); $this->add_help_tab( 'overview', __( 'Overview', 'code-snippets' ), $this->get_intro_text() . __( 'Here you can load snippets from a code snippets export file into the database alongside existing snippets.', 'code-snippets' ) ); $this->add_help_tab( 'import', __( 'Importing', 'code-snippets' ), __( 'You can load your snippets from a code snippets export file using this page.', 'code-snippets' ) . /* translators: %s: URL to Snippets admin menu */ sprintf( __( 'Imported snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.', 'code-snippets' ), $manage_url ) ); $this->add_help_tab( 'export', __( 'Exporting', 'code-snippets' ), /* translators: %s: URL to Manage Snippets admin menu */ sprintf( __( 'You can save your snippets to a code snippets export file using the <a href="%s">Manage Snippets</a> page.', 'code-snippets' ), $manage_url ) ); } } class-data-item.php 0000755 00000015073 15105501234 0010232 0 ustar 00 <?php namespace Code_Snippets; use WP_Exception; /** * Base class for representing an item of data without needing to use direct access or individual getter and setter functions. * * @package Code_Snippets * * @since 3.4.0 */ abstract class Data_Item { /** * List of data fields keyed to their current values. Will be initialised with default values. * * @var array<string, mixed> */ protected array $fields; /** * List of default values provided for fields. * * @var array<string, mixed> */ protected array $default_values; /** * Optional list of field name aliases to map when resolving a field name. * * @var array<string, string> Field alias names keyed to actual field names. */ protected array $field_aliases; /** * Class constructor. * * @param array<string, mixed> $default_values List of valid fields mapped to their default values. * @param array<string, mixed>|Data_Item $initial_data Optional initial data to populate fields. * @param array<string, string> $field_aliases Optional list of field name aliases to map when resolving a field name. */ public function __construct( array $default_values, $initial_data = null, array $field_aliases = [] ) { $this->fields = $default_values; $this->default_values = $default_values; $this->field_aliases = $field_aliases; // If we've accidentally passed an existing object, then fetch its fields before constructing the new object. if ( is_object( $initial_data ) && method_exists( $initial_data, 'get_fields' ) ) { $initial_data = $initial_data->get_fields(); } $this->set_fields( $initial_data ); } /** * Set all data fields from an array or object. Invalid fields will be ignored. * * @param array<string, mixed>|mixed $data List of data. */ public function set_fields( $data ) { // Only accept arrays or objects. if ( ! $data || is_string( $data ) ) { return; } // Convert objects into arrays. if ( is_object( $data ) ) { $data = get_object_vars( $data ); } // Loop through the provided fields and set their values. foreach ( $data as $field => $value ) { $this->set_field( $field, $value ); } } /** * Retrieve list of current data fields. * * @return array<string, mixed> Field names keyed to current values. */ public function get_fields(): array { $fields = []; foreach ( $this->get_allowed_fields() as $field_name ) { $fields[ $field_name ] = $this->$field_name; } return $fields; } /** * Retrieve a list of current data fields, excluding values that are unchanged from the default. * * @return array<string, mixed> */ public function get_modified_fields(): array { return array_filter( $this->get_fields(), function ( $value, $field ) { return $value && $value !== $this->default_values[ $field ]; }, ARRAY_FILTER_USE_BOTH ); } /** * Internal function for resolving the actual name of a field. * * @param string $field A field name, potentially a field alias. * * @return string The resolved field name. */ protected function resolve_field_name( string $field ): string { return $this->field_aliases[ $field ] ?? $field; } /** * Check if a field is set. * * @param string $field The field name. * * @return bool Whether the field is set. */ public function __isset( string $field ) { $field = $this->resolve_field_name( $field ); return isset( $this->fields[ $field ] ) || method_exists( $this, 'get_' . $field ); } /** * Retrieve a field's value. * * @param string $field The field name. * * @return mixed The field value * * @throws WP_Exception If the field name is not allowed. */ public function __get( string $field ) { $field = $this->resolve_field_name( $field ); if ( method_exists( $this, 'get_' . $field ) ) { return call_user_func( array( $this, 'get_' . $field ) ); } if ( ! $this->is_allowed_field( $field ) ) { if ( function_exists( 'wp_trigger_error' ) ) { // translators: 1: class name, 2: field name. $message = sprintf( 'Trying to access invalid property on "%1$s" class: %2$s', get_class( $this ), $field ); wp_trigger_error( __FUNCTION__, $message, E_USER_WARNING ); } return null; } return $this->fields[ $field ]; } /** * Set the value of a field. * * @param string $field The field name. * @param mixed $value The field value. * * @throws WP_Exception If the field name is not allowed. */ public function __set( string $field, $value ) { $field = $this->resolve_field_name( $field ); if ( ! $this->is_allowed_field( $field ) ) { if ( function_exists( 'wp_trigger_error' ) ) { // translators: 1: class name, 2: field name. $message = sprintf( 'Trying to set invalid property on "%s" class: %s', get_class( $this ), $field ); wp_trigger_error( __FUNCTION__, $message, E_USER_ERROR ); } return; } $value = method_exists( $this, 'prepare_' . $field ) ? call_user_func( array( $this, 'prepare_' . $field ), $value ) : $this->prepare_field( $value, $field ); $this->fields[ $field ] = $value; } /** * Prepare a value before it is stored. * * @param mixed $value Value to prepare. * @param string $field Field name. * * @return mixed Value in the correct format. */ abstract protected function prepare_field( $value, string $field ); /** * Retrieve the list of fields that can be written to. * * @return array<string> List of field names. */ public function get_allowed_fields(): array { return array_keys( $this->fields ) + array_keys( $this->field_aliases ); } /** * Determine whether a field is allowed to be written to * * @param string $field The field name. * * @return bool true if the is allowed, false if invalid. */ public function is_allowed_field( string $field ): bool { return ( $this->fields && array_key_exists( $field, $this->fields ) ) || ( $this->field_aliases && array_key_exists( $field, $this->field_aliases ) ); } /** * Safely set the value for a field. * If the field name is invalid, false will be returned instead of an error thrown. * * @param string $field The field name. * @param mixed $value The field value. * * @return bool true if the field was set successfully, false if the field name is invalid. * * @noinspection PhpDocMissingThrowsInspection */ public function set_field( string $field, $value ): bool { if ( ! $this->is_allowed_field( $field ) ) { return false; } /** * Above is_allowed_field check should bypass exception. * * @noinspection PhpUnhandledExceptionInspection */ $this->__set( $field, $value ); return true; } } class-db.php 0000755 00000022261 15105501234 0006747 0 ustar 00 <?php namespace Code_Snippets; /** * Functions used to manage the database tables. * * @package Code_Snippets */ class DB { /** * Unprefixed site-wide table name. */ public const TABLE_NAME = 'snippets'; /** * Unprefixed network-wide table name. */ public const MS_TABLE_NAME = 'ms_snippets'; /** * Side-wide table name. * * @var string */ public string $table; /** * Network-wide table name. * * @var string */ public string $ms_table; /** * Class constructor. */ public function __construct() { $this->set_table_vars(); } /** * Register the snippet table names with WordPress. * * @since 2.0 */ public function set_table_vars() { global $wpdb; $this->table = $wpdb->prefix . self::TABLE_NAME; $this->ms_table = $wpdb->base_prefix . self::MS_TABLE_NAME; // Register the snippet table names with WordPress. $wpdb->snippets = $this->table; $wpdb->ms_snippets = $this->ms_table; $wpdb->tables[] = self::TABLE_NAME; $wpdb->ms_global_tables[] = self::MS_TABLE_NAME; } /** * Validate a provided 'network' or 'multisite' param, converting it to a boolean. * * @param bool|null $network Network argument value. * * @return bool Sanitized value. */ public static function validate_network_param( ?bool $network = null ): bool { // If multisite is not active, then assume the value is false. if ( ! is_multisite() ) { return false; } // If $multisite is null, try to base it on the current admin page. if ( is_null( $network ) && function_exists( 'is_network_admin' ) ) { return is_network_admin(); } return (bool) $network; } /** * Return the appropriate snippet table name * * @param bool|null $is_network Whether retrieve the multisite table name (true) or the site table name (false). * * @return string The snippet table name * @since 2.0 */ public function get_table_name( ?bool $is_network = null ): string { $is_network = is_bool( $is_network ) ? $is_network : self::validate_network_param( $is_network ); return $is_network ? $this->ms_table : $this->table; } /** * Determine whether a database table exists. * * @param string $table_name Name of database table to check. * @param boolean $refresh Rerun the query, instead of using a cached value. * * @return bool Whether the database table exists. */ public static function table_exists( string $table_name, bool $refresh = false ): bool { global $wpdb; static $checked = array(); if ( $refresh || ! isset( $checked[ $table_name ] ) ) { // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, caching is handled through $checked variable. $result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) ); $checked[ $table_name ] = $result === $table_name; } return $checked[ $table_name ]; } /** * Create the snippet tables if they do not already exist */ public function create_missing_tables() { // Create the network snippets table if it doesn't exist. if ( is_multisite() && ! self::table_exists( $this->ms_table ) ) { $this->create_table( $this->ms_table ); } // Create the table if it doesn't exist. if ( ! self::table_exists( $this->table ) ) { $this->create_table( $this->table ); } } /** * Create the snippet tables, or upgrade them if they already exist */ public function create_or_upgrade_tables() { if ( is_multisite() ) { $this->create_table( $this->ms_table ); } $this->create_table( $this->table ); } /** * Create a snippet table if it does not already exist * * @param string $table_name Name of database table. */ public static function create_missing_table( string $table_name ) { if ( ! self::table_exists( $table_name ) ) { self::create_table( $table_name ); } } /** * Create a single snippet table. * * @param string $table_name The name of the table to create. * * @return bool Whether the table creation was successful. * @since 1.6 * @uses dbDelta() to apply the SQL code */ public static function create_table( string $table_name ): bool { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); /* Create the database table */ $sql = "CREATE TABLE $table_name ( id BIGINT(20) NOT NULL AUTO_INCREMENT, name TINYTEXT NOT NULL, description TEXT NOT NULL, code LONGTEXT NOT NULL, tags LONGTEXT NOT NULL, scope VARCHAR(15) NOT NULL DEFAULT 'global', condition_id BIGINT(20) NOT NULL DEFAULT 0, priority SMALLINT NOT NULL DEFAULT 10, active TINYINT(1) NOT NULL DEFAULT 0, modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, revision BIGINT(20) NOT NULL DEFAULT 1, cloud_id VARCHAR(255) NULL, PRIMARY KEY (id), KEY scope (scope), KEY active (active) ) $charset_collate;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); $success = empty( $wpdb->last_error ); if ( $success ) { do_action( 'code_snippets/create_table', $table_name ); } return $success; } /** * Fetch a list of active snippets from a database table. * * @param string $table_name Name of table to fetch snippets from. * @param array<string> $scopes List of scopes to include in query. * @param boolean $active_only Whether to only fetch active snippets from the table. * * @return array<string, array<string, mixed>>|false List of active snippets, if any could be retrieved. * * @phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */ private static function fetch_snippets_from_table( string $table_name, array $scopes, bool $active_only = true ) { global $wpdb; $cache_key = sprintf( 'active_snippets_%s_%s', sanitize_key( join( '_', $scopes ) ), $table_name ); $cached_snippets = wp_cache_get( $cache_key, CACHE_GROUP ); if ( is_array( $cached_snippets ) ) { return $cached_snippets; } if ( ! self::table_exists( $table_name ) ) { return false; } $scopes_format = implode( ',', array_fill( 0, count( $scopes ), '%s' ) ); $extra_where = $active_only ? 'AND active=1' : ''; $snippets = $wpdb->get_results( $wpdb->prepare( " SELECT id, code, scope, active, priority FROM $table_name WHERE scope IN ($scopes_format) $extra_where ORDER BY priority, id", $scopes ), 'ARRAY_A' ); // Cache the full list of snippets. if ( is_array( $snippets ) ) { wp_cache_set( $cache_key, $snippets, CACHE_GROUP ); return $snippets; } return false; } /** * Sort the active snippets by priority, table, and ID. * * @param array $active_snippets List of active snippets to sort. */ private function sort_active_snippets( array &$active_snippets ): void { $comparisons = [ function ( array $a, array $b ) { return $a['priority'] <=> $b['priority']; }, function ( array $a, array $b ) { $a_table = $a['table'] === $this->ms_table ? 0 : 1; $b_table = $b['table'] === $this->ms_table ? 0 : 1; return $a_table <=> $b_table; }, function ( array $a, array $b ) { return $a['id'] <=> $b['id']; }, ]; usort( $active_snippets, static function ( $a, $b ) use ( $comparisons ) { foreach ( $comparisons as $comparison ) { $result = $comparison( $a, $b ); if ( 0 !== $result ) { return $result; } } return 0; } ); } /** * Generate the SQL for fetching active snippets from the database. * * @param string[] $scopes List of scopes to retrieve in. * * @return array{ * id: int, * code: string, * scope: string, * table: string, * network: bool, * priority: int, * } List of active snippets. */ public function fetch_active_snippets( array $scopes ): array { $active_snippets = []; // Fetch the active snippets for the current site, if there are any. $snippets = $this->fetch_snippets_from_table( $this->table, $scopes, true ); if ( $snippets ) { foreach ( $snippets as $snippet ) { $active_snippets[] = [ 'id' => intval( $snippet['id'] ), 'code' => $snippet['code'], 'scope' => $snippet['scope'], 'table' => $this->table, 'network' => false, 'priority' => intval( $snippet['priority'] ), ]; } } // If multisite is enabled, fetch all snippets from the network table, and filter down to only active snippets. if ( is_multisite() ) { $ms_snippets = $this->fetch_snippets_from_table( $this->ms_table, $scopes, false ); if ( $ms_snippets ) { $active_shared_ids = get_option( 'active_shared_network_snippets', [] ); $active_shared_ids = is_array( $active_shared_ids ) ? array_map( 'intval', $active_shared_ids ) : []; foreach ( $ms_snippets as $snippet ) { $id = intval( $snippet['id'] ); if ( ! $snippet['active'] && ! in_array( $id, $active_shared_ids, true ) ) { continue; } $active_snippets[] = [ 'id' => $id, 'code' => $snippet['code'], 'scope' => $snippet['scope'], 'table' => $this->ms_table, 'network' => true, 'priority' => intval( $snippet['priority'] ), ]; } $this->sort_active_snippets( $active_snippets ); } } return $active_snippets; } } class-licensing.php 0000755 00000000472 15105501234 0010335 0 ustar 00 <?php namespace Code_Snippets; /** * Empty class to better support interoperability between core and pro. * * @package Code_Snippets */ class Licensing { /** * Determine whether the current site has an active license. * * @return bool */ public function is_licensed(): bool { return false; } } class-list-table.php 0000755 00000115470 15105501234 0010427 0 ustar 00 <?php /** * Contains the class for handling the snippets table * * @package Code_Snippets * * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited */ namespace Code_Snippets; use WP_List_Table; use function Code_Snippets\Settings\get_setting; // The WP_List_Table base class is not included by default, so we need to load it. if ( ! class_exists( 'WP_List_Table' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; } /** * This class handles the table for the manage snippets menu * * @since 1.5 * @package Code_Snippets */ class List_Table extends WP_List_Table { /** * Whether the current screen is in the network admin * * @var bool */ public bool $is_network; /** * A list of statuses (views) * * @var array<string> */ public array $statuses = [ 'all', 'active', 'inactive', 'recently_activated' ]; /** * Column name to use when ordering the snippets list. * * @var string */ protected string $order_by; /** * Direction to use when ordering the snippets list. Either 'asc' or 'desc'. * * @var string */ protected string $order_dir; /** * List of active snippets indexed by attached condition ID. * * @var array <int, Snippet[]> */ protected array $active_by_condition = []; /** * The constructor function for our class. * Registers hooks, initializes variables, setups class. * * @phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited */ public function __construct() { global $status, $page; $this->is_network = is_network_admin(); // Determine the status. $status = apply_filters( 'code_snippets/list_table/default_view', 'all' ); if ( isset( $_REQUEST['status'] ) && in_array( sanitize_key( $_REQUEST['status'] ), $this->statuses, true ) ) { $status = sanitize_key( $_REQUEST['status'] ); } // Add the search query to the URL. if ( isset( $_REQUEST['s'] ) ) { $_SERVER['REQUEST_URI'] = add_query_arg( 's', sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) ); } // Add a snippets per page screen option. $page = $this->get_pagenum(); add_screen_option( 'per_page', array( 'label' => __( 'Snippets per page', 'code-snippets' ), 'default' => 999, 'option' => 'snippets_per_page', ) ); add_filter( 'default_hidden_columns', array( $this, 'default_hidden_columns' ) ); // Strip the result query arg from the URL. $_SERVER['REQUEST_URI'] = remove_query_arg( 'result' ); // Add filters to format the snippet description in the same way the post content is formatted. $filters = [ 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'shortcode_unautop', 'capital_P_dangit', [ $this, 'wp_kses_desc' ] ]; foreach ( $filters as $filter ) { add_filter( 'code_snippets/list_table/column_description', $filter ); } // Set up the class. parent::__construct( array( 'ajax' => true, 'plural' => 'snippets', 'singular' => 'snippet', ) ); } /** * Determine if a condition is considered 'active' by checking if it is attached to any active snippets. * * @param Snippet $condition Condition snippet to check. * * @return bool */ protected function is_condition_active( Snippet $condition ): bool { return $condition->is_condition() && isset( $this->active_by_condition[ $condition->id ] ) && count( $this->active_by_condition[ $condition->id ] ) > 0; } /** * Apply a more permissive version of wp_kses_post() to the snippet description. * * @param string $data Description content to filter. * * @return string Filtered description content with allowed HTML tags and attributes intact. */ public function wp_kses_desc( string $data ): string { $safe_style_filter = function ( $styles ) { $styles[] = 'display'; return $styles; }; add_filter( 'safe_style_css', $safe_style_filter ); $data = wp_kses_post( $data ); remove_filter( 'safe_style_css', $safe_style_filter ); return $data; } /** * Set the 'id' column as hidden by default. * * @param array<string> $hidden List of hidden columns. * * @return array<string> Modified list of hidden columns. */ public function default_hidden_columns( array $hidden ): array { array_push( $hidden, 'id', 'code', 'cloud_id', 'revision' ); return $hidden; } /** * Set the 'name' column as the primary column. * * @return string */ protected function get_default_primary_column_name(): string { return 'name'; } /** * Define the output of all columns that have no callback function * * @param Snippet $item The snippet used for the current row. * @param string $column_name The name of the column being printed. * * @return string The content of the column to output. */ protected function column_default( $item, $column_name ): string { switch ( $column_name ) { case 'id': return $item->id; case 'description': return apply_filters( 'code_snippets/list_table/column_description', $item->desc ); case 'type': $type = $item->type; $url = add_query_arg( 'type', $type ); return sprintf( '<a class="badge %s-badge" href="%s">%s</a>', esc_attr( $type ), esc_url( $url ), 'cond' === $type ? '<span class="dashicons dashicons-randomize"></span>' : esc_html( $type ) ); case 'date': return $item->modified ? $item->format_modified() : '—'; default: return apply_filters( "code_snippets/list_table/column_$column_name", '—', $item ); } } /** * Retrieve a URL to perform an action on a snippet * * @param string $action Name of action to produce a link for. * @param Snippet $snippet Snippet object to produce link for. * * @return string URL to perform action. */ public function get_action_link( string $action, Snippet $snippet ): string { // Redirect actions to the network dashboard for shared network snippets. $local_actions = array( 'activate', 'activate-shared', 'run-once', 'run-once-shared' ); $network_redirect = $snippet->shared_network && ! $this->is_network && ! in_array( $action, $local_actions, true ); // Edit links go to a different menu. if ( 'edit' === $action ) { return code_snippets()->get_snippet_edit_url( $snippet->id, $network_redirect ? 'network' : 'self' ); } $query_args = array( 'action' => $action, 'id' => $snippet->id, 'scope' => $snippet->scope, ); $url = $network_redirect ? add_query_arg( $query_args, code_snippets()->get_menu_url( 'manage', 'network' ) ) : add_query_arg( $query_args ); // Add a nonce to the URL for security purposes. return wp_nonce_url( $url, 'code_snippets_manage_snippet_' . $snippet->id ); } /** * Build a list of action links for individual snippets * * @param Snippet $snippet The current snippet. * * @return array<string, string> The action links HTML. */ private function get_snippet_action_links( Snippet $snippet ): array { $actions = array(); if ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) { // Display special links if on a subsite and dealing with a network-active snippet. if ( $snippet->active ) { $actions['network_active'] = esc_html__( 'Network Active', 'code-snippets' ); } else { $actions['network_only'] = esc_html__( 'Network Only', 'code-snippets' ); } } elseif ( ! $snippet->shared_network || current_user_can( code_snippets()->get_network_cap_name() ) ) { // If the snippet is a shared network snippet, only display extra actions if the user has network permissions. $simple_actions = array( 'edit' => esc_html__( 'Edit', 'code-snippets' ), 'clone' => esc_html__( 'Clone', 'code-snippets' ), 'export' => esc_html__( 'Export', 'code-snippets' ), ); foreach ( $simple_actions as $action => $label ) { $actions[ $action ] = sprintf( '<a href="%s">%s</a>', esc_url( $this->get_action_link( $action, $snippet ) ), $label ); } $actions['delete'] = sprintf( '<a href="%2$s" class="delete" onclick="%3$s">%1$s</a>', esc_html__( 'Delete', 'code-snippets' ), esc_url( $this->get_action_link( 'delete', $snippet ) ), esc_js( sprintf( 'return confirm("%s");', esc_html__( 'You are about to permanently delete the selected item.', 'code-snippets' ) . "\n" . esc_html__( "'Cancel' to stop, 'OK' to delete.", 'code-snippets' ) ) ) ); } return apply_filters( 'code_snippets/list_table/row_actions', $actions, $snippet ); } /** * Retrieve the code for a snippet activation switch * * @param Snippet $snippet Snippet object. * * @return string Output for activation switch. */ protected function column_activate( Snippet $snippet ): string { if ( $this->is_network && ( $snippet->shared_network || ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) ) ) { return ''; } switch ( $snippet->scope ) { case 'single-use': $class = 'snippet-execution-button'; $action = 'run-once'; $label = esc_html__( 'Run Once', 'code-snippets' ); break; case 'condition': $edit_url = code_snippets()->get_snippet_edit_url( $snippet->id, $snippet->network ? 'network' : 'admin' ); return sprintf( '<a href="%s" class="snippet-condition-count">%s</a>', esc_url( $edit_url ), isset( $this->active_by_condition[ $snippet->id ] ) ? esc_html( count( $this->active_by_condition[ $snippet->id ] ) ) : 0 ); default: $class = 'snippet-activation-switch'; $action = $snippet->active ? 'deactivate' : 'activate'; $label = $snippet->network && ! $snippet->shared_network ? ( $snippet->active ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Network Activate', 'code-snippets' ) ) : ( $snippet->active ? __( 'Deactivate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ) ); break; } if ( $snippet->shared_network ) { $action .= '-shared'; } return $action && $label ? sprintf( '<a class="%1$s" href="%2$s" title="%3$s" aria-label="%3$s"> </a> ', esc_attr( $class ), esc_url( $this->get_action_link( $action, $snippet ) ), esc_attr( $label ) ) : ''; } /** * Build the content of the snippet name column * * @param Snippet $snippet The snippet being used for the current row. * * @return string The content of the column to output. */ protected function column_name( Snippet $snippet ): string { $row_actions = $this->row_actions( $this->get_snippet_action_links( $snippet ), apply_filters( 'code_snippets/list_table/row_actions_always_visible', true ) ); $out = esc_html( $snippet->display_name ); // Add a link to the snippet if it isn't an unreadable network-only snippet. if ( $this->is_network || ! $snippet->network || current_user_can( code_snippets()->get_network_cap_name() ) ) { $out = sprintf( '<a href="%s" class="snippet-name">%s</a>', esc_attr( code_snippets()->get_snippet_edit_url( $snippet->id, $snippet->network ? 'network' : 'admin' ) ), $out ); } if ( $snippet->shared_network ) { $out .= ' <span class="badge">' . esc_html__( 'Shared on Network', 'code-snippets' ) . '</span>'; } $out = apply_filters( 'code_snippets/list_table/column_name', $out, $snippet ); return $out . $row_actions; } /** * Handles the checkbox column output. * * @param Snippet $item The snippet being used for the current row. * * @return string The column content to be printed. */ protected function column_cb( $item ): string { $out = sprintf( '<input type="checkbox" name="%s[]" value="%s">', $item->shared_network ? 'shared_ids' : 'ids', $item->id ); return apply_filters( 'code_snippets/list_table/column_cb', $out, $item ); } /** * Handles the tags column output. * * @param Snippet $snippet The snippet being used for the current row. * * @return string The column output. */ protected function column_tags( Snippet $snippet ): string { // Return now if there are no tags. if ( empty( $snippet->tags ) ) { return ''; } $out = array(); // Loop through the tags and create a link for each one. foreach ( $snippet->tags as $tag ) { $out[] = sprintf( '<a href="%s">%s</a>', esc_url( add_query_arg( 'tag', esc_attr( $tag ) ) ), esc_html( $tag ) ); } return join( ', ', $out ); } /** * Handles the priority column output. * * @param Snippet $snippet The snippet being used for the current row. * * @return string The column output. */ protected function column_priority( Snippet $snippet ): string { return sprintf( '<input type="number" class="snippet-priority" value="%d" step="1" disabled>', $snippet->priority ); } /** * Define the column headers for the table * * @return array<string, string> The column headers, ID paired with label */ public function get_columns(): array { $columns = array( 'cb' => '<input type="checkbox">', 'activate' => '', 'name' => __( 'Name', 'code-snippets' ), 'type' => __( 'Type', 'code-snippets' ), 'description' => __( 'Description', 'code-snippets' ), 'tags' => __( 'Tags', 'code-snippets' ), 'date' => __( 'Modified', 'code-snippets' ), 'priority' => __( 'Priority', 'code-snippets' ), 'id' => __( 'ID', 'code-snippets' ), ); if ( ! get_setting( 'general', 'enable_description' ) ) { unset( $columns['description'] ); } if ( ! get_setting( 'general', 'enable_tags' ) ) { unset( $columns['tags'] ); } return apply_filters( 'code_snippets/list_table/columns', $columns ); } /** * Define the columns that can be sorted. The format is: * 'internal-name' => 'orderby' * or * 'internal-name' => array( 'orderby', true ) * * The second format will make the initial sorting order be descending. * * @return array<string, string|array<string|bool>> The IDs of the columns that can be sorted */ public function get_sortable_columns(): array { $sortable_columns = [ 'id' => [ 'id', true ], 'name' => 'name', 'type' => [ 'type', true ], 'date' => [ 'modified', true ], 'priority' => [ 'priority', true ], ]; return apply_filters( 'code_snippets/list_table/sortable_columns', $sortable_columns ); } /** * Define the bulk actions to include in the drop-down menus * * @return array<string, string> An array of menu items with the ID paired to the label */ public function get_bulk_actions(): array { $actions = [ 'activate-selected' => $this->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ), 'deactivate-selected' => $this->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ), 'clone-selected' => __( 'Clone', 'code-snippets' ), 'download-selected' => __( 'Export Code', 'code-snippets' ), 'export-selected' => __( 'Export', 'code-snippets' ), 'delete-selected' => __( 'Delete', 'code-snippets' ), ]; return apply_filters( 'code_snippets/list_table/bulk_actions', $actions ); } /** * Retrieve the classes for the table * * We override this in order to add 'snippets' as a class for custom styling * * @return array<string> The classes to include on the table element */ public function get_table_classes(): array { $classes = array( 'widefat', $this->_args['plural'] ); return apply_filters( 'code_snippets/list_table/table_classes', $classes ); } /** * Retrieve the 'views' of the table * * Example: active, inactive, recently active * * @return array<string, string> A list of the view labels linked to the view */ public function get_views(): array { global $totals, $status; $status_links = parent::get_views(); // Loop through the view counts. foreach ( $totals as $type => $count ) { $labels = []; if ( ! $count ) { continue; } // translators: %s: total number of snippets. $labels['all'] = _n( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'code-snippets' ); // translators: %s: total number of active snippets. $labels['active'] = _n( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', $count, 'code-snippets' ); // translators: %s: total number of inactive snippets. $labels['inactive'] = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count, 'code-snippets' ); // translators: %s: total number of recently activated snippets. $labels['recently_activated'] = _n( 'Recently Active <span class="count">(%s)</span>', 'Recently Active <span class="count">(%s)</span>', $count, 'code-snippets' ); // The page URL with the status parameter. $url = esc_url( add_query_arg( 'status', $type ) ); // Add a class if this view is currently being viewed. $class = $type === $status ? ' class="current"' : ''; // Add the view count to the label. $text = sprintf( $labels[ $type ], number_format_i18n( $count ) ); $status_links[ $type ] = sprintf( '<a href="%s"%s>%s</a>', $url, $class, $text ); } return apply_filters( 'code_snippets/list_table/views', $status_links ); } /** * Gets the tags of the snippets currently being viewed in the table * * @since 2.0 */ public function get_current_tags() { global $snippets, $status; // If we're not viewing a snippets table, get all used tags instead. if ( ! isset( $snippets, $status ) ) { $tags = get_all_snippet_tags(); } else { $tags = array(); // Merge all tags into a single array. foreach ( $snippets[ $status ] as $snippet ) { $tags = array_merge( $snippet->tags, $tags ); } // Remove duplicate tags. $tags = array_unique( $tags ); } sort( $tags ); return $tags; } /** * Add filters and extra actions above and below the table * * @param string $which Whether the actions are displayed on the before (true) or after (false) the table. */ public function extra_tablenav( $which ) { /** * Status global. * * @var string $status */ global $status; if ( 'top' === $which ) { // Tags dropdown filter. $tags = $this->get_current_tags(); if ( count( $tags ) ) { $query = isset( $_GET['tag'] ) ? sanitize_text_field( wp_unslash( $_GET['tag'] ) ) : ''; echo '<div class="alignleft actions">'; echo '<select name="tag">'; printf( "<option %s value=''>%s</option>\n", selected( $query, '', false ), esc_html__( 'Show all tags', 'code-snippets' ) ); foreach ( $tags as $tag ) { printf( "<option %s value='%s'>%s</option>\n", selected( $query, $tag, false ), esc_attr( $tag ), esc_html( $tag ) ); } echo '</select>'; submit_button( __( 'Filter', 'code-snippets' ), 'button', 'filter_action', false ); echo '</div>'; } } echo '<div class="alignleft actions">'; if ( 'recently_activated' === $status ) { submit_button( __( 'Clear List', 'code-snippets' ), 'secondary', 'clear-recent-list', false ); } do_action( 'code_snippets/list_table/actions', $which ); echo '</div>'; } /** * Output form fields needed to preserve important * query vars over form submissions * * @param string $context The context in which the fields are being outputted. */ public static function required_form_fields( string $context = 'main' ) { $vars = apply_filters( 'code_snippets/list_table/required_form_fields', array( 'page', 's', 'status', 'paged', 'tag' ), $context ); if ( 'search_box' === $context ) { // Remove the 's' var if we're doing this for the search box. $vars = array_diff( $vars, array( 's' ) ); } foreach ( $vars as $var ) { if ( ! empty( $_REQUEST[ $var ] ) ) { $value = sanitize_text_field( wp_unslash( $_REQUEST[ $var ] ) ); printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $var ), esc_attr( $value ) ); echo "\n"; } } do_action( 'code_snippets/list_table/print_required_form_fields', $context ); } /** * Perform an action on a single snippet. * * @param int $id Snippet ID. * @param string $action Action to perform. * * @return bool|string Result of performing action */ private function perform_action( int $id, string $action ) { switch ( $action ) { case 'activate': activate_snippet( $id, $this->is_network ); return 'activated'; case 'deactivate': deactivate_snippet( $id, $this->is_network ); return 'deactivated'; case 'run-once': $this->perform_action( $id, 'activate' ); return 'executed'; case 'run-once-shared': $this->perform_action( $id, 'activate-shared' ); return 'executed'; case 'activate-shared': $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); if ( ! in_array( $id, $active_shared_snippets, true ) ) { $active_shared_snippets[] = $id; update_option( 'active_shared_network_snippets', $active_shared_snippets ); clean_active_snippets_cache( code_snippets()->db->ms_table ); } return 'activated'; case 'deactivate-shared': $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); update_option( 'active_shared_network_snippets', array_diff( $active_shared_snippets, array( $id ) ) ); clean_active_snippets_cache( code_snippets()->db->ms_table ); return 'deactivated'; case 'clone': $this->clone_snippets( [ $id ] ); return 'cloned'; case 'delete': delete_snippet( $id, $this->is_network ); return 'deleted'; case 'export': $export = new Export_Attachment( [ $id ], $this->is_network ); $export->download_snippets_json(); break; case 'download': $export = new Export_Attachment( [ $id ], $this->is_network ); $export->download_snippets_code(); break; } return false; } /** * Processes actions requested by the user. * * @return void */ public function process_requested_actions() { // Clear the recent snippets list if requested to do so. if ( isset( $_POST['clear-recent-list'] ) ) { check_admin_referer( 'bulk-' . $this->_args['plural'] ); if ( $this->is_network ) { update_site_option( 'recently_activated_snippets', array() ); } else { update_option( 'recently_activated_snippets', array() ); } } // Check if there are any single snippet actions to perform. if ( isset( $_GET['action'], $_GET['id'] ) ) { $id = absint( $_GET['id'] ); $scope = isset( $_GET['scope'] ) ? sanitize_key( wp_unslash( $_GET['scope'] ) ) : ''; // Verify they were sent from a trusted source. $nonce_action = 'code_snippets_manage_snippet_' . $id; if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), $nonce_action ) ) { wp_nonce_ays( $nonce_action ); } $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id', 'scope', '_wpnonce' ) ); // If so, then perform the requested action and inform the user of the result. $result = $this->perform_action( $id, sanitize_key( $_GET['action'] ) ); if ( $result ) { wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) ); exit; } } // Only continue from this point if there are bulk actions to process. if ( ! isset( $_POST['ids'] ) && ! isset( $_POST['shared_ids'] ) ) { return; } check_admin_referer( 'bulk-' . $this->_args['plural'] ); $ids = isset( $_POST['ids'] ) ? array_map( 'intval', $_POST['ids'] ) : array(); $_SERVER['REQUEST_URI'] = remove_query_arg( 'action' ); switch ( $this->current_action() ) { case 'activate-selected': activate_snippets( $ids ); // Process the shared network snippets. if ( isset( $_POST['shared_ids'] ) && is_multisite() && ! $this->is_network ) { $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); foreach ( array_map( 'intval', $_POST['shared_ids'] ) as $id ) { if ( ! in_array( $id, $active_shared_snippets, true ) ) { $active_shared_snippets[] = $id; } } update_option( 'active_shared_network_snippets', $active_shared_snippets ); clean_active_snippets_cache( code_snippets()->db->ms_table ); } $result = 'activated-multi'; break; case 'deactivate-selected': foreach ( $ids as $id ) { deactivate_snippet( $id, $this->is_network ); } // Process the shared network snippets. if ( isset( $_POST['shared_ids'] ) && is_multisite() && ! $this->is_network ) { $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); $active_shared_snippets = ( '' === $active_shared_snippets ) ? array() : $active_shared_snippets; $active_shared_snippets = array_diff( $active_shared_snippets, array_map( 'intval', $_POST['shared_ids'] ) ); update_option( 'active_shared_network_snippets', $active_shared_snippets ); clean_active_snippets_cache( code_snippets()->db->ms_table ); } $result = 'deactivated-multi'; break; case 'export-selected': $export = new Export_Attachment( $ids, $this->is_network ); $export->download_snippets_json(); break; case 'download-selected': $export = new Export_Attachment( $ids, $this->is_network ); $export->download_snippets_code(); break; case 'clone-selected': $this->clone_snippets( $ids ); $result = 'cloned-multi'; break; case 'delete-selected': foreach ( $ids as $id ) { delete_snippet( $id, $this->is_network ); } $result = 'deleted-multi'; break; } if ( isset( $result ) ) { wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) ); exit; } } /** * Message to display if no snippets are found. * * @return void */ public function no_items() { if ( ! empty( $GLOBALS['s'] ) || ! empty( $_GET['tag'] ) ) { esc_html_e( 'No snippets were found matching the current search query. Please enter a new query or use the "Clear Filters" button above.', 'code-snippets' ); } else { $add_url = code_snippets()->get_menu_url( 'add' ); if ( empty( $_GET['type'] ) ) { esc_html_e( "It looks like you don't have any snippets.", 'code-snippets' ); } else { esc_html_e( "It looks like you don't have any snippets of this type.", 'code-snippets' ); $add_url = add_query_arg( 'type', sanitize_key( wp_unslash( $_GET['type'] ) ), $add_url ); } printf( ' <a href="%s">%s</a>', esc_url( $add_url ), esc_html__( 'Perhaps you would like to add a new one?', 'code-snippets' ) ); } } /** * Fetch all shared network snippets for the current site. * * @return void */ private function fetch_shared_network_snippets() { /** * Table data. * * @var $snippets array<string, Snippet[]> */ global $snippets; $ids = get_site_option( 'shared_network_snippets' ); if ( ! is_multisite() || ! $ids ) { return; } if ( $this->is_network ) { $limit = count( $snippets['all'] ); for ( $i = 0; $i < $limit; $i++ ) { $snippet = &$snippets['all'][ $i ]; if ( in_array( $snippet->id, $ids, true ) ) { $snippet->shared_network = true; $snippet->tags = array_merge( $snippet->tags, array( 'shared on network' ) ); $snippet->active = false; } } } else { $active_shared_snippets = get_option( 'active_shared_network_snippets', array() ); $shared_snippets = get_snippets( $ids, true ); foreach ( $shared_snippets as $snippet ) { $snippet->shared_network = true; $snippet->tags = array_merge( $snippet->tags, array( 'shared on network' ) ); $snippet->active = in_array( $snippet->id, $active_shared_snippets, true ); } $snippets['all'] = array_merge( $snippets['all'], $shared_snippets ); } } /** * Prepares the items to later display in the table. * Should run before any headers are sent. * * @phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited * * @return void */ public function prepare_items() { /** * Global variables. * * @var string $status Current status view. * @var array<string, Snippet[]> $snippets List of snippets for views. * @var array<string, integer> $totals List of total items for views. * @var string $s Current search term. */ global $status, $snippets, $totals, $s; wp_reset_vars( array( 'orderby', 'order', 's' ) ); // Redirect tag filter from POST to GET. if ( isset( $_POST['filter_action'] ) ) { $location = empty( $_POST['tag'] ) ? remove_query_arg( 'tag' ) : add_query_arg( 'tag', sanitize_text_field( wp_unslash( $_POST['tag'] ) ) ); wp_safe_redirect( esc_url_raw( $location ) ); exit; } $this->process_requested_actions(); $snippets = array_fill_keys( $this->statuses, array() ); $snippets['all'] = apply_filters( 'code_snippets/list_table/get_snippets', get_snippets() ); $this->fetch_shared_network_snippets(); foreach ( $snippets['all'] as $snippet ) { if ( $snippet->active ) { $this->active_by_condition[ $snippet->condition_id ][] = $snippet; } } // Filter snippets by type. $type = sanitize_key( wp_unslash( $_GET['type'] ?? '' ) ); if ( $type && 'all' !== $type ) { $snippets['all'] = array_filter( $snippets['all'], function ( Snippet $snippet ) use ( $type ) { return $type === $snippet->type; } ); } // Add scope tags. foreach ( $snippets['all'] as $snippet ) { if ( 'global' !== $snippet->scope ) { $snippet->add_tag( $snippet->scope ); } } // Filter snippets by tag. if ( ! empty( $_GET['tag'] ) ) { $snippets['all'] = array_filter( $snippets['all'], array( $this, 'tags_filter_callback' ) ); } // Filter snippets based on search query. if ( $s ) { $snippets['all'] = array_filter( $snippets['all'], array( $this, 'search_by_line_callback' ) ); } // Clear recently activated snippets older than a week. $recently_activated = $this->is_network ? get_site_option( 'recently_activated_snippets', array() ) : get_option( 'recently_activated_snippets', array() ); foreach ( $recently_activated as $key => $time ) { if ( $time + WEEK_IN_SECONDS < time() ) { unset( $recently_activated[ $key ] ); } } $this->is_network ? update_site_option( 'recently_activated_snippets', $recently_activated ) : update_option( 'recently_activated_snippets', $recently_activated ); /** * Filter snippets into individual sections * * @var Snippet $snippet */ foreach ( $snippets['all'] as $snippet ) { if ( $snippet->active || $this->is_condition_active( $snippet ) ) { $snippets['active'][] = $snippet; } else { $snippets['inactive'][] = $snippet; // Was the snippet recently deactivated? if ( isset( $recently_activated[ $snippet->id ] ) ) { $snippets['recently_activated'][] = $snippet; } } } // Count the totals for each section. $totals = array_map( function ( $section_snippets ) { return count( $section_snippets ); }, $snippets ); // If the current status is empty, default to all. if ( empty( $snippets[ $status ] ) ) { $status = 'all'; } // Get the current data. $data = $snippets[ $status ]; // Decide how many records per page to show by getting the user's setting in the Screen Options panel. $sort_by = $this->screen->get_option( 'per_page', 'option' ); $per_page = get_user_meta( get_current_user_id(), $sort_by, true ); if ( empty( $per_page ) || $per_page < 1 ) { $per_page = $this->screen->get_option( 'per_page', 'default' ); } $per_page = (int) $per_page; $this->set_order_vars(); usort( $data, array( $this, 'usort_reorder_callback' ) ); // Determine what page the user is currently looking at. $current_page = $this->get_pagenum(); // Check how many items are in the data array. $total_items = count( $data ); // The WP_List_Table class does not handle pagination for us, so we need to ensure that the data is trimmed to only the current page. $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page ); // Now we can add our *sorted* data to the 'items' property, where it can be used by the rest of the class. $this->items = $data; // We register our pagination options and calculations. $this->set_pagination_args( [ 'total_items' => $total_items, // Calculate the total number of items. 'per_page' => $per_page, // Determine how many items to show on a page. 'total_pages' => ceil( $total_items / $per_page ), // Calculate the total number of pages. ] ); } /** * Determine the sort ordering for two pieces of data. * * @param mixed $a_data First piece of data. * @param mixed $b_data Second piece of data. * * @return int Returns -1 if $a_data is less than $b_data; 0 if they are equal; 1 otherwise * @ignore */ private function get_sort_direction( $a_data, $b_data ) { // If the data is numeric, then calculate the ordering directly. if ( is_numeric( $a_data ) && is_numeric( $b_data ) ) { return $a_data - $b_data; } // If only one of the data points is empty, then place it before the one which is not. if ( empty( $a_data ) xor empty( $b_data ) ) { return empty( $a_data ) ? 1 : -1; } // Sort using the default string sort order if possible. if ( is_string( $a_data ) && is_string( $b_data ) ) { return strcasecmp( $a_data, $b_data ); } // Otherwise, use basic comparison operators. return $a_data === $b_data ? 0 : ( $a_data < $b_data ? -1 : 1 ); } /** * Set the $order_by and $order_dir class variables. */ private function set_order_vars() { $order = Settings\get_setting( 'general', 'list_order' ); // set the order by based on the query variable, if set. if ( ! empty( $_REQUEST['orderby'] ) ) { $this->order_by = sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ); } else { // otherwise, fetch the order from the setting, ensuring it is valid. $valid_fields = [ 'id', 'name', 'type', 'modified', 'priority' ]; $order_parts = explode( '-', $order, 2 ); $this->order_by = in_array( $order_parts[0], $valid_fields, true ) ? $order_parts[0] : apply_filters( 'code_snippets/list_table/default_orderby', 'priority' ); } // set the order dir based on the query variable, if set. if ( ! empty( $_REQUEST['order'] ) ) { $this->order_dir = sanitize_key( wp_unslash( $_REQUEST['order'] ) ); } elseif ( '-desc' === substr( $order, -5 ) ) { $this->order_dir = 'desc'; } elseif ( '-asc' === substr( $order, -4 ) ) { $this->order_dir = 'asc'; } else { $this->order_dir = apply_filters( 'code_snippets/list_table/default_order', 'asc' ); } } /** * Callback for usort() used to sort snippets * * @param Snippet $a The first snippet to compare. * @param Snippet $b The second snippet to compare. * * @return int The sort order. * @ignore */ private function usort_reorder_callback( Snippet $a, Snippet $b ) { $orderby = $this->order_by; $result = $this->get_sort_direction( $a->$orderby, $b->$orderby ); if ( 0 === $result && 'id' !== $orderby ) { $result = $this->get_sort_direction( $a->id, $b->id ); } // Apply the sort direction to the calculated order. return ( 'asc' === $this->order_dir ) ? $result : -$result; } /** * Callback for search function * * @param Snippet $snippet The snippet being filtered. * * @return bool The result of the filter * @ignore */ private function search_callback( Snippet $snippet ): bool { global $s; $query = sanitize_text_field( wp_unslash( $s ) ); $fields = [ 'name', 'desc', 'code', 'tags_list' ]; foreach ( $fields as $field ) { if ( false !== stripos( $snippet->$field, $query ) ) { return true; } } return false; } /** * Callback for search function * * @param Snippet $snippet The snippet being filtered. * * @return bool The result of the filter * @ignore */ private function search_by_line_callback( Snippet $snippet ): bool { global $s; static $line_num; if ( is_null( $line_num ) ) { if ( preg_match( '/@line:(?P<line>\d+)/', $s, $matches ) ) { $s = trim( str_replace( $matches[0], '', $s ) ); $line_num = (int) $matches['line'] - 1; } else { $line_num = -1; } } if ( $line_num < 0 ) { return $this->search_callback( $snippet ); } $code_lines = explode( "\n", $snippet->code ); return isset( $code_lines[ $line_num ] ) && false !== stripos( $code_lines[ $line_num ], $s ); } /** * Callback for filtering snippets by tag. * * @param Snippet $snippet The snippet being filtered. * * @return bool The result of the filter. * @ignore */ private function tags_filter_callback( Snippet $snippet ): bool { $tags = isset( $_GET['tag'] ) ? explode( ',', sanitize_text_field( wp_unslash( $_GET['tag'] ) ) ) : array(); foreach ( $tags as $tag ) { if ( in_array( $tag, $snippet->tags, true ) ) { return true; } } return false; } /** * Display a notice showing the current search terms * * @since 1.7 */ public function search_notice() { if ( ! empty( $_REQUEST['s'] ) || ! empty( $_GET['tag'] ) ) { echo '<span class="subtitle">' . esc_html__( 'Search results', 'code-snippets' ); if ( ! empty( $_REQUEST['s'] ) ) { $s = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); if ( preg_match( '/@line:(?P<line>\d+)/', $s, $matches ) ) { // translators: 1: search query, 2: line number. $text = __( ' for “%1$s” on line %2$d', 'code-snippets' ); printf( esc_html( $text ), esc_html( trim( str_replace( $matches[0], '', $s ) ) ), intval( $matches['line'] ) ); } else { // translators: %s: search query. echo esc_html( sprintf( __( ' for “%s”', 'code-snippets' ), $s ) ); } } if ( ! empty( $_GET['tag'] ) ) { $tag = sanitize_text_field( wp_unslash( $_GET['tag'] ) ); // translators: %s: tag name. echo esc_html( sprintf( __( ' in tag “%s”', 'code-snippets' ), $tag ) ); } echo '</span>'; // translators: 1: link URL, 2: link text. printf( ' <a class="button clear-filters" href="%s">%s</a>', esc_url( remove_query_arg( array( 's', 'tag', 'cloud_search' ) ) ), esc_html__( 'Clear Filters', 'code-snippets' ) ); } } /** * Outputs content for a single row of the table * * @param Snippet $item The snippet being used for the current row. */ public function single_row( $item ) { $status = $item->active || $this->is_condition_active( $item ) ? 'active' : 'inactive'; $row_class = "snippet $status-snippet $item->type-snippet $item->scope-scope"; if ( $item->shared_network ) { $row_class .= ' shared-network-snippet'; } printf( '<tr class="%s" data-snippet-scope="%s">', esc_attr( $row_class ), esc_attr( $item->scope ) ); $this->single_row_columns( $item ); echo '</tr>'; } /** * Clone a selection of snippets * * @param array<integer> $ids List of snippet IDs. */ private function clone_snippets( array $ids ) { $snippets = get_snippets( $ids, $this->is_network ); foreach ( $snippets as $snippet ) { $snippet->id = 0; $snippet->active = false; $snippet->cloud_id = ''; // translators: %s: snippet title. $snippet->name = sprintf( __( '%s [CLONE]', 'code-snippets' ), $snippet->name ); $snippet = apply_filters( 'code_snippets/list_table/clone_snippet', $snippet ); save_snippet( $snippet ); } } } class-plugin.php 0000755 00000024477 15105501234 0007673 0 ustar 00 <?php namespace Code_Snippets; use Code_Snippets\Cloud\Cloud_API; use Code_Snippets\REST_API\Snippets_REST_Controller; use Evaluation\Evaluate_Content; use Evaluation\Evaluate_Functions; /** * The main plugin class * * @package Code_Snippets */ class Plugin { /** * Current plugin version number * * @var string */ public string $version; /** * Filesystem path to the main plugin file * * @var string */ public string $file; /** * Database class * * @var DB */ public DB $db; /** * Class for evaluating function snippets. * * @var Evaluate_Functions */ public Evaluate_Functions $evaluate_functions; /** * Class for evaluating content snippets. * * @var Evaluate_Content */ public Evaluate_Content $evaluate_content; /** * Administration area class * * @var Admin */ public Admin $admin; /** * Front-end functionality class * * @var Front_End */ public Front_End $front_end; /** * Class for managing cloud API actions. * * @var Cloud_API */ public Cloud_API $cloud_api; /** * Handles licensing and plugin updates. * * @var Licensing */ public Licensing $licensing; /** * Handles snippet handler registration. * * @var Snippet_Handler_Registry */ public Snippet_Handler_Registry $snippet_handler_registry; /** * Class constructor * * @param string $version Current plugin version. * @param string $file Path to main plugin file. */ public function __construct( string $version, string $file ) { $this->version = $version; $this->file = $file; wp_cache_add_global_groups( CACHE_GROUP ); add_filter( 'code_snippets/execute_snippets', array( $this, 'disable_snippet_execution' ), 5 ); if ( isset( $_REQUEST['snippets-safe-mode'] ) ) { add_filter( 'home_url', array( $this, 'add_safe_mode_query_var' ) ); add_filter( 'admin_url', array( $this, 'add_safe_mode_query_var' ) ); } add_action( 'rest_api_init', [ $this, 'init_rest_api' ] ); add_action( 'allowed_redirect_hosts', [ $this, 'allow_code_snippets_redirect' ] ); } /** * Initialise classes and include files */ public function load_plugin() { $includes_path = __DIR__; // Database operation functions. $this->db = new DB(); // Snippet operation functions. require_once $includes_path . '/snippet-ops.php'; $this->evaluate_content = new Evaluate_Content( $this->db ); $this->evaluate_functions = new Evaluate_Functions( $this->db ); // CodeMirror editor functions. require_once $includes_path . '/editor.php'; // General Administration functions. if ( is_admin() ) { $this->admin = new Admin(); } // Settings component. require_once $includes_path . '/settings/settings-fields.php'; require_once $includes_path . '/settings/editor-preview.php'; require_once $includes_path . '/settings/class-version-switch.php'; require_once $includes_path . '/settings/settings.php'; // Cloud List Table shared functions. require_once $includes_path . '/cloud/list-table-shared-ops.php'; // Snippet files. $this->snippet_handler_registry = new Snippet_Handler_Registry( [ 'php' => new Php_Snippet_Handler(), 'html' => new Html_Snippet_Handler(), ] ); $fs = new WordPress_File_System_Adapter(); $config_repo = new Snippet_Config_Repository( $fs ); ( new Snippet_Files( $this->snippet_handler_registry, $fs, $config_repo ) )->register_hooks(); $this->front_end = new Front_End(); $this->cloud_api = new Cloud_API(); $upgrade = new Upgrade( $this->version, $this->db ); add_action( 'plugins_loaded', array( $upgrade, 'run' ), 0 ); $this->licensing = new Licensing(); } /** * Register custom REST API controllers. * * @return void */ public function init_rest_api() { $snippets_controller = new Snippets_REST_Controller(); $snippets_controller->register_routes(); } /** * Disable snippet execution if the necessary query var is set. * * @param bool $execute_snippets Current filter value. * * @return bool New filter value. */ public function disable_snippet_execution( bool $execute_snippets ): bool { return ! empty( $_REQUEST['snippets-safe-mode'] ) && $this->current_user_can() ? false : $execute_snippets; } /** * Determine whether the menu is full or compact. * * @return bool */ public function is_compact_menu(): bool { return ! is_network_admin() && apply_filters( 'code_snippets_compact_menu', false ); } /** * Fetch the admin menu slug for a menu. * * @param string $menu Name of menu to retrieve the slug for. * * @return string The menu's slug. */ public function get_menu_slug( string $menu = '' ): string { $add = array( 'single', 'add', 'add-new', 'add-snippet', 'new-snippet', 'add-new-snippet' ); $edit = array( 'edit', 'edit-snippet' ); $import = array( 'import', 'import-snippets', 'import-code-snippets' ); $settings = array( 'settings', 'snippets-settings' ); $cloud = array( 'cloud', 'cloud-snippets' ); $welcome = array( 'welcome', 'getting-started', 'code-snippets' ); if ( in_array( $menu, $edit, true ) ) { return 'edit-snippet'; } elseif ( in_array( $menu, $add, true ) ) { return 'add-snippet'; } elseif ( in_array( $menu, $import, true ) ) { return 'import-code-snippets'; } elseif ( in_array( $menu, $settings, true ) ) { return 'snippets-settings'; } elseif ( in_array( $menu, $cloud, true ) ) { return 'snippets&type=cloud'; } elseif ( in_array( $menu, $welcome, true ) ) { return 'code-snippets-welcome'; } else { return 'snippets'; } } /** * Fetch the URL to a snippets admin menu. * * @param string $menu Name of menu to retrieve the URL to. * @param string $context URL scheme to use. * * @return string The menu's URL. */ public function get_menu_url( string $menu = '', string $context = 'self' ): string { $slug = $this->get_menu_slug( $menu ); if ( $this->is_compact_menu() && 'network' !== $context ) { $base_slug = $this->get_menu_slug(); $url = 'tools.php?page=' . $base_slug; if ( $slug !== $base_slug ) { $url .= '&sub=' . $slug; } } else { $url = 'admin.php?page=' . $slug; } if ( 'network' === $context ) { return network_admin_url( $url ); } elseif ( 'admin' === $context ) { return admin_url( $url ); } else { return self_admin_url( $url ); } } /** * Fetch the admin menu slug for a snippets admin menu. * * @param integer $snippet_id Snippet ID. * @param string $context URL scheme to use. * * @return string The URL to the edit snippet page for that snippet. */ public function get_snippet_edit_url( int $snippet_id, string $context = 'self' ): string { return add_query_arg( 'id', absint( $snippet_id ), $this->get_menu_url( 'edit', $context ) ); } /** * Allow redirecting to the Code Snippets site. * * @param array<string> $hosts Allowed hosts. * * @return array Modified allowed hosts. */ public function allow_code_snippets_redirect( array $hosts ): array { $hosts[] = 'codesnippets.pro'; $hosts[] = 'snipco.de'; return $hosts; } /** * Determine whether the current user can perform actions on snippets. * * @return boolean Whether the current user has the required capability. * * @since 2.8.6 */ public function current_user_can(): bool { return current_user_can( $this->get_cap() ); } /** * Retrieve the name of the capability required to manage sub-site snippets. * * @return string */ public function get_cap_name(): string { return apply_filters( 'code_snippets_cap', 'manage_options' ); } /** * Retrieve the name of the capability required to manage network snippets. * * @return string */ public function get_network_cap_name(): string { return apply_filters( 'code_snippets_network_cap', 'manage_network_options' ); } /** * Get the required capability to perform a certain action on snippets. * Does not check if the user has this capability or not. * * If multisite, checks if *Enable Administration Menus: Snippets* is active * under the *Settings > Network Settings* network admin menu * * @return string The capability required to manage snippets. * * @since 2.0 */ public function get_cap(): string { if ( is_multisite() ) { $menu_perms = get_site_option( 'menu_items', array() ); // If multisite is enabled and the snippet menu is not activated, restrict snippet operations to super admins only. if ( empty( $menu_perms['snippets'] ) ) { return $this->get_network_cap_name(); } } return $this->get_cap_name(); } /** * Inject the safe mode query var into URLs * * @param string $url Original URL. * * @return string Modified URL. */ public function add_safe_mode_query_var( string $url ): string { return isset( $_REQUEST['snippets-safe-mode'] ) ? add_query_arg( 'snippets-safe-mode', (bool) $_REQUEST['snippets-safe-mode'], $url ) : $url; } /** * Retrieve a list of available snippet types and their labels. * * @return array<string, string> Snippet types. */ public static function get_types(): array { return apply_filters( 'code_snippets_types', array( 'php' => __( 'Functions', 'code-snippets' ), 'html' => __( 'Content', 'code-snippets' ), 'css' => __( 'Styles', 'code-snippets' ), 'js' => __( 'Scripts', 'code-snippets' ), 'cloud' => __( 'Codevault', 'code-snippets' ), 'cloud_search' => __( 'Cloud Search', 'code-snippets' ), 'bundles' => __( 'Bundles', 'code-snippets' ), ) ); } /** * Localise a plugin script to provide the CODE_SNIPPETS object. * * @param string $handle Script handle. * * @return void */ public function localize_script( string $handle ) { wp_localize_script( $handle, 'CODE_SNIPPETS', [ 'isLicensed' => $this->licensing->is_licensed(), 'isCloudConnected' => Cloud_API::is_cloud_connection_available(), 'restAPI' => [ 'base' => esc_url_raw( rest_url() ), 'snippets' => esc_url_raw( rest_url( Snippets_REST_Controller::get_base_route() ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'localToken' => $this->cloud_api->get_local_token(), ], 'urls' => [ 'plugin' => esc_url_raw( plugins_url( '', PLUGIN_FILE ) ), 'manage' => esc_url_raw( $this->get_menu_url() ), 'edit' => esc_url_raw( $this->get_menu_url( 'edit' ) ), 'addNew' => esc_url_raw( $this->get_menu_url( 'add' ) ), ], ] ); } } class-snippet.php 0000755 00000032511 15105501234 0010043 0 ustar 00 <?php namespace Code_Snippets; use DateTime; use DateTimeZone; use Exception; /** * A snippet object. * * @since 2.4.0 * @package Code_Snippets * * @property int $id The database ID. * @property string $name The snippet title. * @property string $desc The formatted description. * @property string $code The executable code. * @property array<string> $tags An array of the tags. * @property string $scope The scope name. * @property int $condition_id ID of the condition this snippet is linked to. * @property int $priority Execution priority. * @property bool $active The active status. * @property bool $network true if is multisite-wide snippet, false if site-wide. * @property bool $shared_network Whether the snippet is a shared network snippet. * @property string $modified The date and time when the snippet data was most recently saved to the database. * @property array{string,int}|null $code_error Code error encountered when last testing snippet code. * @property int $revision Revision or version number of snippet. * @property string $cloud_id Cloud ID and ownership status of snippet. * * @property-read string $display_name The snippet name if it exists or a placeholder if it does not. * @property-read string $tags_list The tags in string list format. * @property-read string $scope_icon The dashicon used to represent the current scope. * @property-read string $scope_name Human-readable description of the snippet type. * @property-read string $type The type of snippet. * @property-read string $lang The language that the snippet code is written in. * @property-read int $modified_timestamp The last modification date in Unix timestamp format. * @property-read DateTime $modified_local The last modification date in the local timezone. * @property-read boolean $is_pro Whether the snippet type is pro-only. */ class Snippet extends Data_Item { /** * MySQL datetime format (YYYY-MM-DD hh:mm:ss). */ public const DATE_FORMAT = 'Y-m-d H:i:s'; /** * Default value used for a datetime variable. */ public const DEFAULT_DATE = '0000-00-00 00:00:00'; /** * Constructor function. * * @param array<string, mixed>|object $initial_data Initial snippet data. */ public function __construct( $initial_data = null ) { $default_values = array( 'id' => 0, 'name' => '', 'desc' => '', 'code' => '', 'tags' => array(), 'scope' => 'global', 'condition_id' => 0, 'active' => false, 'priority' => 10, 'network' => null, 'shared_network' => null, 'modified' => null, 'code_error' => null, 'revision' => 1, 'cloud_id' => '', ); $field_aliases = array( 'description' => 'desc', 'language' => 'lang', 'conditionId' => 'condition_id', ); parent::__construct( $default_values, $initial_data, $field_aliases ); } /** * Add a new tag * * @param string $tag Tag content to add to list. */ public function add_tag( string $tag ) { $this->fields['tags'][] = $tag; } /** * Determine if the snippet is a condition. * * @return bool */ public function is_condition(): bool { return 'condition' === $this->scope; } /** * Prepare a value before it is stored. * * @param mixed $value Value to prepare. * @param string $field Field name. * * @return mixed Value in the correct format. */ protected function prepare_field( $value, string $field ) { switch ( $field ) { case 'id': case 'priority': case 'condition_id': return absint( $value ); case 'tags': return code_snippets_build_tags_array( $value ); case 'active': return ( is_bool( $value ) ? $value : (bool) $value ) && ! $this->is_condition(); default: return $value; } } /** * Prepare the scope by ensuring that it is a valid choice * * @param int|string $scope The field as provided. * * @return string The field in the correct format. */ protected function prepare_scope( $scope ) { $scopes = self::get_all_scopes(); if ( in_array( $scope, $scopes, true ) ) { return $scope; } if ( is_numeric( $scope ) && isset( $scopes[ $scope ] ) ) { return $scopes[ $scope ]; } return $this->fields['scope']; } /** * If $network is anything other than true, set it to false * * @param bool $network The field as provided. * * @return bool The field in the correct format. */ protected function prepare_network( bool $network ): bool { if ( null === $network && function_exists( 'is_network_admin' ) ) { return is_network_admin(); } return true === $network; } /** * Determine the type of code a given scope will produce. * * @param string $scope Scope name. * * @return string The snippet type – will be a filename extension. */ public static function get_type_from_scope( string $scope ): string { if ( '-css' === substr( $scope, -4 ) ) { return 'css'; } elseif ( '-js' === substr( $scope, -3 ) ) { return 'js'; } elseif ( 'content' === substr( $scope, -7 ) ) { return 'html'; } elseif ( 'condition' === $scope ) { return 'cond'; } else { return 'php'; } } /** * Determine the type of code this snippet is, based on its scope * * @return string The snippet type – will be a filename extension. */ public function get_type(): string { return self::get_type_from_scope( $this->scope ); } /** * Retrieve a list of all valid types. * * @return string[] */ public static function get_types(): array { return [ 'php', 'html', 'css', 'js', 'cond' ]; } /** * Determine the language that the snippet code is written in, based on the scope * * @return string The name of a language filename extension. */ protected function get_lang(): string { return 'cond' === $this->type ? 'json' : $this->type; } /** * Prepare the modification field by ensuring it is in the correct format. * * @param DateTime|string $modified Snippet modification date. * * @return string */ protected function prepare_modified( $modified ): ?string { // If the supplied value is a DateTime object, convert it to string representation. if ( $modified instanceof DateTime ) { return $modified->format( self::DATE_FORMAT ); } // If the supplied value is probably a timestamp, attempt to convert it to a string. if ( is_numeric( $modified ) ) { return gmdate( self::DATE_FORMAT, $modified ); } // If the supplied value is a string, check it is not just the default value. if ( is_string( $modified ) && self::DEFAULT_DATE !== $modified ) { return $modified; } // Otherwise, discard the supplied value. return null; } /** * Update the last modification date to the current date and time. * * @return void */ public function update_modified() { $this->modified = gmdate( self::DATE_FORMAT ); } /** * Retrieve the snippet title if set or a placeholder title if not. * * @return string */ protected function get_display_name(): string { // translators: %s: snippet identifier. return empty( $this->name ) ? sprintf( esc_html__( 'Snippet #%d', 'code-snippets' ), $this->id ) : $this->name; } /** * Retrieve the tags in list format * * @return string The tags separated by a comma and a space. */ protected function get_tags_list(): string { return implode( ', ', $this->tags ); } /** * Retrieve a list of all available scopes * * @return array<string> List of scope names. * * @phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine */ public static function get_all_scopes(): array { return array( 'global', 'admin', 'front-end', 'single-use', 'content', 'head-content', 'footer-content', 'admin-css', 'site-css', 'site-head-js', 'site-footer-js', 'condition', ); } /** * Retrieve a list of all scope icons * * @return array<string, string> Scope name keyed to the class name of a dashicon. */ public static function get_scope_icons(): array { return array( 'global' => 'admin-site', 'admin' => 'admin-tools', 'front-end' => 'admin-appearance', 'single-use' => 'clock', 'content' => 'shortcode', 'head-content' => 'editor-code', 'footer-content' => 'editor-code', 'admin-css' => 'dashboard', 'site-css' => 'admin-customizer', 'site-head-js' => 'media-code', 'site-footer-js' => 'media-code', 'condition' => 'randomize', ); } /** * Retrieve the string representation of the scope * * @return string The name of the scope. */ protected function get_scope_name(): string { switch ( $this->scope ) { case 'global': return __( 'Global function', 'code-snippets' ); case 'admin': return __( 'Admin function', 'code-snippets' ); case 'front-end': return __( 'Front-end function', 'code-snippets' ); case 'single-use': return __( 'Single-use function', 'code-snippets' ); case 'content': return __( 'Content', 'code-snippets' ); case 'head-content': return __( 'Head content', 'code-snippets' ); case 'footer-content': return __( 'Footer content', 'code-snippets' ); case 'admin-css': return __( 'Admin styles', 'code-snippets' ); case 'site-css': return __( 'Front-end styles', 'code-snippets' ); case 'site-head-js': return __( 'Head scripts', 'code-snippets' ); case 'site-footer-js': return __( 'Footer scripts', 'code-snippets' ); } return ''; } /** * Retrieve the icon used for the current scope * * @return string A dashicon name. */ protected function get_scope_icon(): string { $icons = self::get_scope_icons(); return $icons[ $this->scope ]; } /** * Determine if the snippet is a shared network snippet * * @return bool Whether the snippet is a shared network snippet. */ protected function get_shared_network(): bool { if ( isset( $this->fields['shared_network'] ) ) { return $this->fields['shared_network']; } if ( ! is_multisite() || ! $this->fields['network'] ) { $this->fields['shared_network'] = false; } else { $shared_network_snippets = get_site_option( 'shared_network_snippets', array() ); $this->fields['shared_network'] = in_array( $this->fields['id'], $shared_network_snippets, true ); } return $this->fields['shared_network']; } /** * Retrieve the snippet modification date as a timestamp. * * @return integer Timestamp value. */ protected function get_modified_timestamp(): int { $datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) ); return $datetime ? $datetime->getTimestamp() : 0; } /** * Retrieve the modification time in the local timezone. * * @return DateTime */ protected function get_modified_local(): DateTime { $datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) ); if ( function_exists( 'wp_timezone' ) ) { $timezone = wp_timezone(); } else { $timezone = get_option( 'timezone_string' ); // Calculate the timezone manually if it is not available. if ( ! $timezone ) { $offset = (float) get_option( 'gmt_offset' ); $hours = (int) $offset; $minutes = ( $offset - $hours ) * 60; $sign = ( $offset < 0 ) ? '-' : '+'; $timezone = sprintf( '%s%02d:%02d', $sign, abs( $hours ), abs( $minutes ) ); } try { $timezone = new DateTimeZone( $timezone ); } catch ( Exception $exception ) { return $datetime; } } $datetime->setTimezone( $timezone ); return $datetime; } /** * Retrieve the last modified time, nicely formatted for readability. * * @param boolean $include_html Whether to include HTML in the output. * * @return string */ public function format_modified( bool $include_html = true ): string { if ( ! $this->modified ) { return ''; } $timestamp = $this->modified_timestamp; $time_diff = time() - $timestamp; $local_time = $this->modified_local; if ( $time_diff >= 0 && $time_diff < YEAR_IN_SECONDS ) { // translators: %s: Human-readable time difference. $human_time = sprintf( __( '%s ago', 'code-snippets' ), human_time_diff( $timestamp ) ); } else { $human_time = $local_time->format( __( 'Y/m/d', 'code-snippets' ) ); } if ( ! $include_html ) { return $human_time; } // translators: 1: date format, 2: time format. $date_format = _x( '%1$s at %2$s', 'date and time format', 'code-snippets' ); $date_format = sprintf( $date_format, get_option( 'date_format' ), get_option( 'time_format' ) ); return sprintf( '<span title="%s">%s</span>', $local_time->format( $date_format ), $human_time ); } /** * Determine whether the current snippet type is pro-only. */ private function get_is_pro(): bool { return 'css' === $this->type || 'js' === $this->type || 'cond' === $this->type; } /** * Increment the revision number by one. */ public function increment_revision() { ++$this->revision; } } class-upgrade.php 0000755 00000015070 15105501234 0010011 0 ustar 00 <?php namespace Code_Snippets; use WP_User; /** * Manages upgrade tasks such as deleting and updating options */ class Upgrade { /** * Instance of database class * * @var DB */ private DB $db; /** * The current plugin version number * * @var string */ private string $current_version; /** * Class constructor * * @param string $version Current plugin version. * @param DB $db Instance of database class. */ public function __construct( string $version, DB $db ) { $this->db = $db; $this->current_version = $version; } /** * Run the upgrade functions */ public function run() { // Always run multisite upgrades, even if not on the main site, as sub-sites depend on the network snippet table. if ( is_multisite() ) { $this->do_multisite_upgrades(); } $this->do_site_upgrades(); } /** * Perform upgrades for the current site */ private function do_site_upgrades() { $table_name = $this->db->table; $prev_version = get_option( 'code_snippets_version' ); // Do nothing if the plugin has not just been updated or installed. if ( ! version_compare( $prev_version, $this->current_version, '<' ) ) { return; } // Update the plugin version stored in the database. $updated = update_option( 'code_snippets_version', $this->current_version ); if ( ! $updated ) { return; // Bail if the data was not successfully saved to prevent this process from repeating. } $this->db->create_table( $table_name ); // Remove outdated user meta. if ( version_compare( $prev_version, '2.14.1', '<' ) ) { global $wpdb; $prefix = $wpdb->get_blog_prefix(); $menu_slug = code_snippets()->get_menu_slug(); $option_name = "{$prefix}managetoplevel_page_{$menu_slug}columnshidden"; // Loop through each user ID and remove all matching user meta. foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) { delete_metadata( 'user', $user_id, $option_name, '', true ); } } // Update the scope column of the database. if ( version_compare( $prev_version, '2.10.0', '<' ) ) { $this->migrate_scope_data( $table_name ); } // Custom capabilities were removed after version 2.9.5. if ( version_compare( $prev_version, '2.9.5', '<=' ) ) { $role = get_role( apply_filters( 'code_snippets_role', 'administrator' ) ); $role->remove_cap( apply_filters( 'code_snippets_cap', 'manage_snippets' ) ); } if ( false === $prev_version ) { add_action( 'init', [ $this, 'create_sample_content' ] ); } clean_snippets_cache( $table_name ); Welcome_API::clear_cache(); } /** * Create example snippets. * * As this uses translation functions, this should not be called earlier than 'init'. * * @return void */ public function create_sample_content() { if ( apply_filters( 'code_snippets/create_sample_content', true ) ) { $sample_snippets = $this->get_sample_content(); foreach ( $sample_snippets as $sample_snippet ) { save_snippet( $sample_snippet ); } } } /** * Perform multisite-only upgrades */ private function do_multisite_upgrades() { $table_name = $this->db->ms_table; $prev_version = get_site_option( 'code_snippets_version' ); // Do nothing if the plugin has not been updated or installed. if ( ! version_compare( $prev_version, $this->current_version, '<' ) ) { return; } // Always attempt to create or upgrade the database tables. $this->db->create_table( $table_name ); // Update the plugin version stored in the database. update_site_option( 'code_snippets_version', $this->current_version ); // Update the scope column of the database. if ( version_compare( $prev_version, '2.10.0', '<' ) ) { $this->migrate_scope_data( $table_name ); } // Custom capabilities were removed after version 2.9.5. if ( version_compare( $prev_version, '2.9.5', '<=' ) ) { $network_cap = apply_filters( 'code_snippets_network_cap', 'manage_network_snippets' ); foreach ( get_super_admins() as $admin ) { $user = new WP_User( 0, $admin ); $user->remove_cap( $network_cap ); } } clean_snippets_cache( $table_name ); } /** * Migrate data from the old integer method of storing scopes to the new string method * * @param string $table_name Name of database table. */ private function migrate_scope_data( string $table_name ) { global $wpdb; $scopes = array( 0 => 'global', 1 => 'admin', 2 => 'front-end', ); foreach ( $scopes as $scope_number => $scope_name ) { // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, will flush at end of process. $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET scope = %s WHERE scope = %d", $scope_name, $scope_number ) ); } } /** * Build a collection of sample snippets for new users to try out. * * @return array<string, Snippet> List of Snippet objects. */ private function get_sample_content(): array { $tag = "\n\n" . esc_html__( 'This is a sample snippet. Feel free to use it, edit it, or remove it.', 'code-snippets' ); $snippets_data = array( array( 'name' => esc_html__( 'Make upload filenames lowercase', 'code-snippets' ), 'code' => "add_filter( 'sanitize_file_name', 'mb_strtolower' );", 'desc' => esc_html__( 'Makes sure that image and file uploads have lowercase filenames.', 'code-snippets' ) . $tag, 'tags' => array( 'sample', 'media' ), ), array( 'name' => esc_html__( 'Disable admin bar', 'code-snippets' ), 'code' => "add_action( 'wp', function () {\n\tif ( ! current_user_can( 'manage_options' ) ) {\n\t\tshow_admin_bar( false );\n\t}\n} );", 'desc' => esc_html__( 'Turns off the WordPress admin bar for everyone except administrators.', 'code-snippets' ) . $tag, 'tags' => array( 'sample', 'admin-bar' ), 'scope' => 'front-end', ), array( 'name' => esc_html__( 'Allow smilies', 'code-snippets' ), 'code' => "add_filter( 'widget_text', 'convert_smilies' );\nadd_filter( 'the_title', 'convert_smilies' );\nadd_filter( 'wp_title', 'convert_smilies' );\nadd_filter( 'get_bloginfo', 'convert_smilies' );", 'desc' => esc_html__( 'Allows smiley conversion in obscure places.', 'code-snippets' ) . $tag, 'tags' => array( 'sample' ), ), array( 'name' => esc_html__( 'Current year', 'code-snippets' ), 'code' => "<?php echo date( 'Y' ); ?>", 'desc' => esc_html__( 'Shortcode for inserting the current year into a post or page..', 'code-snippets' ) . $tag, 'tags' => array( 'sample', 'dates' ), 'scope' => 'content', ), ); return array_map( function ( $snippet_data ) { return new Snippet( $snippet_data ); }, $snippets_data ); } } class-validator.php 0000755 00000016025 15105501234 0010350 0 ustar 00 <?php namespace Code_Snippets; /** * Validates code prior to execution. * * @package Code_Snippets */ class Validator { /** * Code to validate. * * @var string */ private string $code; /** * List of tokens. * * @var array<string> */ private array $tokens; /** * The index of the token currently being examined. * * @var integer */ private int $current; /** * The total number of tokens. * * @var integer */ private int $length; /** * Array to keep track of the various function, class and interface identifiers which have been defined. * * @var array<string, string[]> */ private array $defined_identifiers = []; /** * Exclude certain tokens from being checked. * * @var array<string, string[]> */ private array $exceptions = []; /** * Class constructor. * * @param string $code Snippet code for parsing. */ public function __construct( string $code ) { $this->code = $code; $this->tokens = token_get_all( "<?php\n" . $this->code ); $this->length = count( $this->tokens ); $this->current = 0; } /** * Determine whether the parser has reached the end of the list of tokens. * * @return bool */ private function end(): bool { return $this->current === $this->length; } /** * Retrieve the next token without moving the pointer * * @return string|array<string|int>|null The current token if the list has not been expended, null otherwise. */ private function peek() { return $this->end() ? null : $this->tokens[ $this->current ]; } /** * Move the pointer to the next token, if there is one. * * If the first argument is provided, only move the pointer if the tokens match. */ private function next() { if ( ! $this->end() ) { ++$this->current; } } /** * Check whether a particular identifier has been used previously. * * @param string $type Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE. * @param string $identifier The name of the identifier itself. * * @return bool true if the identifier is not unique. */ private function check_duplicate_identifier( string $type, string $identifier ): bool { if ( ! isset( $this->defined_identifiers[ $type ] ) ) { switch ( $type ) { case T_FUNCTION: $defined_functions = get_defined_functions(); $this->defined_identifiers[ T_FUNCTION ] = array_merge( $defined_functions['internal'], $defined_functions['user'] ); break; case T_CLASS: $this->defined_identifiers[ T_CLASS ] = get_declared_classes(); break; case T_INTERFACE: $this->defined_identifiers[ T_INTERFACE ] = get_declared_interfaces(); break; default: return false; } } $duplicate = in_array( $identifier, $this->defined_identifiers[ $type ], true ); array_unshift( $this->defined_identifiers[ $type ], $identifier ); return $duplicate && ! ( isset( $this->exceptions[ $type ] ) && in_array( $identifier, $this->exceptions[ $type ], true ) ); } /** * Validate the given PHP code and return the result. * * @return array<string, mixed>|false Array containing message if an error was encountered, false if validation was successful. */ public function validate() { while ( ! $this->end() ) { $token = $this->peek(); $this->next(); if ( ! is_array( $token ) ) { continue; } // If this is a function or class exists check, then allow this function or class to be defined. if ( T_STRING === $token[0] && ( 'function_exists' === $token[1] || 'class_exists' === $token[1] ) ) { $type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS; // Eat tokens until we find the function or class name. while ( ! $this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0] ) { $token = $this->peek(); $this->next(); } // Add the identifier to the list of exceptions. $this->exceptions[ $type ] = $this->exceptions[ $type ] ?? []; $this->exceptions[ $type ][] = trim( $token[1], '\'"' ); continue; } // If we have a double colon, followed by a class, then consume it before the next section. if ( T_DOUBLE_COLON === $token[0] ) { $token = $this->peek(); $this->next(); if ( T_CLASS === $token[0] ) { $this->next(); $token = $this->peek(); } } // Only look for class and function declaration tokens. if ( T_CLASS !== $token[0] && T_FUNCTION !== $token[0] ) { continue; } /** * Ensure the type of $token is inferred correctly. * * @var string|array<string|int> $token */ $structure_type = $token[0]; // Continue eating tokens until we find the name of the class or function. while ( ! $this->end() && T_STRING !== $token[0] && ( T_FUNCTION !== $structure_type || '(' !== $token ) && ( T_CLASS !== $structure_type || '{' !== $token ) ) { $token = $this->peek(); $this->next(); } // If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately. if ( $this->end() ) { return array( 'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ), 'line' => $token[2], ); } // If the function or class is anonymous, with no name, then no need to check. if ( ! ( T_FUNCTION === $structure_type && '(' === $token ) && ! ( T_CLASS === $structure_type && '{' === $token ) ) { // Check whether the name has already been defined. if ( $this->check_duplicate_identifier( $structure_type, $token[1] ) ) { switch ( $structure_type ) { case T_FUNCTION: /* translators: %s: PHP function name */ $message = __( 'Cannot redeclare function %s.', 'code-snippets' ); break; case T_CLASS: /* translators: %s: PHP class name */ $message = __( 'Cannot redeclare class %s.', 'code-snippets' ); break; case T_INTERFACE: /* translators: %s: PHP interface name */ $message = __( 'Cannot redeclare interface %s.', 'code-snippets' ); break; default: /* translators: %s: PHP identifier name*/ $message = __( 'Cannot redeclare %s.', 'code-snippets' ); } return array( 'message' => sprintf( $message, $token[1] ), 'line' => $token[2], ); } } // If we have entered into a class, eat tokens until we find the closing brace. if ( T_CLASS !== $structure_type ) { continue; } // Find the opening brace for the class. while ( ! $this->end() && '{' !== $token ) { $token = $this->peek(); $this->next(); } // Continue traversing the class tokens until we have found the class closing brace. $depth = 1; while ( ! $this->end() && $depth > 0 ) { $token = $this->peek(); if ( '{' === $token ) { ++$depth; } elseif ( '}' === $token ) { --$depth; } $this->next(); } // If we did not make it out of the class, then there's a problem. if ( $depth > 0 ) { return array( 'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ), 'line' => $token[2], ); } } return false; } } class-welcome-api.php 0000755 00000023042 15105501234 0010562 0 ustar 00 <?php namespace Code_Snippets; use DateTimeImmutable; use Exception; use WP_Filesystem_Direct; /** * Class for loading data from the codesnippets.pro website. * * @package Code_Snippets */ class Welcome_API { /** * URL for the welcome page data. * * @var string */ protected const WELCOME_JSON_URL = 'https://codesnippets.pro/wp-content/uploads/cs_welcome/cs_welcome.json'; /** * Limit of number of items to display when loading lists of items. * * @var int */ protected const ITEM_LIMIT = 4; /** * Limit of number of items of historic versions to display in the changelog. * * @var int */ protected const MAX_CHANGELOG_ENTRIES = 4; /** * Key used for caching welcome page data. * * @var string */ protected const CACHE_KEY = 'code_snippets_welcome_data'; /** * Data fetched from the remote API. * * @var array{ * banner: ?array, * hero-item: ?array, * features: ?array, * partners: ?array, * changelog: ?array * } */ private array $welcome_data; /** * Populate the $welcome_data variable when the class is loaded. * * @return void */ public function __construct() { $stored_data = get_transient( self::CACHE_KEY ); if ( is_array( $stored_data ) ) { $this->welcome_data = $stored_data; } else { $this->welcome_data = []; $this->fetch_remote_welcome_data(); $this->build_changelog_data(); set_transient( self::CACHE_KEY, $this->welcome_data, DAY_IN_SECONDS * 2 ); } } /** * Purge the welcome data cache. * * @return void */ public static function clear_cache() { delete_transient( self::CACHE_KEY ); } /** * Safely retrieve an array from an object, ensuring it exists and is valid, returning a default value if not. * * @param array<string, mixed> $items Associative array containing array to extract. * @param string $key Array key. * * @return array Extracted array, or empty array if array is missing. */ private static function safe_get_array( array $items, string $key ): array { return isset( $items[ $key ] ) && is_array( $items[ $key ] ) ? $items[ $key ] : []; } /** * Parse DateTime value from a string without triggering an error. * * @param string $datetime String representation of DateTime value. * * @return DateTimeImmutable|null */ private static function safe_parse_datetime( string $datetime ): ?DateTimeImmutable { try { return new DateTimeImmutable( $datetime ); } catch ( Exception $e ) { return null; } } /** * Parse remote hero item data. * * @param array $remote Remote hero item data. * * @return array */ private function parse_hero_item( array $remote ): array { return [ 'name' => $remote[0]['name'] ?? '', 'follow_url' => $remote[0]['follow_url'] ?? '', 'image_url' => $remote[0]['image_url'] ?? '', ]; } /** * Parse remote banner data. * * @param array $remote Remote hero item data. * * @return array */ private function parse_banner( array $remote ): array { return [ 'key' => sanitize_key( $remote['key'] ) ?? '', 'start_datetime' => self::safe_parse_datetime( $remote['start_datetime'] ), 'end_datetime' => self::safe_parse_datetime( $remote['end_datetime'] ), 'text_free' => $remote['text_free'] ?? '', 'action_url_free' => $remote['action_url_free'] ?? '', 'action_label_free' => $remote['action_label_free'] ?? '', 'text_pro' => $remote['text_pro'] ?? '', 'action_url_pro' => $remote['action_url_pro'] ?? '', 'action_label_pro' => $remote['action_label_pro'] ?? '', ]; } /** * Parse a list of features from a remote dataset. * * @param array $remote Remote data. * * @return array[] Parsed feature data. */ private function parse_features( array $remote ): array { $limit = max( self::ITEM_LIMIT, count( $remote ) ); $features = []; for ( $i = 0; $i < $limit; $i++ ) { $feature = $remote[ $i ]; $features[] = [ 'title' => $feature['title'] ?? '', 'follow_url' => $feature['follow_url'] ?? '', 'image_url' => $feature['image_url'] ?? '', 'category' => $feature['category'] ?? '', 'description' => $feature['description'] ?? '', ]; } return $features; } /** * Parse a list of partners from a remote dataset. * * @param array $remote Remote data. * * @return array[] Parsed partner data. */ private function parse_partners( array $remote ): array { $limit = max( self::ITEM_LIMIT, count( $remote ) ); $partners = []; for ( $i = 0; $i < $limit; $i++ ) { $partner = $remote[ $i ]; $partners[] = [ 'title' => $partner['title'] ?? '', 'follow_url' => $partner['follow_url'] ?? '', 'image_url' => $partner['image_url'] ?? '', ]; } return $partners; } /** * Fetch remote welcome data from the remote server and add it to the stored data. * * @return void */ protected function fetch_remote_welcome_data() { $remote_welcome_data = wp_remote_get( self::WELCOME_JSON_URL ); if ( is_wp_error( $remote_welcome_data ) ) { return; } $remote_welcome_data = json_decode( wp_remote_retrieve_body( $remote_welcome_data ), true ); if ( ! is_array( $remote_welcome_data ) ) { return; } $this->welcome_data['banner'] = $this->parse_banner( self::safe_get_array( $remote_welcome_data, 'banner' ) ); $this->welcome_data['hero-item'] = $this->parse_hero_item( self::safe_get_array( $remote_welcome_data, 'hero-item' ) ); $this->welcome_data['features'] = $this->parse_features( self::safe_get_array( $remote_welcome_data, 'features' ) ); $this->welcome_data['partners'] = $this->parse_partners( self::safe_get_array( $remote_welcome_data, 'partners' ) ); } /** * Build the full list of latest changes for caching. * * @return void */ protected function build_changelog_data() { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; $filesystem = new WP_Filesystem_Direct( null ); $changelog_filename = 'CHANGELOG.md'; $changelog = []; $changelog_dir = plugin_dir_path( PLUGIN_FILE ); while ( plugin_dir_path( $changelog_dir ) !== $changelog_dir && ! $filesystem->exists( $changelog_dir . $changelog_filename ) ) { $changelog_dir = plugin_dir_path( $changelog_dir ); } if ( ! $filesystem->exists( $changelog_dir . $changelog_filename ) ) { return; } $changelog_contents = $filesystem->get_contents( $changelog_dir . $changelog_filename ); $changelog_releases = explode( "\n## ", $changelog_contents ); foreach ( array_slice( $changelog_releases, 1, self::MAX_CHANGELOG_ENTRIES ) as $changelog_release ) { $sections = explode( "\n### ", $changelog_release ); if ( count( $sections ) < 2 ) { continue; } $header_parts = explode( '(', $sections[0], 2 ); $version = trim( trim( $header_parts[0] ), '[]' ); $changelog[ $version ] = []; foreach ( array_slice( $sections, 1 ) as $section_contents ) { $lines = array_filter( array_map( 'trim', explode( "\n", $section_contents ) ) ); $section_type = $lines[0]; foreach ( array_slice( $lines, 1 ) as $line ) { $entry = trim( str_replace( '(PRO)', '', str_replace( '*', '', $line ) ) ); $core_or_pro = false === strpos( $line, '(PRO)' ) ? 'core' : 'pro'; if ( ! isset( $changelog[ $version ][ $section_type ] ) ) { $changelog[ $version ][ $section_type ] = [ $core_or_pro => [ $entry ], ]; } elseif ( ! isset( $changelog[ $version ][ $section_type ][ $core_or_pro ] ) ) { $changelog[ $version ][ $section_type ][ $core_or_pro ] = [ $entry ]; } else { $changelog[ $version ][ $section_type ][ $core_or_pro ][] = $entry; } } } } $this->welcome_data['changelog'] = $changelog; } /** * Retrieve banner information. * * @return array{ * key: string, * start_datetime: ?DateTimeImmutable, * end_datetime: ?DateTimeImmutable, * text_free: string, * action_url_free: string, * action_label_free: string, * text_pro: string, * action_url_pro: string, * action_label_pro: string tet * } */ public function get_banner(): array { return $this->welcome_data['banner'] ?? []; } /** * Retrieve hero information. * * @return array{ * name: string, * follow_url: string, * image_url: string * } */ public function get_hero_item(): array { return $this->welcome_data['hero-item'] ?? []; } /** * Retrieve the list of features retrieved from the remote API. * * @return array{ * title: string, * follow_url: string, * image_url: string, * category: string, * description: string * }[] Feature details. */ public function get_features(): array { return $this->welcome_data['features'] ?? []; } /** * Retrieve the list of partners retrieved from the remote API. * * @return array{ * title: string, * follow_url: string, * image_url: string * }[] Partner details. */ public function get_partners(): array { return $this->welcome_data['partners'] ?? []; } /** * Retrieve a list of latest changes for display. * * @return array<string, array{ * 'Added': ?array<'core' | 'pro', string>, * 'Fixed': ?array<'core' | 'pro', string>, * 'Improved': ?array<'core' | 'pro', string>, * 'Other': ?array<'core' | 'pro', string> * }> */ public function get_changelog(): array { return $this->welcome_data['changelog'] ?? []; } } deactivation-notice.php 0000755 00000003707 15105501234 0011214 0 ustar 00 <?php /** * File loaded when the plugin cannot be activated. * * All code in this file should be compatible with PHP 5.2 or later. * * @package Code_Snippets * * @noinspection PhpNestedDirNameCallsCanBeReplacedWithLevelParameterInspection * * phpcs:disable Modernize.FunctionCalls.Dirname.FileConstant */ if ( ! defined( 'ABSPATH' ) || function_exists( 'code_snippets_deactivation_notice' ) ) { return; } /** * Deactivate the plugin and display a notice informing the user that this has happened. * * @return void * * @since 3.3.0 */ function code_snippets_deactivation_notice() { $plugins = array(); $required_php_version = '7.4'; if ( version_compare( phpversion(), $required_php_version, '<' ) ) { echo '<div class="error fade"><p><strong>'; // translators: %s: required PHP version number. echo esc_html( sprintf( __( 'Code Snippets requires PHP %s or later.', 'code-snippets' ), $required_php_version ) ); echo '</strong><br>'; $update_url = function_exists( 'wp_get_default_update_php_url' ) ? wp_get_default_update_php_url() : 'https://wordpress.org/support/update-php/'; // translators: %s: Update PHP URL. $text = __( 'Please <a href="%s">upgrade your server to the latest version of PHP</a> to continue using Code Snippets.', 'code-snippets' ); echo wp_kses( sprintf( $text, $update_url ), array( 'a' => array( 'href' => array() ) ) ); echo '</p></div>'; $plugins[] = plugin_basename( dirname( dirname( __FILE__ ) ) . '/code-snippets.php' ); } if ( defined( 'CODE_SNIPPETS_FILE' ) ) { echo '<div class="error fade"><p>'; esc_html_e( 'Another version of Code Snippets appears to be installed. Deactivating this version.', 'code-snippets' ); echo '</p></div>'; $plugins[] = 'code-snippets/code-snippets.php'; } if ( $plugins ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; deactivate_plugins( array_unique( $plugins ) ); } } add_action( 'admin_notices', 'code_snippets_deactivation_notice' ); editor.php 0000755 00000006750 15105501234 0006552 0 ustar 00 <?php /** * Functions for using the built-in code editor library * * @package Code_Snippets */ namespace Code_Snippets; use function Code_Snippets\Settings\get_setting; /** * Register and load the CodeMirror library. * * @param string $type Type of code editor – either 'php', 'css', 'js', or 'html'. * @param array<string, mixed> $extra_atts Pass a list of attributes to override the saved ones. */ function enqueue_code_editor( string $type, array $extra_atts = [] ) { $plugin = code_snippets(); $modes = [ 'css' => 'text/css', 'php' => 'php-snippet', 'js' => 'javascript', 'html' => 'application/x-httpd-php', ]; if ( ! isset( $modes[ $type ] ) ) { $type = 'php'; } $default_atts = [ 'mode' => $modes[ $type ], 'inputStyle' => 'textarea', 'matchBrackets' => true, 'extraKeys' => [ 'Alt-F' => 'findPersistent', 'Ctrl-Space' => 'autocomplete', 'Ctrl-/' => 'toggleComment', 'Cmd-/' => 'toggleComment', 'Alt-Up' => 'swapLineUp', 'Alt-Down' => 'swapLineDown', ], 'gutters' => [ 'CodeMirror-lint-markers', 'CodeMirror-foldgutter' ], 'lint' => 'css' === $type || 'php' === $type, 'direction' => 'ltr', 'colorpicker' => [ 'mode' => 'edit' ], 'foldOptions' => [ 'widget' => '...' ], ]; // Add relevant saved setting values to the default attributes. $plugin_settings = Settings\get_settings_values(); $setting_fields = Settings\get_settings_fields(); foreach ( $setting_fields['editor'] as $field_id => $field ) { // The 'codemirror' setting field specifies the name of the attribute. $default_atts[ $field['codemirror'] ] = $plugin_settings['editor'][ $field_id ]; } // Merge the default attributes with the ones passed into the function. $atts = wp_parse_args( $default_atts, $extra_atts ); $atts = apply_filters( 'code_snippets_codemirror_atts', $atts ); // Ensure number values are not formatted as strings. foreach ( [ 'indentUnit', 'tabSize' ] as $number_att ) { $atts[ $number_att ] = intval( $atts[ $number_att ] ); } // Remove fontSize from the options and add it as an inline style. if ( isset( $atts['fontSize'] ) ) { $font_size = intval( $atts['fontSize'] ); unset( $atts['fontSize'] ); wp_add_inline_style( 'code-editor', ".CodeMirror { font-size: {$font_size}px !important; }" ); } wp_enqueue_code_editor( [ 'type' => $modes[ $type ], 'codemirror' => $atts, ] ); wp_enqueue_script( 'htmlhint' ); wp_enqueue_script( 'csslint' ); wp_enqueue_script( 'jshint' ); wp_enqueue_script( 'code-snippets-code-editor', plugins_url( 'dist/editor.js', $plugin->file ), [ 'code-editor' ], $plugin->version, true ); // CodeMirror Theme. $theme = get_setting( 'editor', 'theme' ); if ( 'default' !== $theme ) { wp_enqueue_style( 'code-snippets-editor-theme-' . $theme, plugins_url( "dist/editor-themes/$theme.css", $plugin->file ), [ 'code-editor' ], $plugin->version ); } } /** * Retrieve a list of the available CodeMirror themes. * * @return array<string> The available themes. */ function get_editor_themes(): array { static $themes = null; if ( ! is_null( $themes ) ) { return $themes; } $themes = array(); $themes_dir = plugin_dir_path( PLUGIN_FILE ) . 'dist/editor-themes/'; $theme_files = glob( $themes_dir . '*.css' ); foreach ( $theme_files as $theme ) { $theme = str_replace( $themes_dir, '', $theme ); $theme = str_replace( '.css', '', $theme ); $themes[] = $theme; } return $themes; } load.php 0000755 00000002463 15105501234 0006200 0 ustar 00 <?php /** * Initialise and load the plugin under the proper namespace. * * @package Code_Snippets */ namespace Code_Snippets; /** * The version number for this release of the plugin. * This will later be used for upgrades and enqueuing files. * * This should be set to the 'Plugin Version' value defined * in the plugin header. * * @var string A PHP-standardized version number string. */ const PLUGIN_VERSION = CODE_SNIPPETS_VERSION; /** * The full path to the main file of this plugin. * * This can later be used with functions such as * plugin_dir_path(), plugins_url() and plugin_basename() * to retrieve information about plugin paths. * * @var string */ const PLUGIN_FILE = CODE_SNIPPETS_FILE; /** * Name of the group used for caching data. * * @var string */ const CACHE_GROUP = 'code_snippets'; /** * Namespace used for REST API endpoints. * * @var string */ const REST_API_NAMESPACE = 'code-snippets/v'; // Load dependencies with Composer. require_once dirname( __DIR__ ) . '/vendor/autoload.php'; /** * Retrieve the instance of the main plugin class. * * @return Plugin * @since 2.6.0 */ function code_snippets(): Plugin { static $plugin; if ( is_null( $plugin ) ) { $plugin = new Plugin( PLUGIN_VERSION, PLUGIN_FILE ); } return $plugin; } code_snippets()->load_plugin(); snippet-ops.php 0000755 00000046202 15105501234 0007541 0 ustar 00 <?php /** * Functions to perform snippet operations * * @package Code_Snippets */ namespace Code_Snippets; use ParseError; use function Code_Snippets\Settings\get_self_option; use function Code_Snippets\Settings\update_self_option; /** * Clean the cache where active snippets are stored. * * @param string $table_name Snippets table name. * @param array<string>|false $scopes List of scopes. Optional. If not provided, will flush the cache for all scopes. * * @return void */ function clean_active_snippets_cache( string $table_name, $scopes = false ) { $scope_groups = $scopes ? [ $scopes ] : [ [ 'head-content', 'footer-content' ], [ 'global', 'single-use', 'front-end' ], [ 'global', 'single-use', 'admin' ], ]; foreach ( $scope_groups as $scopes ) { wp_cache_delete( sprintf( 'active_snippets_%s_%s', sanitize_key( join( '_', $scopes ) ), $table_name ), CACHE_GROUP ); } } /** * Flush all snippets caches for a given database table. * * @param string $table_name Snippets table name. * * @return void */ function clean_snippets_cache( string $table_name ) { wp_cache_delete( "all_snippet_tags_$table_name", CACHE_GROUP ); wp_cache_delete( "all_snippets_$table_name", CACHE_GROUP ); clean_active_snippets_cache( $table_name ); } /** * Retrieve a list of snippets from the database. * Read operation. * * @param array<string> $ids The IDs of the snippets to fetch. * @param bool|null $network Retrieve multisite-wide snippets (true) or site-wide snippets (false). * * @return array<Snippet> List of Snippet objects. * * @since 2.0 */ function get_snippets( array $ids = array(), ?bool $network = null ): array { global $wpdb; // If only one ID has been passed in, defer to the get_snippet() function. $ids_count = count( $ids ); if ( 1 === $ids_count ) { return array( get_snippet( $ids[0], $network ) ); } $network = DB::validate_network_param( $network ); $table_name = code_snippets()->db->get_table_name( $network ); $snippets = wp_cache_get( "all_snippets_$table_name", CACHE_GROUP ); // Fetch all snippets from the database if none are cached. if ( ! is_array( $snippets ) ) { $results = $wpdb->get_results( "SELECT * FROM $table_name", ARRAY_A ); $snippets = $results ? array_map( function ( $snippet_data ) use ( $network ) { $snippet_data['network'] = $network; return new Snippet( $snippet_data ); }, $results ) : array(); $snippets = apply_filters( 'code_snippets/get_snippets', $snippets, $network ); if ( 0 === $ids_count ) { wp_cache_set( "all_snippets_$table_name", $snippets, CACHE_GROUP ); } } // If a list of IDs are provided, narrow down the snippets list. if ( $ids_count > 0 ) { $ids = array_map( 'intval', $ids ); return array_values( array_filter( $snippets, function ( Snippet $snippet ) use ( $ids ) { return in_array( $snippet->id, $ids, true ); } ) ); } return $snippets; } /** * Gets all used tags from the database. * Read operation. * * @since 2.0 */ function get_all_snippet_tags() { global $wpdb; $table_name = code_snippets()->db->get_table_name(); $cache_key = "all_snippet_tags_$table_name"; $tags = wp_cache_get( $cache_key, CACHE_GROUP ); if ( $tags ) { return $tags; } // Grab all tags from the database. $tags = array(); $all_tags = $wpdb->get_col( "SELECT tags FROM $table_name" ); // Merge all tags into a single array. foreach ( $all_tags as $snippet_tags ) { $snippet_tags = code_snippets_build_tags_array( $snippet_tags ); $tags = array_merge( $snippet_tags, $tags ); } // Remove duplicate tags. $tags = array_values( array_unique( $tags, SORT_REGULAR ) ); wp_cache_set( $cache_key, $tags, CACHE_GROUP ); return $tags; } /** * Make sure that the tags are a valid array. * * @param array|string $tags The tags to convert into an array. * * @return array<string> The converted tags. * * @since 2.0.0 */ function code_snippets_build_tags_array( $tags ): array { /* If there are no tags set, return an empty array. */ if ( empty( $tags ) ) { return array(); } /* If the tags are set as a string, convert them into an array. */ if ( is_string( $tags ) ) { $tags = wp_strip_all_tags( $tags ); $tags = str_replace( ', ', ',', $tags ); $tags = explode( ',', $tags ); } /* If we still don't have an array, just convert whatever we do have into one. */ return (array) $tags; } /** * Retrieve a single snippets from the database. * Will return empty snippet object if no snippet ID is specified. * Read operation. * * @param int $id The ID of the snippet to retrieve. 0 to build a new snippet. * @param bool|null $network Retrieve a multisite-wide snippet (true) or site-wide snippet (false). * * @return Snippet A single snippet object. * * @since 2.0.0 */ function get_snippet( int $id = 0, ?bool $network = null ): Snippet { global $wpdb; $id = absint( $id ); $network = DB::validate_network_param( $network ); $table_name = code_snippets()->db->get_table_name( $network ); if ( 0 === $id ) { // If an invalid ID is provided, then return an empty snippet object. $snippet = new Snippet(); } else { $cached_snippets = wp_cache_get( "all_snippets_$table_name", CACHE_GROUP ); // Attempt to fetch snippet from the cached list, if it exists. if ( is_array( $cached_snippets ) ) { foreach ( $cached_snippets as $snippet ) { if ( $snippet->id === $id ) { return apply_filters( 'code_snippets/get_snippet', $snippet, $id, $network ); } } } // Otherwise, retrieve the snippet from the database. // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching $snippet_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $id ) ); $snippet = new Snippet( $snippet_data ); } $snippet->network = $network; return apply_filters( 'code_snippets/get_snippet', $snippet, $id, $network ); } /** * Ensure the list of shared network snippets is correct if one has been recently activated or deactivated. * Write operation. * * @access private * * @param Snippet[] $snippets Snippets that was recently updated. * * @return boolean Whether an update was performed. */ function update_shared_network_snippets( array $snippets ): bool { $shared_ids = []; $unshared_ids = []; if ( ! is_multisite() ) { return false; } foreach ( $snippets as $snippet ) { if ( $snippet->network ) { if ( $snippet->shared_network ) { $shared_ids[] = $snippet->id; } else { $unshared_ids[] = $snippet->id; } } } if ( ! $shared_ids && ! $unshared_ids ) { return false; } $existing_shared_ids = get_site_option( 'shared_network_snippets', [] ); $updated_shared_ids = array_values( array_diff( array_merge( $existing_shared_ids, $shared_ids ), $unshared_ids ) ); if ( $existing_shared_ids === $updated_shared_ids ) { return false; } update_site_option( 'shared_network_snippets', $updated_shared_ids ); // Deactivate the snippet on all sites if necessary. if ( $unshared_ids ) { $sites = get_sites( [ 'fields' => 'ids' ] ); foreach ( $sites as $site ) { switch_to_blog( $site ); $active_shared_ids = get_option( 'active_shared_network_snippets' ); if ( is_array( $active_shared_ids ) ) { $active_shared_ids = array_diff( $active_shared_ids, $unshared_ids ); update_option( 'active_shared_network_snippets', $active_shared_ids ); } clean_active_snippets_cache( code_snippets()->db->ms_table ); } restore_current_blog(); } return true; } /** * Activates a snippet. * Write operation. * * @param int $id ID of the snippet to activate. * @param bool|null $network Whether the snippets are multisite-wide (true) or site-wide (false). * * @return Snippet|string Snippet object on success, error message on failure. * @since 2.0.0 */ function activate_snippet( int $id, ?bool $network = null ) { global $wpdb; $network = DB::validate_network_param( $network ); $table_name = code_snippets()->db->get_table_name( $network ); // Retrieve the snippet code from the database for validation before activating. $snippet = get_snippet( $id, $network ); if ( 0 === $snippet->id ) { // translators: %d: snippet identifier. return sprintf( __( 'Could not locate snippet with ID %d.', 'code-snippets' ), $id ); } if('php' == $snippet->type ){ $validator = new Validator( $snippet->code ); if ( $validator->validate() ) { return __( 'Could not activate snippet: code did not pass validation.', 'code-snippets' ); } } $result = $wpdb->update( $table_name, array( 'active' => '1' ), array( 'id' => $id ), array( '%d' ), array( '%d' ) ); if ( ! $result ) { return __( 'Could not activate snippet.', 'code-snippets' ); } update_shared_network_snippets( [ $snippet ] ); do_action( 'code_snippets/activate_snippet', $snippet, $network ); clean_snippets_cache( $table_name ); return $snippet; } /** * Activates multiple snippets. * Write operation. * * @param array<integer> $ids The IDs of the snippets to activate. * @param bool|null $network Whether the snippets are multisite-wide (true) or site-wide (false). * * @return Snippet[]|null Snippets which were successfully activated, or null on failure. * * @since 2.0.0 */ function activate_snippets( array $ids, ?bool $network = null ): ?array { global $wpdb; $network = DB::validate_network_param( $network ); $table_name = code_snippets()->db->get_table_name( $network ); $snippets = get_snippets( $ids, $network ); if ( ! $snippets ) { return null; } // Loop through each snippet code and validate individually. $valid_ids = []; $valid_snippets = []; foreach ( $snippets as $snippet ) { $validator = new Validator( $snippet->code ); $code_error = $validator->validate(); if ( ! $code_error ) { $valid_ids[] = $snippet->id; $valid_snippets[] = $snippet; } } // If there are no valid snippets, then we're done. if ( ! $valid_ids ) { return null; } // Build a SQL query containing all IDs, as wpdb::update does not support OR conditionals. $ids_format = implode( ',', array_fill( 0, count( $valid_ids ), '%d' ) ); // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $rows_updated = $wpdb->query( $wpdb->prepare( "UPDATE $table_name SET active = 1 WHERE id IN ($ids_format)", $valid_ids ) ); if ( ! $rows_updated ) { return null; } update_shared_network_snippets( $valid_snippets ); do_action( 'code_snippets/activate_snippets', $valid_snippets, $table_name ); clean_snippets_cache( $table_name ); return $valid_ids; } /** * Deactivate a snippet. * Write operation. * * @param int $id ID of the snippet to deactivate. * @param bool|null $network Whether the snippets are multisite-wide (true) or site-wide (false). * * @return Snippet|null Snippet that was deactivated on success, or null on failure. * * @since 2.0.0 */ function deactivate_snippet( int $id, ?bool $network = null ): ?Snippet { global $wpdb; $network = DB::validate_network_param( $network ); $table = code_snippets()->db->get_table_name( $network ); // Set the snippet to inactive. $result = $wpdb->update( $table, array( 'active' => '0' ), array( 'id' => $id ), array( '%d' ), array( '%d' ) ); if ( ! $result ) { return null; } // Update the recently active list. $snippet = get_snippet( $id ); $recently_active = [ $id => time() ] + get_self_option( $network, 'recently_activated_snippets', [] ); update_self_option( $network, 'recently_activated_snippets', $recently_active ); update_shared_network_snippets( [ $snippet ] ); do_action( 'code_snippets/deactivate_snippet', $id, $network ); clean_snippets_cache( $table ); return $snippet; } /** * Deletes a snippet from the database. * Write operation. * * @param int $id ID of the snippet to delete. * @param bool|null $network Delete from network-wide (true) or site-wide (false) table. * * @return bool Whether the snippet was deleted successfully. * * @since 2.0.0 */ function delete_snippet( int $id, ?bool $network = null ): bool { global $wpdb; $network = DB::validate_network_param( $network ); $table = code_snippets()->db->get_table_name( $network ); $snippet = get_snippet( $id, $network ); $result = $wpdb->delete( $table, array( 'id' => $id ), array( '%d' ) ); if ( $result ) { do_action( 'code_snippets/delete_snippet', $snippet, $network ); clean_snippets_cache( $table ); code_snippets()->cloud_api->delete_snippet_from_transient_data( $id ); } return (bool) $result; } /** * Test snippet code for errors, augmenting the snippet object. * * @param Snippet $snippet Snippet object. */ function test_snippet_code( Snippet $snippet ) { $snippet->code_error = null; if ( 'php' !== $snippet->type ) { return; } $validator = new Validator( $snippet->code ); $result = $validator->validate(); if ( $result ) { $snippet->code_error = [ $result['message'], $result['line'] ]; } if ( ! $snippet->code_error && 'single-use' !== $snippet->scope ) { $result = execute_snippet( $snippet->code, $snippet->id, true ); if ( $result instanceof ParseError ) { $snippet->code_error = [ ucfirst( rtrim( $result->getMessage(), '.' ) ) . '.', $result->getLine(), ]; } } } /** * Saves a snippet to the database. * Write operation. * * @param Snippet|array<string, mixed> $snippet The snippet to add/update to the database. * * @return Snippet|null Updated snippet. * * @since 2.0.0 */ function save_snippet( $snippet ) { global $wpdb; $table = code_snippets()->db->get_table_name( $snippet->network ); if ( ! $snippet instanceof Snippet ) { $snippet = new Snippet( $snippet ); } // Update the last modification date if necessary. $snippet->update_modified(); if ( 'php' === $snippet->type ) { // Remove tags from beginning and end of snippet. $snippet->code = preg_replace( '|^\s*<\?(php)?|', '', $snippet->code ); $snippet->code = preg_replace( '|\?>\s*$|', '', $snippet->code ); // Deactivate snippet if code contains errors. if ( $snippet->active && 'single-use' !== $snippet->scope ) { test_snippet_code( $snippet ); if ( $snippet->code_error ) { $snippet->active = 0; } } } // Increment the revision number unless revision = 1 or revision is not set. if ( $snippet->revision && $snippet->revision > 1 ) { $snippet->increment_revision(); } // Shared network snippets are always considered inactive. $snippet->active = $snippet->active && ! $snippet->shared_network; // Build the list of data to insert. $data = [ 'name' => $snippet->name, 'description' => $snippet->desc, 'code' => $snippet->code, 'tags' => $snippet->tags_list, 'scope' => $snippet->scope, 'condition_id' => intval( $snippet->condition_id ), 'priority' => $snippet->priority, 'active' => intval( $snippet->active ), 'modified' => $snippet->modified, 'revision' => $snippet->revision, 'cloud_id' => $snippet->cloud_id ? $snippet->cloud_id : null, ]; // Create a new snippet if the ID is not set. if ( 0 === $snippet->id ) { $result = $wpdb->insert( $table, $data, '%s' ); if ( false === $result ) { return null; } $snippet->id = $wpdb->insert_id; do_action( 'code_snippets/create_snippet', $snippet, $table ); } else { // Otherwise, update the snippet data. $result = $wpdb->update( $table, $data, [ 'id' => $snippet->id ], null, [ '%d' ] ); if ( false === $result ) { return null; } do_action( 'code_snippets/update_snippet', $snippet, $table ); } update_shared_network_snippets( [ $snippet ] ); clean_snippets_cache( $table ); return $snippet; } /** * Execute a snippet. * Execute operation. * * Code must NOT be escaped, as it will be executed directly. * * @param string $code Snippet code to execute. * @param integer $id Snippet ID. * @param boolean $force Force snippet execution, even if save mode is active. * * @return ParseError|mixed Code error if encountered during execution, or result of snippet execution otherwise. * * @since 2.0.0 */ function execute_snippet( string $code, int $id = 0, bool $force = false ) { /** * Do not continue if safe mode is active. * * @noinspection PhpUndefinedConstantInspection */ if ( empty( $code ) || ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ) { return false; } ob_start(); try { $result = eval( $code ); } catch ( ParseError $parse_error ) { $result = $parse_error; } ob_end_clean(); do_action( 'code_snippets/after_execute_snippet', $code, $id, $result ); return $result; } /** * Retrieve a single snippets from the database using its cloud ID. * * Read operation. * * @param string $cloud_id The Cloud ID of the snippet to retrieve. * @param boolean|null $multisite Retrieve a multisite-wide snippet (true) or site-wide snippet (false). * * @return Snippet|null A single snippet object or null if no snippet was found. * * @since 3.5.0 */ function get_snippet_by_cloud_id( string $cloud_id, ?bool $multisite = null ): ?Snippet { global $wpdb; $multisite = DB::validate_network_param( $multisite ); $table_name = code_snippets()->db->get_table_name( $multisite ); $cached_snippets = wp_cache_get( "all_snippets_$table_name", CACHE_GROUP ); // Attempt to fetch snippet from the cached list, if it exists. if ( is_array( $cached_snippets ) ) { foreach ( $cached_snippets as $snippet ) { if ( $snippet->cloud_id === $cloud_id ) { return apply_filters( 'code_snippets/get_snippet_by_cloud_id', $snippet, $cloud_id, $multisite ); } } } // Otherwise, search for the snippet from the database. $snippet_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE cloud_id = %s", $cloud_id ) ); // cache pass, db call ok. $snippet = $snippet_data ? new Snippet( $snippet_data ) : null; return apply_filters( 'code_snippets/get_snippet_by_cloud_id', $snippet, $cloud_id, $multisite ); } /** * Update a snippet entry given a list of fields. * Write operation. * * @param int $snippet_id ID of the snippet to update. * @param array<string, mixed> $fields An array of fields mapped to their values. * @param bool|null $network Update in network-wide (true) or site-wide (false) table. */ function update_snippet_fields( int $snippet_id, array $fields, ?bool $network = null ) { global $wpdb; $table = code_snippets()->db->get_table_name( $network ); // Build a new snippet object for the validation. $snippet = new Snippet(); $snippet->id = $snippet_id; // Validate fields through the snippet class and copy them into a clean array. $clean_fields = array(); foreach ( $fields as $field => $value ) { if ( $snippet->set_field( $field, $value ) ) { $clean_fields[ $field ] = $snippet->$field; } } // Update the snippet in the database. $wpdb->update( $table, $clean_fields, array( 'id' => $snippet->id ), null, array( '%d' ) ); do_action( 'code_snippets/update_snippet', $snippet->id, $table ); clean_snippets_cache( $table ); } function execute_snippet_from_flat_file( $code, $file, int $id = 0, bool $force = false ) { if ( ! is_file( $file ) ) { return execute_snippet( $code, $id, $force ); } if ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) { return false; } require_once $file; do_action( 'code_snippets/after_execute_snippet_from_flat_file', $file, $id ); } strings.php 0000755 00000003466 15105501234 0006756 0 ustar 00 <?php /** * Additional strings needed for translation, but not currently present within code. * * @package Code_Snippets */ __( 'You can now safely remove the free version of Code Snippets', 'code-snippets' ); __( 'Success', 'code-snippets' ); __( 'Notice', 'code-snippets' ); __( 'Thanks', 'code-snippets' ); __( 'Ok', 'code-snippets' ); // settings-fields.php. __( 'Minify Snippet Output', 'code-snippets' ); __( 'Minify snippet output by removing whitespace and optimizing code to reduce load times.', 'code-snippets' ); // edit.php. __( 'View Full Stylesheet', 'code-snippets' ); __( 'View Full Script', 'code-snippets' ); array( 'site-css' => __( 'Site front-end stylesheet', 'code-snippets' ), 'admin-css' => __( 'Administration area stylesheet', 'code-snippets' ), 'site-head-js' => __( 'JavaScript loaded in the site &lt;head&gt; section', 'code-snippets' ), 'site-footer-js' => __( 'JavaScript loaded just before the closing &lt;/body&gt; tag', 'code-snippets' ), ); // class-content-widget.php. __( 'Processing Options', 'code-snippets' ); __( 'Alignment', 'code-snippets' ); __( 'Left', 'code-snippets' ); __( 'Center', 'code-snippets' ); __( 'Right', 'code-snippets' ); __( 'Justified', 'code-snippets' ); __( 'Text Color', 'code-snippets' ); __( 'Select a snippet to show', 'code-snippets' ); // class-source-widget.php. __( 'Code Snippet Source', 'code-snippets' ); __( 'Functions (PHP)', 'code-snippets' ); __( 'Content (Mixed)', 'code-snippets' ); __( 'Styles (CSS)', 'code-snippets' ); __( 'Scripts (JS)', 'code-snippets' ); __( 'Highlight Lines', 'code-snippets' ); __( 'Word Wrap', 'code-snippets' ); __( 'On', 'code-snippets' ); __( 'Off', 'code-snippets' ); __( 'Height', 'code-snippets' ); __( 'Font Size', 'code-snippets' ); __( 'Select a snippet to display', 'code-snippets' ); uninstall.php 0000755 00000005047 15105501234 0007273 0 ustar 00 <?php /** * Functions for cleaning data when the plugin is uninstalled. * * @package Code_Snippets * * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching */ namespace Code_Snippets\Uninstall; /** * Determine whether the option for allowing a complete uninstallation is enabled. * * @return boolean */ function complete_uninstall_enabled(): bool { $unified = false; if ( is_multisite() ) { $menu_perms = get_site_option( 'menu_items', [] ); $unified = empty( $menu_perms['snippets_settings'] ); } $settings = $unified ? get_site_option( 'code_snippets_settings' ) : get_option( 'code_snippets_settings' ); return isset( $settings['general']['complete_uninstall'] ) && $settings['general']['complete_uninstall']; } /** * Clean up data created by this plugin for a single site * * phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange */ function uninstall_current_site() { global $wpdb; $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}snippets" ); delete_option( 'code_snippets_version' ); delete_option( 'recently_activated_snippets' ); delete_option( 'code_snippets_settings' ); delete_option( 'code_snippets_cloud_settings' ); delete_transient( 'cs_codevault_snippets' ); delete_transient( 'cs_local_to_cloud_map' ); } /** * Clean up data created by this plugin on multisite. * * phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange */ function uninstall_multisite() { global $wpdb; // Loop through sites. $blog_ids = get_sites( [ 'fields' => 'ids' ] ); foreach ( $blog_ids as $site_id ) { switch_to_blog( $site_id ); uninstall_current_site(); } restore_current_blog(); // Remove network snippets table. $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}ms_snippets" ); // Remove saved options. delete_site_option( 'code_snippets_version' ); delete_site_option( 'recently_activated_snippets' ); } function delete_flat_files_directory() { $flat_files_dir = WP_CONTENT_DIR . '/code-snippets'; if ( ! is_dir( $flat_files_dir ) ) { return; } if ( ! function_exists( 'request_filesystem_credentials' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } global $wp_filesystem; WP_Filesystem(); if ( $wp_filesystem && $wp_filesystem->is_dir( $flat_files_dir ) ) { $wp_filesystem->delete( $flat_files_dir, true ); } } /** * Uninstall the Code Snippets plugin. * * @return void */ function uninstall_plugin() { if ( complete_uninstall_enabled() ) { if ( is_multisite() ) { uninstall_multisite(); } else { uninstall_current_site(); } delete_flat_files_directory(); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.03 |
proxy
|
phpinfo
|
Настройка