Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
207
plugins/code-snippets/php/admin-menus/class-admin-menu.php
Normal file
207
plugins/code-snippets/php/admin-menus/class-admin-menu.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
240
plugins/code-snippets/php/admin-menus/class-edit-menu.php
Normal file
240
plugins/code-snippets/php/admin-menus/class-edit-menu.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?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"><small style="position: relative; top: 14px;">%s</small></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();
|
||||
|
||||
$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.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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
85
plugins/code-snippets/php/admin-menus/class-import-menu.php
Normal file
85
plugins/code-snippets/php/admin-menus/class-import-menu.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets for the import menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_assets() {
|
||||
$plugin = code_snippets();
|
||||
|
||||
wp_enqueue_script(
|
||||
'code-snippets-import',
|
||||
plugins_url( 'dist/import.js', $plugin->file ),
|
||||
[
|
||||
'react',
|
||||
'react-dom',
|
||||
'wp-i18n',
|
||||
'wp-components',
|
||||
],
|
||||
$plugin->version,
|
||||
true
|
||||
);
|
||||
|
||||
$plugin->localize_script( 'code-snippets-import' );
|
||||
}
|
||||
}
|
||||
337
plugins/code-snippets/php/admin-menus/class-manage-menu.php
Normal file
337
plugins/code-snippets/php/admin-menus/class-manage-menu.php
Normal file
@@ -0,0 +1,337 @@
|
||||
<?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();
|
||||
|
||||
wp_enqueue_style(
|
||||
'code-snippets-manage',
|
||||
plugins_url( 'dist/manage.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();
|
||||
}
|
||||
}
|
||||
239
plugins/code-snippets/php/admin-menus/class-settings-menu.php
Normal file
239
plugins/code-snippets/php/admin-menus/class-settings-menu.php
Normal file
@@ -0,0 +1,239 @@
|
||||
<?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.
|
||||
}
|
||||
}
|
||||
88
plugins/code-snippets/php/admin-menus/class-welcome-menu.php
Normal file
88
plugins/code-snippets/php/admin-menus/class-welcome-menu.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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://codesnippets.pro/support/',
|
||||
'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;
|
||||
}
|
||||
}
|
||||
423
plugins/code-snippets/php/class-admin.php
Normal file
423
plugins/code-snippets/php/class-admin.php
Normal file
@@ -0,0 +1,423 @@
|
||||
<?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( 'manage_sites_action_links', array( $this, 'add_sites_row_action' ), 10, 2 );
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "Snippets" row action to the Network Sites table.
|
||||
*
|
||||
* @param array<string, string> $actions Existing row actions.
|
||||
* @param int $site_id Current site ID.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function add_sites_row_action( array $actions, int $site_id ): array {
|
||||
if ( ! is_multisite() || ! current_user_can( code_snippets()->get_network_cap_name() ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$menu_slug = code_snippets()->get_menu_slug();
|
||||
$actions['code_snippets'] = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( get_admin_url( $site_id, 'admin.php?page=' . $menu_slug ) ),
|
||||
esc_html__( 'Snippets', 'code-snippets' )
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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://codesnippets.pro/support/',
|
||||
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>';
|
||||
}
|
||||
}
|
||||
}
|
||||
204
plugins/code-snippets/php/class-contextual-help.php
Normal file
204
plugins/code-snippets/php/class-contextual-help.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?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://codesnippets.pro/docs/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://codesnippets.pro/doc/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://codesnippets.pro/docs/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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
246
plugins/code-snippets/php/class-data-item.php
Normal file
246
plugins/code-snippets/php/class-data-item.php
Normal file
@@ -0,0 +1,246 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
374
plugins/code-snippets/php/class-db.php
Normal file
374
plugins/code-snippets/php/class-db.php
Normal file
@@ -0,0 +1,374 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'] );
|
||||
$active_value = intval( $snippet['active'] );
|
||||
|
||||
if ( ! self::is_network_snippet_enabled( $active_value, $id, $active_shared_ids ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a network snippet should execute on the current site.
|
||||
*
|
||||
* Network snippets execute when active=1, or when the snippet is listed as active-shared for the site.
|
||||
* Trashed snippets (active=-1) should never execute.
|
||||
*
|
||||
* @param int $active_value Raw active value: 1=active, 0=inactive, -1=trashed (can be stored as a string in the database).
|
||||
* @param int $snippet_id Snippet ID.
|
||||
* @param int[] $active_shared_ids Active shared network snippet IDs for the current site.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_network_snippet_enabled( int $active_value, int $snippet_id, array $active_shared_ids ): bool {
|
||||
if ( -1 === $active_value ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 1 === $active_value ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array( $snippet_id, $active_shared_ids, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
20
plugins/code-snippets/php/class-licensing.php
Normal file
20
plugins/code-snippets/php/class-licensing.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
1518
plugins/code-snippets/php/class-list-table.php
Normal file
1518
plugins/code-snippets/php/class-list-table.php
Normal file
File diff suppressed because it is too large
Load Diff
433
plugins/code-snippets/php/class-plugin.php
Normal file
433
plugins/code-snippets/php/class-plugin.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?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();
|
||||
|
||||
// Importers.
|
||||
new Plugins_Import_Manager();
|
||||
new Files_Import_Manager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a subsite user menu is enabled via *Network Settings > Enable administration menus*.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_subsite_menu_enabled(): bool {
|
||||
if ( ! is_multisite() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$menu_perms = get_site_option( 'menu_items', array() );
|
||||
return ! empty( $menu_perms['snippets'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current user should have the network snippets capability.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function user_can_manage_network_snippets(): bool {
|
||||
return is_super_admin() || current_user_can( $this->get_network_cap_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current request originates in the network admin.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_network_context(): bool {
|
||||
return is_network_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the required capability to perform a certain action on snippets.
|
||||
* Does not check if the user has this capability or not.
|
||||
*
|
||||
* If multisite, adjusts the capability based on whether the user is viewing
|
||||
* the network dashboard or a subsite and whether the menu is enabled for subsites.
|
||||
*
|
||||
* @return string The capability required to manage snippets.
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public function get_cap(): string {
|
||||
if ( is_multisite() && $this->is_network_context() ) {
|
||||
return $this->get_network_cap_name();
|
||||
}
|
||||
|
||||
if ( is_multisite() && ! $this->is_subsite_menu_enabled() ) {
|
||||
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' ) ),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
488
plugins/code-snippets/php/class-snippet.php
Normal file
488
plugins/code-snippets/php/class-snippet.php
Normal file
@@ -0,0 +1,488 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
* Raw active value from database before processing.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $raw_active_value;
|
||||
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param array<string, mixed>|object $initial_data Initial snippet data.
|
||||
*/
|
||||
public function __construct( $initial_data = null ) {
|
||||
if ( is_array( $initial_data ) && isset( $initial_data['active'] ) ) {
|
||||
$this->raw_active_value = $initial_data['active'];
|
||||
} elseif ( is_object( $initial_data ) && isset( $initial_data->active ) ) {
|
||||
$this->raw_active_value = $initial_data->active;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the snippet is trashed (soft deleted).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_trashed(): bool {
|
||||
return -1 === (int) $this->raw_active_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.
|
||||
*/
|
||||
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() && (int) $value != -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
226
plugins/code-snippets/php/class-upgrade.php
Normal file
226
plugins/code-snippets/php/class-upgrade.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
266
plugins/code-snippets/php/class-validator.php
Normal file
266
plugins/code-snippets/php/class-validator.php
Normal file
@@ -0,0 +1,266 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
356
plugins/code-snippets/php/class-welcome-api.php
Normal file
356
plugins/code-snippets/php/class-welcome-api.php
Normal file
@@ -0,0 +1,356 @@
|
||||
<?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'] ?? [];
|
||||
}
|
||||
}
|
||||
527
plugins/code-snippets/php/cloud/class-cloud-api.php
Normal file
527
plugins/code-snippets/php/cloud/class-cloud-api.php
Normal file
@@ -0,0 +1,527 @@
|
||||
<?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'
|
||||
);
|
||||
|
||||
$raw = self::unpack_request_json( wp_remote_get( $api_url ) );
|
||||
|
||||
$results = new Cloud_Snippets( $raw );
|
||||
$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 );
|
||||
}
|
||||
}
|
||||
61
plugins/code-snippets/php/cloud/class-cloud-link.php
Normal file
61
plugins/code-snippets/php/cloud/class-cloud-link.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
<?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() {
|
||||
$per_page = $this->get_items_per_page( 'snippets_per_page', 10 );
|
||||
$user_per_page = (int) get_user_option( 'snippets_per_page', get_current_user_id() );
|
||||
if ( $user_per_page > 0 ) {
|
||||
$per_page = $user_per_page;
|
||||
}
|
||||
|
||||
// Fetch snippets, passing a 0-based page index to the Cloud API (WP list tables are 1-based).
|
||||
$page_index = max( 0, $this->get_pagenum() - 1 );
|
||||
$this->cloud_snippets = $this->fetch_snippets( $per_page, $page_index );
|
||||
$this->items = $this->cloud_snippets->snippets;
|
||||
|
||||
$this->process_actions();
|
||||
|
||||
$this->set_pagination_args(
|
||||
[
|
||||
'per_page' => $per_page,
|
||||
'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 from the cloud search page.
|
||||
if ( ! isset( $_REQUEST['type'] ) || 'cloud_search' !== sanitize_key( wp_unslash( $_REQUEST['type'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$action = sanitize_key( wp_unslash( $_REQUEST['action'] ) );
|
||||
$source = sanitize_key( wp_unslash( $_REQUEST['source'] ) );
|
||||
$snippet_id = absint( wp_unslash( $_REQUEST['snippet'] ) );
|
||||
|
||||
if ( ! in_array( $action, [ 'download', 'update' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $snippet_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_admin_referer( cloud_lts_get_snippet_action_nonce_action( $action, $snippet_id, $source ) );
|
||||
|
||||
cloud_lts_process_download_action(
|
||||
$action,
|
||||
$source,
|
||||
(string) $snippet_id,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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-snippet-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="column-name">
|
||||
<h3>
|
||||
<?php
|
||||
|
||||
// Grab first tag in array of tags.
|
||||
$category = count( $item->tags ) > 0 ? strtolower( esc_attr( $item->tags[0] ) ) : 'general';
|
||||
|
||||
printf(
|
||||
'<img src="%s" class="title-icon" alt="%s">',
|
||||
esc_url( "https://codesnippets.cloud/images/plugin-icons/$category-logo.png" ),
|
||||
esc_attr( $category )
|
||||
);
|
||||
|
||||
$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 );
|
||||
|
||||
|
||||
|
||||
echo '</a>';
|
||||
?>
|
||||
</h3>
|
||||
<ul class="action-buttons">
|
||||
<?php echo cloud_lts_build_action_links( $item, 'search' ); ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="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="cloud-meta-row">
|
||||
<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-votes">
|
||||
<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-votes" aria-hidden="true">
|
||||
<?php
|
||||
echo esc_html( number_format_i18n( $item->vote_count ) );
|
||||
?>
|
||||
</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>
|
||||
</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( int $per_page = 10, int $page_index = 0 ): 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'] ) );
|
||||
|
||||
// Pass the provided 0-based page index to the API.
|
||||
return Cloud_API::fetch_search_results( $search_by, $search_query, $page_index );
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
if ( empty( $this->_pagination_args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$total_items = $this->_pagination_args['total_items'] ?? 0;
|
||||
$total_pages = $this->_pagination_args['total_pages'] ?? 0;
|
||||
// get_pagenum already returns a 1-based page number used for display.
|
||||
$pagenum_display = $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_display );
|
||||
$page_class = $paginate['page_class'];
|
||||
$output = $paginate['output'];
|
||||
|
||||
$this->_pagination = "<div class='tablenav-pages$page_class'>$output</div>";
|
||||
|
||||
echo wp_kses_post( $this->_pagination );
|
||||
}
|
||||
}
|
||||
87
plugins/code-snippets/php/cloud/class-cloud-snippet.php
Normal file
87
plugins/code-snippets/php/cloud/class-cloud-snippet.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
plugins/code-snippets/php/cloud/class-cloud-snippets.php
Normal file
107
plugins/code-snippets/php/cloud/class-cloud-snippets.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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 ) {
|
||||
$initial_data = $this->normalize_cloud_api( $initial_data );
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize payloads returned by the cloud API into the shape expected by this class.
|
||||
*
|
||||
* @param mixed $initial_data Raw data passed into the constructor.
|
||||
*
|
||||
* @return mixed Normalized data array or original value when no normalization is required.
|
||||
*/
|
||||
private function normalize_cloud_api( $initial_data ) {
|
||||
// pagination metadata is nested under a 'meta' key.
|
||||
if ( is_array( $initial_data ) && isset( $initial_data['meta'] ) ) {
|
||||
$meta = $initial_data['meta'];
|
||||
$normalized = [];
|
||||
$normalized['snippets'] = $initial_data['snippets'] ?? $initial_data['data'] ?? [];
|
||||
$normalized['total_snippets'] = isset( $meta['total'] ) ? (int) $meta['total'] : 0;
|
||||
$normalized['total_pages'] = isset( $meta['total_pages'] ) ? (int) $meta['total_pages'] : 0;
|
||||
$normalized['page'] = isset( $meta['page'] ) ? max( 0, (int) $meta['page'] - 1 ) : 0;
|
||||
$normalized['cloud_id_rev'] = $initial_data['cloud_id_rev'] ?? [];
|
||||
$initial_data = $normalized;
|
||||
}
|
||||
|
||||
return $initial_data;
|
||||
}
|
||||
}
|
||||
287
plugins/code-snippets/php/cloud/list-table-shared-ops.php
Normal file
287
plugins/code-snippets/php/cloud/list-table-shared-ops.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
/**
|
||||
* Functions to perform snippet operations
|
||||
*
|
||||
* @package Code_Snippets
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Cloud;
|
||||
|
||||
use function Code_Snippets\code_snippets;
|
||||
|
||||
/**
|
||||
* Build the nonce action string for cloud snippet state-changing operations.
|
||||
*
|
||||
* @param string $action Action - 'download' or 'update'.
|
||||
* @param int $snippet_id Cloud snippet ID.
|
||||
* @param string $source Source - 'search' or 'cloud'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function cloud_lts_get_snippet_action_nonce_action( string $action, int $snippet_id, string $source ): string {
|
||||
return sprintf( 'cloud-snippet-action|%s|%s|%d', $action, $source, $snippet_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 string HTML
|
||||
*/
|
||||
function cloud_lts_build_column_hidden_input( string $column_name, Cloud_Snippet $snippet ): string {
|
||||
return sprintf(
|
||||
'<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 string Action link HTML.
|
||||
*/
|
||||
function cloud_lts_build_action_links( Cloud_Snippet $cloud_snippet, string $source ): string {
|
||||
$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 );
|
||||
$snippet_id = (int) $cloud_snippet->id;
|
||||
|
||||
if ( $link ) {
|
||||
if ( $is_licensed && $link->update_available ) {
|
||||
$update_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'action' => 'update',
|
||||
'snippet' => $snippet_id,
|
||||
'source' => $source,
|
||||
]
|
||||
),
|
||||
cloud_lts_get_snippet_action_nonce_action( 'update', $snippet_id, $source )
|
||||
);
|
||||
return sprintf(
|
||||
'<li><a class="button button-primary" href="%s">%s</a></li>',
|
||||
esc_url( $update_url ),
|
||||
esc_html__( 'Update Available', 'code-snippets' )
|
||||
);
|
||||
} else {
|
||||
return sprintf(
|
||||
'<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' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $download ) {
|
||||
$download_query = [
|
||||
'action' => 'download',
|
||||
'snippet' => $snippet_id,
|
||||
'source' => $source,
|
||||
];
|
||||
|
||||
// Preserve current cloud page if present so downstream handlers receive pagination context.
|
||||
if ( isset( $_REQUEST['cloud_page'] ) ) {
|
||||
$download_query['cloud_page'] = (int) wp_unslash( $_REQUEST['cloud_page'] );
|
||||
}
|
||||
|
||||
$download_url = wp_nonce_url(
|
||||
add_query_arg( $download_query ),
|
||||
cloud_lts_get_snippet_action_nonce_action( 'download', $snippet_id, $source )
|
||||
);
|
||||
|
||||
$download_button = sprintf(
|
||||
'<li><a class="button button-primary" href="%s">%s</a></li>',
|
||||
esc_url( $download_url ),
|
||||
esc_html__( 'Download', 'code-snippets' )
|
||||
);
|
||||
} else {
|
||||
$download_button = sprintf(
|
||||
'<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' )
|
||||
);
|
||||
}
|
||||
|
||||
$preview_button = sprintf(
|
||||
'<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 button',
|
||||
esc_attr( $cloud_snippet->id ),
|
||||
esc_attr( $lang ),
|
||||
esc_html__( 'Preview', 'code-snippets' )
|
||||
);
|
||||
|
||||
return $download_button . $preview_button;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>';
|
||||
|
||||
$param_key = $source . '_page';
|
||||
$current = isset( $_REQUEST[ $param_key ] ) ? (int) $_REQUEST[ $param_key ] : $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>';
|
||||
|
||||
$page_class = $total_pages ? '' : ' no-pages';
|
||||
|
||||
return [
|
||||
'output' => $output,
|
||||
'page_class' => $page_class,
|
||||
];
|
||||
}
|
||||
62
plugins/code-snippets/php/deactivation-notice.php
Normal file
62
plugins/code-snippets/php/deactivation-notice.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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' );
|
||||
132
plugins/code-snippets/php/editor.php
Normal file
132
plugins/code-snippets/php/editor.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?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;
|
||||
}
|
||||
130
plugins/code-snippets/php/evaluation/class-evaluate-content.php
Normal file
130
plugins/code-snippets/php/evaluation/class-evaluate-content.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?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'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
55
plugins/code-snippets/php/export/class-export-attachment.php
Normal file
55
plugins/code-snippets/php/export/class-export-attachment.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
194
plugins/code-snippets/php/export/class-export.php
Normal file
194
plugins/code-snippets/php/export/class-export.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
208
plugins/code-snippets/php/export/class-import.php
Normal file
208
plugins/code-snippets/php/export/class-import.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,690 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
/**
|
||||
* Manage file-based snippet execution.
|
||||
*
|
||||
* Responsible for writing snippet code to disk, maintaining per-table config indexes,
|
||||
* and retrieving the active snippet list from those config files.
|
||||
*/
|
||||
class Snippet_Files {
|
||||
|
||||
/**
|
||||
* Flag file name that indicates flat files are enabled.
|
||||
*/
|
||||
private const ENABLED_FLAG_FILE = 'flat-files-enabled.flag';
|
||||
|
||||
/**
|
||||
* Snippet handler registry.
|
||||
*
|
||||
* @var Snippet_Handler_Registry
|
||||
*/
|
||||
private Snippet_Handler_Registry $handler_registry;
|
||||
|
||||
/**
|
||||
* File system adapter.
|
||||
*
|
||||
* @var File_System_Interface
|
||||
*/
|
||||
private File_System_Interface $fs;
|
||||
|
||||
/**
|
||||
* Config repository.
|
||||
*
|
||||
* @var Snippet_Config_Repository_Interface
|
||||
*/
|
||||
private Snippet_Config_Repository_Interface $config_repo;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Snippet_Handler_Registry $handler_registry Handler registry instance.
|
||||
* @param File_System_Interface $fs File system adapter.
|
||||
* @param Snippet_Config_Repository_Interface $config_repo Config repository instance.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path to the flat-file enabled flag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_flag_file_path(): string {
|
||||
return self::get_base_dir() . '/' . self::ENABLED_FLAG_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or delete the enabled flag file.
|
||||
*
|
||||
* @param bool $enabled Whether file-based execution is enabled.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks used by file-based execution.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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/trash_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, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate multiple snippets and regenerate their flat files.
|
||||
*
|
||||
* @param Snippet[] $valid_snippets Snippets to activate.
|
||||
* @param string $table Table name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function activate_snippets( $valid_snippets, $table ): void {
|
||||
foreach ( $valid_snippets as $snippet ) {
|
||||
$snippet->active = true;
|
||||
$this->handle_snippet( $snippet, $table );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a snippet file and update its config index entry.
|
||||
*
|
||||
* @param Snippet $snippet Snippet object.
|
||||
* @param string $table Table name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a snippet file and remove it from the config index.
|
||||
*
|
||||
* @param Snippet $snippet Snippet object.
|
||||
* @param bool $network Whether the snippet is network-wide.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a snippet by writing its code file and updating config.
|
||||
*
|
||||
* @param Snippet $snippet Snippet object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate a snippet by updating its config entry.
|
||||
*
|
||||
* @param int $snippet_id Snippet ID.
|
||||
* @param bool $network Whether the snippet is network-wide.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base directory for flat files.
|
||||
*
|
||||
* @param string $table Optional hashed table name.
|
||||
* @param string $snippet_type Optional snippet type directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base URL for flat files.
|
||||
*
|
||||
* @param string $table Optional hashed table name.
|
||||
* @param string $snippet_type Optional snippet type directory.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory if it does not exist.
|
||||
*
|
||||
* @param string $dir Directory path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the file path for a snippet's code file.
|
||||
*
|
||||
* @param string $base_dir Base directory path.
|
||||
* @param int $snippet_id Snippet ID.
|
||||
* @param string $ext File extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ): string {
|
||||
return trailingslashit( $base_dir ) . $snippet_id . '.' . $ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file if it exists.
|
||||
*
|
||||
* @param string $file_path File path.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function delete_file( string $file_path ): void {
|
||||
if ( $this->fs->exists( $file_path ) ) {
|
||||
$this->fs->delete( $file_path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the active shared network snippets list to a config file.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @param mixed $old_value Previous value.
|
||||
* @param mixed $value New value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the active shared network snippets list to a config file when first added.
|
||||
*
|
||||
* @param string $option Option name.
|
||||
* @param mixed $value Option value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update the active shared network snippets config file.
|
||||
*
|
||||
* @param mixed $value Option value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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';
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export -- var_export is required for writing PHP config files.
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a table name for file system usage.
|
||||
*
|
||||
* @param string $table Table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_hashed_table_name( string $table ): string {
|
||||
return wp_hash( $table );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of active snippets from flat file config.
|
||||
*
|
||||
* @param array<string> $scopes Scopes to include.
|
||||
* @param string $snippet_type Snippet type directory.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public static function get_active_snippets_from_flat_files(
|
||||
array $scopes = [],
|
||||
$snippet_type = 'php'
|
||||
): array {
|
||||
$active_snippets = [];
|
||||
$db = code_snippets()->db;
|
||||
|
||||
// Always use the site table for "local" snippets, even in Network Admin.
|
||||
$table = self::get_hashed_table_name( $db->get_table_name( false ) );
|
||||
$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'] );
|
||||
$active_value = intval( $snippet['active'] );
|
||||
|
||||
if ( ! DB::is_network_snippet_enabled( $active_value, $id, $active_shared_ids ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort active snippet entries for execution order.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $active_snippets Active snippets list.
|
||||
* @param DB $db Database instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function sort_active_snippets( array &$active_snippets, DB $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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load active snippets from a flat file config index.
|
||||
*
|
||||
* @param string $table Hashed table directory name.
|
||||
* @param string $snippet_type Snippet type directory.
|
||||
* @param string[] $scopes Scopes to include.
|
||||
* @param int[]|null $active_shared_ids Optional list of active shared network snippet IDs.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
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;
|
||||
$shared_ids = is_array( $active_shared_ids )
|
||||
? array_map( 'intval', $active_shared_ids )
|
||||
: [];
|
||||
|
||||
$filtered_snippets = array_filter(
|
||||
$file_snippets,
|
||||
function ( $snippet ) use ( $scopes, $shared_ids ) {
|
||||
$active_value = isset( $snippet['active'] ) ? intval( $snippet['active'] ) : 0;
|
||||
|
||||
$is_active = DB::is_network_snippet_enabled( $active_value, intval( $snippet['id'] ), $shared_ids );
|
||||
|
||||
return ( $is_active || 'condition' === $snippet['scope'] ) && in_array( $snippet['scope'], $scopes, true );
|
||||
}
|
||||
);
|
||||
|
||||
wp_cache_set( $cache_key, $filtered_snippets, CACHE_GROUP );
|
||||
|
||||
return $filtered_snippets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file-based execution settings fields.
|
||||
*
|
||||
* @param array<string, mixed> $fields Settings fields.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreate all flat files when file-based execution settings are updated.
|
||||
*
|
||||
* @param array<string, mixed> $settings Settings data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create_all_flat_files( array $settings ): 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create snippet code files and config indexes for all active snippets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create active shared network snippet config files for each site (multisite) or the current site.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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 {
|
||||
$output = "<?php\n\n" . apply_filters( 'code_snippets_php_snippet_file_code', $code );
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?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;
|
||||
}
|
||||
47
plugins/code-snippets/php/flat-files/registry.php
Normal file
47
plugins/code-snippets/php/flat-files/registry.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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 ];
|
||||
}
|
||||
}
|
||||
501
plugins/code-snippets/php/front-end/class-front-end.php
Normal file
501
plugins/code-snippets/php/front-end/class-front-end.php
Normal file
@@ -0,0 +1,501 @@
|
||||
<?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, EXTR_SKIP );
|
||||
|
||||
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, EXTR_SKIP );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
55
plugins/code-snippets/php/front-end/mce-strings.php
Normal file
55
plugins/code-snippets/php/front-end/mce-strings.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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 ) . ');';
|
||||
83
plugins/code-snippets/php/load.php
Normal file
83
plugins/code-snippets/php/load.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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.
|
||||
$code_snippets_autoloader = require dirname( __DIR__ ) . '/vendor/autoload.php';
|
||||
|
||||
// Remove all original (non-prefixed) vendor namespace mappings to prevent collisions with other plugins.
|
||||
// Since Imposter rewrites namespaces to Code_Snippets\Vendor\*, we need to remove the original PSR-4
|
||||
// mappings that Composer generates so other plugins can load their own copies of these libraries.
|
||||
if ( $code_snippets_autoloader instanceof \Composer\Autoload\ClassLoader ) {
|
||||
$prefixes = $code_snippets_autoloader->getPrefixesPsr4();
|
||||
$our_prefix = 'Code_Snippets\\Vendor\\';
|
||||
|
||||
foreach ( $prefixes as $namespace => $paths ) {
|
||||
// Remove any non-Code_Snippets namespace that has a corresponding prefixed version
|
||||
if ( strpos( $namespace, $our_prefix ) === false ) {
|
||||
$prefixed_namespace = $our_prefix . $namespace;
|
||||
if ( isset( $prefixes[ $prefixed_namespace ] ) ) {
|
||||
$code_snippets_autoloader->setPsr4( $namespace, [] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Request;
|
||||
use WP_Error;
|
||||
use const Code_Snippets\REST_API_NAMESPACE;
|
||||
|
||||
class Files_Import_Manager {
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
|
||||
}
|
||||
|
||||
public function register_rest_routes() {
|
||||
$namespace = REST_API_NAMESPACE . self::VERSION;
|
||||
|
||||
register_rest_route( $namespace, 'file-upload/parse', [
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'parse_uploaded_files' ],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
] );
|
||||
|
||||
register_rest_route( $namespace, 'file-upload/import', [
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'import_selected_snippets' ],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
'args' => [
|
||||
'snippets' => [
|
||||
'description' => __( 'Array of snippet data to import', 'code-snippets' ),
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
],
|
||||
'duplicate_action' => [
|
||||
'description' => __( 'Action to take when duplicate snippets are found', 'code-snippets' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'ignore', 'replace', 'skip' ],
|
||||
'default' => 'ignore',
|
||||
],
|
||||
'network' => [
|
||||
'description' => __( 'Whether to import to network table', 'code-snippets' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
public function parse_uploaded_files( WP_REST_Request $request ) {
|
||||
// Verify nonce for security
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
__( 'Cookie check failed', 'code-snippets' ),
|
||||
[ 'status' => 403 ]
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above via REST API header
|
||||
if ( empty( $_FILES['files'] ) ) {
|
||||
return new WP_Error(
|
||||
'no_files',
|
||||
__( 'No files were uploaded.', 'code-snippets' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Nonce verified above, file data validated below
|
||||
$files = $_FILES['files'];
|
||||
|
||||
if ( ! isset( $files['name'], $files['type'], $files['tmp_name'], $files['error'] ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_file_data',
|
||||
__( 'Invalid file upload data.', 'code-snippets' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
$all_snippets = [];
|
||||
$errors = [];
|
||||
|
||||
$file_count = is_array( $files['name'] ) ? count( $files['name'] ) : 1;
|
||||
|
||||
for ( $i = 0; $i < $file_count; $i++ ) {
|
||||
$file_name = is_array( $files['name'] ) ? $files['name'][ $i ] : $files['name'];
|
||||
$file_type = is_array( $files['type'] ) ? $files['type'][ $i ] : $files['type'];
|
||||
$file_tmp = is_array( $files['tmp_name'] ) ? $files['tmp_name'][ $i ] : $files['tmp_name'];
|
||||
$file_error = is_array( $files['error'] ) ? $files['error'][ $i ] : $files['error'];
|
||||
|
||||
if ( UPLOAD_ERR_OK !== $file_error ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: %1$s: file name, %2$s: error message */
|
||||
__( 'Upload error for file %1$s: %2$s', 'code-snippets' ),
|
||||
$file_name,
|
||||
$this->get_upload_error_message( $file_error )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_info = pathinfo( $file_name );
|
||||
$extension = strtolower( $file_info['extension'] ?? '' );
|
||||
$mime_type = sanitize_mime_type( $file_type );
|
||||
|
||||
if ( ! $this->is_valid_file_type( $extension, $mime_type ) ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: %s: file name */
|
||||
__( 'Invalid file type for %s. Only JSON and XML files are allowed.', 'code-snippets' ),
|
||||
$file_name,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$snippets = $this->parse_file_content( $file_tmp, $extension, $mime_type, $file_name );
|
||||
|
||||
if ( is_wp_error( $snippets ) ) {
|
||||
$errors[] = sprintf(
|
||||
/* translators: %1$s: file name, %2$s: error message */
|
||||
__( 'Error parsing %1$s: %2$s', 'code-snippets' ),
|
||||
$file_name,
|
||||
$snippets->get_error_message(),
|
||||
);
|
||||
} else {
|
||||
$all_snippets = array_merge( $all_snippets, $snippets );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $all_snippets ) ) {
|
||||
return new WP_Error(
|
||||
'no_snippets_found',
|
||||
__( 'No valid snippets found in the uploaded files.', 'code-snippets' ),
|
||||
[
|
||||
'status' => 400,
|
||||
'errors' => $errors,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$response = [
|
||||
'snippets' => $all_snippets,
|
||||
'total_count' => count( $all_snippets ),
|
||||
'message' => sprintf(
|
||||
/* translators: %d: number of snippets */
|
||||
_n(
|
||||
'Found %d snippet ready for import.',
|
||||
'Found %d snippets ready for import.',
|
||||
count( $all_snippets ),
|
||||
'code-snippets',
|
||||
),
|
||||
count( $all_snippets )
|
||||
),
|
||||
];
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
$response['warnings'] = $errors;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
public function import_selected_snippets( WP_REST_Request $request ) {
|
||||
$snippets_data = $request->get_param( 'snippets' );
|
||||
$duplicate_action = $request->get_param( 'duplicate_action' ) ?? 'ignore';
|
||||
$network = $request->get_param( 'network' ) ?? false;
|
||||
|
||||
if ( empty( $snippets_data ) || ! is_array( $snippets_data ) ) {
|
||||
return new WP_Error(
|
||||
'no_snippets',
|
||||
__( 'No snippet data provided for import.', 'code-snippets' ),
|
||||
[ 'status' => 400 ]
|
||||
);
|
||||
}
|
||||
|
||||
$snippets = [];
|
||||
foreach ( $snippets_data as $snippet_data ) {
|
||||
$snippet = new Snippet();
|
||||
$snippet->network = $network;
|
||||
|
||||
$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, $duplicate_action, $network );
|
||||
|
||||
$response = [
|
||||
'imported' => count( $imported ),
|
||||
'imported_ids' => $imported,
|
||||
'message' => sprintf(
|
||||
/* translators: %d: number of snippets */
|
||||
_n(
|
||||
'Successfully imported %d snippet.',
|
||||
'Successfully imported %d snippets.',
|
||||
count( $imported ),
|
||||
'code-snippets',
|
||||
),
|
||||
count( $imported )
|
||||
),
|
||||
];
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
private function parse_file_content( string $file_path, string $extension, string $mime_type, string $file_name ) {
|
||||
if ( ! file_exists( $file_path ) || ! is_file( $file_path ) ) {
|
||||
return new WP_Error(
|
||||
'file_not_found',
|
||||
__( 'File not found or is not a valid file.', 'code-snippets' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'json' === $extension || 'application/json' === $mime_type ) {
|
||||
return $this->parse_json_file( $file_path, $file_name );
|
||||
} elseif ( 'xml' === $extension || in_array( $mime_type, [ 'text/xml', 'application/xml' ], true ) ) {
|
||||
return $this->parse_xml_file( $file_path, $file_name );
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'unsupported_file_type',
|
||||
__( 'Unsupported file type.', 'code-snippets' )
|
||||
);
|
||||
}
|
||||
|
||||
private function parse_json_file( string $file_path, string $file_name ) {
|
||||
$raw_data = file_get_contents( $file_path );
|
||||
$data = json_decode( $raw_data, true );
|
||||
|
||||
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||
return new WP_Error(
|
||||
'invalid_json',
|
||||
sprintf(
|
||||
/* translators: %1$s: file name, %2$s: error message */
|
||||
__( 'Invalid JSON in file %1$s: %2$s', 'code-snippets' ),
|
||||
$file_name,
|
||||
json_last_error_msg()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $data['snippets'] ) || ! is_array( $data['snippets'] ) ) {
|
||||
return new WP_Error(
|
||||
'no_snippets_in_file',
|
||||
sprintf(
|
||||
/* translators: %s: file name */
|
||||
__( 'No snippets found in file %s', 'code-snippets' ),
|
||||
$file_name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$snippets = [];
|
||||
foreach ( $data['snippets'] as $snippet_data ) {
|
||||
$snippet_data['source_file'] = $file_name;
|
||||
|
||||
$snippet_data['table_data'] = [
|
||||
'id' => $snippet_data['id'] ?? uniqid(),
|
||||
'title' => $snippet_data['name'] ?? __( 'Untitled Snippet', 'code-snippets' ),
|
||||
'scope' => $snippet_data['scope'] ?? 'global',
|
||||
'tags' => is_array( $snippet_data['tags'] ?? null ) ? implode( ', ', $snippet_data['tags'] ) : '',
|
||||
'description' => $snippet_data['desc'] ?? $snippet_data['description'] ?? '',
|
||||
'type' => Snippet::get_type_from_scope( $snippet_data['scope'] ?? 'global' )
|
||||
];
|
||||
|
||||
$snippets[] = $snippet_data;
|
||||
}
|
||||
|
||||
return $snippets;
|
||||
}
|
||||
|
||||
private function parse_xml_file( string $file_path, string $file_name ) {
|
||||
$dom = new \DOMDocument( '1.0', get_bloginfo( 'charset' ) );
|
||||
|
||||
if ( ! $dom->load( $file_path ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_xml',
|
||||
sprintf(
|
||||
/* translators: %s: file name */
|
||||
__( 'Invalid XML in file %s', 'code-snippets' ),
|
||||
$file_name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$snippets_xml = $dom->getElementsByTagName( 'snippet' );
|
||||
$fields = [ 'name', 'description', 'desc', 'code', 'tags', 'scope' ];
|
||||
|
||||
$snippets = [];
|
||||
$index = 0;
|
||||
|
||||
foreach ( $snippets_xml as $snippet_xml ) {
|
||||
$snippet_data = [];
|
||||
|
||||
foreach ( $fields as $field_name ) {
|
||||
$field = $snippet_xml->getElementsByTagName( $field_name )->item( 0 );
|
||||
|
||||
if ( isset( $field->nodeValue ) ) {
|
||||
$snippet_data[ $field_name ] = $field->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
$scope = $snippet_xml->getAttribute( 'scope' );
|
||||
if ( ! empty( $scope ) ) {
|
||||
$snippet_data['scope'] = $scope;
|
||||
}
|
||||
|
||||
$snippet_data['source_file'] = $file_name;
|
||||
|
||||
$snippet_data['table_data'] = [
|
||||
'id' => ++$index,
|
||||
'title' => $snippet_data['name'] ?? __( 'Untitled Snippet', 'code-snippets' ),
|
||||
'scope' => $snippet_data['scope'] ?? 'global',
|
||||
'tags' => $snippet_data['tags'] ?? '',
|
||||
'description' => $snippet_data['desc'] ?? $snippet_data['description'] ?? '',
|
||||
'type' => Snippet::get_type_from_scope( $snippet_data['scope'] ?? 'global' ),
|
||||
];
|
||||
|
||||
$snippets[] = $snippet_data;
|
||||
}
|
||||
|
||||
return $snippets;
|
||||
}
|
||||
|
||||
private function save_snippets( array $snippets, string $duplicate_action, bool $network ): array {
|
||||
$existing_snippets = [];
|
||||
|
||||
if ( 'replace' === $duplicate_action || 'skip' === $duplicate_action ) {
|
||||
$all_snippets = get_snippets( [], $network );
|
||||
|
||||
foreach ( $all_snippets as $snippet ) {
|
||||
if ( $snippet->name ) {
|
||||
$existing_snippets[ $snippet->name ] = $snippet->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$imported = [];
|
||||
|
||||
foreach ( $snippets as $snippet ) {
|
||||
if ( 'ignore' !== $duplicate_action && isset( $existing_snippets[ $snippet->name ] ) ) {
|
||||
if ( 'replace' === $duplicate_action ) {
|
||||
$snippet->id = $existing_snippets[ $snippet->name ];
|
||||
} elseif ( 'skip' === $duplicate_action ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$saved_snippet = save_snippet( $snippet );
|
||||
|
||||
$snippet_id = $saved_snippet->id;
|
||||
|
||||
if ( $snippet_id ) {
|
||||
$imported[] = $snippet_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $imported;
|
||||
}
|
||||
|
||||
private function is_valid_file_type( string $extension, string $mime_type ): bool {
|
||||
$valid_extensions = [ 'json', 'xml' ];
|
||||
$valid_mime_types = [ 'application/json', 'text/xml', 'application/xml' ];
|
||||
|
||||
return in_array( $extension, $valid_extensions, true ) ||
|
||||
in_array( $mime_type, $valid_mime_types, true );
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function get_upload_error_message( int $error_code ): string {
|
||||
$error_messages = [
|
||||
UPLOAD_ERR_INI_SIZE => __( 'File exceeds the upload_max_filesize directive.', 'code-snippets' ),
|
||||
UPLOAD_ERR_FORM_SIZE => __( 'File exceeds the MAX_FILE_SIZE directive.', 'code-snippets' ),
|
||||
UPLOAD_ERR_PARTIAL => __( 'File was only partially uploaded.', 'code-snippets' ),
|
||||
UPLOAD_ERR_NO_FILE => __( 'No file was uploaded.', 'code-snippets' ),
|
||||
UPLOAD_ERR_NO_TMP_DIR => __( 'Missing a temporary folder.', 'code-snippets' ),
|
||||
UPLOAD_ERR_CANT_WRITE => __( 'Failed to write file to disk.', 'code-snippets' ),
|
||||
UPLOAD_ERR_EXTENSION => __( 'A PHP extension stopped the file upload.', 'code-snippets' ),
|
||||
];
|
||||
|
||||
return $error_messages[ $error_code ] ?? __( 'Unknown upload error.', 'code-snippets' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
class Header_Footer_Code_Manager_Importer extends Importer_Base {
|
||||
|
||||
private const FIELD_MAPPINGS = [
|
||||
'name' => 'name',
|
||||
'snippet' => 'code',
|
||||
'location' => 'scope',
|
||||
'created' => 'modified',
|
||||
];
|
||||
|
||||
private const HTML_SCOPE_TRANSFORMATIONS = [
|
||||
'' => 'content',
|
||||
'header' => 'head-content',
|
||||
'footer' => 'footer-content',
|
||||
];
|
||||
|
||||
public function get_name() {
|
||||
return 'header-footer-code-manager';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Header Footer Code Manager', 'code-snippets' );
|
||||
}
|
||||
|
||||
public static function is_active(): bool {
|
||||
return is_plugin_active( 'header-footer-code-manager/99robots-header-footer-code-manager.php' );
|
||||
}
|
||||
|
||||
public function get_data( array $ids_to_import = [] ) {
|
||||
global $wpdb;
|
||||
$nnr_hfcm_table_name = $wpdb->prefix . 'hfcm_scripts';
|
||||
$sql = "SELECT * FROM `{$nnr_hfcm_table_name}`";
|
||||
|
||||
if ( ! empty( $ids_to_import ) ) {
|
||||
$sql .= ' WHERE script_id IN (' . implode( ',', $ids_to_import ) . ')';
|
||||
}
|
||||
|
||||
$snippets = $wpdb->get_results(
|
||||
$sql
|
||||
);
|
||||
|
||||
foreach ( $snippets as $snippet ) {
|
||||
$snippet->table_data = [
|
||||
'id' => (int) $snippet->script_id,
|
||||
'title' => $snippet->name,
|
||||
];
|
||||
}
|
||||
|
||||
return $snippets;
|
||||
}
|
||||
|
||||
public function create_snippet( $snippet_data, bool $multisite ): ?Snippet {
|
||||
$code_type = $snippet_data->snippet_type ?? '';
|
||||
|
||||
$snippet = new Snippet();
|
||||
$snippet->network = $multisite;
|
||||
|
||||
foreach ( self::FIELD_MAPPINGS as $source_field => $target_field ) {
|
||||
if ( ! isset( $snippet_data->$source_field ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->transform_field_value(
|
||||
$target_field,
|
||||
$snippet_data->$source_field,
|
||||
$snippet_data
|
||||
);
|
||||
|
||||
$scope_not_supported = 'scope' === $target_field && null === $value;
|
||||
if ( $scope_not_supported ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$snippet->set_field( $target_field, $value );
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
private function transform_field_value( string $target_field, $value, $snippet_data ) {
|
||||
if ( 'scope' === $target_field ) {
|
||||
return $this->transform_scope_value( $value, $snippet_data );
|
||||
}
|
||||
|
||||
if ( 'code' === $target_field ) {
|
||||
return $this->transform_code_value( $value, $snippet_data );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function transform_scope_value( $location_value, $snippet_data ): ?string {
|
||||
if ( ! is_scalar( $location_value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$code_type = $snippet_data->snippet_type;
|
||||
|
||||
switch ( $code_type ) {
|
||||
case 'html':
|
||||
$transformations = self::HTML_SCOPE_TRANSFORMATIONS;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return $transformations[ $location_value ] ?? null;
|
||||
}
|
||||
|
||||
private function transform_code_value( $code_value, $snippet_data ): ?string {
|
||||
$code = html_entity_decode( $code_value );
|
||||
$code_type = $snippet_data->snippet_type ?? '';
|
||||
|
||||
$code = $this->strip_wrapper_tags( $code, $code_type );
|
||||
$code = $this->apply_minification( $code, $code_type );
|
||||
|
||||
return trim( $code );
|
||||
}
|
||||
|
||||
private function strip_wrapper_tags( string $code, string $code_type ): string {
|
||||
switch ( $code_type ) {
|
||||
case 'css':
|
||||
return preg_replace( '/<\s*style[^>]*>|<\s*\/\s*style\s*>/i', '', $code );
|
||||
case 'js':
|
||||
return preg_replace( '/<\s*script[^>]*>|<\s*\/\s*script\s*>/i', '', $code );
|
||||
default:
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
private function apply_minification( string $code, string $code_type ): string {
|
||||
if ( ! in_array( $code_type, [ 'css', 'js' ], true ) ) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$setting = Settings\get_setting( 'general', 'minify_output' );
|
||||
if ( ! is_array( $setting ) || ! in_array( $code_type, $setting, true ) ) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$minifier = 'css' === $code_type ? new Minify\CSS( $code ) : new Minify\JS( $code );
|
||||
return $minifier->minify();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
use WP_REST_Server;
|
||||
use const Code_Snippets\REST_API_NAMESPACE;
|
||||
|
||||
abstract class Importer_Base {
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
|
||||
}
|
||||
|
||||
abstract public function get_name();
|
||||
|
||||
abstract public function get_title();
|
||||
|
||||
abstract public function get_data( array $ids_to_import = [] );
|
||||
|
||||
abstract public function create_snippet( $snippet_data, bool $multisite ): ?Snippet;
|
||||
|
||||
abstract public static function is_active(): bool;
|
||||
|
||||
public function transform( array $data, bool $multisite, bool $auto_add_tags = false, string $tag_value = '' ): array {
|
||||
$snippets = [];
|
||||
|
||||
foreach ( $data as $snippet_item ) {
|
||||
if ( ! is_array( $snippet_item ) && ! is_object( $snippet_item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snippet = $this->create_snippet( $snippet_item, $multisite );
|
||||
|
||||
if ( $snippet ) {
|
||||
if ( $auto_add_tags && ! empty( $tag_value ) ) {
|
||||
if ( ! empty( $snippet->tags ) ) {
|
||||
$snippet->add_tag( $tag_value );
|
||||
} else {
|
||||
$snippet->tags = [ $tag_value ];
|
||||
}
|
||||
}
|
||||
|
||||
$snippets[] = $snippet;
|
||||
}
|
||||
}
|
||||
|
||||
return $snippets;
|
||||
}
|
||||
|
||||
public function import( $request ) {
|
||||
$ids_to_import = $request->get_param( 'ids' ) ?? [];
|
||||
$multisite = $request->get_param( 'network' ) ?? false;
|
||||
$auto_add_tags = $request->get_param( 'auto_add_tags' ) ?? false;
|
||||
$tag_value = $request->get_param( 'tag_value' ) ?? '';
|
||||
|
||||
$data = $this->get_data( $ids_to_import );
|
||||
|
||||
$snippets = $this->transform( $data, $multisite, $auto_add_tags, $tag_value );
|
||||
|
||||
$imported = $this->save_snippets( $snippets );
|
||||
|
||||
return [
|
||||
'imported' => $imported,
|
||||
];
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
return $this->get_data();
|
||||
}
|
||||
|
||||
protected function save_snippets( array $snippets ): array {
|
||||
$imported = [];
|
||||
|
||||
foreach ( $snippets as $snippet ) {
|
||||
$saved_snippet = save_snippet( $snippet );
|
||||
|
||||
$snippet_id = $saved_snippet->id;
|
||||
|
||||
if ( $snippet_id ) {
|
||||
$imported[] = $snippet_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $imported;
|
||||
}
|
||||
|
||||
public function register_rest_routes() {
|
||||
$namespace = REST_API_NAMESPACE . self::VERSION;
|
||||
|
||||
register_rest_route( $namespace, $this->get_name(), [
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_items' ],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
] );
|
||||
|
||||
register_rest_route( $namespace, $this->get_name() . '/import', [
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'import' ],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
'args' => [
|
||||
'ids' => [
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
],
|
||||
'network' => [
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
],
|
||||
'auto_add_tags' => [
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
],
|
||||
'tag_value' => [
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
class Insert_Headers_And_Footers_Importer extends Importer_Base {
|
||||
|
||||
private const FIELD_MAPPINGS = [
|
||||
'title' => 'name',
|
||||
'note' => 'desc',
|
||||
'code' => 'code',
|
||||
'tags' => 'tags',
|
||||
'location' => 'scope',
|
||||
'priority' => 'priority',
|
||||
'modified' => 'modified',
|
||||
];
|
||||
|
||||
private const PHP_SCOPE_TRANSFORMATIONS = [
|
||||
'everywhere' => 'global',
|
||||
'admin_only' => 'admin',
|
||||
'frontend_only' => 'front-end',
|
||||
];
|
||||
|
||||
private const HTML_SCOPE_TRANSFORMATIONS = [
|
||||
'' => 'content',
|
||||
'site_wide_header' => 'head-content',
|
||||
'site_wide_footer' => 'footer-content',
|
||||
];
|
||||
|
||||
public function get_name() {
|
||||
return 'insert-headers-and-footers';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'WPCode (Insert Headers and Footers)', 'code-snippets' );
|
||||
}
|
||||
|
||||
public static function is_active(): bool {
|
||||
return is_plugin_active( 'insert-headers-and-footers/ihaf.php' );
|
||||
}
|
||||
|
||||
public function get_data( array $ids_to_import = [] ) {
|
||||
$query_args = [
|
||||
'post_type' => 'wpcode',
|
||||
'post_status' => [
|
||||
'publish',
|
||||
'draft',
|
||||
],
|
||||
'nopaging' => true,
|
||||
];
|
||||
|
||||
if ( ! empty( $ids_to_import ) ) {
|
||||
$query_args['include'] = $ids_to_import;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$snippets = get_posts( $query_args );
|
||||
|
||||
foreach ( $snippets as $snippet_item ) {
|
||||
$snippet = new \WPCode_Snippet( $snippet_item );
|
||||
$snippet_data = $snippet->get_data_for_caching();
|
||||
$snippet_data['tags'] = $snippet->get_tags();
|
||||
$snippet_data['note'] = $snippet->get_note();
|
||||
$snippet_data['cloud_id'] = null;
|
||||
$snippet_data['custom_shortcode'] = $snippet->get_custom_shortcode();
|
||||
$snippet_data['table_data'] = [
|
||||
'id' => $snippet_item->ID,
|
||||
'title' => $snippet_item->post_title,
|
||||
];
|
||||
|
||||
$data[] = apply_filters( 'wpcode_export_snippet_data', $snippet_data, $snippet );
|
||||
}
|
||||
|
||||
$data = array_reverse( $data );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function create_snippet( $snippet_data, bool $multisite ): ?Snippet {
|
||||
$code_type = $snippet_data['code_type'] ?? '';
|
||||
$is_supported_code_type = in_array( $code_type, [ 'php', 'css', 'html', 'js' ], true );
|
||||
if ( ! $is_supported_code_type ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$snippet = new Snippet();
|
||||
$snippet->network = $multisite;
|
||||
|
||||
foreach ( self::FIELD_MAPPINGS as $source_field => $target_field ) {
|
||||
if ( ! isset( $snippet_data[ $source_field ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->transform_field_value(
|
||||
$target_field,
|
||||
$snippet_data[ $source_field ],
|
||||
$snippet_data
|
||||
);
|
||||
|
||||
$scope_not_supported = 'scope' === $target_field && null === $value;
|
||||
if ( $scope_not_supported ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$snippet->set_field( $target_field, $value );
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
private function transform_field_value( string $target_field, $value, array $snippet_data ) {
|
||||
if ( 'scope' === $target_field ) {
|
||||
return $this->transform_scope_value( $value, $snippet_data );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function transform_scope_value( $location_value, array $snippet_data ): ?string {
|
||||
if ( ! is_scalar( $location_value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$code_type = $snippet_data['code_type'];
|
||||
|
||||
switch ( $code_type ) {
|
||||
case 'html':
|
||||
$transformations = self::HTML_SCOPE_TRANSFORMATIONS;
|
||||
break;
|
||||
case 'php':
|
||||
$transformations = self::PHP_SCOPE_TRANSFORMATIONS;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return $transformations[ $location_value ] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
class Insert_PHP_Code_Snippet_Importer extends Importer_Base {
|
||||
|
||||
private const FIELD_MAPPINGS = [
|
||||
'title' => 'name',
|
||||
'content' => 'code',
|
||||
'insertionLocationType' => 'scope',
|
||||
];
|
||||
|
||||
private const SCOPE_TRANSFORMATIONS = [
|
||||
0 => 'single-use',
|
||||
2 => 'admin',
|
||||
3 => 'front-end',
|
||||
];
|
||||
|
||||
private const SHORTCODE_SCOPE_TRANSFORMATIONS = [
|
||||
3 => 'content',
|
||||
];
|
||||
|
||||
public function get_name() {
|
||||
return 'insert-php-code-snippet';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Insert PHP Code Snippet', 'code-snippets' );
|
||||
}
|
||||
|
||||
public static function is_active(): bool {
|
||||
return is_plugin_active( 'insert-php-code-snippet/insert-php-code-snippet.php' );
|
||||
}
|
||||
|
||||
public function get_data( array $ids_to_import = [] ) {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . 'xyz_ips_short_code';
|
||||
$sql = "SELECT * FROM `{$table_name}`";
|
||||
|
||||
if ( ! empty( $ids_to_import ) ) {
|
||||
$sql .= " WHERE id IN (" . implode( ',', $ids_to_import ) . ")";
|
||||
}
|
||||
|
||||
$snippets = $wpdb->get_results(
|
||||
$sql
|
||||
);
|
||||
|
||||
foreach ( $snippets as $snippet ) {
|
||||
$snippet->table_data = [
|
||||
'id' => (int) $snippet->id,
|
||||
'title' => $snippet->title,
|
||||
];
|
||||
}
|
||||
|
||||
return $snippets;
|
||||
}
|
||||
|
||||
public function create_snippet( $snippet_data, bool $multisite ): ?Snippet {
|
||||
$code_type = $snippet_data->snippet_type ?? '';
|
||||
|
||||
$snippet = new Snippet();
|
||||
$snippet->network = $multisite;
|
||||
|
||||
foreach ( self::FIELD_MAPPINGS as $source_field => $target_field ) {
|
||||
if ( ! isset( $snippet_data->$source_field ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->transform_field_value(
|
||||
$target_field,
|
||||
$snippet_data->$source_field,
|
||||
$snippet_data
|
||||
);
|
||||
|
||||
$scope_not_supported = 'scope' === $target_field && null === $value;
|
||||
if ( $scope_not_supported ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$snippet->set_field( $target_field, $value );
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
private function transform_field_value( string $target_field, $value, $snippet_data ) {
|
||||
if ( 'scope' === $target_field ) {
|
||||
return $this->transform_scope_value( $value, $snippet_data );
|
||||
}
|
||||
|
||||
if ( 'code' === $target_field ) {
|
||||
return $this->transform_code_value( $value, $snippet_data );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function transform_scope_value( $location_value, $snippet_data ): ?string {
|
||||
if ( ! is_scalar( $location_value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$transformations = self::SCOPE_TRANSFORMATIONS;
|
||||
|
||||
if ( '2' === $snippet_data->insertionMethod ) {
|
||||
$transformations = self::SHORTCODE_SCOPE_TRANSFORMATIONS;
|
||||
}
|
||||
|
||||
return $transformations[ $location_value ] ?? null;
|
||||
}
|
||||
|
||||
private function transform_code_value( $code_value, $snippet_data ): ?string {
|
||||
$code = html_entity_decode( $code_value );
|
||||
|
||||
if ( '2' !== $snippet_data->insertionMethod ) {
|
||||
$code = $this->strip_wrapper_tags( $code );
|
||||
}
|
||||
|
||||
return trim( $code );
|
||||
}
|
||||
|
||||
private function strip_wrapper_tags( string $code ): string {
|
||||
return preg_replace( '/^\s*<\?\s*(php)?\s*|\?\>\s*$/i', '', $code );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Code_Snippets;
|
||||
|
||||
use WP_REST_Server;
|
||||
use const Code_Snippets\REST_API_NAMESPACE;
|
||||
|
||||
class Plugins_Import_Manager {
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
private $plugin_importers = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->init_plugin_importers();
|
||||
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
|
||||
}
|
||||
|
||||
private function init_plugin_importers() {
|
||||
$this->plugin_importers = [
|
||||
'insert-headers-and-footers' => new Insert_Headers_And_Footers_Importer(),
|
||||
'header-footer-code-manager' => new Header_Footer_Code_Manager_Importer(),
|
||||
'insert-php-code-snippet' => new Insert_PHP_Code_Snippet_Importer(),
|
||||
];
|
||||
}
|
||||
|
||||
public function get_importer( string $source ) {
|
||||
return $this->plugin_importers[ $source ] ?? null;
|
||||
}
|
||||
|
||||
public function get_importers() {
|
||||
if ( empty( $this->plugin_importers ) ) {
|
||||
$this->init_plugin_importers();
|
||||
}
|
||||
|
||||
$plugins_list = [];
|
||||
|
||||
foreach ( $this->plugin_importers as $importer ) {
|
||||
$plugins_list[] = [
|
||||
'name' => $importer->get_name(),
|
||||
'title' => $importer->get_title(),
|
||||
'is_active' => $importer::is_active(),
|
||||
];
|
||||
}
|
||||
|
||||
return $plugins_list;
|
||||
}
|
||||
|
||||
public function register_rest_routes() {
|
||||
$namespace = REST_API_NAMESPACE . self::VERSION;
|
||||
|
||||
register_rest_route( $namespace, 'importers', [
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_importers' ],
|
||||
'permission_callback' => function() {
|
||||
return current_user_can( 'manage_options' );
|
||||
},
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,784 @@
|
||||
<?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\clean_active_snippets_cache;
|
||||
use function Code_Snippets\code_snippets;
|
||||
use function Code_Snippets\deactivate_snippet;
|
||||
use function Code_Snippets\trash_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, 'toggle_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, 'toggle_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 );
|
||||
$all_snippets = $this->get_network_items( $all_snippets, $network );
|
||||
|
||||
$total_items = count( $all_snippets );
|
||||
$query_params = $request->get_query_params();
|
||||
|
||||
if ( isset( $query_params['per_page'] ) || isset( $query_params['page'] ) ) {
|
||||
$collection_params = $this->get_collection_params();
|
||||
$per_page = isset( $query_params['per_page'] )
|
||||
? max( 1, (int) $query_params['per_page'] )
|
||||
: (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'] );
|
||||
$total_pages = (int) ceil( $total_items / $per_page );
|
||||
|
||||
$offset = ( $page - 1 ) * $per_page;
|
||||
$snippets = array_slice( $all_snippets, $offset, $per_page );
|
||||
} else {
|
||||
$snippets = $all_snippets;
|
||||
$total_pages = 1;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and merge shared network snippets.
|
||||
*
|
||||
* @param array<Snippet> $all_snippets List of snippets to merge with.
|
||||
* @param bool|null $network Whether fetching network snippets.
|
||||
*
|
||||
* @return array<Snippet> Modified list of snippets.
|
||||
*/
|
||||
private function get_network_items( array $all_snippets, $network ): array {
|
||||
if ( ! is_multisite() || $network ) {
|
||||
return $all_snippets;
|
||||
}
|
||||
|
||||
$shared_ids = get_site_option( 'shared_network_snippets' );
|
||||
|
||||
if ( ! $shared_ids || ! is_array( $shared_ids ) ) {
|
||||
return $all_snippets;
|
||||
}
|
||||
|
||||
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
|
||||
$shared_snippets = get_snippets( $shared_ids, true );
|
||||
|
||||
foreach ( $shared_snippets as $snippet ) {
|
||||
$snippet->shared_network = true;
|
||||
$snippet->active = in_array( $snippet->id, $active_shared_snippets, true );
|
||||
}
|
||||
|
||||
return array_merge( $all_snippets, $shared_snippets );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (trash)
|
||||
*
|
||||
* @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 = trash_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 );
|
||||
$snippet = $item ? get_snippet( $item->id, $item->network ) : null;
|
||||
|
||||
if ( ! $snippet || ! $snippet->id ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_activate',
|
||||
__( 'The snippet could not be found.', 'code-snippets' ),
|
||||
[ 'status' => 404 ]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $snippet->shared_network ) {
|
||||
$this->set_shared_network_active( $snippet->id, true );
|
||||
$snippet->active = true;
|
||||
return rest_ensure_response( $snippet );
|
||||
}
|
||||
|
||||
$result = activate_snippet( $snippet->id, $snippet->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 );
|
||||
$snippet = $item ? get_snippet( $item->id, $item->network ) : null;
|
||||
|
||||
if ( ! $snippet || ! $snippet->id ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_activate',
|
||||
__( 'The snippet could not be found.', 'code-snippets' ),
|
||||
[ 'status' => 404 ]
|
||||
);
|
||||
}
|
||||
|
||||
if ( $snippet->shared_network ) {
|
||||
$this->set_shared_network_active( $snippet->id, false );
|
||||
$snippet->active = false;
|
||||
return rest_ensure_response( $snippet );
|
||||
}
|
||||
|
||||
$result = deactivate_snippet( $snippet->id, $snippet->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 ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle a shared network snippet's active state for the current site only.
|
||||
*
|
||||
* @param int $snippet_id Snippet identifier.
|
||||
* @param bool $active Whether the snippet should be active on the current site.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_shared_network_active( int $snippet_id, bool $active ): void {
|
||||
$active_shared_snippets = get_option( 'active_shared_network_snippets', [] );
|
||||
|
||||
if ( ! is_array( $active_shared_snippets ) ) {
|
||||
$active_shared_snippets = [];
|
||||
}
|
||||
|
||||
$already_active = in_array( $snippet_id, $active_shared_snippets, true );
|
||||
|
||||
if ( $active === $already_active ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$active_shared_snippets = $active ?
|
||||
array_merge( $active_shared_snippets, [ $snippet_id ] ) :
|
||||
array_values( array_diff( $active_shared_snippets, [ $snippet_id ] ) );
|
||||
|
||||
update_option( 'active_shared_network_snippets', $active_shared_snippets );
|
||||
clean_active_snippets_cache( code_snippets()->db->ms_table );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a request targets network-scoped snippets.
|
||||
*
|
||||
* Only the literal boolean `true` (or its common string/integer equivalents)
|
||||
* is treated as a network-scoped request. A missing or null `network` param
|
||||
* means "site-scoped", and must not be escalated to the network capability.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_network_scoped_request( $request ): bool {
|
||||
if ( ! is_multisite() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $request instanceof WP_REST_Request || ! $request->has_param( 'network' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$network = $request->get_param( 'network' );
|
||||
|
||||
if ( is_bool( $network ) ) {
|
||||
return $network;
|
||||
}
|
||||
|
||||
if ( is_string( $network ) ) {
|
||||
return in_array( strtolower( $network ), [ '1', 'true', 'yes' ], true );
|
||||
}
|
||||
|
||||
return (bool) $network;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the current user has permission for the scope implied by the request.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function check_request_capability( $request ): bool {
|
||||
if ( $this->is_network_scoped_request( $request ) ) {
|
||||
return code_snippets()->user_can_manage_network_snippets();
|
||||
}
|
||||
|
||||
return code_snippets()->current_user_can();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the request targets a shared network snippet.
|
||||
*
|
||||
* Shared network snippets are stored network-wide but each site decides whether
|
||||
* to activate them via the per-site `active_shared_network_snippets` option. The
|
||||
* `id` route parameter is used to look up the snippet so the result reflects the
|
||||
* actual stored row rather than a value supplied in the request payload.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_shared_network_snippet_request( $request ): bool {
|
||||
if ( ! is_multisite() || ! $request instanceof WP_REST_Request ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snippet_id = absint( $request->get_param( 'id' ) );
|
||||
|
||||
if ( ! $snippet_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$snippet = get_snippet( $snippet_id, true );
|
||||
|
||||
return $snippet && $snippet->id && $snippet->shared_network;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $this->check_request_capability( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to get a specific item.
|
||||
*
|
||||
* Shared network snippets are readable by any user who can manage snippets on
|
||||
* the current site, since the snippet is intentionally exposed to subsites.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function get_item_permissions_check( $request ): bool {
|
||||
if ( $this->is_shared_network_snippet_request( $request ) ) {
|
||||
return code_snippets()->current_user_can();
|
||||
}
|
||||
|
||||
return $this->check_request_capability( $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 $this->check_request_capability( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->check_request_capability( $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->check_request_capability( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to toggle a snippet's activation.
|
||||
*
|
||||
* For shared network snippets the activation toggle only writes to the
|
||||
* per-site `active_shared_network_snippets` option, so the site capability
|
||||
* is sufficient. For all other snippets we keep the strict capability check
|
||||
* that prevents a subsite admin from forging `network=true` to operate on
|
||||
* exclusive network-scoped snippets.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function toggle_item_permissions_check( $request ): bool {
|
||||
if ( $this->is_shared_network_snippet_request( $request ) ) {
|
||||
return code_snippets()->current_user_can();
|
||||
}
|
||||
|
||||
return $this->check_request_capability( $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;
|
||||
}
|
||||
}
|
||||
271
plugins/code-snippets/php/settings/class-setting-field.php
Normal file
271
plugins/code-snippets/php/settings/class-setting-field.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* This file handles rendering the settings fields
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @package Code_Snippets
|
||||
* @subpackage Settings
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Settings;
|
||||
|
||||
/**
|
||||
* Represents a single setting field
|
||||
*
|
||||
* @property-read string $desc Field description.
|
||||
* @property-read string $label Field label.
|
||||
* @property-read string $type Field type.
|
||||
* @property-read string $name Setting name.
|
||||
*
|
||||
* @property-read int $min Minimum value (for numerical inputs).
|
||||
* @property-read int $max Maximum value(for numerical inputs).
|
||||
* @property-read array<string, string> $options List of options for a select or checkboxes field.
|
||||
* @property-read callable $render_callback Custom function to use when rendering a callback field.
|
||||
* @property-read callable $sanitize_callback Custom function to use when sanitize the setting value.
|
||||
* @property-read mixed $default Default setting value.
|
||||
*
|
||||
* @property-read string $input_name Value of `name` HTML attribute on an input element.
|
||||
* @property-read string $element_id
|
||||
*/
|
||||
class Setting_Field {
|
||||
|
||||
/**
|
||||
* Input field identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $field_id;
|
||||
|
||||
/**
|
||||
* Settings section identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $section;
|
||||
|
||||
/**
|
||||
* List of possible arguments.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $args = array(
|
||||
'desc' => '',
|
||||
'label' => '',
|
||||
'min' => null,
|
||||
'max' => null,
|
||||
'options' => [],
|
||||
);
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param string $section_id Settings section identifier.
|
||||
* @param string $field_id Setting field identifier.
|
||||
* @param array<string, mixed> $args The setting field attributes.
|
||||
*/
|
||||
public function __construct( string $section_id, string $field_id, array $args ) {
|
||||
$this->field_id = $field_id;
|
||||
$this->section = $section_id;
|
||||
$this->args = array_merge( $this->args, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single setting attribute.
|
||||
*
|
||||
* @param string $argument Attribute name.
|
||||
*
|
||||
* @return mixed Attribute value.
|
||||
*/
|
||||
public function __get( string $argument ) {
|
||||
|
||||
if ( 'input_name' === $argument ) {
|
||||
return sprintf( '%s[%s][%s]', OPTION_NAME, $this->section, $this->field_id );
|
||||
}
|
||||
|
||||
return $this->args[ $argument ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the saved value for this setting.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function get_saved_value() {
|
||||
return get_setting( $this->section, $this->field_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the setting field
|
||||
*/
|
||||
public function render() {
|
||||
$method_name = 'render_' . $this->type . '_field';
|
||||
|
||||
if ( method_exists( $this, $method_name ) ) {
|
||||
call_user_func( array( $this, $method_name ) );
|
||||
} else {
|
||||
// Error message, not necessary to translate.
|
||||
printf( 'Cannot render a %s field.', esc_html( $this->type ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->desc ) {
|
||||
echo '<p class="description">', wp_kses_post( $this->desc ), '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a callback field.
|
||||
*/
|
||||
public function render_callback_field() {
|
||||
if ( ! is_callable( $this->render_callback ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_user_func( $this->render_callback, $this->args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single checkbox field.
|
||||
*
|
||||
* @param string $input_name Input name.
|
||||
* @param string $label Input label.
|
||||
* @param boolean $checked Whether the checkbox should be checked.
|
||||
*/
|
||||
private static function render_checkbox( string $input_name, string $label, bool $checked ) {
|
||||
|
||||
$checkbox = sprintf(
|
||||
'<input type="checkbox" name="%s"%s>',
|
||||
esc_attr( $input_name ),
|
||||
checked( $checked, true, false )
|
||||
);
|
||||
|
||||
$kses = [
|
||||
'input' => [
|
||||
'type' => [],
|
||||
'name' => [],
|
||||
'checked' => [],
|
||||
],
|
||||
];
|
||||
|
||||
if ( $label ) {
|
||||
printf(
|
||||
'<label>%s %s</label>',
|
||||
wp_kses( $checkbox, $kses ),
|
||||
wp_kses_post( $label )
|
||||
);
|
||||
} else {
|
||||
echo wp_kses( $checkbox, $kses );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a checkbox field for a setting
|
||||
*
|
||||
* @return void
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function render_checkbox_field() {
|
||||
$this->render_checkbox( $this->input_name, $this->label, $this->get_saved_value() ?? false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a checkbox field for a setting
|
||||
*
|
||||
* @return void
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function render_checkboxes_field() {
|
||||
$saved_value = $this->get_saved_value();
|
||||
$saved_value = is_array( $saved_value ) ? $saved_value : [];
|
||||
|
||||
echo '<fieldset>';
|
||||
printf( '<legend class="screen-reader-text"><span>%s</span></legend>', esc_html( $this->name ) );
|
||||
|
||||
foreach ( $this->options as $option => $label ) {
|
||||
$this->render_checkbox( $this->input_name . "[$option]", $label, in_array( $option, $saved_value, true ) );
|
||||
echo '<br>';
|
||||
}
|
||||
|
||||
echo '</fieldset>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a basic text field for an editor setting.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_text_field() {
|
||||
printf(
|
||||
'<input id="%s" type="text" name="%s" value="%s" class="regular-text %s">',
|
||||
esc_attr( $this->element_id ),
|
||||
esc_attr( $this->input_name ),
|
||||
esc_attr( $this->get_saved_value() ),
|
||||
esc_attr( $this->element_id )
|
||||
);
|
||||
|
||||
if ( $this->label ) {
|
||||
echo ' ' . wp_kses_post( $this->label );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a number select field for an editor setting
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private function render_number_field() {
|
||||
printf(
|
||||
'<input type="number" name="%s" value="%s"',
|
||||
esc_attr( $this->input_name ),
|
||||
esc_attr( $this->get_saved_value() )
|
||||
);
|
||||
|
||||
if ( is_numeric( $this->min ) ) {
|
||||
printf( ' min="%d"', intval( $this->min ) );
|
||||
}
|
||||
|
||||
if ( is_numeric( $this->max ) ) {
|
||||
printf( ' max="%d"', intval( $this->max ) );
|
||||
}
|
||||
|
||||
echo '>';
|
||||
|
||||
if ( $this->label ) {
|
||||
echo ' ' . wp_kses_post( $this->label );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a number select field for an editor setting.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
private function render_select_field() {
|
||||
$saved_value = $this->get_saved_value();
|
||||
printf( '<select name="%s">', esc_attr( $this->input_name ) );
|
||||
|
||||
foreach ( $this->options as $option => $option_label ) {
|
||||
printf(
|
||||
'<option value="%s"%s>%s</option>',
|
||||
esc_attr( $option ),
|
||||
selected( $option, $saved_value, false ),
|
||||
esc_html( $option_label )
|
||||
);
|
||||
}
|
||||
|
||||
echo '</select>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a button link.
|
||||
*
|
||||
* @since 3.5.1
|
||||
*/
|
||||
private function render_action_field() {
|
||||
printf(
|
||||
'<button type="submit" name="%s" class="button">%s</button>',
|
||||
esc_attr( $this->input_name ),
|
||||
esc_html( $this->label ? $this->label : $this->name )
|
||||
);
|
||||
}
|
||||
}
|
||||
362
plugins/code-snippets/php/settings/class-version-switch.php
Normal file
362
plugins/code-snippets/php/settings/class-version-switch.php
Normal file
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
/**
|
||||
* Class-based version switching functionality for the Code Snippets plugin.
|
||||
*
|
||||
* Converted from procedural `version-switch.php` to an OO class `Version_Switch`.
|
||||
*
|
||||
* @package Code_Snippets
|
||||
* @subpackage Settings
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Settings;
|
||||
|
||||
// Configuration constants for version switching
|
||||
const VERSION_CACHE_KEY = 'code_snippets_available_versions';
|
||||
const PROGRESS_KEY = 'code_snippets_version_switch_progress';
|
||||
const VERSION_CACHE_DURATION = HOUR_IN_SECONDS;
|
||||
const PROGRESS_TIMEOUT = 5 * MINUTE_IN_SECONDS;
|
||||
const WORDPRESS_API_ENDPOINT = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets';
|
||||
|
||||
class Version_Switch {
|
||||
/**
|
||||
* Initialize hook registrations.
|
||||
* Call this after the file is required.
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_action( 'wp_ajax_code_snippets_switch_version', [ __CLASS__, 'ajax_switch_version' ] );
|
||||
add_action( 'wp_ajax_code_snippets_refresh_versions', [ __CLASS__, 'ajax_refresh_versions' ] );
|
||||
}
|
||||
|
||||
public static function get_available_versions(): array {
|
||||
$versions = get_transient( VERSION_CACHE_KEY );
|
||||
|
||||
if ( false === $versions ) {
|
||||
$response = wp_remote_get( WORDPRESS_API_ENDPOINT );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
if ( ! $data || ! isset( $data['versions'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out 'trunk' and sort versions
|
||||
$versions = [];
|
||||
foreach ( $data['versions'] as $version => $download_url ) {
|
||||
if ( 'trunk' !== $version ) {
|
||||
$versions[] = [
|
||||
'version' => $version,
|
||||
'url' => $download_url,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort versions in descending order
|
||||
usort( $versions, function( $a, $b ) {
|
||||
return version_compare( $b['version'], $a['version'] );
|
||||
});
|
||||
|
||||
// Cache for configured duration
|
||||
set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION );
|
||||
}
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
public static function get_current_version(): string {
|
||||
return defined( 'CODE_SNIPPETS_VERSION' ) ? CODE_SNIPPETS_VERSION : '0.0.0';
|
||||
}
|
||||
|
||||
public static function is_version_switch_in_progress(): bool {
|
||||
return get_transient( PROGRESS_KEY ) !== false;
|
||||
}
|
||||
|
||||
public static function clear_version_caches(): void {
|
||||
delete_transient( VERSION_CACHE_KEY );
|
||||
delete_transient( PROGRESS_KEY );
|
||||
}
|
||||
|
||||
public static function validate_target_version( string $target_version, array $available_versions ): array {
|
||||
if ( empty( $target_version ) ) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => __( 'No target version specified.', 'code-snippets' ),
|
||||
'download_url' => '',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ( $available_versions as $version_info ) {
|
||||
if ( $version_info['version'] === $target_version ) {
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '',
|
||||
'download_url' => $version_info['url'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => __( 'Invalid version specified.', 'code-snippets' ),
|
||||
'download_url' => '',
|
||||
];
|
||||
}
|
||||
|
||||
public static function create_error_response( string $message, string $technical_details = '' ): array {
|
||||
if ( ! empty( $technical_details ) ) {
|
||||
if ( function_exists( 'error_log' ) ) {
|
||||
error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) );
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
public static function perform_version_install( string $download_url ) {
|
||||
if ( ! function_exists( 'wp_update_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/update.php';
|
||||
}
|
||||
if ( ! function_exists( 'show_message' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/misc.php';
|
||||
}
|
||||
if ( ! class_exists( 'Plugin_Upgrader' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
}
|
||||
|
||||
$update_handler = new \WP_Ajax_Upgrader_Skin();
|
||||
$upgrader = new \Plugin_Upgrader( $update_handler );
|
||||
|
||||
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
|
||||
$code_snippets_last_update_handler = $update_handler;
|
||||
$code_snippets_last_upgrader = $upgrader;
|
||||
|
||||
return $upgrader->install( $download_url, [
|
||||
'overwrite_package' => true,
|
||||
'clear_update_cache' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
public static function extract_handler_messages( $update_handler, $upgrader ): string {
|
||||
$handler_messages = '';
|
||||
|
||||
if ( isset( $update_handler ) ) {
|
||||
if ( method_exists( $update_handler, 'get_errors' ) ) {
|
||||
$errs = $update_handler->get_errors();
|
||||
if ( $errs instanceof \WP_Error && $errs->has_errors() ) {
|
||||
$handler_messages .= implode( "\n", $errs->get_error_messages() );
|
||||
}
|
||||
}
|
||||
if ( method_exists( $update_handler, 'get_error_messages' ) ) {
|
||||
$em = $update_handler->get_error_messages();
|
||||
if ( $em ) {
|
||||
$handler_messages .= "\n" . $em;
|
||||
}
|
||||
}
|
||||
if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) {
|
||||
$upgrade_msgs = $update_handler->get_upgrade_messages();
|
||||
if ( is_array( $upgrade_msgs ) ) {
|
||||
$handler_messages .= "\n" . implode( "\n", $upgrade_msgs );
|
||||
} elseif ( $upgrade_msgs ) {
|
||||
$handler_messages .= "\n" . (string) $upgrade_msgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $handler_messages ) && isset( $upgrader->result ) ) {
|
||||
if ( is_wp_error( $upgrader->result ) ) {
|
||||
$handler_messages = implode( "\n", $upgrader->result->get_error_messages() );
|
||||
} else {
|
||||
$handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true );
|
||||
}
|
||||
}
|
||||
|
||||
return trim( $handler_messages );
|
||||
}
|
||||
|
||||
public static function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void {
|
||||
if ( function_exists( 'error_log' ) ) {
|
||||
error_log( sprintf( 'Code Snippets version switch failed. target=%s, result=%s, details=%s', $target_version, var_export( $result, true ), $details ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static function handle_installation_failure( string $target_version, string $download_url, $install_result ): array {
|
||||
global $code_snippets_last_update_handler, $code_snippets_last_upgrader;
|
||||
|
||||
$handler_messages = self::extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader );
|
||||
self::log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" );
|
||||
|
||||
$fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' );
|
||||
if ( ! empty( $handler_messages ) ) {
|
||||
$short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' );
|
||||
$fallback_message = sprintf( '%s %s', $fallback_message, $short );
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $fallback_message,
|
||||
];
|
||||
}
|
||||
|
||||
public static function handle_version_switch( string $target_version ): array {
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
return self::create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) );
|
||||
}
|
||||
|
||||
$available_versions = self::get_available_versions();
|
||||
$validation = self::validate_target_version( $target_version, $available_versions );
|
||||
|
||||
if ( ! $validation['success'] ) {
|
||||
return self::create_error_response( $validation['message'] );
|
||||
}
|
||||
|
||||
if ( self::get_current_version() === $target_version ) {
|
||||
return self::create_error_response( __( 'Already on the specified version.', 'code-snippets' ) );
|
||||
}
|
||||
|
||||
set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT );
|
||||
|
||||
$install_result = self::perform_version_install( $validation['download_url'] );
|
||||
|
||||
delete_transient( PROGRESS_KEY );
|
||||
|
||||
if ( is_wp_error( $install_result ) ) {
|
||||
return self::create_error_response( $install_result->get_error_message() );
|
||||
}
|
||||
|
||||
if ( $install_result ) {
|
||||
delete_transient( VERSION_CACHE_KEY );
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => sprintf( __( 'Successfully switched to version %s. Please refresh the page to see changes.', 'code-snippets' ), $target_version ),
|
||||
];
|
||||
}
|
||||
|
||||
return self::handle_installation_failure( $target_version, $validation['download_url'], $install_result );
|
||||
}
|
||||
|
||||
public static function render_version_switch_field( array $args ): void {
|
||||
$current_version = self::get_current_version();
|
||||
$available_versions = self::get_available_versions();
|
||||
$is_switching = self::is_version_switch_in_progress();
|
||||
|
||||
?>
|
||||
<div class="code-snippets-version-switch">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Current Version:', 'code-snippets' ); ?></strong>
|
||||
<span class="current-version"><?php echo esc_html( $current_version ); ?></span>
|
||||
</p>
|
||||
|
||||
<?php if ( $is_switching ) : ?>
|
||||
<div class="notice notice-info inline">
|
||||
<p><?php esc_html_e( 'Version switch in progress. Please wait...', 'code-snippets' ); ?></p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<p>
|
||||
<label for="target_version">
|
||||
<?php esc_html_e( 'Switch to Version:', 'code-snippets' ); ?>
|
||||
</label>
|
||||
<select id="target_version" name="target_version" <?php disabled( empty( $available_versions ) ); ?>>
|
||||
<option value=""><?php esc_html_e( 'Select a version...', 'code-snippets' ); ?></option>
|
||||
<?php foreach ( $available_versions as $version_info ) : ?>
|
||||
<option value="<?php echo esc_attr( $version_info['version'] ); ?>"
|
||||
<?php selected( $version_info['version'], $current_version ); ?>>
|
||||
<?php echo esc_html( $version_info['version'] ); ?>
|
||||
<?php if ( $version_info['version'] === $current_version ) : ?>
|
||||
<?php esc_html_e( ' (Current)', 'code-snippets' ); ?>
|
||||
<?php endif; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button type="button" id="switch-version-btn" class="button button-secondary" disabled
|
||||
<?php disabled( empty( $available_versions ) ); ?>>
|
||||
<?php esc_html_e( 'Switch Version', 'code-snippets' ); ?>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<div id="version-switch-result" class="notice" style="display: none;"></div>
|
||||
<?php endif; ?>
|
||||
</div><?php
|
||||
}
|
||||
|
||||
public static function ajax_switch_version(): void {
|
||||
if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'code_snippets_version_switch' ) ) {
|
||||
wp_die( __( 'Security check failed.', 'code-snippets' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
wp_send_json_error( [
|
||||
'message' => __( 'You do not have permission to update plugins.', 'code-snippets' ),
|
||||
] );
|
||||
}
|
||||
|
||||
$target_version = sanitize_text_field( $_POST['target_version'] ?? '' );
|
||||
|
||||
if ( empty( $target_version ) ) {
|
||||
wp_send_json_error( [
|
||||
'message' => __( 'No target version specified.', 'code-snippets' ),
|
||||
] );
|
||||
}
|
||||
|
||||
$result = self::handle_version_switch( $target_version );
|
||||
|
||||
if ( $result['success'] ) {
|
||||
wp_send_json_success( $result );
|
||||
} else {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
}
|
||||
|
||||
public static function render_refresh_versions_field( array $args ): void {
|
||||
?>
|
||||
<button type="button" id="refresh-versions-btn" class="button button-secondary">
|
||||
<?php esc_html_e( 'Refresh Available Versions', 'code-snippets' ); ?>
|
||||
</button>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Check for the latest available plugin versions from WordPress.org.', 'code-snippets' ); ?>
|
||||
</p><?php
|
||||
}
|
||||
|
||||
public static function ajax_refresh_versions(): void {
|
||||
if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'code_snippets_refresh_versions' ) ) {
|
||||
wp_die( __( 'Security check failed.', 'code-snippets' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( [
|
||||
'message' => __( 'You do not have permission to manage options.', 'code-snippets' ),
|
||||
] );
|
||||
}
|
||||
|
||||
delete_transient( VERSION_CACHE_KEY );
|
||||
self::get_available_versions();
|
||||
|
||||
wp_send_json_success( [
|
||||
'message' => __( 'Available versions updated successfully.', 'code-snippets' ),
|
||||
] );
|
||||
}
|
||||
|
||||
public static function render_version_switch_warning(): void {
|
||||
?>
|
||||
<div id="version-switch-warning" class="notice notice-warning" style="display: none; margin-block-start: 20px;">
|
||||
<p>
|
||||
<strong><?php esc_html_e( 'Warning:', 'code-snippets' ); ?></strong>
|
||||
<?php esc_html_e( 'Switching versions may cause compatibility issues. Always backup your site before switching versions.', 'code-snippets' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize hooks when the file is loaded.
|
||||
Version_Switch::init();
|
||||
132
plugins/code-snippets/php/settings/editor-preview.php
Normal file
132
plugins/code-snippets/php/settings/editor-preview.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* This file handles the editor preview setting
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @package Code_Snippets
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Settings;
|
||||
|
||||
use function Code_Snippets\code_snippets;
|
||||
use function Code_Snippets\enqueue_code_editor;
|
||||
use function Code_Snippets\get_editor_themes;
|
||||
|
||||
/**
|
||||
* Load the CSS and JavaScript for the editor preview field
|
||||
*/
|
||||
function enqueue_editor_preview_assets() {
|
||||
$plugin = code_snippets();
|
||||
|
||||
enqueue_code_editor( 'php' );
|
||||
|
||||
// Enqueue all editor themes.
|
||||
$themes = get_editor_themes();
|
||||
|
||||
foreach ( $themes as $theme ) {
|
||||
wp_enqueue_style(
|
||||
'code-snippets-editor-theme-' . $theme,
|
||||
plugins_url( "dist/editor-themes/$theme.css", $plugin->file ),
|
||||
[ 'code-editor' ],
|
||||
$plugin->version
|
||||
);
|
||||
}
|
||||
|
||||
// Enqueue the menu scripts.
|
||||
wp_enqueue_script(
|
||||
'code-snippets-settings-menu',
|
||||
plugins_url( 'dist/settings.js', $plugin->file ),
|
||||
[ 'code-snippets-code-editor' ],
|
||||
$plugin->version,
|
||||
true
|
||||
);
|
||||
|
||||
wp_set_script_translations( 'code-snippets-settings-menu', 'code-snippets' );
|
||||
|
||||
// Extract the CodeMirror-specific editor settings.
|
||||
$setting_fields = get_settings_fields();
|
||||
$editor_fields = array();
|
||||
|
||||
foreach ( $setting_fields['editor'] as $name => $field ) {
|
||||
if ( empty( $field['codemirror'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$editor_fields[] = array(
|
||||
'name' => $name,
|
||||
'type' => $field['type'],
|
||||
'codemirror' => addslashes( $field['codemirror'] ),
|
||||
);
|
||||
}
|
||||
|
||||
// Pass the saved options to the external JavaScript file.
|
||||
$inline_script = 'var code_snippets_editor_settings = ' . wp_json_encode( $editor_fields ) . ';';
|
||||
|
||||
wp_add_inline_script( 'code-snippets-settings-menu', $inline_script, 'before' );
|
||||
|
||||
// Provide configuration and simple i18n for the version switch JS module.
|
||||
$version_switch = array(
|
||||
'ajaxurl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce_switch' => wp_create_nonce( 'code_snippets_version_switch' ),
|
||||
'nonce_refresh' => wp_create_nonce( 'code_snippets_refresh_versions' ),
|
||||
);
|
||||
|
||||
$strings = array(
|
||||
'selectDifferent' => esc_html__( 'Please select a different version to switch to.', 'code-snippets' ),
|
||||
'switching' => esc_html__( 'Switching...', 'code-snippets' ),
|
||||
'processing' => esc_html__( 'Processing version switch. Please wait...', 'code-snippets' ),
|
||||
'error' => esc_html__( 'An error occurred.', 'code-snippets' ),
|
||||
'errorSwitch' => esc_html__( 'An error occurred while switching versions. Please try again.', 'code-snippets' ),
|
||||
'refreshing' => esc_html__( 'Refreshing...', 'code-snippets' ),
|
||||
'refreshed' => esc_html__( 'Refreshed!', 'code-snippets' ),
|
||||
);
|
||||
|
||||
wp_add_inline_script( 'code-snippets-settings-menu', 'var code_snippets_version_switch = ' . wp_json_encode( $version_switch ) . '; var __code_snippets_i18n = ' . wp_json_encode( $strings ) . ';', 'before' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of code editor themes.
|
||||
*
|
||||
* @return array<string, string> List of editor themes.
|
||||
*/
|
||||
function get_editor_theme_list(): array {
|
||||
$themes = [
|
||||
'default' => __( 'Default', 'code-snippets' ),
|
||||
];
|
||||
|
||||
foreach ( get_editor_themes() as $theme ) {
|
||||
|
||||
// Skip mobile themes.
|
||||
if ( '-mobile' === substr( $theme, -7 ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$themes[ $theme ] = ucwords( str_replace( '-', ' ', $theme ) );
|
||||
}
|
||||
|
||||
return $themes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the editor preview setting
|
||||
*/
|
||||
function render_editor_preview() {
|
||||
$settings = get_settings_values();
|
||||
$settings = $settings['editor'];
|
||||
|
||||
$indent_unit = absint( $settings['indent_unit'] );
|
||||
$tab_size = absint( $settings['tab_size'] );
|
||||
|
||||
$n_tabs = $settings['indent_with_tabs'] ? floor( $indent_unit / $tab_size ) : 0;
|
||||
$n_spaces = $settings['indent_with_tabs'] ? $indent_unit % $tab_size : $indent_unit;
|
||||
|
||||
$indent = str_repeat( "\t", $n_tabs ) . str_repeat( ' ', $n_spaces );
|
||||
|
||||
$code = "add_filter( 'admin_footer_text', function ( \$text ) {\n\n" .
|
||||
$indent . "\$site_name = get_bloginfo( 'name' );\n\n" .
|
||||
$indent . '$text = "Thank you for visiting $site_name.";' . "\n" .
|
||||
$indent . 'return $text;' . "\n" .
|
||||
"} );\n";
|
||||
|
||||
echo '<textarea id="code_snippets_editor_preview">', esc_textarea( $code ), '</textarea>';
|
||||
}
|
||||
263
plugins/code-snippets/php/settings/settings-fields.php
Normal file
263
plugins/code-snippets/php/settings/settings-fields.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* Manages the settings field definitions.
|
||||
*
|
||||
* @package Code_Snippets
|
||||
* @subpackage Settings
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Settings;
|
||||
|
||||
use function Code_Snippets\code_snippets;
|
||||
|
||||
/**
|
||||
* Retrieve the default setting values
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
function get_default_settings(): array {
|
||||
static $defaults;
|
||||
|
||||
if ( isset( $defaults ) ) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
$defaults = [
|
||||
'general' => [
|
||||
'activate_by_default' => true,
|
||||
'enable_tags' => true,
|
||||
'enable_description' => true,
|
||||
'visual_editor_rows' => 5,
|
||||
'list_order' => 'priority-asc',
|
||||
'disable_prism' => false,
|
||||
'hide_upgrade_menu' => false,
|
||||
'complete_uninstall' => false,
|
||||
'enable_flat_files' => false,
|
||||
],
|
||||
'editor' => [
|
||||
'indent_with_tabs' => true,
|
||||
'tab_size' => 4,
|
||||
'indent_unit' => 4,
|
||||
'font_size' => 14,
|
||||
'wrap_lines' => true,
|
||||
'code_folding' => true,
|
||||
'line_numbers' => true,
|
||||
'auto_close_brackets' => true,
|
||||
'highlight_selection_matches' => true,
|
||||
'highlight_active_line' => true,
|
||||
'keymap' => 'default',
|
||||
'theme' => 'default',
|
||||
],
|
||||
'version-switch' => [
|
||||
'selected_version' => '',
|
||||
],
|
||||
'debug' => [
|
||||
'enable_version_change' => false,
|
||||
],
|
||||
];
|
||||
|
||||
$defaults = apply_filters( 'code_snippets_settings_defaults', $defaults );
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings fields
|
||||
*
|
||||
* @return array<string, array<string, array>>
|
||||
*/
|
||||
function get_settings_fields(): array {
|
||||
static $fields;
|
||||
|
||||
if ( isset( $fields ) ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
|
||||
$fields['debug'] = [
|
||||
'database_update' => [
|
||||
'name' => __( 'Database Table Upgrade', 'code-snippets' ),
|
||||
'type' => 'action',
|
||||
'label' => __( 'Upgrade Database Table', 'code-snippets' ),
|
||||
'desc' => __( 'Use this button to manually upgrade the Code Snippets database table. This action will only affect the snippets table and should be used only when necessary.', 'code-snippets' ),
|
||||
],
|
||||
'reset_caches' => [
|
||||
'name' => __( 'Reset Caches', 'code-snippets' ),
|
||||
'type' => 'action',
|
||||
'desc' => __( 'Use this button to manually clear snippets caches.', 'code-snippets' ),
|
||||
],
|
||||
'enable_version_change' => [
|
||||
'name' => __( 'Version Change', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Enable the ability to switch or rollback versions of the Code Snippets core plugin.', 'code-snippets' ),
|
||||
],
|
||||
];
|
||||
|
||||
$fields['version-switch'] = [
|
||||
'version_switcher' => [
|
||||
'name' => __( 'Switch Version', 'code-snippets' ),
|
||||
'type' => 'callback',
|
||||
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_field' ],
|
||||
],
|
||||
'refresh_versions' => [
|
||||
'name' => __( 'Refresh Versions', 'code-snippets' ),
|
||||
'type' => 'callback',
|
||||
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_refresh_versions_field' ],
|
||||
],
|
||||
'version_warning' => [
|
||||
'name' => '',
|
||||
'type' => 'callback',
|
||||
'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_warning' ],
|
||||
],
|
||||
];
|
||||
|
||||
$fields['general'] = [
|
||||
'activate_by_default' => [
|
||||
'name' => __( 'Activate by Default', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( "Make the 'Save and Activate' button the default action when saving a snippet.", 'code-snippets' ),
|
||||
],
|
||||
'enable_tags' => [
|
||||
'name' => __( 'Enable Snippet Tags', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Show snippet tags on admin pages.', 'code-snippets' ),
|
||||
],
|
||||
'enable_description' => [
|
||||
'name' => __( 'Enable Snippet Descriptions', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Show snippet descriptions on admin pages.', 'code-snippets' ),
|
||||
],
|
||||
'visual_editor_rows' => [
|
||||
'name' => __( 'Description Editor Height', 'code-snippets' ),
|
||||
'type' => 'number',
|
||||
'label' => _x( 'rows', 'unit', 'code-snippets' ),
|
||||
'min' => 0,
|
||||
],
|
||||
'list_order' => [
|
||||
'name' => __( 'Snippets List Order', 'code-snippets' ),
|
||||
'type' => 'select',
|
||||
'desc' => __( 'Default way to order snippets on the All Snippets admin menu.', 'code-snippets' ),
|
||||
'options' => [
|
||||
'priority-asc' => __( 'Priority', 'code-snippets' ),
|
||||
'name-asc' => __( 'Name (A-Z)', 'code-snippets' ),
|
||||
'name-desc' => __( 'Name (Z-A)', 'code-snippets' ),
|
||||
'modified-desc' => __( 'Modified (latest first)', 'code-snippets' ),
|
||||
'modified-asc' => __( 'Modified (oldest first)', 'code-snippets' ),
|
||||
],
|
||||
],
|
||||
'disable_prism' => [
|
||||
'name' => __( 'Disable Syntax Highlighter', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Disable syntax highlighting when displaying snippet code on the front-end.', 'code-snippets' ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! code_snippets()->licensing->is_licensed() ) {
|
||||
$fields['general']['hide_upgrade_menu'] = [
|
||||
'name' => __( 'Hide Upgrade Notices', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Hide notices inviting you to upgrade to Code Snippets Pro.', 'code-snippets' ),
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! is_multisite() || is_main_site() ) {
|
||||
$fields['general']['complete_uninstall'] = [
|
||||
'name' => __( 'Complete Uninstall', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'When the plugin is deleted from the Plugins menu, also delete all snippets and plugin settings.', 'code-snippets' ),
|
||||
];
|
||||
}
|
||||
|
||||
$fields['editor'] = [
|
||||
'indent_with_tabs' => [
|
||||
'name' => __( 'Indent With Tabs', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Use hard tabs instead of spaces for indentation.', 'code-snippets' ),
|
||||
'codemirror' => 'indentWithTabs',
|
||||
],
|
||||
'tab_size' => [
|
||||
'name' => __( 'Tab Size', 'code-snippets' ),
|
||||
'type' => 'number',
|
||||
'desc' => __( 'The width of a tab character.', 'code-snippets' ),
|
||||
'label' => _x( 'spaces', 'unit', 'code-snippets' ),
|
||||
'codemirror' => 'tabSize',
|
||||
'min' => 0,
|
||||
],
|
||||
'indent_unit' => [
|
||||
'name' => __( 'Indent Unit', 'code-snippets' ),
|
||||
'type' => 'number',
|
||||
'desc' => __( 'The number of spaces to indent a block.', 'code-snippets' ),
|
||||
'label' => _x( 'spaces', 'unit', 'code-snippets' ),
|
||||
'codemirror' => 'indentUnit',
|
||||
'min' => 0,
|
||||
],
|
||||
'font_size' => [
|
||||
'name' => __( 'Font Size', 'code-snippets' ),
|
||||
'type' => 'number',
|
||||
'label' => _x( 'px', 'unit', 'code-snippets' ),
|
||||
'codemirror' => 'fontSize',
|
||||
'min' => 8,
|
||||
'max' => 28,
|
||||
],
|
||||
'wrap_lines' => [
|
||||
'name' => __( 'Wrap Lines', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Soft-wrap long lines of code instead of horizontally scrolling.', 'code-snippets' ),
|
||||
'codemirror' => 'lineWrapping',
|
||||
],
|
||||
|
||||
'code_folding' => [
|
||||
'name' => __( 'Code Folding', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Allow folding functions or other blocks into a single line.', 'code-snippets' ),
|
||||
'codemirror' => 'foldGutter',
|
||||
],
|
||||
'line_numbers' => [
|
||||
'name' => __( 'Line Numbers', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Show line numbers to the left of the editor.', 'code-snippets' ),
|
||||
'codemirror' => 'lineNumbers',
|
||||
],
|
||||
'auto_close_brackets' => [
|
||||
'name' => __( 'Auto Close Brackets', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Auto-close brackets and quotes when typed.', 'code-snippets' ),
|
||||
'codemirror' => 'autoCloseBrackets',
|
||||
],
|
||||
'highlight_selection_matches' => [
|
||||
'name' => __( 'Highlight Selection Matches', 'code-snippets' ),
|
||||
'label' => __( 'Highlight all instances of a currently selected word.', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'codemirror' => 'highlightSelectionMatches',
|
||||
],
|
||||
'highlight_active_line' => [
|
||||
'name' => __( 'Highlight Active Line', 'code-snippets' ),
|
||||
'label' => __( 'Highlight the line that is currently being edited.', 'code-snippets' ),
|
||||
'type' => 'checkbox',
|
||||
'codemirror' => 'styleActiveLine',
|
||||
],
|
||||
'keymap' => [
|
||||
'name' => __( 'Keymap', 'code-snippets' ),
|
||||
'type' => 'select',
|
||||
'desc' => __( 'The set of keyboard shortcuts to use in the code editor.', 'code-snippets' ),
|
||||
'options' => [
|
||||
'default' => __( 'Default', 'code-snippets' ),
|
||||
'vim' => __( 'Vim', 'code-snippets' ),
|
||||
'emacs' => __( 'Emacs', 'code-snippets' ),
|
||||
'sublime' => __( 'Sublime Text', 'code-snippets' ),
|
||||
],
|
||||
'codemirror' => 'keyMap',
|
||||
],
|
||||
'theme' => [
|
||||
'name' => __( 'Theme', 'code-snippets' ),
|
||||
'type' => 'select',
|
||||
'options' => get_editor_theme_list(),
|
||||
'codemirror' => 'theme',
|
||||
],
|
||||
];
|
||||
|
||||
$fields = apply_filters( 'code_snippets_settings_fields', $fields );
|
||||
|
||||
return $fields;
|
||||
}
|
||||
351
plugins/code-snippets/php/settings/settings.php
Normal file
351
plugins/code-snippets/php/settings/settings.php
Normal file
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
/**
|
||||
* This file registers the settings
|
||||
*
|
||||
* @package Code_Snippets
|
||||
* @subpackage Settings
|
||||
*/
|
||||
|
||||
namespace Code_Snippets\Settings;
|
||||
|
||||
use Code_Snippets\Welcome_API;
|
||||
use function Code_Snippets\clean_snippets_cache;
|
||||
use function Code_Snippets\code_snippets;
|
||||
use const Code_Snippets\CACHE_GROUP;
|
||||
|
||||
const CACHE_KEY = 'code_snippets_settings';
|
||||
const OPTION_GROUP = 'code-snippets';
|
||||
const OPTION_NAME = 'code_snippets_settings';
|
||||
|
||||
/**
|
||||
* Add a new option for either the current site or the current network
|
||||
*
|
||||
* @param bool $network Whether to add a network-wide option.
|
||||
* @param string $option Name of option to add. Expected to not be SQL-escaped.
|
||||
* @param mixed $value Option value, can be anything. Expected to not be SQL-escaped.
|
||||
*
|
||||
* @return bool False if the option was not added. True if the option was added.
|
||||
*/
|
||||
function add_self_option( bool $network, string $option, $value ): bool {
|
||||
return $network ? add_site_option( $option, $value ) : add_option( $option, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an option value based on an option name from either the current site or the current network
|
||||
*
|
||||
* @param bool $network Whether to get a network-wide option.
|
||||
* @param string $option Name of option to retrieve. Expected to not be SQL-escaped.
|
||||
* @param mixed $default_value Optional value to return if option doesn't exist. Default false.
|
||||
*
|
||||
* @return mixed Value set for the option.
|
||||
*/
|
||||
function get_self_option( bool $network, string $option, $default_value = false ) {
|
||||
return $network ? get_site_option( $option, $default_value ) : get_option( $option, $default_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value of an option that was already added on the current site or the current network
|
||||
*
|
||||
* @param bool $network Whether to update a network-wide option.
|
||||
* @param string $option Name of option. Expected to not be SQL-escaped.
|
||||
* @param mixed $value Option value. Expected to not be SQL-escaped.
|
||||
*
|
||||
* @return bool False if value was not updated. True if value was updated.
|
||||
*/
|
||||
function update_self_option( bool $network, string $option, $value ): bool {
|
||||
return $network ? update_site_option( $option, $value ) : update_option( $option, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'true' if plugin settings are unified on a multisite installation
|
||||
* under the Network Admin settings menu
|
||||
*
|
||||
* This option is controlled by the "Enable administration menus" setting on the Network Settings menu
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function are_settings_unified(): bool {
|
||||
if ( ! is_multisite() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$menu_perms = get_site_option( 'menu_items', array() );
|
||||
return empty( $menu_perms['snippets_settings'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the setting values from the database.
|
||||
* If a setting does not exist in the database, the default value will be returned.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
function get_settings_values(): array {
|
||||
$settings = wp_cache_get( CACHE_KEY, CACHE_GROUP );
|
||||
if ( $settings ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$settings = get_default_settings();
|
||||
$saved = get_self_option( are_settings_unified(), OPTION_NAME, array() );
|
||||
|
||||
foreach ( $settings as $section => $fields ) {
|
||||
if ( isset( $saved[ $section ] ) ) {
|
||||
$settings[ $section ] = array_replace( $fields, $saved[ $section ] );
|
||||
}
|
||||
}
|
||||
|
||||
wp_cache_set( CACHE_KEY, $settings, CACHE_GROUP );
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an individual setting field value
|
||||
*
|
||||
* @param string $section ID of the section the setting belongs to.
|
||||
* @param string $field ID of the setting field.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function get_setting( string $section, string $field ) {
|
||||
$settings = get_settings_values();
|
||||
|
||||
return $settings[ $section ][ $field ] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single setting to a new value.
|
||||
*
|
||||
* @param string $section ID of the section the setting belongs to.
|
||||
* @param string $field ID of the setting field.
|
||||
* @param mixed $new_value Setting value. Expected to not be SQL-escaped.
|
||||
*
|
||||
* @return bool False if value was not updated. True if value was updated.
|
||||
*/
|
||||
function update_setting( string $section, string $field, $new_value ): bool {
|
||||
$settings = get_settings_values();
|
||||
|
||||
$settings[ $section ][ $field ] = $new_value;
|
||||
|
||||
wp_cache_set( CACHE_KEY, $settings, CACHE_GROUP );
|
||||
return update_self_option( are_settings_unified(), OPTION_NAME, $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the settings sections
|
||||
*
|
||||
* @return array<string, string> Settings sections.
|
||||
*/
|
||||
function get_settings_sections(): array {
|
||||
$sections = array(
|
||||
'general' => __( 'General', 'code-snippets' ),
|
||||
'editor' => __( 'Code Editor', 'code-snippets' ),
|
||||
'debug' => __( 'Debug', 'code-snippets' ),
|
||||
);
|
||||
|
||||
// Only show the Version section when the debug setting to enable version changes is enabled.
|
||||
$enable_version = get_setting( 'debug', 'enable_version_change' );
|
||||
if ( $enable_version ) {
|
||||
$sections['version-switch'] = __( 'Version', 'code-snippets' );
|
||||
}
|
||||
|
||||
return apply_filters( 'code_snippets_settings_sections', $sections );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register settings sections, fields, etc
|
||||
*/
|
||||
function register_plugin_settings() {
|
||||
if ( are_settings_unified() ) {
|
||||
if ( ! get_site_option( OPTION_NAME ) ) {
|
||||
add_site_option( OPTION_NAME, get_default_settings() );
|
||||
}
|
||||
} elseif ( ! get_option( OPTION_NAME ) ) {
|
||||
add_option( OPTION_NAME, get_default_settings() );
|
||||
}
|
||||
|
||||
// Register the setting.
|
||||
register_setting(
|
||||
OPTION_GROUP,
|
||||
OPTION_NAME,
|
||||
[ 'sanitize_callback' => __NAMESPACE__ . '\\sanitize_settings' ]
|
||||
);
|
||||
|
||||
// Register settings sections.
|
||||
foreach ( get_settings_sections() as $section_id => $section_name ) {
|
||||
add_settings_section( $section_id, $section_name, '__return_empty_string', 'code-snippets' );
|
||||
}
|
||||
|
||||
// Register settings fields. Only register fields for sections that exist (some sections may be gated by settings).
|
||||
$registered_sections = get_settings_sections();
|
||||
foreach ( get_settings_fields() as $section_id => $fields ) {
|
||||
if ( ! isset( $registered_sections[ $section_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $fields as $field_id => $field ) {
|
||||
$field_object = new Setting_Field( $section_id, $field_id, $field );
|
||||
add_settings_field( $field_id, $field['name'], [ $field_object, 'render' ], 'code-snippets', $section_id );
|
||||
}
|
||||
}
|
||||
|
||||
// Add editor preview as a field.
|
||||
add_settings_field(
|
||||
'editor_preview',
|
||||
__( 'Editor Preview', 'code-snippets' ),
|
||||
__NAMESPACE__ . '\\render_editor_preview',
|
||||
'code-snippets',
|
||||
'editor'
|
||||
);
|
||||
}
|
||||
|
||||
add_action( 'admin_init', __NAMESPACE__ . '\\register_plugin_settings' );
|
||||
|
||||
/**
|
||||
* Sanitize a single setting value.
|
||||
*
|
||||
* @param array<string, mixed> $field Setting field information.
|
||||
* @param mixed $input_value User input setting value, or null if missing.
|
||||
*
|
||||
* @return mixed Sanitized setting value, or null if unset.
|
||||
*/
|
||||
function sanitize_setting_value( array $field, $input_value ) {
|
||||
switch ( $field['type'] ) {
|
||||
|
||||
case 'checkbox':
|
||||
return 'on' === $input_value;
|
||||
|
||||
case 'number':
|
||||
return intval( $input_value );
|
||||
|
||||
case 'select':
|
||||
$select_options = array_map( 'strval', array_keys( $field['options'] ) );
|
||||
return in_array( strval( $input_value ), $select_options, true ) ? $input_value : null;
|
||||
|
||||
case 'checkboxes':
|
||||
$results = [];
|
||||
|
||||
if ( ! empty( $input_value ) ) {
|
||||
foreach ( $field['options'] as $option_id => $option_label ) {
|
||||
if ( isset( $input_value[ $option_id ] ) && 'on' === $input_value[ $option_id ] ) {
|
||||
$results[] = $option_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
case 'text':
|
||||
case 'hidden':
|
||||
return trim( sanitize_text_field( $input_value ) );
|
||||
|
||||
case 'callback':
|
||||
return isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) ?
|
||||
call_user_func( $field['sanitize_callback'], $input_value ) :
|
||||
null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process settings actions.
|
||||
*
|
||||
* @param array $input Provided settings input.
|
||||
*
|
||||
* @return array|null New $input value to return, or null to continue with settings update process.
|
||||
*/
|
||||
function process_settings_actions( array $input ): ?array {
|
||||
|
||||
if ( isset( $input['reset_settings'] ) ) {
|
||||
add_settings_error(
|
||||
OPTION_NAME,
|
||||
'settings_reset',
|
||||
__( 'All settings have been reset to their defaults.', 'code-snippets' ),
|
||||
'updated'
|
||||
);
|
||||
|
||||
delete_option( 'code_snippets_cloud_settings' );
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( isset( $input['debug']['database_update'] ) ) {
|
||||
code_snippets()->db->create_or_upgrade_tables();
|
||||
|
||||
add_settings_error(
|
||||
OPTION_NAME,
|
||||
'database_update_done',
|
||||
__( 'Successfully performed database table upgrade.', 'code-snippets' ),
|
||||
'updated'
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $input['debug']['reset_caches'] ) ) {
|
||||
Welcome_API::clear_cache();
|
||||
clean_snippets_cache( code_snippets()->db->get_table_name( false ) );
|
||||
|
||||
if ( is_multisite() ) {
|
||||
clean_snippets_cache( code_snippets()->db->get_table_name( true ) );
|
||||
}
|
||||
|
||||
add_settings_error(
|
||||
OPTION_NAME,
|
||||
'snippet_caches_reset',
|
||||
__( 'Successfully reset snippets caches.', 'code-snippets' ),
|
||||
'updated'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the settings
|
||||
*
|
||||
* @param array<string, array<string, mixed>> $input The received settings.
|
||||
*
|
||||
* @return array<string, array<string, mixed>> The validated settings.
|
||||
*/
|
||||
function sanitize_settings( array $input ): array {
|
||||
wp_cache_delete( CACHE_KEY, CACHE_GROUP );
|
||||
$result = process_settings_actions( $input );
|
||||
|
||||
if ( ! is_null( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$settings = get_settings_values();
|
||||
$updated = false;
|
||||
|
||||
// Don't directly loop through $input as it does not include as deselected checkboxes.
|
||||
foreach ( get_settings_fields() as $section_id => $fields ) {
|
||||
foreach ( $fields as $field_id => $field ) {
|
||||
|
||||
// Fetch the corresponding input value from the posted data.
|
||||
$input_value = $input[ $section_id ][ $field_id ] ?? null;
|
||||
|
||||
// Attempt to sanitize the setting value.
|
||||
$sanitized_value = sanitize_setting_value( $field, $input_value );
|
||||
|
||||
$current_value = $settings[ $section_id ][ $field_id ] ?? null;
|
||||
if ( ! is_null( $sanitized_value ) && $current_value !== $sanitized_value ) {
|
||||
$settings[ $section_id ][ $field_id ] = $sanitized_value;
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add an updated message.
|
||||
if ( $updated ) {
|
||||
add_settings_error(
|
||||
OPTION_NAME,
|
||||
'settings-saved',
|
||||
__( 'Settings saved.', 'code-snippets' ),
|
||||
'updated'
|
||||
);
|
||||
|
||||
do_action( 'code_snippets/settings_updated', $settings, $input );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
771
plugins/code-snippets/php/snippet-ops.php
Normal file
771
plugins/code-snippets/php/snippet-ops.php
Normal file
@@ -0,0 +1,771 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trashes a snippet from the database.
|
||||
* Write operation.
|
||||
*
|
||||
* @param int $id ID of the snippet to trash.
|
||||
* @param bool|null $network Trash from network-wide (true) or site-wide (false) table.
|
||||
*
|
||||
* @return bool Whether the snippet was trashed successfully.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
function trash_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->update(
|
||||
$table,
|
||||
array( 'active' => '-1' ),
|
||||
array( 'id' => $id ),
|
||||
array( '%d' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'code_snippets/trash_snippet', $snippet, $network );
|
||||
clean_snippets_cache( $table );
|
||||
code_snippets()->cloud_api->delete_snippet_from_transient_data( $id );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a trashed snippet by setting its active status back to 0 (inactive).
|
||||
* Write operation.
|
||||
*
|
||||
* @param int $id Snippet ID to restore.
|
||||
* @param bool|null $network Whether the snippet is multisite-wide (true) or site-wide (false).
|
||||
*
|
||||
* @return bool Whether the restore was successful.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
function restore_snippet( int $id, ?bool $network = null ): bool {
|
||||
global $wpdb;
|
||||
$network = DB::validate_network_param( $network );
|
||||
$table = code_snippets()->db->get_table_name( $network );
|
||||
|
||||
$result = $wpdb->update(
|
||||
$table,
|
||||
array( 'active' => '0' ),
|
||||
array( 'id' => $id ),
|
||||
array( '%d' )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
do_action( 'code_snippets/restore_snippet', $id, $network );
|
||||
clean_snippets_cache( $table );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
try {
|
||||
require_once $file;
|
||||
$result = null;
|
||||
} catch ( ParseError $parse_error ) {
|
||||
$result = $parse_error;
|
||||
} catch ( Error $error ) {
|
||||
$result = $error;
|
||||
} catch ( Throwable $throwable ) {
|
||||
$result = $throwable;
|
||||
}
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
do_action( 'code_snippets/after_execute_snippet_from_flat_file', $file, $id );
|
||||
|
||||
return $result ?? null;
|
||||
}
|
||||
52
plugins/code-snippets/php/strings.php
Normal file
52
plugins/code-snippets/php/strings.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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' );
|
||||
110
plugins/code-snippets/php/uninstall.php
Normal file
110
plugins/code-snippets/php/uninstall.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
37
plugins/code-snippets/php/views/import.php
Normal file
37
plugins/code-snippets/php/views/import.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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>
|
||||
|
||||
<div id="import-container"></div>
|
||||
</div>
|
||||
129
plugins/code-snippets/php/views/manage.php
Normal file
129
plugins/code-snippets/php/views/manage.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?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>
|
||||
76
plugins/code-snippets/php/views/partials/cloud-search.php
Normal file
76
plugins/code-snippets/php/views/partials/cloud-search.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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>
|
||||
112
plugins/code-snippets/php/views/partials/list-table-notices.php
Normal file
112
plugins/code-snippets/php/views/partials/list-table-notices.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?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://codesnippets.pro/doc/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 = [
|
||||
'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>trashed</strong>.', 'code-snippets' ),
|
||||
'deleted-multi' => __( 'Selected snippets <strong>trashed</strong>.', 'code-snippets' ),
|
||||
'deleted_permanently' => __( 'Snippet <strong>permanently deleted</strong>.', 'code-snippets' ),
|
||||
'deleted-permanently-multi' => __( 'Selected snippets <strong>permanently deleted</strong>.', 'code-snippets' ),
|
||||
'restored' => __( 'Snippet <strong>restored</strong>.', 'code-snippets' ),
|
||||
'restored-multi' => __( 'Selected snippets <strong>restored</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' ),
|
||||
];
|
||||
|
||||
// Add undo link for single snippet trash action
|
||||
if ( 'deleted' === $result && ! empty( $_REQUEST['ids'] ) ) {
|
||||
$deleted_ids = sanitize_text_field( $_REQUEST['ids'] );
|
||||
$undo_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
[
|
||||
'action' => 'restore',
|
||||
'ids' => $deleted_ids,
|
||||
]
|
||||
),
|
||||
'bulk-snippets'
|
||||
);
|
||||
|
||||
// translators: %s: Undo URL.
|
||||
$undo_message = __( 'Snippet <strong>trashed</strong>. <a href="%s">Undo</a>', 'code-snippets' );
|
||||
$result_messages['deleted'] = sprintf( $undo_message, esc_url( $undo_url ) );
|
||||
}
|
||||
|
||||
// Add undo link for bulk snippet trash action
|
||||
if ( 'deleted-multi' === $result && ! empty( $_REQUEST['ids'] ) ) {
|
||||
$deleted_ids = sanitize_text_field( $_REQUEST['ids'] );
|
||||
$undo_url = wp_nonce_url(
|
||||
add_query_arg( array(
|
||||
'action' => 'restore',
|
||||
'ids' => $deleted_ids,
|
||||
) ),
|
||||
'bulk-snippets'
|
||||
);
|
||||
|
||||
// translators: %s: Undo URL.
|
||||
$undo_message = __( 'Selected snippets <strong>trashed</strong>. <a href="%s">Undo</a>', 'code-snippets' );
|
||||
$result_messages['deleted-multi'] = sprintf( $undo_message, esc_url( $undo_url ) );
|
||||
}
|
||||
|
||||
$result_messages = apply_filters( 'code_snippets/manage/result_messages', $result_messages );
|
||||
|
||||
if ( isset( $result_messages[ $result ] ) ) {
|
||||
$result_kses = [
|
||||
'strong' => [],
|
||||
'a' => [
|
||||
'href' => [],
|
||||
],
|
||||
];
|
||||
|
||||
printf(
|
||||
'<div id="message" class="notice notice-success fade is-dismissible"><p>%s</p></div>',
|
||||
wp_kses( $result_messages[ $result ], $result_kses )
|
||||
);
|
||||
}
|
||||
33
plugins/code-snippets/php/views/partials/list-table.php
Normal file
33
plugins/code-snippets/php/views/partials/list-table.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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>
|
||||
200
plugins/code-snippets/php/views/welcome.php
Normal file
200
plugins/code-snippets/php/views/welcome.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user