File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/php.tar
admin-menus/class-admin-menu.php 0000644 00000011166 15153632426 0012643 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Base class for a plugin admin menu.
*/
abstract class Admin_Menu {
/**
* The snippet page short name.
*
* @var string
*/
public string $name;
/**
* The label shown in the admin menu.
*
* @var string
*/
public string $label;
/**
* The text used for the page title.
*
* @var string
*/
public string $title;
/**
* The base slug for the top-level admin menu.
*
* @var string
*/
protected string $base_slug;
/**
* The slug for this admin menu.
*
* @var string
*/
protected string $slug;
/**
* Constructor.
*
* @param string $name The snippet page short name.
* @param string $label The label shown in the admin menu.
* @param string $title The text used for the page title.
*/
public function __construct( string $name, string $label, string $title ) {
$this->name = $name;
$this->label = $label;
$this->title = $title;
$this->base_slug = code_snippets()->get_menu_slug();
$this->slug = code_snippets()->get_menu_slug( $name );
}
/**
* Register action and filter hooks.
*
* @return void
*/
public function run() {
if ( ! code_snippets()->is_compact_menu() ) {
add_action( 'admin_menu', array( $this, 'register' ) );
add_action( 'network_admin_menu', array( $this, 'register' ) );
}
}
/**
* Add a sub-menu to the Snippets menu.
*
* @param string $slug Menu slug.
* @param string $label Label shown in admin menu.
* @param string $title Page title.
*
* @return void
*/
public function add_menu( string $slug, string $label, string $title ) {
$hook = add_submenu_page(
$this->base_slug,
$title,
$label,
code_snippets()->get_cap(),
$slug,
array( $this, 'render' )
);
add_action( 'load-' . $hook, array( $this, 'load' ) );
}
/**
* Register the admin menu
*/
public function register() {
$this->add_menu( $this->slug, $this->label, $this->title );
}
/**
* Render the content of a vew template
*
* @param string $name Name of view template to render.
*/
protected function render_view( string $name ) {
include dirname( PLUGIN_FILE ) . '/php/views/' . $name . '.php';
}
/**
* Render the menu
*/
public function render() {
$this->render_view( $this->name );
}
/**
* Print the status and error messages
*/
protected function print_messages() {
// None required by default.
}
/**
* Executed when the admin page is loaded
*/
public function load() {
// Make sure the user has permission to be here.
if ( ! current_user_can( code_snippets()->get_cap() ) ) {
wp_die( esc_html__( 'You are not authorized to access this page.', 'code-snippets' ) );
}
// Create the snippet tables if they are missing.
$db = code_snippets()->db;
if ( is_multisite() ) {
$db->create_missing_table( $db->ms_table );
}
$db->create_missing_table( $db->table );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
/**
* Enqueue scripts and stylesheets for the admin page, if necessary
*/
abstract public function enqueue_assets();
/**
* Generate a list of page title links for passing to React.
*
* @param array<string> $actions List of actions to convert into links, as array values.
*
* @return array<string, string> Link labels keyed to link URLs.
*/
public function page_title_action_links( array $actions ): array {
$plugin = code_snippets();
$links = [];
foreach ( $actions as $action ) {
if ( 'settings' === $action && ! isset( $plugin->admin->menus['settings'] ) ) {
continue;
}
$url = $plugin->get_menu_url( $action );
if ( isset( $_GET['type'] ) && in_array( $_GET['type'], Snippet::get_types(), true ) ) {
$url = add_query_arg( 'type', sanitize_key( wp_unslash( $_GET['type'] ) ), $url );
}
switch ( $action ) {
case 'manage':
$label = _x( 'Manage', 'snippets', 'code-snippets' );
break;
case 'add':
$label = _x( 'Add New', 'snippet', 'code-snippets' );
break;
case 'import':
$label = _x( 'Import', 'snippets', 'code-snippets' );
break;
case 'settings':
$label = _x( 'Settings', 'snippets', 'code-snippets' );
break;
default:
$label = '';
}
if ( $label && $url ) {
$links[ $label ] = $url;
}
}
return $links;
}
/**
* Render a list of links to other pages in the page title
*
* @param array<string> $actions List of actions to render as links, as array values.
*/
public function render_page_title_actions( array $actions ) {
foreach ( $this->page_title_action_links( $actions ) as $label => $url ) {
printf( '<a href="%s" class="page-title-action">%s</a>', esc_url( $url ), esc_html( $label ) );
}
}
}
admin-menus/class-edit-menu.php 0000644 00000013577 15153632426 0012510 0 ustar 00 <?php
namespace Code_Snippets;
use function Code_Snippets\Settings\get_setting;
/**
* This class handles the add/edit menu.
*/
class Edit_Menu extends Admin_Menu {
/**
* Handle for JavaScript asset file.
*/
const JS_HANDLE = 'code-snippets-edit-menu';
/**
* Handle for CSS asset file.
*/
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' ),
__( 'Add New Snippet', 'code-snippets' )
);
}
/**
* Executed when the menu is loaded.
*
* @return void
*/
public function load() {
parent::load();
$this->load_snippet_data();
$this->ensure_correct_page();
$contextual_help = new Contextual_Help( 'edit' );
$contextual_help->load();
}
/**
* Disallow vising the Edit Snippet page without a valid ID.
*
* @return void
*/
protected function ensure_correct_page() {
$screen = get_current_screen();
$edit_hook = get_plugin_page_hookname( $this->slug, $this->base_slug );
$edit_hook .= $screen->in_admin( 'network' ) ? '-network' : '';
// Disallow visiting the edit snippet page without a valid ID.
if ( $screen->base === $edit_hook && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id ) &&
! isset( $_REQUEST['preview'] ) ) {
wp_safe_redirect( code_snippets()->get_menu_url( 'add' ) );
exit;
}
}
/**
* Render the edit menu interface.
*
* @return void
*/
public function render() {
printf(
'<div id="edit-snippet-form-container">%s</div>',
esc_html__( 'Loading edit page…', 'code-snippets' )
);
}
/**
* Load the data for the snippet currently being edited.
*/
public function load_snippet_data() {
$edit_id = isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0;
$this->snippet = get_snippet( $edit_id );
if ( 0 === $edit_id && isset( $_GET['type'] ) && sanitize_key( $_GET['type'] ) !== $this->snippet->type ) {
$type = sanitize_key( $_GET['type'] );
$default_scopes = [
'php' => 'global',
'css' => 'site-css',
'html' => 'content',
'js' => 'site-head-js',
];
if ( isset( $default_scopes[ $type ] ) ) {
$this->snippet->scope = $default_scopes[ $type ];
}
}
$this->snippet = apply_filters( 'code_snippets/admin/load_snippet_data', $this->snippet );
}
/**
* Enqueue assets for the edit menu
*
* @return void
*/
public function enqueue_assets() {
$plugin = code_snippets();
$rtl = is_rtl() ? '-rtl' : '';
$settings = Settings\get_settings_values();
$tags_enabled = $settings['general']['enable_tags'];
$desc_enabled = $settings['general']['enable_description'];
enqueue_code_editor( $this->snippet->type );
wp_enqueue_style(
self::CSS_HANDLE,
plugins_url( "dist/edit$rtl.css", $plugin->file ),
[
'code-editor',
'wp-components',
],
$plugin->version
);
wp_enqueue_script(
self::JS_HANDLE,
plugins_url( 'dist/edit.js', $plugin->file ),
[
'code-snippets-code-editor',
'react',
'react-dom',
'wp-url',
'wp-i18n',
'wp-api-fetch',
'wp-components',
'wp-block-editor',
],
$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' ),
'scrollToNotices' => apply_filters( 'code_snippets/scroll_to_notices', true ),
'extraSaveButtons' => apply_filters( 'code_snippets/extra_save_buttons', true ),
'enableDownloads' => apply_filters( 'code_snippets/enable_downloads', true ),
'enableDescription' => $desc_enabled,
'tagOptions' => apply_filters(
'code_snippets/tag_editor_options',
[
'enabled' => $tags_enabled,
'allowSpaces' => true,
'availableTags' => $tags_enabled ? get_all_snippet_tags() : [],
]
),
'descEditorOptions' => [
'rows' => $settings['general']['visual_editor_rows'],
],
]
);
}
/**
* Remove the old CodeMirror version used by the Debug Bar Console plugin that is messing up the snippet editor.
*/
public function remove_debug_bar_codemirror() {
// Try to discern if we are on the single snippet page as good as we can at this early time.
$is_codemirror_page =
is_admin() && 'admin.php' === $GLOBALS['pagenow'] && isset( $_GET['page'] ) && (
code_snippets()->get_menu_slug( 'edit' ) === $_GET['page'] ||
code_snippets()->get_menu_slug( 'settings' ) === $_GET['page']
);
if ( $is_codemirror_page ) {
remove_action( 'debug_bar_enqueue_scripts', 'debug_bar_console_scripts' );
}
}
}
admin-menus/class-import-menu.php 0000644 00000010556 15153632426 0013067 0 ustar 00 <?php
namespace Code_Snippets;
/**
* This class handles the import admin menu.
*
* @since 2.4.0
* @package Code_Snippets
*/
class Import_Menu extends Admin_Menu {
/**
* Class constructor
*/
public function __construct() {
parent::__construct(
'import',
_x( 'Import', 'menu label', 'code-snippets' ),
__( 'Import Snippets', 'code-snippets' )
);
}
/**
* Register action and filter hooks
*/
public function run() {
parent::run();
add_action( 'admin_init', array( $this, 'register_importer' ) );
add_action( 'load-importer-code-snippets', array( $this, 'load' ) );
}
/**
* Executed when the menu is loaded
*/
public function load() {
parent::load();
$contextual_help = new Contextual_Help( 'import' );
$contextual_help->load();
$this->process_import_files();
}
/**
* Process the uploaded import files
*/
private function process_import_files() {
// Ensure the import file exists.
if ( ! isset(
$_FILES['code_snippets_import_files']['name'],
$_FILES['code_snippets_import_files']['type'],
$_FILES['code_snippets_import_files']['tmp_name']
) ) {
return;
}
check_admin_referer( 'import_code_snippets_file' );
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$upload_files = $_FILES['code_snippets_import_files']['tmp_name'];
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$upload_filenames = $_FILES['code_snippets_import_files']['name'];
$upload_mime_types = array_map( 'sanitize_mime_type', wp_unslash( $_FILES['code_snippets_import_files']['type'] ) );
$count = 0;
$network = is_network_admin();
$error = false;
$dup_action = isset( $_POST['duplicate_action'] ) ? sanitize_key( $_POST['duplicate_action'] ) : 'ignore';
// Loop through the uploaded files and import the snippets.
foreach ( $upload_files as $i => $import_file ) {
$filename_info = pathinfo( $upload_filenames[ $i ] );
$ext = $filename_info['extension'];
$mime_type = $upload_mime_types[ $i ];
$import = new Import( $import_file, $network, $dup_action );
if ( 'json' === $ext || 'application/json' === $mime_type ) {
$result = $import->import_json();
} elseif ( 'xml' === $ext || 'text/xml' === $mime_type ) {
$result = $import->import_xml();
} else {
$result = false;
}
if ( false === $result ) {
$error = true;
} else {
$count += count( $result );
}
}
// Send the amount of imported snippets to the page.
$url = add_query_arg( $error ? array( 'error' => true ) : array( 'imported' => $count ) );
wp_safe_redirect( esc_url_raw( $url ) );
exit;
}
/**
* Add the importer to the Tools > Import menu
*/
public function register_importer() {
/* Only register the importer if the current user can manage snippets */
if ( ! defined( 'WP_LOAD_IMPORTERS' ) || ! code_snippets()->current_user_can() ) {
return;
}
/* Register the Code Snippets importer with WordPress */
register_importer(
'code-snippets',
__( 'Code Snippets', 'code-snippets' ),
__( 'Import snippets from a code snippets export file', 'code-snippets' ),
array( $this, 'render' )
);
}
/**
* Print the status and error messages
*/
protected function print_messages() {
if ( ! empty( $_REQUEST['error'] ) ) {
echo '<div id="message" class="error fade"><p>';
esc_html_e( 'An error occurred when processing the import files.', 'code-snippets' );
echo '</p></div>';
}
if ( isset( $_REQUEST['imported'] ) ) {
echo '<div id="message" class="updated fade"><p>';
$imported = intval( $_REQUEST['imported'] );
if ( 0 === $imported ) {
esc_html_e( 'No snippets were imported.', 'code-snippets' );
} else {
/* translators: 1: amount of snippets imported, 2: link to Snippets menu */
$text = _n(
'Successfully imported <strong>%1$d</strong> snippet. <a href="%2$s">Have fun!</a>',
'Successfully imported <strong>%1$d</strong> snippets. <a href="%2$s">Have fun!</a>',
$imported,
'code-snippets'
);
printf( wp_kses_post( $text ), esc_html( $imported ), esc_url( code_snippets()->get_menu_url( 'manage' ) ) );
}
echo '</p></div>';
}
}
/**
* Empty implementation for enqueue_assets.
*
* @return void
*/
public function enqueue_assets() {
// none required.
}
}
admin-menus/class-manage-menu.php 0000644 00000023512 15153632426 0013001 0 ustar 00 <?php
namespace Code_Snippets;
use Code_Snippets\Cloud\Cloud_Search_List_Table;
use function Code_Snippets\Settings\get_setting;
/**
* This class handles the manage snippets menu
*
* @since 2.4.0
* @package Code_Snippets
*/
class Manage_Menu extends Admin_Menu {
/**
* Instance of the list table class.
*
* @var List_Table
*/
public List_Table $list_table;
/**
* Instance of the cloud list table class for search results.
*
* @var Cloud_Search_List_Table
*/
public Cloud_Search_List_Table $cloud_search_list_table;
/**
* Class constructor
*/
public function __construct() {
parent::__construct(
'manage',
_x( 'All Snippets', 'menu label', 'code-snippets' ),
__( 'Snippets', 'code-snippets' )
);
}
/**
* Register action and filter hooks
*/
public function run() {
parent::run();
if ( code_snippets()->is_compact_menu() ) {
add_action( 'admin_menu', array( $this, 'register_compact_menu' ), 2 );
add_action( 'network_admin_menu', array( $this, 'register_compact_menu' ), 2 );
}
add_action( 'admin_menu', array( $this, 'register_upgrade_menu' ), 500 );
add_filter( 'set-screen-option', array( $this, 'save_screen_option' ), 10, 3 );
add_action( 'wp_ajax_update_code_snippet', array( $this, 'ajax_callback' ) );
}
/**
* Register the top-level 'Snippets' menu and associated 'Manage' subpage
*/
public function register() {
$icon_xml = '<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024"><path fill="transparent" d="M191.968 464.224H350.88c23.68 0 42.656 19.2 42.656 42.656 0 11.488-4.48 21.984-11.968 29.632l.192.448-108.768 108.736c-75.104 75.136-75.104 196.512 0 271.584 74.88 74.848 196.448 74.848 271.552 0 74.88-75.104 74.88-196.48 0-271.584-21.76-21.504-47.36-37.12-74.464-46.272l28.608-28.576h365.248c87.04 0 157.856-74.016 159.968-166.4 0-1.472.224-2.752 0-4.256-2.112-23.904-22.368-42.656-46.912-42.656h-264.96L903.36 166.208c17.504-17.504 18.56-45.024 3.2-63.36-1.024-1.28-2.08-2.144-3.2-3.2-66.528-63.552-169.152-65.92-230.56-4.48L410.432 357.536h-46.528c12.8-25.6 20.032-54.624 20.032-85.344 0-106.016-85.952-192-192-192-106.016 0-191.968 85.984-191.968 192 .032 106.08 85.984 192.032 192 192.032zm85.344-191.968c0 47.136-38.176 85.344-85.344 85.344-47.136 0-85.312-38.176-85.312-85.344s38.176-85.344 85.312-85.344c47.168 0 85.344 38.208 85.344 85.344zm191.776 449.056c33.28 33.248 33.28 87.264 0 120.512-33.28 33.472-87.264 33.472-120.736 0-33.28-33.248-33.28-87.264 0-120.512 33.472-33.504 87.456-33.504 120.736 0z"/></svg>';
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$encoded_icon = base64_encode( $icon_xml );
// Register the top-level menu.
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' ),
"data:image/svg+xml;base64,$encoded_icon",
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 ( 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' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_menu_button_css' ] );
}
/**
* Print CSS required for the upgrade button.
*
* @return void
*/
public function enqueue_menu_button_css() {
wp_enqueue_style(
'code-snippets-menu-button',
plugins_url( 'dist/menu-button.css', PLUGIN_FILE ),
[],
PLUGIN_VERSION
);
}
/**
* Redirect the user upon opening the upgrade menu.
*
* @return void
*/
public function load_upgrade_menu() {
wp_safe_redirect( 'https://snipco.de/JE2f' );
exit;
}
/**
* Add menu pages for the compact menu
*/
public function register_compact_menu() {
if ( ! code_snippets()->is_compact_menu() ) {
return;
}
$sub = code_snippets()->get_menu_slug( isset( $_GET['sub'] ) ? sanitize_key( $_GET['sub'] ) : 'snippets' );
$classmap = array(
'snippets' => 'manage',
'add-snippet' => 'edit',
'edit-snippet' => 'edit',
'import-code-snippets' => 'import',
'snippets-settings' => 'settings',
);
$menus = code_snippets()->admin->menus;
$class = isset( $classmap[ $sub ], $menus[ $classmap[ $sub ] ] ) ? $menus[ $classmap[ $sub ] ] : $this;
/* Add a submenu to the Tools menu */
$hook = add_submenu_page(
'tools.php',
__( 'Snippets', 'code-snippets' ),
_x( 'Snippets', 'tools submenu label', 'code-snippets' ),
code_snippets()->get_cap(),
code_snippets()->get_menu_slug(),
array( $class, 'render' )
);
add_action( 'load-' . $hook, array( $class, 'load' ) );
}
/**
* Executed when the admin page is loaded
*/
public function load() {
parent::load();
$contextual_help = new Contextual_Help( 'manage' );
$contextual_help->load();
$this->cloud_search_list_table = new Cloud_Search_List_Table();
$this->cloud_search_list_table->prepare_items();
$this->list_table = new List_Table();
$this->list_table->prepare_items();
}
/**
* Enqueue scripts and stylesheets for the admin page
*/
public function enqueue_assets() {
$plugin = code_snippets();
$rtl = is_rtl() ? '-rtl' : '';
wp_enqueue_style(
'code-snippets-manage',
plugins_url( "dist/manage$rtl.css", $plugin->file ),
[],
$plugin->version
);
wp_enqueue_script(
'code-snippets-manage-js',
plugins_url( 'dist/manage.js', $plugin->file ),
[ 'wp-i18n' ],
$plugin->version,
true
);
wp_set_script_translations( 'code-snippets-manage-js', 'code-snippets' );
if ( 'cloud' === $this->get_current_type() || 'cloud_search' === $this->get_current_type() ) {
Front_End::enqueue_all_prism_themes();
}
}
/**
* Get the currently displayed snippet type.
*
* @return string
*/
protected function get_current_type(): string {
$types = Plugin::get_types();
$current_type = isset( $_GET['type'] ) ? sanitize_key( wp_unslash( $_GET['type'] ) ) : 'all';
return isset( $types[ $current_type ] ) ? $current_type : 'all';
}
/**
* Display a Go Pro badge.
*
* @return void
*/
public function print_pro_message() {
if ( ! code_snippets()->licensing->is_licensed() ) {
echo '<span class="go-pro-badge">', esc_html_x( 'Pro', 'go pro badge', 'code-snippets' ), '</span>';
}
}
/**
* Print the status and error messages
*
* @return void
*/
protected function print_messages() {
$this->render_view( 'partials/list-table-notices' );
}
/**
* Handles saving the user's snippets per page preference
*
* @param mixed $status Current screen option status.
* @param string $option The screen option name.
* @param mixed $value Screen option value.
*
* @return mixed
*/
public function save_screen_option( $status, string $option, $value ) {
return 'snippets_per_page' === $option ? $value : $status;
}
/**
* Update the priority value for a snippet.
*
* @param Snippet $snippet Snippet to update.
*
* @return void
*/
private function update_snippet_priority( Snippet $snippet ) {
global $wpdb;
$table = code_snippets()->db->get_table_name( $snippet->network );
$wpdb->update(
$table,
array( 'priority' => $snippet->priority ),
array( 'id' => $snippet->id ),
array( '%d' ),
array( '%d' )
);
clean_snippets_cache( $table );
}
/**
* Handle AJAX requests
*/
public function ajax_callback() {
check_ajax_referer( 'code_snippets_manage_ajax' );
if ( ! isset( $_POST['field'], $_POST['snippet'] ) ) {
wp_send_json_error(
array(
'type' => 'param_error',
'message' => 'incomplete request',
)
);
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$snippet_data = array_map( 'sanitize_text_field', json_decode( wp_unslash( $_POST['snippet'] ), true ) );
$snippet = new Snippet( $snippet_data );
$field = sanitize_key( $_POST['field'] );
if ( 'priority' === $field ) {
if ( ! isset( $snippet_data['priority'] ) || ! is_numeric( $snippet_data['priority'] ) ) {
wp_send_json_error(
array(
'type' => 'param_error',
'message' => 'missing snippet priority data',
)
);
}
$this->update_snippet_priority( $snippet );
} elseif ( 'active' === $field ) {
if ( ! isset( $snippet_data['active'] ) ) {
wp_send_json_error(
array(
'type' => 'param_error',
'message' => 'missing snippet active data',
)
);
}
if ( $snippet->shared_network ) {
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
if ( in_array( $snippet->id, $active_shared_snippets, true ) !== $snippet->active ) {
$active_shared_snippets = $snippet->active ?
array_merge( $active_shared_snippets, array( $snippet->id ) ) :
array_diff( $active_shared_snippets, array( $snippet->id ) );
update_option( 'active_shared_network_snippets', $active_shared_snippets );
clean_active_snippets_cache( code_snippets()->db->ms_table );
}
} elseif ( $snippet->active ) {
$result = activate_snippet( $snippet->id, $snippet->network );
if ( is_string( $result ) ) {
wp_send_json_error(
array(
'type' => 'action_error',
'message' => $result,
)
);
}
} else {
deactivate_snippet( $snippet->id, $snippet->network );
}
}
wp_send_json_success();
}
}
admin-menus/class-settings-menu.php 0000644 00000013543 15153632426 0013414 0 ustar 00 <?php
namespace Code_Snippets;
use const Code_Snippets\Settings\CACHE_KEY;
use const Code_Snippets\Settings\OPTION_GROUP;
use const Code_Snippets\Settings\OPTION_NAME;
/**
* This class handles the settings admin menu
*
* @since 2.4.0
* @package Code_Snippets
*/
class Settings_Menu extends Admin_Menu {
/**
* Settings page name as registered with the Settings API.
*/
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 ( $section['title'] ) {
printf(
'<h2 id="%s-settings" class="settings-section-title">%s</h2>' . "\n",
esc_attr( $section['id'] ),
esc_html( $section['title'] )
);
}
if ( $section['callback'] ) {
call_user_func( $section['callback'], $section );
}
printf( '<div class="settings-section %s-settings"><table class="form-table">', esc_attr( $section['id'] ) );
do_settings_fields( self::SETTINGS_PAGE, $section['id'] );
echo '</table></div>';
}
}
/**
* Fill in for the Settings API in the Network Admin
*/
public function update_network_options() {
// Ensure the settings have been saved.
if ( empty( $_GET['update_site_option'] ) || empty( $_POST[ OPTION_NAME ] ) ) {
return;
}
check_admin_referer( 'code-snippets-options' );
// Retrieve the saved options and save them to the database.
$value = map_deep( wp_unslash( $_POST[ OPTION_NAME ] ), 'sanitize_key' );
update_site_option( OPTION_NAME, $value );
wp_cache_delete( CACHE_KEY );
// Add an updated notice.
if ( ! count( get_settings_errors() ) ) {
add_settings_error( 'general', 'settings_updated', __( 'Settings saved.', 'code-snippets' ), 'updated' );
}
set_transient( 'settings_errors', get_settings_errors(), 30 );
// Redirect back to the settings menu.
$redirect = add_query_arg( 'settings-updated', 'true', remove_query_arg( 'update_site_option', wp_get_referer() ) );
wp_safe_redirect( esc_url_raw( $redirect ) );
exit;
}
/**
* Empty implementation for print_messages.
*
* @return void
*/
protected function print_messages() {
// none required.
}
}
admin-menus/class-welcome-menu.php 0000644 00000003522 15153632426 0013203 0 ustar 00 <?php
namespace Code_Snippets;
/**
* This class handles the welcome menu.
*
* @since 3.7.0
* @package Code_Snippets
*/
class Welcome_Menu extends Admin_Menu {
/**
* Instance of Welcome_API class.
*
* @var Welcome_API
*/
protected Welcome_API $api;
/**
* Class constructor
*
* @param Welcome_API $api Instance of API class.
*/
public function __construct( $api ) {
parent::__construct(
'welcome',
_x( "What's New", 'menu label', 'code-snippets' ),
__( 'Welcome to Code Snippets', 'code-snippets' )
);
$this->api = $api;
}
/**
* Enqueue assets necessary for the welcome menu.
*
* @return void
*/
public function enqueue_assets() {
wp_enqueue_style(
'code-snippets-welcome',
plugins_url( 'dist/welcome.css', PLUGIN_FILE ),
[],
PLUGIN_VERSION
);
}
/**
* Retrieve a list of links to display in the page header.
*
* @return array<string, array{url: string, icon: string, label: string}>
*/
protected function get_header_links(): array {
$links = [
'cloud' => [
'url' => 'https://codesnippets.cloud',
'icon' => 'cloud',
'label' => __( 'Cloud', 'code-snippets' ),
],
'resources' => [
'url' => 'https://help.codesnippets.pro/',
'icon' => 'sos',
'label' => __( 'Support', 'code-snippets' ),
],
'facebook' => [
'url' => 'https://www.facebook.com/groups/282962095661875/',
'icon' => 'facebook',
'label' => __( 'Community', 'code-snippets' ),
],
'discord' => [
'url' => 'https://snipco.de/discord',
'icon' => 'discord',
'label' => __( 'Discord', 'code-snippets' ),
],
];
if ( ! code_snippets()->licensing->is_licensed() ) {
$links['pro'] = [
'url' => 'https://codesnippets.pro/pricing/',
'icon' => 'cart',
'label' => __( 'Get Pro', 'code-snippets' ),
];
}
return $links;
}
}
class-active-snippets.php 0000644 00000003544 15153632426 0011513 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Class for loading active snippets of various types.
*
* @package Code_Snippets
*/
class Active_Snippets {
/**
* Cached list of active snippets.
*
* @var Snippet[]
*/
private array $active_snippets = [];
/**
* Class constructor.
*/
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
}
/**
* Initialise class functions.
*/
public function init() {
add_action( 'wp_head', [ $this, 'load_head_content' ] );
add_action( 'wp_footer', [ $this, 'load_footer_content' ] );
}
/**
* Fetch active snippets for a given scope, and cache the data in this class.
*
* @param string|string[] $scope Snippet scope.
*
* @return array[][]
*/
protected function fetch_active_snippets( $scope ) {
$scope_key = is_array( $scope ) ? implode( '|', $scope ) : $scope;
if ( ! isset( $this->active_snippets[ $scope_key ] ) ) {
$this->active_snippets[ $scope_key ] = code_snippets()->db->fetch_active_snippets( $scope );
}
return $this->active_snippets[ $scope_key ];
}
/**
* 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 ) {
$snippets_list = $this->fetch_active_snippets( [ 'head-content', 'footer-content' ] );
foreach ( $snippets_list as $snippets ) {
foreach ( $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' );
}
}
class-admin.php 0000644 00000025133 15153632426 0007463 0 ustar 00 <?php
namespace Code_Snippets;
use DateTimeImmutable;
use DateTimeZone;
use Exception;
/**
* Functions specific to the administration interface
*
* @package Code_Snippets
*/
class Admin {
/**
* Admin_Menu class instances
*
* @var array<string, Admin_Menu>
*/
public array $menus = array();
/**
* Welcome_API class instance.
*
* @var Welcome_API
*/
public Welcome_API $welcome_api;
/**
* Class constructor
*/
public function __construct() {
if ( is_admin() ) {
$this->welcome_api = new Welcome_API();
$this->run();
}
}
/**
* Initialise classes
*/
public function load_classes() {
$this->menus['manage'] = new Manage_Menu();
$this->menus['edit'] = new Edit_Menu();
$this->menus['import'] = new Import_Menu();
if ( is_network_admin() === Settings\are_settings_unified() ) {
$this->menus['settings'] = new Settings_Menu();
}
$this->menus['welcome'] = new Welcome_Menu( $this->welcome_api );
foreach ( $this->menus as $menu ) {
$menu->run();
}
}
/**
* Register action and filter hooks
*/
public function run() {
add_action( 'init', array( $this, 'load_classes' ), 11 );
add_filter( 'mu_menu_items', array( $this, 'mu_menu_items' ) );
add_filter( 'plugin_action_links_' . plugin_basename( PLUGIN_FILE ), array( $this, 'plugin_action_links' ), 10, 2 );
add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
add_filter( 'debug_information', array( $this, 'debug_information' ) );
add_action( 'code_snippets/admin/manage', array( $this, 'print_notices' ) );
}
/**
* Allow super admins to control site admin access to
* snippet admin menus
*
* Adds a checkbox to the *Settings > Network Settings*
* network admin menu
*
* @param array<string, string> $menu_items Current mu menu items.
*
* @return array<string, string> The modified mu menu items
*
* @since 1.7.1
*/
public function mu_menu_items( array $menu_items ): array {
$menu_items['snippets'] = __( 'Snippets', 'code-snippets' );
$menu_items['snippets_settings'] = __( 'Snippets » Settings', 'code-snippets' );
return $menu_items;
}
/**
* Modify the action links for this plugin.
*
* @param array<string> $actions Existing plugin action links.
* @param string $plugin_file The plugin the links are for.
*
* @return array<string> Modified plugin action links.
* @since 2.0.0
*/
public function plugin_action_links( array $actions, string $plugin_file ): array {
if ( plugin_basename( PLUGIN_FILE ) !== $plugin_file ) {
return $actions;
}
$format = '<a href="%1$s" title="%2$s">%3$s</a>';
$actions = array_merge(
[
sprintf(
$format,
esc_url( code_snippets()->get_menu_url( 'settings' ) ),
esc_html__( 'Change plugin settings', 'code-snippets' ),
esc_html__( 'Settings', 'code-snippets' )
),
sprintf(
$format,
esc_url( code_snippets()->get_menu_url() ),
esc_html__( '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_html__( 'Go Pro', 'code-snippets' )
);
}
return $actions;
}
/**
* Adds extra links related to the plugin
*
* @param array<string> $plugin_meta Existing plugin info links.
* @param string $plugin_file The plugin the links are for.
*
* @return array<string> Modified plugin info links.
* @since 2.0.0
*/
public function plugin_row_meta( array $plugin_meta, string $plugin_file ): array {
if ( plugin_basename( PLUGIN_FILE ) !== $plugin_file ) {
return $plugin_meta;
}
$format = '<a href="%1$s" title="%2$s" target="_blank">%3$s</a>';
return array_merge(
$plugin_meta,
array(
sprintf(
$format,
'https://help.codesnippets.pro/',
esc_attr__( 'Find out how to get support with Code Snippets', 'code-snippets' ),
esc_html__( 'Docs and Support', 'code-snippets' )
),
sprintf(
$format,
'https://www.facebook.com/groups/codesnippetsplugin/',
esc_attr__( 'Join our community on Facebook', 'code-snippets' ),
esc_html__( 'Community', 'code-snippets' )
),
)
);
}
/**
* Add Code Snippets information to Site Health information.
*
* @param array<string, array<string, mixed>> $info Current Site Health information.
*
* @return array<string, array<string, mixed>> Updated Site Health information.
* @author sc0ttkclark
*/
public function debug_information( array $info ): array {
$fields = array();
// build the debug information from snippet data.
foreach ( get_snippets() as $snippet ) {
$values = [ $snippet->scope_name ];
$debug = [];
if ( ! $snippet->active ) {
continue;
}
if ( $snippet->name ) {
$debug[] = 'name: ' . $snippet->name;
}
$debug[] = 'scope: ' . $snippet->scope;
if ( $snippet->modified ) {
/* translators: %s: formatted last modified date */
$values[] = sprintf( __( 'Last modified %s', 'code-snippets' ), $snippet->format_modified( false ) );
$debug[] = 'modified: ' . $snippet->modified;
}
if ( $snippet->tags ) {
$values[] = $snippet->tags_list;
$debug[] = 'tags: [' . $snippet->tags_list . ']';
}
$fields[ 'snippet-' . $snippet->id ] = [
'label' => $snippet->display_name,
'value' => implode( "\n | ", $values ),
'debug' => implode( ', ', $debug ),
];
}
$snippets_info = array(
'label' => __( 'Active Snippets', 'code-snippets' ),
'show_count' => true,
'fields' => $fields,
);
// attempt to insert the new section right after the Inactive Plugins section.
$index = array_search( 'wp-plugins-inactive', array_keys( $info ), true );
if ( false === $index ) {
$info['code-snippets'] = $snippets_info;
} else {
$info = array_merge(
array_slice( $info, 0, $index + 1 ),
[ 'code-snippets' => $snippets_info ],
array_slice( $info, $index + 1 )
);
}
return $info;
}
/**
* Print any admin notices that have not been dismissed.
*
* @return void
*/
public function print_notices() {
global $current_user;
if ( apply_filters( 'code_snippets/hide_welcome_banner', false ) ) {
return;
}
$meta_key = 'ignore_code_snippets_survey_message';
$dismissed = get_user_meta( $current_user->ID, $meta_key );
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 ( isset( $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: auto .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_attr__( 'Dismiss', 'code-snippets' )
);
echo '</p></div>';
}
/**
* Render a nav tab for a snippet type.
*
* @param string $type_name Type identifier.
* @param string $label Type label.
* @param string $current_type Identifier of currently-selected type.
*
* @return void
*/
public static function render_snippet_type_tab( string $type_name, string $label, string $current_type = '' ) {
$cloud_tabs = [ 'cloud', 'cloud_search', 'bundles' ];
$nav_tab_inactive = false;
if ( $type_name === $current_type ) {
printf( '<a class="nav-tab nav-tab-active" data-snippet-type="%s">', esc_attr( $type_name ) );
} elseif ( ! code_snippets()->licensing->is_licensed() && Plugin::is_pro_type( $type_name ) ) {
printf(
'<a class="nav-tab nav-tab-inactive" data-snippet-type="%s" title="%s" href="https://codesnippets.pro/pricing/" target="_blank">',
esc_attr( $type_name ),
esc_attr__( 'Available in Code Snippets Pro (external link)', 'code-snippets' )
);
} else {
$current_url = remove_query_arg( [ 'cloud_select', 'cloud_search' ] );
if ( in_array( $type_name, $cloud_tabs, true ) && ! code_snippets()->cloud_api->is_cloud_key_verified() ) {
$nav_tab_inactive = true;
}
printf(
'<a class="nav-tab %s" href="%s" data-snippet-type="%s">',
$nav_tab_inactive ? 'nav-tab-inactive' : '',
esc_url( add_query_arg( 'type', $type_name, $current_url ) ),
esc_attr( $type_name )
);
}
if ( 'all' === $type_name ) {
$label_class = 'all-snippets-label';
} else {
$label_class = 'snippet-label';
}
echo '<span class="' . $label_class . '">', esc_html( $label ), '</span>';
switch ( $type_name ) {
case 'all':
break;
case 'cloud':
echo '<span class="cloud-badge dashicons dashicons-cloud cloud-icon cloud-synced"></span>';
break;
case 'cloud_search':
echo '<span class="cloud-badge dashicons dashicons-search cloud-icon cloud-downloaded"></span>';
break;
case 'bundles':
echo '<span class="cloud-badge dashicons dashicons-screenoptions cloud-icon cloud-bundle"></span>';
break;
case 'ai':
echo '<span class="cloud-badge ai-icon">', esc_html__( 'AI', 'code-snippets' ), '</span>';
break;
default:
echo '<span class="badge">' . esc_html( $type_name ) . '</span>';
break;
}
echo '</a>';
}
}
class-contextual-help.php 0000644 00000014364 15153632426 0011513 0 ustar 00 <?php
namespace Code_Snippets;
use WP_Screen;
/**
* This file holds all the content for the contextual help screens.
*
* @package Code_Snippets
*/
class Contextual_Help {
/**
* Current screen object
*
* @see get_current_screen()
*
* @var WP_Screen
*/
public WP_Screen $screen;
/**
* Name of current screen
*
* @see get_current_screen()
*
* @var string
*/
public string $screen_name;
/**
* Class constructor
*
* @param string $screen_name Name of current screen.
*/
public function __construct( string $screen_name ) {
$this->screen_name = $screen_name;
}
/**
* Load the contextual help
*/
public function load() {
$this->screen = get_current_screen();
switch ( $this->screen_name ) {
case 'manage':
$this->load_manage_help();
break;
case 'edit':
$this->load_edit_help();
break;
case 'import':
$this->load_import_help();
break;
}
$this->load_help_sidebar();
}
/**
* Load the help sidebar
*/
private function load_help_sidebar() {
$sidebar_links = [
'https://wordpress.org/plugins/code-snippets' => __( 'About Plugin', 'code-snippets' ),
'https://help.codesnippets.pro/collection/3-faq' => __( 'FAQ', 'code-snippets' ),
'https://wordpress.org/support/plugin/code-snippets' => __( 'Support Forum', 'code-snippets' ),
'https://codesnippets.pro' => __( 'Plugin Website', 'code-snippets' ),
];
$contents = '<p><strong>' . __( 'For more information:', 'code-snippets' ) . "</strong></p>\n";
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_post( $contents ) );
}
/**
* 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' ),
__( 'You can find out how to enable safe mode in the <a href="https://help.codesnippets.pro/article/12-safe-mode">Code Snippets Pro Docs</a>.', 'code-snippets' ),
]
);
}
/**
* 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' ),
__( "If you're not sure about the types of snippets you can add, take a look at the <a href=\"https://help.codesnippets.pro/collection/2-adding-snippets\">Code Snippets Pro Docs</a> for inspiration.", 'code-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 minimise the chance of a faulty snippet becoming active on your site.', 'code-snippets' ),
]
);
}
/**
* Register and handle the help tabs for the import snippets admin page
*/
private function load_import_help() {
$manage_url = code_snippets()->get_menu_url( 'manage' );
$this->add_help_tab(
'overview',
__( 'Overview', 'code-snippets' ),
$this->get_intro_text() .
__( 'Here you can load snippets from a code snippets export file into the database alongside existing snippets.', 'code-snippets' )
);
$this->add_help_tab(
'import',
__( 'Importing', 'code-snippets' ),
__( 'You can load your snippets from a code snippets export file using this page.', 'code-snippets' ) .
/* translators: %s: URL to Snippets admin menu */
sprintf( __( 'Imported snippets will be added to the database along with your existing snippets. Regardless of whether the snippets were active on the previous site, imported snippets are always inactive until activated using the <a href="%s">Manage Snippets</a> page.', 'code-snippets' ), $manage_url )
);
$this->add_help_tab(
'export',
__( 'Exporting', 'code-snippets' ),
/* translators: %s: URL to Manage Snippets admin menu */
sprintf( __( 'You can save your snippets to a code snippets export file using the <a href="%s">Manage Snippets</a> page.', 'code-snippets' ), $manage_url )
);
}
}
class-data-item.php 0000644 00000014353 15153632426 0010242 0 ustar 00 <?php
namespace Code_Snippets;
/**
* 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
*/
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 ( WP_DEBUG ) {
$message = sprintf( 'Trying to access invalid property on "%s" class: %s', get_class( $this ), $field );
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error( esc_html( $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.
*/
public function __set( string $field, $value ) {
$field = $this->resolve_field_name( $field );
if ( ! $this->is_allowed_field( $field ) ) {
if ( WP_DEBUG ) {
$message = sprintf( 'Trying to set invalid property on "%s" class: %s', get_class( $this ), $field );
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
trigger_error( esc_html( $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.
*/
public function set_field( string $field, $value ): bool {
if ( ! $this->is_allowed_field( $field ) ) {
return false;
}
$this->__set( $field, $value );
return true;
}
}
class-db.php 0000644 00000017276 15153632426 0006771 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Functions used to manage the database tables.
*
* @package Code_Snippets
*/
class DB {
/**
* Unprefixed site-wide table name.
*/
const TABLE_NAME = 'snippets';
/**
* Unprefixed network-wide table name.
*/
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',
priority SMALLINT NOT NULL DEFAULT 10,
active TINYINT(1) NOT NULL DEFAULT 0,
modified DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
revision BIGINT(20) NOT NULL DEFAULT 1,
cloud_id VARCHAR(255) NULL,
PRIMARY KEY (id),
KEY scope (scope),
KEY active (active)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
$success = empty( $wpdb->last_error );
if ( $success ) {
do_action( 'code_snippets/create_table', $table_name );
}
return $success;
}
/**
* Fetch a list of active snippets from a database table.
*
* @param string $table_name Name of table to fetch snippets from.
* @param array<string> $scopes List of scopes to include in query.
* @param boolean $active_only Whether to only fetch active snippets from the table.
*
* @return array<string, array<string, mixed>>|false List of active snippets, if any could be retrieved.
*
* @phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
*/
private static function fetch_snippets_from_table( string $table_name, array $scopes, bool $active_only = true ) {
global $wpdb;
$cache_key = sprintf( 'active_snippets_%s_%s', sanitize_key( join( '_', $scopes ) ), $table_name );
$cached_snippets = wp_cache_get( $cache_key, CACHE_GROUP );
if ( is_array( $cached_snippets ) ) {
return $cached_snippets;
}
if ( ! self::table_exists( $table_name ) ) {
return false;
}
$scopes_format = implode( ',', array_fill( 0, count( $scopes ), '%s' ) );
$extra_where = $active_only ? 'AND active=1' : '';
$snippets = $wpdb->get_results(
$wpdb->prepare(
"
SELECT id, code, scope, active
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;
}
/**
* Generate the SQL for fetching active snippets from the database.
*
* @param array<string>|string $scopes List of scopes to retrieve in.
*
* @return array<string, array<string, mixed>> List of active snippets, indexed by table.
*/
public function fetch_active_snippets( $scopes ): array {
$active_snippets = array();
// Ensure that the list of scopes is an array.
if ( ! is_array( $scopes ) ) {
$scopes = array( $scopes );
}
// Fetch the active snippets for the current site, if there are any.
$snippets = $this->fetch_snippets_from_table( $this->table, $scopes );
if ( $snippets ) {
$active_snippets[ $this->table ] = $snippets;
}
// If multisite is enabled, fetch all snippets from the network table, and filter down to only active snippets.
if ( is_multisite() ) {
$active_shared_ids = (array) get_option( 'active_shared_network_snippets', array() );
$ms_snippets = $this->fetch_snippets_from_table( $this->ms_table, $scopes, false );
if ( $ms_snippets ) {
$active_snippets[ $this->ms_table ] = array_filter(
$ms_snippets,
function ( $snippet ) use ( $active_shared_ids ) {
return $snippet['active'] || in_array( intval( $snippet['id'] ), $active_shared_ids, true );
}
);
}
}
return $active_snippets;
}
}
class-licensing.php 0000644 00000000472 15153632426 0010345 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Empty class to better support interoperability between core and pro.
*
* @package Code_Snippets
*/
class Licensing {
/**
* Determine whether the current site has an active license.
*
* @return bool
*/
public function is_licensed(): bool {
return false;
}
}
class-list-table.php 0000644 00000113272 15153632426 0010435 0 ustar 00 <?php
/**
* Contains the class for handling the snippets table
*
* @package Code_Snippets
*
* phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
*/
namespace Code_Snippets;
use WP_List_Table;
use function Code_Snippets\Settings\get_setting;
// The WP_List_Table base class is not included by default, so we need to load it.
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
/**
* This class handles the table for the manage snippets menu
*
* @since 1.5
* @package Code_Snippets
*/
class List_Table extends WP_List_Table {
/**
* Whether the current screen is in the network admin
*
* @var bool
*/
public bool $is_network;
/**
* A list of statuses (views)
*
* @var array<string>
*/
public array $statuses = [ 'all', 'active', 'inactive', 'recently_activated' ];
/**
* Column name to use when ordering the snippets list.
*
* @var string
*/
protected string $order_by;
/**
* Direction to use when ordering the snippets list. Either 'asc' or 'desc'.
*
* @var string
*/
protected string $order_dir;
/**
* The constructor function for our class.
* Registers hooks, initializes variables, setups class.
*
* @phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
*/
public function __construct() {
global $status, $page;
$this->is_network = is_network_admin();
// Determine the status.
$status = apply_filters( 'code_snippets/list_table/default_view', 'all' );
if ( isset( $_REQUEST['status'] ) && in_array( sanitize_key( $_REQUEST['status'] ), $this->statuses, true ) ) {
$status = sanitize_key( $_REQUEST['status'] );
}
// Add the search query to the URL.
if ( isset( $_REQUEST['s'] ) ) {
$_SERVER['REQUEST_URI'] = add_query_arg( 's', sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) );
}
// Add a snippets per page screen option.
$page = $this->get_pagenum();
add_screen_option(
'per_page',
array(
'label' => __( 'Snippets per page', 'code-snippets' ),
'default' => 999,
'option' => 'snippets_per_page',
)
);
add_filter( 'default_hidden_columns', array( $this, 'default_hidden_columns' ) );
// Strip the result query arg from the URL.
$_SERVER['REQUEST_URI'] = remove_query_arg( 'result' );
// Add filters to format the snippet description in the same way the post content is formatted.
$filters = [ 'wptexturize', 'convert_smilies', 'convert_chars', 'wpautop', 'shortcode_unautop', 'capital_P_dangit', [ $this, 'wp_kses_desc' ] ];
foreach ( $filters as $filter ) {
add_filter( 'code_snippets/list_table/column_description', $filter );
}
// Set up the class.
parent::__construct(
array(
'ajax' => true,
'plural' => 'snippets',
'singular' => 'snippet',
)
);
}
/**
* Apply a more permissive version of wp_kses_post() to the snippet description.
*
* @param string $data Description content to filter.
*
* @return string Filtered description content with allowed HTML tags and attributes intact.
*/
public function wp_kses_desc( string $data ): string {
$safe_style_filter = function ( $styles ) {
$styles[] = 'display';
return $styles;
};
add_filter( 'safe_style_css', $safe_style_filter );
$data = wp_kses_post( $data );
remove_filter( 'safe_style_css', $safe_style_filter );
return $data;
}
/**
* Set the 'id' column as hidden by default.
*
* @param array<string> $hidden List of hidden columns.
*
* @return array<string> Modified list of hidden columns.
*/
public function default_hidden_columns( array $hidden ): array {
array_push( $hidden, 'id', 'code', 'cloud_id', 'revision' );
return $hidden;
}
/**
* Set the 'name' column as the primary column.
*
* @return string
*/
protected function get_default_primary_column_name(): string {
return 'name';
}
/**
* Define the output of all columns that have no callback function
*
* @param Snippet $item The snippet used for the current row.
* @param string $column_name The name of the column being printed.
*
* @return string The content of the column to output.
*/
protected function column_default( $item, $column_name ): string {
switch ( $column_name ) {
case 'id':
return $item->id;
case 'description':
return apply_filters( 'code_snippets/list_table/column_description', $item->desc );
case 'type':
$type = $item->type;
$url = add_query_arg( 'type', $type );
return sprintf(
'<a class="snippet-type-badge" href="%s" data-snippet-type="%s">%s</a>',
esc_url( $url ),
esc_attr( $type ),
esc_html( $type )
);
case 'date':
return $item->modified ? $item->format_modified() : '—';
default:
return apply_filters( "code_snippets/list_table/column_$column_name", '—', $item );
}
}
/**
* Retrieve a URL to perform an action on a snippet
*
* @param string $action Name of action to produce a link for.
* @param Snippet $snippet Snippet object to produce link for.
*
* @return string URL to perform action.
*/
public function get_action_link( string $action, Snippet $snippet ): string {
// Redirect actions to the network dashboard for shared network snippets.
$local_actions = array( 'activate', 'activate-shared', 'run-once', 'run-once-shared' );
$network_redirect = $snippet->shared_network && ! $this->is_network && ! in_array( $action, $local_actions, true );
// Edit links go to a different menu.
if ( 'edit' === $action ) {
return code_snippets()->get_snippet_edit_url( $snippet->id, $network_redirect ? 'network' : 'self' );
}
$query_args = array(
'action' => $action,
'id' => $snippet->id,
'scope' => $snippet->scope,
);
$url = $network_redirect ?
add_query_arg( $query_args, code_snippets()->get_menu_url( 'manage', 'network' ) ) :
add_query_arg( $query_args );
// Add a nonce to the URL for security purposes.
return wp_nonce_url( $url, 'code_snippets_manage_snippet_' . $snippet->id );
}
/**
* Build a list of action links for individual snippets
*
* @param Snippet $snippet The current snippet.
*
* @return array<string, string> The action links HTML.
*/
private function get_snippet_action_links( Snippet $snippet ): array {
$actions = array();
if ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) {
// Display special links if on a subsite and dealing with a network-active snippet.
if ( $snippet->active ) {
$actions['network_active'] = esc_html__( 'Network Active', 'code-snippets' );
} else {
$actions['network_only'] = esc_html__( 'Network Only', 'code-snippets' );
}
} elseif ( ! $snippet->shared_network || current_user_can( code_snippets()->get_network_cap_name() ) ) {
// If the snippet is a shared network snippet, only display extra actions if the user has network permissions.
$simple_actions = array(
'edit' => esc_html__( 'Edit', 'code-snippets' ),
'clone' => esc_html__( 'Clone', 'code-snippets' ),
'export' => esc_html__( 'Export', 'code-snippets' ),
);
foreach ( $simple_actions as $action => $label ) {
$actions[ $action ] = sprintf( '<a href="%s">%s</a>', esc_url( $this->get_action_link( $action, $snippet ) ), $label );
}
$actions['delete'] = sprintf(
'<a href="%2$s" class="delete" onclick="%3$s">%1$s</a>',
esc_html__( 'Delete', 'code-snippets' ),
esc_url( $this->get_action_link( 'delete', $snippet ) ),
esc_js(
sprintf(
'return confirm("%s");',
esc_html__( 'You are about to permanently delete the selected item.', 'code-snippets' ) . "\n" .
esc_html__( "'Cancel' to stop, 'OK' to delete.", 'code-snippets' )
)
)
);
}
return apply_filters( 'code_snippets/list_table/row_actions', $actions, $snippet );
}
/**
* Retrieve the code for a snippet activation switch
*
* @param Snippet $snippet Snippet object.
*
* @return string Output for activation switch.
*/
protected function column_activate( Snippet $snippet ): string {
if ( $this->is_network && ( $snippet->shared_network || ( ! $this->is_network && $snippet->network && ! $snippet->shared_network ) ) ) {
return '';
}
if ( 'single-use' === $snippet->scope ) {
$class = 'snippet-execution-button';
$action = 'run-once';
$label = esc_html__( 'Run Once', 'code-snippets' );
} else {
$class = 'snippet-activation-switch';
$action = $snippet->active ? 'deactivate' : 'activate';
$label = $snippet->network && ! $snippet->shared_network ?
( $snippet->active ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Network Activate', 'code-snippets' ) ) :
( $snippet->active ? __( 'Deactivate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ) );
}
if ( $snippet->shared_network ) {
$action .= '-shared';
}
return sprintf(
'<a class="%s" href="%s" title="%s"> </a> ',
esc_attr( $class ),
esc_url( $this->get_action_link( $action, $snippet ) ),
esc_attr( $label )
);
}
/**
* Build the content of the snippet name column
*
* @param Snippet $snippet The snippet being used for the current row.
*
* @return string The content of the column to output.
*/
protected function column_name( Snippet $snippet ): string {
$row_actions = $this->row_actions(
$this->get_snippet_action_links( $snippet ),
apply_filters( 'code_snippets/list_table/row_actions_always_visible', true )
);
$out = esc_html( $snippet->display_name );
if ( 'global' !== $snippet->scope ) {
$out .= sprintf( ' <span class="dashicons dashicons-%s"></span>', $snippet->scope_icon );
}
// Add a link to the snippet if it isn't an unreadable network-only snippet.
if ( $this->is_network || ! $snippet->network || current_user_can( code_snippets()->get_network_cap_name() ) ) {
$out = sprintf(
'<a href="%s" class="snippet-name">%s</a>',
esc_attr( code_snippets()->get_snippet_edit_url( $snippet->id, $snippet->network ? 'network' : 'admin' ) ),
$out
);
}
if ( $snippet->shared_network ) {
$out .= ' <span class="badge">' . esc_html__( 'Shared on Network', 'code-snippets' ) . '</span>';
}
// Return the name contents.
$out = apply_filters( 'code_snippets/list_table/column_name', $out, $snippet );
return $out . $row_actions;
}
/**
* Handles the checkbox column output.
*
* @param Snippet $item The snippet being used for the current row.
*
* @return string The column content to be printed.
*/
protected function column_cb( $item ): string {
$out = sprintf(
'<input type="checkbox" name="%s[]" value="%s">',
$item->shared_network ? 'shared_ids' : 'ids',
$item->id
);
return apply_filters( 'code_snippets/list_table/column_cb', $out, $item );
}
/**
* Handles the tags column output.
*
* @param Snippet $snippet The snippet being used for the current row.
*
* @return string The column output.
*/
protected function column_tags( Snippet $snippet ): string {
// Return now if there are no tags.
if ( empty( $snippet->tags ) ) {
return '';
}
$out = array();
// Loop through the tags and create a link for each one.
foreach ( $snippet->tags as $tag ) {
$out[] = sprintf(
'<a href="%s">%s</a>',
esc_url( add_query_arg( 'tag', esc_attr( $tag ) ) ),
esc_html( $tag )
);
}
return join( ', ', $out );
}
/**
* Handles the priority column output.
*
* @param Snippet $snippet The snippet being used for the current row.
*
* @return string The column output.
*/
protected function column_priority( Snippet $snippet ): string {
return sprintf( '<input type="number" class="snippet-priority" value="%d" step="1" disabled>', $snippet->priority );
}
/**
* Define the column headers for the table
*
* @return array<string, string> The column headers, ID paired with label
*/
public function get_columns(): array {
$columns = array(
'cb' => '<input type="checkbox">',
'activate' => '',
'name' => __( 'Name', 'code-snippets' ),
'type' => __( 'Type', 'code-snippets' ),
'description' => __( 'Description', 'code-snippets' ),
'tags' => __( 'Tags', 'code-snippets' ),
'date' => __( 'Modified', 'code-snippets' ),
'priority' => __( 'Priority', 'code-snippets' ),
'id' => __( 'ID', 'code-snippets' ),
);
if ( isset( $_GET['type'] ) && 'all' !== $_GET['type'] ) {
unset( $columns['type'] );
}
if ( ! get_setting( 'general', 'enable_description' ) ) {
unset( $columns['description'] );
}
if ( ! get_setting( 'general', 'enable_tags' ) ) {
unset( $columns['tags'] );
}
return apply_filters( 'code_snippets/list_table/columns', $columns );
}
/**
* Define the columns that can be sorted. The format is:
* 'internal-name' => 'orderby'
* or
* 'internal-name' => array( 'orderby', true )
*
* The second format will make the initial sorting order be descending.
*
* @return array<string, string|array<string|bool>> The IDs of the columns that can be sorted
*/
public function get_sortable_columns(): array {
$sortable_columns = [
'id' => [ 'id', true ],
'name' => 'name',
'type' => [ 'type', true ],
'date' => [ 'modified', true ],
'priority' => [ 'priority', true ],
];
return apply_filters( 'code_snippets/list_table/sortable_columns', $sortable_columns );
}
/**
* Define the bulk actions to include in the drop-down menus
*
* @return array<string, string> An array of menu items with the ID paired to the label
*/
public function get_bulk_actions(): array {
$actions = [
'activate-selected' => $this->is_network ? __( 'Network Activate', 'code-snippets' ) : __( 'Activate', 'code-snippets' ),
'deactivate-selected' => $this->is_network ? __( 'Network Deactivate', 'code-snippets' ) : __( 'Deactivate', 'code-snippets' ),
'clone-selected' => __( 'Clone', 'code-snippets' ),
'download-selected' => __( 'Export Code', 'code-snippets' ),
'export-selected' => __( 'Export', 'code-snippets' ),
'delete-selected' => __( 'Delete', 'code-snippets' ),
];
return apply_filters( 'code_snippets/list_table/bulk_actions', $actions );
}
/**
* Retrieve the classes for the table
*
* We override this in order to add 'snippets' as a class for custom styling
*
* @return array<string> The classes to include on the table element
*/
public function get_table_classes(): array {
$classes = array( 'widefat', $this->_args['plural'] );
return apply_filters( 'code_snippets/list_table/table_classes', $classes );
}
/**
* Retrieve the 'views' of the table
*
* Example: active, inactive, recently active
*
* @return array<string, string> A list of the view labels linked to the view
*/
public function get_views(): array {
global $totals, $status;
$status_links = parent::get_views();
// Loop through the view counts.
foreach ( $totals as $type => $count ) {
$labels = [];
if ( ! $count ) {
continue;
}
// translators: %s: total number of snippets.
$labels['all'] = _n(
'All <span class="count">(%s)</span>',
'All <span class="count">(%s)</span>',
$count,
'code-snippets'
);
// translators: %s: total number of active snippets.
$labels['active'] = _n(
'Active <span class="count">(%s)</span>',
'Active <span class="count">(%s)</span>',
$count,
'code-snippets'
);
// translators: %s: total number of inactive snippets.
$labels['inactive'] = _n(
'Inactive <span class="count">(%s)</span>',
'Inactive <span class="count">(%s)</span>',
$count,
'code-snippets'
);
// translators: %s: total number of recently activated snippets.
$labels['recently_activated'] = _n(
'Recently Active <span class="count">(%s)</span>',
'Recently Active <span class="count">(%s)</span>',
$count,
'code-snippets'
);
// The page URL with the status parameter.
$url = esc_url( add_query_arg( 'status', $type ) );
// Add a class if this view is currently being viewed.
$class = $type === $status ? ' class="current"' : '';
// Add the view count to the label.
$text = sprintf( $labels[ $type ], number_format_i18n( $count ) );
$status_links[ $type ] = sprintf( '<a href="%s"%s>%s</a>', $url, $class, $text );
}
return apply_filters( 'code_snippets/list_table/views', $status_links );
}
/**
* Gets the tags of the snippets currently being viewed in the table
*
* @since 2.0
*/
public function get_current_tags() {
global $snippets, $status;
// If we're not viewing a snippets table, get all used tags instead.
if ( ! isset( $snippets, $status ) ) {
$tags = get_all_snippet_tags();
} else {
$tags = array();
// Merge all tags into a single array.
foreach ( $snippets[ $status ] as $snippet ) {
$tags = array_merge( $snippet->tags, $tags );
}
// Remove duplicate tags.
$tags = array_unique( $tags );
}
sort( $tags );
return $tags;
}
/**
* Add filters and extra actions above and below the table
*
* @param string $which Whether the actions are displayed on the before (true) or after (false) the table.
*/
public function extra_tablenav( $which ) {
/**
* Status global.
*
* @var string $status
*/
global $status;
if ( 'top' === $which ) {
// Tags dropdown filter.
$tags = $this->get_current_tags();
if ( count( $tags ) ) {
$query = isset( $_GET['tag'] ) ? sanitize_text_field( wp_unslash( $_GET['tag'] ) ) : '';
echo '<div class="alignleft actions">';
echo '<select name="tag">';
printf(
"<option %s value=''>%s</option>\n",
selected( $query, '', false ),
esc_html__( 'Show all tags', 'code-snippets' )
);
foreach ( $tags as $tag ) {
printf(
"<option %s value='%s'>%s</option>\n",
selected( $query, $tag, false ),
esc_attr( $tag ),
esc_html( $tag )
);
}
echo '</select>';
submit_button( __( 'Filter', 'code-snippets' ), 'button', 'filter_action', false );
echo '</div>';
}
}
echo '<div class="alignleft actions">';
if ( 'recently_activated' === $status ) {
submit_button( __( 'Clear List', 'code-snippets' ), 'secondary', 'clear-recent-list', false );
}
do_action( 'code_snippets/list_table/actions', $which );
echo '</div>';
}
/**
* Output form fields needed to preserve important
* query vars over form submissions
*
* @param string $context The context in which the fields are being outputted.
*/
public static function required_form_fields( string $context = 'main' ) {
$vars = apply_filters(
'code_snippets/list_table/required_form_fields',
array( 'page', 's', 'status', 'paged', 'tag' ),
$context
);
if ( 'search_box' === $context ) {
// Remove the 's' var if we're doing this for the search box.
$vars = array_diff( $vars, array( 's' ) );
}
foreach ( $vars as $var ) {
if ( ! empty( $_REQUEST[ $var ] ) ) {
$value = sanitize_text_field( wp_unslash( $_REQUEST[ $var ] ) );
printf( '<input type="hidden" name="%s" value="%s" />', esc_attr( $var ), esc_attr( $value ) );
echo "\n";
}
}
do_action( 'code_snippets/list_table/print_required_form_fields', $context );
}
/**
* Perform an action on a single snippet.
*
* @param int $id Snippet ID.
* @param string $action Action to perform.
*
* @return bool|string Result of performing action
*/
private function perform_action( int $id, string $action ) {
switch ( $action ) {
case 'activate':
activate_snippet( $id, $this->is_network );
return 'activated';
case 'deactivate':
deactivate_snippet( $id, $this->is_network );
return 'deactivated';
case 'run-once':
$this->perform_action( $id, 'activate' );
return 'executed';
case 'run-once-shared':
$this->perform_action( $id, 'activate-shared' );
return 'executed';
case 'activate-shared':
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
if ( ! in_array( $id, $active_shared_snippets, true ) ) {
$active_shared_snippets[] = $id;
update_option( 'active_shared_network_snippets', $active_shared_snippets );
clean_active_snippets_cache( code_snippets()->db->ms_table );
}
return 'activated';
case 'deactivate-shared':
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
update_option( 'active_shared_network_snippets', array_diff( $active_shared_snippets, array( $id ) ) );
clean_active_snippets_cache( code_snippets()->db->ms_table );
return 'deactivated';
case 'clone':
$this->clone_snippets( [ $id ] );
return 'cloned';
case 'delete':
delete_snippet( $id, $this->is_network );
return 'deleted';
case 'export':
$export = new Export_Attachment( [ $id ], $this->is_network );
$export->download_snippets_json();
break;
case 'download':
$export = new Export_Attachment( [ $id ], $this->is_network );
$export->download_snippets_code();
break;
}
return false;
}
/**
* Processes actions requested by the user.
*
* @return void
*/
public function process_requested_actions() {
// Clear the recent snippets list if requested to do so.
if ( isset( $_POST['clear-recent-list'] ) ) {
check_admin_referer( 'bulk-' . $this->_args['plural'] );
if ( $this->is_network ) {
update_site_option( 'recently_activated_snippets', array() );
} else {
update_option( 'recently_activated_snippets', array() );
}
}
// Check if there are any single snippet actions to perform.
if ( isset( $_GET['action'], $_GET['id'] ) ) {
$id = absint( $_GET['id'] );
$scope = isset( $_GET['scope'] ) ? sanitize_key( wp_unslash( $_GET['scope'] ) ) : '';
// Verify they were sent from a trusted source.
$nonce_action = 'code_snippets_manage_snippet_' . $id;
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), $nonce_action ) ) {
wp_nonce_ays( $nonce_action );
}
$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'action', 'id', 'scope', '_wpnonce' ) );
// If so, then perform the requested action and inform the user of the result.
$result = $this->perform_action( $id, sanitize_key( $_GET['action'] ), $scope );
if ( $result ) {
wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) );
exit;
}
}
// Only continue from this point if there are bulk actions to process.
if ( ! isset( $_POST['ids'] ) && ! isset( $_POST['shared_ids'] ) ) {
return;
}
check_admin_referer( 'bulk-' . $this->_args['plural'] );
$ids = isset( $_POST['ids'] ) ? array_map( 'intval', $_POST['ids'] ) : array();
$_SERVER['REQUEST_URI'] = remove_query_arg( 'action' );
switch ( $this->current_action() ) {
case 'activate-selected':
activate_snippets( $ids );
// Process the shared network snippets.
if ( isset( $_POST['shared_ids'] ) && is_multisite() && ! $this->is_network ) {
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
foreach ( array_map( 'intval', $_POST['shared_ids'] ) as $id ) {
if ( ! in_array( $id, $active_shared_snippets, true ) ) {
$active_shared_snippets[] = $id;
}
}
update_option( 'active_shared_network_snippets', $active_shared_snippets );
clean_active_snippets_cache( code_snippets()->db->ms_table );
}
$result = 'activated-multi';
break;
case 'deactivate-selected':
foreach ( $ids as $id ) {
deactivate_snippet( $id, $this->is_network );
}
// Process the shared network snippets.
if ( isset( $_POST['shared_ids'] ) && is_multisite() && ! $this->is_network ) {
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
$active_shared_snippets = ( '' === $active_shared_snippets ) ? array() : $active_shared_snippets;
$active_shared_snippets = array_diff( $active_shared_snippets, array_map( 'intval', $_POST['shared_ids'] ) );
update_option( 'active_shared_network_snippets', $active_shared_snippets );
clean_active_snippets_cache( code_snippets()->db->ms_table );
}
$result = 'deactivated-multi';
break;
case 'export-selected':
$export = new Export_Attachment( $ids, $this->is_network );
$export->download_snippets_json();
break;
case 'download-selected':
$export = new Export_Attachment( $ids, $this->is_network );
$export->download_snippets_code();
break;
case 'clone-selected':
$this->clone_snippets( $ids );
$result = 'cloned-multi';
break;
case 'delete-selected':
foreach ( $ids as $id ) {
delete_snippet( $id, $this->is_network );
}
$result = 'deleted-multi';
break;
}
if ( isset( $result ) ) {
wp_safe_redirect( esc_url_raw( add_query_arg( 'result', $result ) ) );
exit;
}
}
/**
* Message to display if no snippets are found.
*
* @return void
*/
public function no_items() {
if ( ! empty( $GLOBALS['s'] ) || ! empty( $_GET['tag'] ) ) {
esc_html_e( 'No snippets were found matching the current search query. Please enter a new query or use the "Clear Filters" button above.', 'code-snippets' );
} else {
$add_url = code_snippets()->get_menu_url( 'add' );
if ( empty( $_GET['type'] ) ) {
esc_html_e( "It looks like you don't have any snippets.", 'code-snippets' );
} else {
esc_html_e( "It looks like you don't have any snippets of this type.", 'code-snippets' );
$add_url = add_query_arg( 'type', sanitize_key( wp_unslash( $_GET['type'] ) ), $add_url );
}
printf(
' <a href="%s">%s</a>',
esc_url( $add_url ),
esc_html__( 'Perhaps you would like to add a new one?', 'code-snippets' )
);
}
}
/**
* Fetch all shared network snippets for the current site.
*
* @return void
*/
private function fetch_shared_network_snippets() {
/**
* Table data.
*
* @var $snippets array<string, Snippet[]>
*/
global $snippets;
$ids = get_site_option( 'shared_network_snippets' );
if ( ! is_multisite() || ! $ids ) {
return;
}
if ( $this->is_network ) {
$limit = count( $snippets['all'] );
for ( $i = 0; $i < $limit; $i++ ) {
$snippet = &$snippets['all'][ $i ];
if ( in_array( $snippet->id, $ids, true ) ) {
$snippet->shared_network = true;
$snippet->tags = array_merge( $snippet->tags, array( 'shared on network' ) );
$snippet->active = false;
}
}
} else {
$active_shared_snippets = get_option( 'active_shared_network_snippets', array() );
$shared_snippets = get_snippets( $ids, true );
foreach ( $shared_snippets as $snippet ) {
$snippet->shared_network = true;
$snippet->tags = array_merge( $snippet->tags, array( 'shared on network' ) );
$snippet->active = in_array( $snippet->id, $active_shared_snippets, true );
}
$snippets['all'] = array_merge( $snippets['all'], $shared_snippets );
}
}
/**
* Prepares the items to later display in the table.
* Should run before any headers are sent.
*
* @phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
*
* @return void
*/
public function prepare_items() {
/**
* Global variables.
*
* @var string $status Current status view.
* @var array<string, Snippet[]> $snippets List of snippets for views.
* @var array<string, integer> $totals List of total items for views.
* @var string $s Current search term.
*/
global $status, $snippets, $totals, $s;
wp_reset_vars( array( 'orderby', 'order', 's' ) );
// Redirect tag filter from POST to GET.
if ( isset( $_POST['filter_action'] ) ) {
$location = empty( $_POST['tag'] ) ?
remove_query_arg( 'tag' ) :
add_query_arg( 'tag', sanitize_text_field( wp_unslash( $_POST['tag'] ) ) );
wp_safe_redirect( esc_url_raw( $location ) );
exit;
}
$this->process_requested_actions();
$snippets = array_fill_keys( $this->statuses, array() );
$snippets['all'] = apply_filters( 'code_snippets/list_table/get_snippets', get_snippets() );
$this->fetch_shared_network_snippets();
// Filter snippets by type.
$type = sanitize_key( wp_unslash( $_GET['type'] ?? '' ) );
if ( $type && 'all' !== $type ) {
$snippets['all'] = array_filter(
$snippets['all'],
function ( Snippet $snippet ) use ( $type ) {
return $type === $snippet->type;
}
);
}
// Add scope tags.
foreach ( $snippets['all'] as $snippet ) {
if ( 'global' !== $snippet->scope ) {
$snippet->add_tag( $snippet->scope );
}
}
// Filter snippets by tag.
if ( ! empty( $_GET['tag'] ) ) {
$snippets['all'] = array_filter( $snippets['all'], array( $this, 'tags_filter_callback' ) );
}
// Filter snippets based on search query.
if ( $s ) {
$snippets['all'] = array_filter( $snippets['all'], array( $this, 'search_by_line_callback' ) );
}
// Clear recently activated snippets older than a week.
$recently_activated = $this->is_network ?
get_site_option( 'recently_activated_snippets', array() ) :
get_option( 'recently_activated_snippets', array() );
foreach ( $recently_activated as $key => $time ) {
if ( $time + WEEK_IN_SECONDS < time() ) {
unset( $recently_activated[ $key ] );
}
}
$this->is_network ?
update_site_option( 'recently_activated_snippets', $recently_activated ) :
update_option( 'recently_activated_snippets', $recently_activated );
/**
* Filter snippets into individual sections
*
* @var Snippet $snippet
*/
foreach ( $snippets['all'] as $snippet ) {
if ( $snippet->active ) {
$snippets['active'][] = $snippet;
} else {
$snippets['inactive'][] = $snippet;
// Was the snippet recently deactivated?
if ( isset( $recently_activated[ $snippet->id ] ) ) {
$snippets['recently_activated'][] = $snippet;
}
}
}
// Count the totals for each section.
$totals = array_map(
function ( $section_snippets ) {
return count( $section_snippets );
},
$snippets
);
// If the current status is empty, default to all.
if ( empty( $snippets[ $status ] ) ) {
$status = 'all';
}
// Get the current data.
$data = $snippets[ $status ];
// Decide how many records per page to show by getting the user's setting in the Screen Options panel.
$sort_by = $this->screen->get_option( 'per_page', 'option' );
$per_page = get_user_meta( get_current_user_id(), $sort_by, true );
if ( empty( $per_page ) || $per_page < 1 ) {
$per_page = $this->screen->get_option( 'per_page', 'default' );
}
$per_page = (int) $per_page;
$this->set_order_vars();
usort( $data, array( $this, 'usort_reorder_callback' ) );
// Determine what page the user is currently looking at.
$current_page = $this->get_pagenum();
// Check how many items are in the data array.
$total_items = count( $data );
// The WP_List_Table class does not handle pagination for us, so we need to ensure that the data is trimmed to only the current page.
$data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page );
// Now we can add our *sorted* data to the 'items' property, where it can be used by the rest of the class.
$this->items = $data;
// We register our pagination options and calculations.
$this->set_pagination_args(
[
'total_items' => $total_items, // Calculate the total number of items.
'per_page' => $per_page, // Determine how many items to show on a page.
'total_pages' => ceil( $total_items / $per_page ), // Calculate the total number of pages.
]
);
}
/**
* Determine the sort ordering for two pieces of data.
*
* @param mixed $a_data First piece of data.
* @param mixed $b_data Second piece of data.
*
* @return int Returns -1 if $a_data is less than $b_data; 0 if they are equal; 1 otherwise
* @ignore
*/
private function get_sort_direction( $a_data, $b_data ) {
// If the data is numeric, then calculate the ordering directly.
if ( is_numeric( $a_data ) && is_numeric( $b_data ) ) {
return $a_data - $b_data;
}
// If only one of the data points is empty, then place it before the one which is not.
if ( empty( $a_data ) xor empty( $b_data ) ) {
return empty( $a_data ) ? 1 : -1;
}
// Sort using the default string sort order if possible.
if ( is_string( $a_data ) && is_string( $b_data ) ) {
return strcasecmp( $a_data, $b_data );
}
// Otherwise, use basic comparison operators.
return $a_data === $b_data ? 0 : ( $a_data < $b_data ? -1 : 1 );
}
/**
* Set the $order_by and $order_dir class variables.
*/
private function set_order_vars() {
$order = Settings\get_setting( 'general', 'list_order' );
// set the order by based on the query variable, if set.
if ( ! empty( $_REQUEST['orderby'] ) ) {
$this->order_by = sanitize_key( wp_unslash( $_REQUEST['orderby'] ) );
} else {
// otherwise, fetch the order from the setting, ensuring it is valid.
$valid_fields = [ 'id', 'name', 'type', 'modified', 'priority' ];
$order_parts = explode( '-', $order, 2 );
$this->order_by = in_array( $order_parts[0], $valid_fields, true ) ? $order_parts[0] :
apply_filters( 'code_snippets/list_table/default_orderby', 'priority' );
}
// set the order dir based on the query variable, if set.
if ( ! empty( $_REQUEST['order'] ) ) {
$this->order_dir = sanitize_key( wp_unslash( $_REQUEST['order'] ) );
} elseif ( '-desc' === substr( $order, -5 ) ) {
$this->order_dir = 'desc';
} elseif ( '-asc' === substr( $order, -4 ) ) {
$this->order_dir = 'asc';
} else {
$this->order_dir = apply_filters( 'code_snippets/list_table/default_order', 'asc' );
}
}
/**
* Callback for usort() used to sort snippets
*
* @param Snippet $a The first snippet to compare.
* @param Snippet $b The second snippet to compare.
*
* @return int The sort order.
* @ignore
*/
private function usort_reorder_callback( Snippet $a, Snippet $b ) {
$orderby = $this->order_by;
$result = $this->get_sort_direction( $a->$orderby, $b->$orderby );
if ( 0 === $result && 'id' !== $orderby ) {
$result = $this->get_sort_direction( $a->id, $b->id );
}
// Apply the sort direction to the calculated order.
return ( 'asc' === $this->order_dir ) ? $result : -$result;
}
/**
* Callback for search function
*
* @param Snippet $snippet The snippet being filtered.
*
* @return bool The result of the filter
* @ignore
*/
private function search_callback( Snippet $snippet ): bool {
global $s;
$fields = array( 'name', 'desc', 'code', 'tags_list' );
foreach ( $fields as $field ) {
if ( false !== stripos( $snippet->$field, $s ) ) {
return true;
}
}
return false;
}
/**
* Callback for search function
*
* @param Snippet $snippet The snippet being filtered.
*
* @return bool The result of the filter
* @ignore
*/
private function search_by_line_callback( Snippet $snippet ): bool {
global $s;
static $line_num;
if ( is_null( $line_num ) ) {
if ( preg_match( '/@line:(?P<line>\d+)/', $s, $matches ) ) {
$s = trim( str_replace( $matches[0], '', $s ) );
$line_num = (int) $matches['line'] - 1;
} else {
$line_num = -1;
}
}
if ( $line_num < 0 ) {
return $this->search_callback( $snippet );
}
$code_lines = explode( "\n", $snippet->code );
return isset( $code_lines[ $line_num ] ) && false !== stripos( $code_lines[ $line_num ], $s );
}
/**
* Callback for filtering snippets by tag.
*
* @param Snippet $snippet The snippet being filtered.
*
* @return bool The result of the filter.
* @ignore
*/
private function tags_filter_callback( Snippet $snippet ): bool {
$tags = isset( $_GET['tag'] ) ?
explode( ',', sanitize_text_field( wp_unslash( $_GET['tag'] ) ) ) :
array();
foreach ( $tags as $tag ) {
if ( in_array( $tag, $snippet->tags, true ) ) {
return true;
}
}
return false;
}
/**
* Display a notice showing the current search terms
*
* @since 1.7
*/
public function search_notice() {
if ( ! empty( $_REQUEST['s'] ) || ! empty( $_GET['tag'] ) ) {
echo '<span class="subtitle">' . esc_html__( 'Search results', 'code-snippets' );
if ( ! empty( $_REQUEST['s'] ) ) {
$s = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) );
if ( preg_match( '/@line:(?P<line>\d+)/', $s, $matches ) ) {
// translators: 1: search query, 2: line number.
$text = __( ' for “%1$s” on line %2$d', 'code-snippets' );
printf(
esc_html( $text ),
esc_html( trim( str_replace( $matches[0], '', $s ) ) ),
intval( $matches['line'] )
);
} else {
// translators: %s: search query.
echo esc_html( sprintf( __( ' for “%s”', 'code-snippets' ), $s ) );
}
}
if ( ! empty( $_GET['tag'] ) ) {
$tag = sanitize_text_field( wp_unslash( $_GET['tag'] ) );
// translators: %s: tag name.
echo esc_html( sprintf( __( ' in tag “%s”', 'code-snippets' ), $tag ) );
}
echo '</span>';
// translators: 1: link URL, 2: link text.
printf(
' <a class="button clear-filters" href="%s">%s</a>',
esc_url( remove_query_arg( array( 's', 'tag', 'cloud_search' ) ) ),
esc_html__( 'Clear Filters', 'code-snippets' )
);
}
}
/**
* Outputs content for a single row of the table
*
* @param Snippet $item The snippet being used for the current row.
*/
public function single_row( $item ) {
$status = $item->active ? 'active' : 'inactive';
$row_class = "snippet $status-snippet $item->type-snippet $item->scope-scope";
if ( $item->shared_network ) {
$row_class .= ' shared-network-snippet';
}
printf( '<tr class="%s" data-snippet-scope="%s">', esc_attr( $row_class ), esc_attr( $item->scope ) );
$this->single_row_columns( $item );
echo '</tr>';
}
/**
* Clone a selection of snippets
*
* @param array<integer> $ids List of snippet IDs.
*/
private function clone_snippets( array $ids ) {
$snippets = get_snippets( $ids, $this->is_network );
foreach ( $snippets as $snippet ) {
$snippet->id = 0;
$snippet->active = false;
$snippet->cloud_id = '';
// translators: %s: snippet title.
$snippet->name = sprintf( __( '%s [CLONE]', 'code-snippets' ), $snippet->name );
$snippet = apply_filters( 'code_snippets/list_table/clone_snippet', $snippet );
save_snippet( $snippet );
}
}
}
class-plugin.php 0000644 00000023431 15153632426 0007670 0 ustar 00 <?php
namespace Code_Snippets;
use Code_Snippets\Cloud\Cloud_API;
use Code_Snippets\REST_API\Snippets_REST_Controller;
/**
* 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;
/**
* 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;
/**
* Class for managing active snippets
*
* @var Active_Snippets
*/
public Active_Snippets $active_snippets;
/**
* Handles licensing and plugin updates.
*
* @var Licensing
*/
public Licensing $licensing;
/**
* 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';
// 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/settings.php';
// Cloud List Table shared functions.
require_once $includes_path . '/cloud/list-table-shared-ops.php';
$this->active_snippets = new Active_Snippets();
$this->front_end = new Front_End();
$this->cloud_api = new Cloud_API();
$upgrade = new Upgrade( $this->version, $this->db );
add_action( 'plugins_loaded', array( $upgrade, 'run' ), 0 );
$this->licensing = new Licensing();
}
/**
* Register custom REST API controllers.
*
* @return void
*/
public function init_rest_api() {
$snippets_controller = new Snippets_REST_Controller();
$snippets_controller->register_routes();
}
/**
* Disable snippet execution if the necessary query var is set.
*
* @param bool $execute_snippets Current filter value.
*
* @return bool New filter value.
*/
public function disable_snippet_execution( bool $execute_snippets ): bool {
return ! empty( $_REQUEST['snippets-safe-mode'] ) && $this->current_user_can() ? false : $execute_snippets;
}
/**
* Determine whether the menu is full or compact.
*
* @return bool
*/
public function is_compact_menu(): bool {
return ! is_network_admin() && apply_filters( 'code_snippets_compact_menu', false );
}
/**
* Fetch the admin menu slug for a menu.
*
* @param string $menu Name of menu to retrieve the slug for.
*
* @return string The menu's slug.
*/
public function get_menu_slug( string $menu = '' ): string {
$add = array( 'single', 'add', 'add-new', 'add-snippet', 'new-snippet', 'add-new-snippet' );
$edit = array( 'edit', 'edit-snippet' );
$import = array( 'import', 'import-snippets', 'import-code-snippets' );
$settings = array( 'settings', 'snippets-settings' );
$cloud = array( 'cloud', 'cloud-snippets' );
$welcome = array( 'welcome', 'getting-started', 'code-snippets' );
if ( in_array( $menu, $edit, true ) ) {
return 'edit-snippet';
} elseif ( in_array( $menu, $add, true ) ) {
return 'add-snippet';
} elseif ( in_array( $menu, $import, true ) ) {
return 'import-code-snippets';
} elseif ( in_array( $menu, $settings, true ) ) {
return 'snippets-settings';
} elseif ( in_array( $menu, $cloud, true ) ) {
return 'snippets&type=cloud';
} elseif ( in_array( $menu, $welcome, true ) ) {
return 'code-snippets-welcome';
} else {
return 'snippets';
}
}
/**
* Fetch the URL to a snippets admin menu.
*
* @param string $menu Name of menu to retrieve the URL to.
* @param string $context URL scheme to use.
*
* @return string The menu's URL.
*/
public function get_menu_url( string $menu = '', string $context = 'self' ): string {
$slug = $this->get_menu_slug( $menu );
if ( $this->is_compact_menu() && 'network' !== $context ) {
$base_slug = $this->get_menu_slug();
$url = 'tools.php?page=' . $base_slug;
if ( $slug !== $base_slug ) {
$url .= '&sub=' . $slug;
}
} else {
$url = 'admin.php?page=' . $slug;
}
if ( 'network' === $context || 'snippets-settings' === $slug ) {
return network_admin_url( $url );
} elseif ( 'admin' === $context ) {
return admin_url( $url );
} else {
return self_admin_url( $url );
}
}
/**
* Fetch the admin menu slug for a snippets admin menu.
*
* @param integer $snippet_id Snippet ID.
* @param string $context URL scheme to use.
*
* @return string The URL to the edit snippet page for that snippet.
*/
public function get_snippet_edit_url( int $snippet_id, string $context = 'self' ): string {
return add_query_arg(
'id',
absint( $snippet_id ),
$this->get_menu_url( 'edit', $context )
);
}
/**
* Allow redirecting to the Code Snippets site.
*
* @param array<string> $hosts Allowed hosts.
*
* @return array Modified allowed hosts.
*/
public function allow_code_snippets_redirect( array $hosts ): array {
$hosts[] = 'codesnippets.pro';
$hosts[] = 'snipco.de';
return $hosts;
}
/**
* Determine whether the current user can perform actions on snippets.
*
* @return boolean Whether the current user has the required capability.
*
* @since 2.8.6
*/
public function current_user_can(): bool {
return current_user_can( $this->get_cap() );
}
/**
* Retrieve the name of the capability required to manage sub-site snippets.
*
* @return string
*/
public function get_cap_name(): string {
return apply_filters( 'code_snippets_cap', 'manage_options' );
}
/**
* Retrieve the name of the capability required to manage network snippets.
*
* @return string
*/
public function get_network_cap_name(): string {
return apply_filters( 'code_snippets_network_cap', 'manage_network_options' );
}
/**
* Get the required capability to perform a certain action on snippets.
* Does not check if the user has this capability or not.
*
* If multisite, checks if *Enable Administration Menus: Snippets* is active
* under the *Settings > Network Settings* network admin menu
*
* @return string The capability required to manage snippets.
*
* @since 2.0
*/
public function get_cap(): string {
if ( is_multisite() ) {
$menu_perms = get_site_option( 'menu_items', array() );
// If multisite is enabled and the snippet menu is not activated, restrict snippet operations to super admins only.
if ( empty( $menu_perms['snippets'] ) ) {
return $this->get_network_cap_name();
}
}
return $this->get_cap_name();
}
/**
* Inject the safe mode query var into URLs
*
* @param string $url Original URL.
*
* @return string Modified URL.
*/
public function add_safe_mode_query_var( string $url ): string {
return isset( $_REQUEST['snippets-safe-mode'] ) ?
add_query_arg( 'snippets-safe-mode', (bool) $_REQUEST['snippets-safe-mode'], $url ) :
$url;
}
/**
* Retrieve a list of available snippet types and their labels.
*
* @return array<string, string> Snippet types.
*/
public static function get_types(): array {
return apply_filters(
'code_snippets_types',
array(
'php' => __( 'Functions', 'code-snippets' ),
'html' => __( 'Content', 'code-snippets' ),
'css' => __( 'Styles', 'code-snippets' ),
'js' => __( 'Scripts', 'code-snippets' ),
'cloud' => __( 'Codevault', 'code-snippets' ),
'cloud_search' => __( 'Cloud Search', 'code-snippets' ),
'bundles' => __( 'Bundles', 'code-snippets' ),
)
);
}
/**
* Determine whether a snippet type is Pro-only.
*
* @param string $type Snippet type name.
*
* @return bool
*/
public static function is_pro_type( string $type ): bool {
return 'css' === $type || 'js' === $type || 'cloud' === $type || 'bundles' === $type;
}
/**
* Localise a plugin script to provide the CODE_SNIPPETS object.
*
* @param string $handle Script handle.
*
* @return void
*/
public function localize_script( string $handle ) {
wp_localize_script(
$handle,
'CODE_SNIPPETS',
[
'isLicensed' => $this->licensing->is_licensed(),
'isCloudConnected' => Cloud_API::is_cloud_connection_available(),
'restAPI' => [
'base' => esc_url_raw( rest_url() ),
'snippets' => esc_url_raw( rest_url( Snippets_REST_Controller::get_base_route() ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'localToken' => $this->cloud_api->get_local_token(),
],
'urls' => [
'plugin' => esc_url_raw( plugins_url( '', PLUGIN_FILE ) ),
'manage' => esc_url_raw( $this->get_menu_url() ),
'edit' => esc_url_raw( $this->get_menu_url( 'edit' ) ),
'addNew' => esc_url_raw( $this->get_menu_url( 'add' ) ),
],
]
);
}
}
class-snippet.php 0000644 00000032105 15153632426 0010052 0 ustar 00 <?php
namespace Code_Snippets;
use DateTime;
use DateTimeZone;
use Exception;
/**
* A snippet object.
*
* @since 2.4.0
* @package Code_Snippets
*
* @property int $id The database ID.
* @property string $name The snippet title.
* @property string $desc The formatted description.
* @property string $code The executable code.
* @property array<string> $tags An array of the tags.
* @property string $scope The scope name.
* @property int $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 object|null $conditions Snippet conditionals
* @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 string $type_desc Human-readable description of the snippet type.
* @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).
*/
const DATE_FORMAT = 'Y-m-d H:i:s';
/**
* Default value used for a datetime variable.
*/
const DEFAULT_DATE = '0000-00-00 00:00:00';
/**
* Constructor function.
*
* @param array<string, mixed>|object $initial_data Initial snippet data.
*/
public function __construct( $initial_data = null ) {
$default_values = array(
'id' => 0,
'name' => '',
'desc' => '',
'code' => '',
'tags' => array(),
'scope' => 'global',
'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',
);
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;
}
/**
* 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':
return absint( $value );
case 'tags':
return code_snippets_build_tags_array( $value );
case 'active':
return is_bool( $value ) ? $value : (bool) $value;
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 this snippet is, based on its scope
*
* @return string The snippet type – will be a filename extension.
*/
protected function get_type(): string {
if ( '-css' === substr( $this->scope, -4 ) ) {
return 'css';
} elseif ( '-js' === substr( $this->scope, -3 ) ) {
return 'js';
} elseif ( 'content' === substr( $this->scope, -7 ) ) {
return 'html';
} else {
return 'php';
}
}
/**
* Retrieve a list of all valid types.
*
* @return string[]
*/
public static function get_types(): array {
return [ 'php', 'html', 'css', 'js' ];
}
/**
* Retrieve description of snippet type.
*
* @return string
*/
protected function get_type_desc(): string {
$labels = [
'php' => __( 'Functions', 'code-snippets' ),
'html' => __( 'Content', 'code-snippets' ),
'css' => __( 'Styles', 'code-snippets' ),
'js' => __( 'Scripts', 'code-snippets' ),
];
return isset( $labels[ $this->type ] ) ? $labels[ $this->type ] : strtoupper( $this->type );
}
/**
* 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 $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: %d: snippet ID.
return empty( $this->name ) ? sprintf( esc_html__( 'Untitled #%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',
);
}
/**
* 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',
);
}
/**
* 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 styles', 'code-snippets' );
case 'site-footer-js':
return __( 'Footer styles', '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;
}
/**
* Increment the revision number by one.
*/
public function increment_revision() {
++$this->revision;
}
}
class-upgrade.php 0000644 00000015070 15153632426 0010021 0 ustar 00 <?php
namespace Code_Snippets;
use WP_User;
/**
* Manages upgrade tasks such as deleting and updating options
*/
class Upgrade {
/**
* Instance of database class
*
* @var DB
*/
private DB $db;
/**
* The current plugin version number
*
* @var string
*/
private string $current_version;
/**
* Class constructor
*
* @param string $version Current plugin version.
* @param DB $db Instance of database class.
*/
public function __construct( string $version, DB $db ) {
$this->db = $db;
$this->current_version = $version;
}
/**
* Run the upgrade functions
*/
public function run() {
// Always run multisite upgrades, even if not on the main site, as sub-sites depend on the network snippet table.
if ( is_multisite() ) {
$this->do_multisite_upgrades();
}
$this->do_site_upgrades();
}
/**
* Perform upgrades for the current site
*/
private function do_site_upgrades() {
$table_name = $this->db->table;
$prev_version = get_option( 'code_snippets_version' );
// Do nothing if the plugin has not just been updated or installed.
if ( ! version_compare( $prev_version, $this->current_version, '<' ) ) {
return;
}
// Update the plugin version stored in the database.
$updated = update_option( 'code_snippets_version', $this->current_version );
if ( ! $updated ) {
return; // Bail if the data was not successfully saved to prevent this process from repeating.
}
$this->db->create_table( $table_name );
// Remove outdated user meta.
if ( version_compare( $prev_version, '2.14.1', '<' ) ) {
global $wpdb;
$prefix = $wpdb->get_blog_prefix();
$menu_slug = code_snippets()->get_menu_slug();
$option_name = "{$prefix}managetoplevel_page_{$menu_slug}columnshidden";
// Loop through each user ID and remove all matching user meta.
foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
delete_metadata( 'user', $user_id, $option_name, '', true );
}
}
// Update the scope column of the database.
if ( version_compare( $prev_version, '2.10.0', '<' ) ) {
$this->migrate_scope_data( $table_name );
}
// Custom capabilities were removed after version 2.9.5.
if ( version_compare( $prev_version, '2.9.5', '<=' ) ) {
$role = get_role( apply_filters( 'code_snippets_role', 'administrator' ) );
$role->remove_cap( apply_filters( 'code_snippets_cap', 'manage_snippets' ) );
}
if ( false === $prev_version ) {
add_action( 'init', [ $this, 'create_sample_content' ] );
}
clean_snippets_cache( $table_name );
Welcome_API::clear_cache();
}
/**
* Create example snippets.
*
* As this uses translation functions, this should not be called earlier than 'init'.
*
* @return void
*/
public function create_sample_content() {
if ( apply_filters( 'code_snippets/create_sample_content', true ) ) {
$sample_snippets = $this->get_sample_content();
foreach ( $sample_snippets as $sample_snippet ) {
save_snippet( $sample_snippet );
}
}
}
/**
* Perform multisite-only upgrades
*/
private function do_multisite_upgrades() {
$table_name = $this->db->ms_table;
$prev_version = get_site_option( 'code_snippets_version' );
// Do nothing if the plugin has not been updated or installed.
if ( ! version_compare( $prev_version, $this->current_version, '<' ) ) {
return;
}
// Always attempt to create or upgrade the database tables.
$this->db->create_table( $table_name );
// Update the plugin version stored in the database.
update_site_option( 'code_snippets_version', $this->current_version );
// Update the scope column of the database.
if ( version_compare( $prev_version, '2.10.0', '<' ) ) {
$this->migrate_scope_data( $table_name );
}
// Custom capabilities were removed after version 2.9.5.
if ( version_compare( $prev_version, '2.9.5', '<=' ) ) {
$network_cap = apply_filters( 'code_snippets_network_cap', 'manage_network_snippets' );
foreach ( get_super_admins() as $admin ) {
$user = new WP_User( 0, $admin );
$user->remove_cap( $network_cap );
}
}
clean_snippets_cache( $table_name );
}
/**
* Migrate data from the old integer method of storing scopes to the new string method
*
* @param string $table_name Name of database table.
*/
private function migrate_scope_data( string $table_name ) {
global $wpdb;
$scopes = array(
0 => 'global',
1 => 'admin',
2 => 'front-end',
);
foreach ( $scopes as $scope_number => $scope_name ) {
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, will flush at end of process.
$wpdb->query(
$wpdb->prepare(
"UPDATE $table_name SET scope = %s WHERE scope = %d",
$scope_name,
$scope_number
)
);
}
}
/**
* Build a collection of sample snippets for new users to try out.
*
* @return array<string, Snippet> List of Snippet objects.
*/
private function get_sample_content(): array {
$tag = "\n\n" . esc_html__( 'This is a sample snippet. Feel free to use it, edit it, or remove it.', 'code-snippets' );
$snippets_data = array(
array(
'name' => esc_html__( 'Make upload filenames lowercase', 'code-snippets' ),
'code' => "add_filter( 'sanitize_file_name', 'mb_strtolower' );",
'desc' => esc_html__( 'Makes sure that image and file uploads have lowercase filenames.', 'code-snippets' ) . $tag,
'tags' => array( 'sample', 'media' ),
),
array(
'name' => esc_html__( 'Disable admin bar', 'code-snippets' ),
'code' => "add_action( 'wp', function () {\n\tif ( ! current_user_can( 'manage_options' ) ) {\n\t\tshow_admin_bar( false );\n\t}\n} );",
'desc' => esc_html__( 'Turns off the WordPress admin bar for everyone except administrators.', 'code-snippets' ) . $tag,
'tags' => array( 'sample', 'admin-bar' ),
'scope' => 'front-end',
),
array(
'name' => esc_html__( 'Allow smilies', 'code-snippets' ),
'code' => "add_filter( 'widget_text', 'convert_smilies' );\nadd_filter( 'the_title', 'convert_smilies' );\nadd_filter( 'wp_title', 'convert_smilies' );\nadd_filter( 'get_bloginfo', 'convert_smilies' );",
'desc' => esc_html__( 'Allows smiley conversion in obscure places.', 'code-snippets' ) . $tag,
'tags' => array( 'sample' ),
),
array(
'name' => esc_html__( 'Current year', 'code-snippets' ),
'code' => "<?php echo date( 'Y' ); ?>",
'desc' => esc_html__( 'Shortcode for inserting the current year into a post or page..', 'code-snippets' ) . $tag,
'tags' => array( 'sample', 'dates' ),
'scope' => 'content',
),
);
return array_map(
function ( $snippet_data ) {
return new Snippet( $snippet_data );
},
$snippets_data
);
}
}
class-validator.php 0000644 00000016024 15153632426 0010357 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Validates code prior to execution.
*
* @package Code_Snippets
*/
class Validator {
/**
* Code to validate.
*
* @var string
*/
private string $code;
/**
* List of tokens.
*
* @var array<string>
*/
private array $tokens;
/**
* The index of the token currently being examined.
*
* @var integer
*/
private int $current;
/**
* The total number of tokens.
*
* @var integer
*/
private int $length;
/**
* Array to keep track of the various function, class and interface identifiers which have been defined.
*
* @var array<string, string[]>
*/
private array $defined_identifiers = [];
/**
* Exclude certain tokens from being checked.
*
* @var array<string, string[]>
*/
private array $exceptions = [];
/**
* Class constructor.
*
* @param string $code Snippet code for parsing.
*/
public function __construct( string $code ) {
$this->code = $code;
$this->tokens = token_get_all( "<?php\n" . $this->code );
$this->length = count( $this->tokens );
$this->current = 0;
}
/**
* Determine whether the parser has reached the end of the list of tokens.
*
* @return bool
*/
private function end(): bool {
return $this->current === $this->length;
}
/**
* Retrieve the next token without moving the pointer
*
* @return string|array<string|int>|null The current token if the list has not been expended, null otherwise.
*/
private function peek() {
return $this->end() ? null : $this->tokens[ $this->current ];
}
/**
* Move the pointer to the next token, if there is one.
*
* If the first argument is provided, only move the pointer if the tokens match.
*/
private function next() {
if ( ! $this->end() ) {
++$this->current;
}
}
/**
* Check whether a particular identifier has been used previously.
*
* @param string $type Which type of identifier this is. Supports T_FUNCTION, T_CLASS and T_INTERFACE.
* @param string $identifier The name of the identifier itself.
*
* @return bool true if the identifier is not unique.
*/
private function check_duplicate_identifier( string $type, string $identifier ): bool {
if ( ! isset( $this->defined_identifiers[ $type ] ) ) {
switch ( $type ) {
case T_FUNCTION:
$defined_functions = get_defined_functions();
$this->defined_identifiers[ T_FUNCTION ] = array_merge( $defined_functions['internal'], $defined_functions['user'] );
break;
case T_CLASS:
$this->defined_identifiers[ T_CLASS ] = get_declared_classes();
break;
case T_INTERFACE:
$this->defined_identifiers[ T_INTERFACE ] = get_declared_interfaces();
break;
default:
return false;
}
}
$duplicate = in_array( $identifier, $this->defined_identifiers[ $type ], true );
array_unshift( $this->defined_identifiers[ $type ], $identifier );
return $duplicate && ! ( isset( $this->exceptions[ $type ] ) && in_array( $identifier, $this->exceptions[ $type ], true ) );
}
/**
* Validate the given PHP code and return the result.
*
* @return array<string, mixed>|false Array containing message if an error was encountered, false if validation was successful.
*/
public function validate() {
while ( ! $this->end() ) {
$token = $this->peek();
$this->next();
if ( ! is_array( $token ) ) {
continue;
}
// If this is a function or class exists check, then allow this function or class to be defined.
if ( T_STRING === $token[0] && ( 'function_exists' === $token[1] || 'class_exists' === $token[1] ) ) {
$type = 'function_exists' === $token[1] ? T_FUNCTION : T_CLASS;
// Eat tokens until we find the function or class name.
while ( ! $this->end() && T_CONSTANT_ENCAPSED_STRING !== $token[0] ) {
$token = $this->peek();
$this->next();
}
// Add the identifier to the list of exceptions.
$this->exceptions[ $type ] = $this->exceptions[ $type ] ?? [];
$this->exceptions[ $type ][] = trim( $token[1], '\'"' );
continue;
}
// If we have a double colon, followed by a class, then consume it before the next section.
if ( T_DOUBLE_COLON === $token[0] ) {
$token = $this->peek();
$this->next();
if ( T_CLASS === $token[0] ) {
$this->next();
$token = $this->peek();
}
}
// Only look for class and function declaration tokens.
if ( T_CLASS !== $token[0] && T_FUNCTION !== $token[0] ) {
continue;
}
/**
* Ensure the type of $token is inferred correctly.
*
* @var string|array<string|int> $token
*/
$structure_type = $token[0];
// Continue eating tokens until we find the name of the class or function.
while ( ! $this->end() && T_STRING !== $token[0] &&
( T_FUNCTION !== $structure_type || '(' !== $token ) && ( T_CLASS !== $structure_type || '{' !== $token ) ) {
$token = $this->peek();
$this->next();
}
// If we've eaten all the tokens without discovering a name, then there must be a syntax error, so return appropriately.
if ( $this->end() ) {
return array(
'message' => __( 'Parse error: syntax error, unexpected end of snippet.', 'code-snippets' ),
'line' => $token[2],
);
}
// If the function or class is anonymous, with no name, then no need to check.
if ( ! ( T_FUNCTION === $structure_type && '(' === $token ) && ! ( T_CLASS === $structure_type && '{' === $token ) ) {
// Check whether the name has already been defined.
if ( $this->check_duplicate_identifier( $structure_type, $token[1] ) ) {
switch ( $structure_type ) {
case T_FUNCTION:
/* translators: %s: PHP function name */
$message = __( 'Cannot redeclare function %s.', 'code-snippets' );
break;
case T_CLASS:
/* translators: %s: PHP class name */
$message = __( 'Cannot redeclare class %s.', 'code-snippets' );
break;
case T_INTERFACE:
/* translators: %s: PHP interface name */
$message = __( 'Cannot redeclare interface %s.', 'code-snippets' );
break;
default:
/* translators: %s: PHP identifier name*/
$message = __( 'Cannot redeclare %s.', 'code-snippets' );
}
return array(
'message' => sprintf( $message, $token[1] ),
'line' => $token[2],
);
}
}
// If we have entered into a class, eat tokens until we find the closing brace.
if ( T_CLASS !== $structure_type ) {
continue;
}
// Find the opening brace for the class.
while ( ! $this->end() && '{' !== $token ) {
$token = $this->peek();
$this->next();
}
// Continue traversing the class tokens until we have found the class closing brace.
$depth = 1;
while ( ! $this->end() && $depth > 0 ) {
$token = $this->peek();
if ( '{' === $token ) {
++$depth;
} elseif ( '}' === $token ) {
--$depth;
}
$this->next();
}
// If we did not make it out of the class, then there's a problem.
if ( $depth > 0 ) {
return array(
'message' => __( 'Parse error: syntax error, unexpected end of snippet', 'code-snippets' ),
'line' => $token[2],
);
}
}
return false;
}
}
class-welcome-api.php 0000644 00000023042 15153632426 0010572 0 ustar 00 <?php
namespace Code_Snippets;
use DateTimeImmutable;
use Exception;
use WP_Filesystem_Direct;
/**
* Class for loading data from the codesnippets.pro website.
*
* @package Code_Snippets
*/
class Welcome_API {
/**
* URL for the welcome page data.
*
* @var string
*/
protected const WELCOME_JSON_URL = 'https://codesnippets.pro/wp-content/uploads/cs_welcome/cs_welcome.json';
/**
* Limit of number of items to display when loading lists of items.
*
* @var int
*/
protected const ITEM_LIMIT = 4;
/**
* Limit of number of items of historic versions to display in the changelog.
*
* @var int
*/
protected const MAX_CHANGELOG_ENTRIES = 4;
/**
* Key used for caching welcome page data.
*
* @var string
*/
protected const CACHE_KEY = 'code_snippets_welcome_data';
/**
* Data fetched from the remote API.
*
* @var array{
* banner: ?array,
* hero-item: ?array,
* features: ?array,
* partners: ?array,
* changelog: ?array
* }
*/
private array $welcome_data;
/**
* Populate the $welcome_data variable when the class is loaded.
*
* @return void
*/
public function __construct() {
$stored_data = get_transient( self::CACHE_KEY );
if ( is_array( $stored_data ) ) {
$this->welcome_data = $stored_data;
} else {
$this->welcome_data = [];
$this->fetch_remote_welcome_data();
$this->build_changelog_data();
set_transient( self::CACHE_KEY, $this->welcome_data, DAY_IN_SECONDS * 2 );
}
}
/**
* Purge the welcome data cache.
*
* @return void
*/
public static function clear_cache() {
delete_transient( self::CACHE_KEY );
}
/**
* Safely retrieve an array from an object, ensuring it exists and is valid, returning a default value if not.
*
* @param array<string, mixed> $items Associative array containing array to extract.
* @param string $key Array key.
*
* @return array Extracted array, or empty array if array is missing.
*/
private static function safe_get_array( array $items, string $key ): array {
return isset( $items[ $key ] ) && is_array( $items[ $key ] ) ? $items[ $key ] : [];
}
/**
* Parse DateTime value from a string without triggering an error.
*
* @param string $datetime String representation of DateTime value.
*
* @return DateTimeImmutable|null
*/
private static function safe_parse_datetime( string $datetime ): ?DateTimeImmutable {
try {
return new DateTimeImmutable( $datetime );
} catch ( Exception $e ) {
return null;
}
}
/**
* Parse remote hero item data.
*
* @param array $remote Remote hero item data.
*
* @return array
*/
private function parse_hero_item( array $remote ): array {
return [
'name' => $remote[0]['name'] ?? '',
'follow_url' => $remote[0]['follow_url'] ?? '',
'image_url' => $remote[0]['image_url'] ?? '',
];
}
/**
* Parse remote banner data.
*
* @param array $remote Remote hero item data.
*
* @return array
*/
private function parse_banner( array $remote ): array {
return [
'key' => sanitize_key( $remote['key'] ) ?? '',
'start_datetime' => self::safe_parse_datetime( $remote['start_datetime'] ),
'end_datetime' => self::safe_parse_datetime( $remote['end_datetime'] ),
'text_free' => $remote['text_free'] ?? '',
'action_url_free' => $remote['action_url_free'] ?? '',
'action_label_free' => $remote['action_label_free'] ?? '',
'text_pro' => $remote['text_pro'] ?? '',
'action_url_pro' => $remote['action_url_pro'] ?? '',
'action_label_pro' => $remote['action_label_pro'] ?? '',
];
}
/**
* Parse a list of features from a remote dataset.
*
* @param array $remote Remote data.
*
* @return array[] Parsed feature data.
*/
private function parse_features( array $remote ): array {
$limit = max( self::ITEM_LIMIT, count( $remote ) );
$features = [];
for ( $i = 0; $i < $limit; $i++ ) {
$feature = $remote[ $i ];
$features[] = [
'title' => $feature['title'] ?? '',
'follow_url' => $feature['follow_url'] ?? '',
'image_url' => $feature['image_url'] ?? '',
'category' => $feature['category'] ?? '',
'description' => $feature['description'] ?? '',
];
}
return $features;
}
/**
* Parse a list of partners from a remote dataset.
*
* @param array $remote Remote data.
*
* @return array[] Parsed partner data.
*/
private function parse_partners( array $remote ): array {
$limit = max( self::ITEM_LIMIT, count( $remote ) );
$partners = [];
for ( $i = 0; $i < $limit; $i++ ) {
$partner = $remote[ $i ];
$partners[] = [
'title' => $partner['title'] ?? '',
'follow_url' => $partner['follow_url'] ?? '',
'image_url' => $partner['image_url'] ?? '',
];
}
return $partners;
}
/**
* Fetch remote welcome data from the remote server and add it to the stored data.
*
* @return void
*/
protected function fetch_remote_welcome_data() {
$remote_welcome_data = wp_remote_get( self::WELCOME_JSON_URL );
if ( is_wp_error( $remote_welcome_data ) ) {
return;
}
$remote_welcome_data = json_decode( wp_remote_retrieve_body( $remote_welcome_data ), true );
if ( ! is_array( $remote_welcome_data ) ) {
return;
}
$this->welcome_data['banner'] = $this->parse_banner( self::safe_get_array( $remote_welcome_data, 'banner' ) );
$this->welcome_data['hero-item'] = $this->parse_hero_item( self::safe_get_array( $remote_welcome_data, 'hero-item' ) );
$this->welcome_data['features'] = $this->parse_features( self::safe_get_array( $remote_welcome_data, 'features' ) );
$this->welcome_data['partners'] = $this->parse_partners( self::safe_get_array( $remote_welcome_data, 'partners' ) );
}
/**
* Build the full list of latest changes for caching.
*
* @return void
*/
protected function build_changelog_data() {
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
$filesystem = new WP_Filesystem_Direct( null );
$changelog_filename = 'CHANGELOG.md';
$changelog = [];
$changelog_dir = plugin_dir_path( PLUGIN_FILE );
while ( plugin_dir_path( $changelog_dir ) !== $changelog_dir && ! $filesystem->exists( $changelog_dir . $changelog_filename ) ) {
$changelog_dir = plugin_dir_path( $changelog_dir );
}
if ( ! $filesystem->exists( $changelog_dir . $changelog_filename ) ) {
return;
}
$changelog_contents = $filesystem->get_contents( $changelog_dir . $changelog_filename );
$changelog_releases = explode( "\n## ", $changelog_contents );
foreach ( array_slice( $changelog_releases, 1, self::MAX_CHANGELOG_ENTRIES ) as $changelog_release ) {
$sections = explode( "\n### ", $changelog_release );
if ( count( $sections ) < 2 ) {
continue;
}
$header_parts = explode( '(', $sections[0], 2 );
$version = trim( trim( $header_parts[0] ), '[]' );
$changelog[ $version ] = [];
foreach ( array_slice( $sections, 1 ) as $section_contents ) {
$lines = array_filter( array_map( 'trim', explode( "\n", $section_contents ) ) );
$section_type = $lines[0];
foreach ( array_slice( $lines, 1 ) as $line ) {
$entry = trim( str_replace( '(PRO)', '', str_replace( '*', '', $line ) ) );
$core_or_pro = false === strpos( $line, '(PRO)' ) ? 'core' : 'pro';
if ( ! isset( $changelog[ $version ][ $section_type ] ) ) {
$changelog[ $version ][ $section_type ] = [
$core_or_pro => [ $entry ],
];
} elseif ( ! isset( $changelog[ $version ][ $section_type ][ $core_or_pro ] ) ) {
$changelog[ $version ][ $section_type ][ $core_or_pro ] = [ $entry ];
} else {
$changelog[ $version ][ $section_type ][ $core_or_pro ][] = $entry;
}
}
}
}
$this->welcome_data['changelog'] = $changelog;
}
/**
* Retrieve banner information.
*
* @return array{
* key: string,
* start_datetime: ?DateTimeImmutable,
* end_datetime: ?DateTimeImmutable,
* text_free: string,
* action_url_free: string,
* action_label_free: string,
* text_pro: string,
* action_url_pro: string,
* action_label_pro: string tet
* }
*/
public function get_banner(): array {
return $this->welcome_data['banner'] ?? [];
}
/**
* Retrieve hero information.
*
* @return array{
* name: string,
* follow_url: string,
* image_url: string
* }
*/
public function get_hero_item(): array {
return $this->welcome_data['hero-item'] ?? [];
}
/**
* Retrieve the list of features retrieved from the remote API.
*
* @return array{
* title: string,
* follow_url: string,
* image_url: string,
* category: string,
* description: string
* }[] Feature details.
*/
public function get_features(): array {
return $this->welcome_data['features'] ?? [];
}
/**
* Retrieve the list of partners retrieved from the remote API.
*
* @return array{
* title: string,
* follow_url: string,
* image_url: string
* }[] Partner details.
*/
public function get_partners(): array {
return $this->welcome_data['partners'] ?? [];
}
/**
* Retrieve a list of latest changes for display.
*
* @return array<string, array{
* 'Added': ?array<'core' | 'pro', string>,
* 'Fixed': ?array<'core' | 'pro', string>,
* 'Improved': ?array<'core' | 'pro', string>,
* 'Other': ?array<'core' | 'pro', string>
* }>
*/
public function get_changelog(): array {
return $this->welcome_data['changelog'] ?? [];
}
}
cloud/class-cloud-api.php 0000644 00000031716 15153632426 0011362 0 ustar 00 <?php
namespace Code_Snippets\Cloud;
use Code_Snippets\Snippet;
use WP_Error;
use function Code_Snippets\get_snippet_by_cloud_id;
use function Code_Snippets\get_snippets;
use function Code_Snippets\save_snippet;
use function Code_Snippets\update_snippet_fields;
/**
* Functions used to manage cloud synchronisation.
*
* @package Code_Snippets
*/
class Cloud_API {
/**
* Key used to access the local-to-cloud map transient data.
*/
private const CLOUD_MAP_TRANSIENT_KEY = 'cs_local_to_cloud_map';
/**
* Days to cache data retrieved from API.
*/
private const DAYS_TO_STORE_CS = 1;
/**
* Name of key used to cache cloud settings.
*
* @var string
*/
private const CLOUD_SETTINGS_CACHE_KEY = 'code_snippets_cloud_settings';
/**
* 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;
/**
* Retrieve the Cloud URL from wp-config or fallback to default.
*
* @return string
*/
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
*/
public static function get_cloud_api_url(): string {
return defined( 'CS_CLOUD_API_URL' )
? CS_CLOUD_API_URL
: self::get_cloud_url() . 'api/v1/';
}
/**
* Retrieve the cloud local token.
*
* @return string
*/
public static function get_local_token(): string {
return self::CLOUD_SEARCH_API_TOKEN;
}
/**
* Check that the cloud key is valid and verified.
*
* @return boolean
*/
public static function is_cloud_key_verified(): bool {
return false;
}
/**
* Check if the API key is set and verified.
*
* @return boolean
*/
public static function is_cloud_connection_available(): bool {
return false;
}
/**
* Create local-to-cloud map to keep track of local snippets that have been synced to the cloud.
*
* @return Cloud_Link[]
*/
private function get_cloud_links(): ?array {
// Return the cached data if available.
if ( is_array( $this->cached_cloud_links ) ) {
return $this->cached_cloud_links;
}
// Fetch data from the stored transient, if available.
$transient_data = get_transient( self::CLOUD_MAP_TRANSIENT_KEY );
if ( is_array( $transient_data ) ) {
$this->cached_cloud_links = $transient_data;
return $this->cached_cloud_links;
}
// Otherwise, regenerate the local-to-cloud-map.
$this->cached_cloud_links = [];
// Fetch and iterate through all local snippets to create the map.
foreach ( get_snippets() as $local_snippet ) {
// Skip snippets that are only stored locally.
if ( ! $local_snippet->cloud_id ) {
continue;
}
$link = new Cloud_Link();
$cloud_id_owner = $this->get_cloud_id_and_ownership( $local_snippet->cloud_id );
$cloud_id_int = intval( $cloud_id_owner['cloud_id'] );
$link->local_id = $local_snippet->id;
$link->cloud_id = $cloud_id_int;
$link->is_owner = $cloud_id_owner['is_owner'];
// Check if cloud id exists in cloud_id_rev array - this shows if the snippet is in the codevault.
$link->in_codevault = $cloud_id_rev[ $cloud_id_int ] ?? false;
// Get the cloud snippet revision if in codevault get from cloud_id_rev array otherwise get from cloud.
if ( $link->in_codevault ) {
$cloud_snippet_revision = $cloud_id_rev[ $cloud_id_int ] ?? $this->get_cloud_snippet_revision( $local_snippet->cloud_id );
$link->update_available = $local_snippet->revision < $cloud_snippet_revision;
}
$this->cached_cloud_links[] = $link;
}
set_transient(
self::CLOUD_MAP_TRANSIENT_KEY,
$this->cached_cloud_links,
DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
);
return $this->cached_cloud_links;
}
/**
* Get ownership and Cloud ID of a snippet.
*
* @param string $cloud_id Cloud ID.
*
* @return array<string, mixed>
*/
public function get_cloud_id_and_ownership( string $cloud_id ): array {
$cloud_id_owner = explode( '_', $cloud_id );
return [
'cloud_id' => (int) $cloud_id_owner[0] ?? '',
'is_owner' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1],
'is_owner_string' => isset( $cloud_id_owner[1] ) && $cloud_id_owner[1] ? '1' : '0',
];
}
/**
* Unpack JSON data from a request response.
*
* @param array|WP_Error $response Response from wp_request_*.
*
* @return array<string, mixed>|null Associative array of JSON data on success, null on failure.
*/
private static function unpack_request_json( $response ): ?array {
$body = wp_remote_retrieve_body( $response );
return $body ? json_decode( $body, true ) : null;
}
/**
* Search Code Snippets Cloud -> Static Function
*
* @param string $search_method Search by name of codevault or keyword(s).
* @param string $search Search query.
* @param integer $page Search result page to retrieve. Defaults to '0'.
*
* @return Cloud_Snippets Result of search query.
*/
public static function fetch_search_results( string $search_method, string $search, int $page = 0 ): Cloud_Snippets {
$api_url = add_query_arg(
[
's_method' => $search_method,
's' => $search,
'page' => $page,
'site_token' => self::get_local_token(),
'site_host' => wp_parse_url( get_site_url(), PHP_URL_HOST ),
],
self::get_cloud_api_url() . 'public/search'
);
$results = self::unpack_request_json( wp_remote_get( $api_url ) );
$results = new Cloud_Snippets( $results );
$results->page = $page;
return $results;
}
/**
* Add a new link item to the local-to-cloud map.
*
* @param Cloud_Link $link Link to add.
*
* @return void
*/
public function add_cloud_link( Cloud_Link $link ) {
$local_to_cloud_map = get_transient( self::CLOUD_MAP_TRANSIENT_KEY );
$local_to_cloud_map[] = $link;
set_transient(
self::CLOUD_MAP_TRANSIENT_KEY,
$local_to_cloud_map,
DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
);
}
/**
* Delete a snippet from local-to-cloud map.
*
* @param int $snippet_id Local snippet ID.
*
* @return void
*/
public function delete_snippet_from_transient_data( int $snippet_id ) {
if ( ! $this->cached_cloud_links ) {
$this->get_cloud_links();
}
foreach ( $this->cached_cloud_links as $link ) {
if ( $link->local_id === $snippet_id ) {
// Remove the link from the local_to_cloud_map.
$index = array_search( $link, $this->cached_cloud_links, true );
unset( $this->cached_cloud_links[ $index ] );
// Update the transient data.
set_transient(
self::CLOUD_MAP_TRANSIENT_KEY,
$this->cached_cloud_links,
DAY_IN_SECONDS * self::DAYS_TO_STORE_CS
);
}
}
}
/**
* Retrieve a single cloud snippet from the API.
*
* @param int $cloud_id Remote cloud snippet ID.
*
* @return Cloud_Snippet Retrieved snippet.
*/
public static function get_single_snippet_from_cloud( int $cloud_id ): Cloud_Snippet {
$url = self::get_cloud_api_url() . sprintf( 'public/getsnippet/%s', $cloud_id );
$response = wp_remote_get( $url );
$cloud_snippet = self::unpack_request_json( $response );
return new Cloud_Snippet( $cloud_snippet['snippet'] );
}
/**
* Get the current revision of a single cloud snippet.
*
* @param string $cloud_id Cloud snippet ID.
*
* @return string|null Revision number on success, null otherwise.
*/
public static function get_cloud_snippet_revision( string $cloud_id ): ?string {
$api_url = self::get_cloud_api_url() . sprintf( 'public/getsnippetrevision/%s', $cloud_id );
$body = wp_remote_retrieve_body( wp_remote_get( $api_url ) );
if ( ! $body ) {
return null;
}
$cloud_snippet_revision = json_decode( $body, true );
return $cloud_snippet_revision['snippet_revision'] ?? null;
}
/**
* Download a snippet from the cloud.
*
* @param int|string $cloud_id The cloud ID of the snippet as string from query args.
* @param string $source Unused in Core.
* @param string $action The action to be performed: 'download' or 'update'.
*
* @return array<string, string|bool> Result of operation: an array with `success` and `error_message` keys.
*
* @noinspection PhpUnusedParameterInspection
*/
public function download_or_update_snippet( int $cloud_id, string $source, string $action ): array {
$cloud_id = intval( $cloud_id );
$snippet_to_store = $this->get_single_snippet_from_cloud( $cloud_id );
switch ( $action ) {
case 'download':
return $this->download_snippet_from_cloud( $snippet_to_store );
case 'update':
return $this->update_snippet_from_cloud( $snippet_to_store );
default:
return [
'success' => false,
'error' => __( 'Invalid action.', 'code-snippets' ),
];
}
}
/**
* Download a snippet from the cloud.
*
* @param Cloud_Snippet $snippet_to_store The snippet to be downloaded.
*
* @return array The result of the download.
*/
public function download_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array {
$snippet = new Snippet( $snippet_to_store );
// Set the snippet id to 0 to ensure that the snippet is saved as a new snippet.
$ownership = $snippet_to_store->is_owner ? '1' : '0';
$snippet->id = 0;
$snippet->active = 0;
$snippet->cloud_id = $snippet_to_store->id . '_' . $ownership;
$snippet->desc = $snippet_to_store->description ? $snippet_to_store->description : '';
// Save the snippet to the database.
$new_snippet = save_snippet( $snippet );
$link = new Cloud_Link();
$link->local_id = $new_snippet->id;
$link->cloud_id = $snippet_to_store->id;
$link->is_owner = $snippet_to_store->is_owner;
$link->in_codevault = false;
$link->update_available = false;
$this->add_cloud_link( $link );
return [
'success' => true,
'action' => 'Single Downloaded',
'snippet_id' => $new_snippet->id,
'link_id' => $link->cloud_id,
];
}
/**
* Update a snippet from the cloud.
*
* @param Cloud_Snippet $snippet_to_store Snippet to be updated.
*
* @return array The result of the update.
*/
public function update_snippet_from_cloud( Cloud_Snippet $snippet_to_store ): array {
$cloud_id = $snippet_to_store->id . '_' . ( $snippet_to_store->is_owner ? '1' : '0' );
$local_snippet = get_snippet_by_cloud_id( sanitize_key( $cloud_id ) );
// Only update the code, active and revision fields.
$fields = [
'code' => $snippet_to_store->code,
'active' => false,
'revision' => $snippet_to_store->revision,
];
update_snippet_fields( $local_snippet->id, $fields );
$this->clear_caches();
return [
'success' => true,
'action' => __( 'Updated', 'code-snippets' ),
];
}
/**
* Find the cloud link for a given cloud snippet identifier.
*
* @param int $cloud_id Cloud ID.
*
* @return Cloud_Link|null
*/
public function get_link_for_cloud_id( int $cloud_id ): ?Cloud_Link {
$cloud_links = $this->get_cloud_links();
if ( $cloud_links ) {
foreach ( $cloud_links as $cloud_link ) {
if ( $cloud_link->cloud_id === $cloud_id ) {
return $cloud_link;
}
}
}
return null;
}
/**
* Find the cloud link for a given cloud snippet.
*
* @param Cloud_Snippet $cloud_snippet Cloud snippet.
*
* @return Cloud_Link|null
*/
public function get_link_for_cloud_snippet( Cloud_Snippet $cloud_snippet ): ?Cloud_Link {
return $this->get_link_for_cloud_id( $cloud_snippet->id );
}
/**
* Translate a snippet scope to a type.
*
* @param string $scope The scope of the snippet.
*
* @return string The type of the snippet.
*/
public static function get_type_from_scope( string $scope ): string {
switch ( $scope ) {
case 'global':
return 'php';
case 'site-css':
return 'css';
case 'site-footer-js':
return 'js';
case 'content':
return 'html';
default:
return '';
}
}
/**
* Translate a snippet status to a status-name.
*
* @param int $status The scope of the snippet.
*
* @return string The style to be used for the stats badge.
*/
public static function get_status_name_from_status( int $status ): string {
switch ( $status ) {
case 3:
return __( 'Private', 'code-snippets' );
case 4:
return __( 'Public', 'code-snippets' );
case 5:
return __( 'Unverified', 'code-snippets' );
case 6:
return __( 'AI Verified', 'code-snippets' );
case 8:
return __( 'Pro Verified', 'code-snippets' );
default:
return '';
}
}
/**
* Renders the html for the preview thickbox popup.
*
* @return void
*/
public static function render_cloud_snippet_thickbox() {
add_thickbox();
?>
<div id="show-code-preview" style="display: none;">
<h3 id="snippet-name-thickbox"></h3>
<h4><?php esc_html_e( 'Snippet Code:', 'code-snippets' ); ?></h4>
<pre class="thickbox-code-viewer">
<code id="snippet-code-thickbox"></code>
</pre>
</div>
<?php
}
/**
* Refresh the cached synced data.
*
* @return void
*/
public function clear_caches() {
$this->cached_cloud_links = null;
delete_transient( self::CLOUD_MAP_TRANSIENT_KEY );
}
}
cloud/class-cloud-link.php 0000644 00000003071 15153632426 0011537 0 ustar 00 <?php
namespace Code_Snippets\Cloud;
use Code_Snippets\Data_Item;
/**
* A connection between a local snippet and remote cloud snippet.
*
* @package Code_Snippets
*
* @property integer $local_id ID of local snippet as stored in WordPress database, if applicable.
* @property integer $cloud_id ID of remote snippet on cloud platform, if applicable.
* @property boolean $is_owner Ownership status of remote snippet on cloud platform.
* @property boolean $in_codevault Whether the remote snippet is stored in the users' codevault.
* @property boolean $update_available If synchronised, whether there is an update available on the cloud platform.
*/
class Cloud_Link extends Data_Item {
/**
* Constructor function
*
* @param array<string, mixed>|object $data Initial data fields.
*/
public function __construct( $data = null ) {
parent::__construct(
[
'local_id' => 0,
'cloud_id' => 0,
'is_owner' => false,
'in_codevault' => false,
'update_available' => false,
],
$data
);
}
/**
* Prepare a value before it is stored.
*
* @param mixed $value Value to prepare.
* @param string $field Field name.
*
* @return mixed Value in the correct format.
*/
protected function prepare_field( $value, string $field ) {
switch ( $field ) {
case 'local_id':
case 'remote_id':
return absint( $value );
case 'is_owner':
case 'in_codevault':
case 'update_available':
return is_bool( $value ) ? $value : (bool) $value;
default:
return $value;
}
}
}
cloud/class-cloud-search-list-table.php 0000644 00000030257 15153632426 0014113 0 ustar 00 <?php
/**
* Contains the class for handling the cloud search results table
*
* @package Code_Snippets
*/
namespace Code_Snippets\Cloud;
use WP_Plugin_Install_List_Table;
use function Code_Snippets\code_snippets;
if ( ! class_exists( 'WP_Plugin_Install_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-plugin-install-list-table.php';
}
/**
* Class for handling the cloud search results table.
*
* @property string $_pagination Pagination HTML.
*
* @package Code_Snippets
*/
class Cloud_Search_List_Table extends WP_Plugin_Install_List_Table {
/**
* Instance of Cloud API class.
*
* @var Cloud_API
*/
protected Cloud_API $cloud_api;
/**
* Items for the cloud list table.
*
* @var Cloud_Snippets
*/
protected Cloud_Snippets $cloud_snippets;
/**
* Class constructor.
*/
public function __construct() {
/**
* Declare global variable due to undeclared warning.
*
* @noinspection PhpUnusedLocalVariableInspection
*/
global $tab;
parent::__construct(
[
'singular' => 'cloud-snippet',
'plural' => 'cloud-snippets',
'ajax' => false,
]
);
// Strip the result query arg from the URL.
$_SERVER['REQUEST_URI'] = remove_query_arg( [ 'result' ] );
$this->cloud_api = code_snippets()->cloud_api;
}
/**
* Prepare items for the table.
*
* @return void
*/
public function prepare_items() {
$this->cloud_snippets = $this->fetch_snippets();
$this->items = $this->cloud_snippets->snippets;
$this->process_actions();
$this->set_pagination_args(
[
'per_page' => count( $this->cloud_snippets->snippets ),
'total_items' => $this->cloud_snippets->total_snippets,
'total_pages' => $this->cloud_snippets->total_pages,
]
);
}
/**
* Process any actions that have been submitted, such as downloading cloud snippets to the local database.
*
* @return void
*/
public function process_actions() {
$_SERVER['REQUEST_URI'] = remove_query_arg(
[ 'action', 'snippet', '_wpnonce', 'source', 'cloud-bundle-run', 'cloud-bundle-show', 'bundle_share_name', 'cloud_bundles' ]
);
// Check request is coming form the cloud search page.
if ( isset( $_REQUEST['type'] ) && 'cloud_search' === $_REQUEST['type'] ) {
if ( isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) {
cloud_lts_process_download_action(
sanitize_key( wp_unslash( $_REQUEST['action'] ) ),
sanitize_key( wp_unslash( $_REQUEST['source'] ) ),
sanitize_key( wp_unslash( $_REQUEST['snippet'] ) )
);
}
}
}
/**
* Output table rows.
*
* @return void
*/
public function display_rows() {
/**
* The current table item.
*
* @var $item Cloud_Snippet
*/
foreach ( $this->items as $item ) {
?>
<div class="plugin-card cloud-search-card plugin-card-<?php echo sanitize_html_class( $item->id ); ?>">
<?php
cloud_lts_display_column_hidden_input( 'code', $item );
cloud_lts_display_column_hidden_input( 'name', $item );
?>
<div class="plugin-card-top">
<div class="name column-name">
<h3>
<?php
$link = code_snippets()->cloud_api->get_link_for_cloud_snippet( $item );
if ( $link ) {
printf( '<a href="%s">', esc_url( code_snippets()->get_snippet_edit_url( $link->local_id ) ) );
} else {
printf(
'<a href="%s" title="%s" class="cloud-snippet-preview thickbox" data-snippet="%s" data-lang="%s">',
'#TB_inline?&width=700&height=500&inlineId=show-code-preview',
esc_attr__( 'Preview this snippet', 'code-snippets' ),
esc_attr( $item->id ),
esc_attr( Cloud_API::get_type_from_scope( $item->scope ) )
);
}
echo esc_html( $item->name );
// Grab first tag in array of tags.
$category = count( $item->tags ) > 0 ? strtolower( esc_attr( $item->tags[0] ) ) : '';
printf(
'<img src="%s" class="plugin-icon" alt="%s">',
esc_url( "https://codesnippets.cloud/images/plugin-icons/$category-logo.png" ),
esc_attr( $category )
);
echo '</a>';
?>
</h3>
</div>
<div class="action-links">
<?php echo wp_kses_post( cloud_lts_build_action_links( $item, 'search' ) ); ?>
</div>
<div class="desc column-description">
<p><?php echo wp_kses_post( $this->process_description( $item->description ) ); ?></p>
<p class="authors">
<cite>
<?php
esc_html_e( 'Codevault: ', 'code-snippets' );
printf(
'<a target="_blank" href="%s">%s</a>',
esc_url( sprintf( 'https://codesnippets.cloud/codevault/%s', $item->codevault ) ),
esc_html( $item->codevault )
);
?>
</cite>
</p>
</div>
</div>
<div class="plugin-card-bottom cloud-search-card-bottom">
<div class="vers column-rating voted-info">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="thumbs-up">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6.633 10.5c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 0 01.75-.75A2.25 2.25 0 0116.5 4.5c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 00-1.423-.23H5.904M14.25 9h2.25M5.904 18.75c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 01-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 10.203 4.167 9.75 5 9.75h1.053c.472 0 .745.556.5.96a8.958 8.958 0 00-1.302 4.665c0 1.194.232 2.333.654 3.375z"></path>
</svg>
<span class="num-ratings" aria-hidden="true">
<?php
// translators: 1: number of votes.
$votes_text = _nx( '%d time', '%d times', $item->vote_count, 'vote count', 'code-snippets' );
$votes_text = sprintf( $votes_text, number_format_i18n( $item->vote_count ) );
// translators: 1: number of users.
$users_text = _n( '%d user', '%d users', $item->total_votes, 'code-snippets' );
$users_text = sprintf( $users_text, number_format_i18n( $item->total_votes ) );
// translators: 1: number of votes with label, 2: number of users with label.
echo esc_html( sprintf( _x( '%1$s by %2$s', 'votes', 'code-snippets' ), $votes_text, $users_text ) );
?>
</span>
</div>
<div class="column-updated">
<strong><?php esc_html_e( 'Last Updated:', 'code-snippets' ); ?></strong>
<?php
// translators: %s: Human-readable time difference.
echo esc_html( sprintf( __( '%s ago', 'code-snippets' ), human_time_diff( strtotime( $item->updated ) ) ) );
?>
</div>
<div class="column-downloaded">
<?php
$status_name = $this->cloud_api->get_status_name_from_status( $item->status );
printf(
'<a class="snippet-type-badge snippet-status" data-type="%s">%s</a>',
esc_attr( sanitize_title_with_dashes( strtolower( $status_name ) ) ),
esc_html( $status_name )
);
?>
<div class="tooltip-box">
<span class="dashicons dashicons-editor-help"></span>
<div class="tooltip-text">
<p class="tooltip-text-title">
<?php esc_html_e( 'Snippet Statuses', 'code-snippets' ); ?>
</p>
<p class="tooltip-text-item">
<a class="snippet-type-badge snippet-status" data-type="public">
<?php esc_html_e( 'Public', 'code-snippets' ); ?></a>
<?php esc_html_e( 'Snippet has passed basic review.', 'code-snippets' ); ?>
</p>
<p class="tooltip-text-item">
<a class="snippet-type-badge snippet-status" data-type="ai-verified">
<?php esc_html_e( 'AI Verified', 'code-snippets' ); ?></a>
<?php esc_html_e( ' Snippet has been tested by our AI bot.', 'code-snippets' ); ?>
</p>
<p class="tooltip-text-item">
<a class="snippet-type-badge snippet-status" data-type="unverified">
<?php esc_html_e( 'Unverified', 'code-snippets' ); ?></a>
<?php esc_html_e( ' Snippet has not undergone any review yet.', 'code-snippets' ); ?>
</p>
<p class="tooltip-text-link">
<a class="tooltip-text-link"
href="https://codesnippets.cloud/getstarted"
target="_blankx">
<?php esc_html_e( 'View the full list.', 'code-snippets' ); ?></a>
</p>
</div>
</div>
</div>
<div class="column-compatibility">
<strong><?php esc_html_e( 'WP Compatability:', 'code-snippets' ); ?></strong>
<?php
if ( empty( $wp_tested ) ) {
echo '<span class="compatibility-untested">', esc_html__( 'Not indicated by author', 'code-snippets' ), '</span>';
} else {
// translators: tested status.
$text = sprintf( __( 'Author states %s', 'code-snippets' ), $wp_tested );
echo '<span class="compatibility-compatible">', esc_html( $text ), '</span>';
}
?>
</div>
</div>
</div>
<?php
}
}
/**
* Process the description text - limit to 150 characters.
*
* @param string|null $description Description as provided by the API.
*
* @return string formatted description string max 150 chars.
*/
protected function process_description( ?string $description ): string {
$description = wp_strip_all_tags( $description );
return strlen( $description ) > 150 ? substr( $description, 0, 150 ) . '…' : $description;
}
/**
* Text displayed when no snippet data is available.
*
* @return void
*/
public function no_items() {
if ( ! empty( $_REQUEST['cloud_search'] ) && count( $this->cloud_snippets->snippets ) < 1 ) {
echo '<p class="no-results">',
esc_html__( 'No snippets or codevault could be found with that search term. Please try again.', 'code-snippets' ),
'</p>';
} else {
echo '<p>', esc_html__( 'Please enter a term to start searching code snippets in the cloud.', 'code-snippets' ), '</p>';
}
}
/**
* Fetch the snippets used to populate the table.
*
* @return Cloud_Snippets
*/
public function fetch_snippets(): Cloud_Snippets {
// Check if search term has been entered.
if ( isset( $_REQUEST['type'], $_REQUEST['cloud_search'], $_REQUEST['cloud_select'] ) &&
'cloud_search' === sanitize_key( wp_unslash( $_REQUEST['type'] ) )
) {
// If we have a search query, then send a search request to cloud server API search endpoint.
$search_query = sanitize_text_field( wp_unslash( $_REQUEST['cloud_search'] ) );
$search_by = sanitize_text_field( wp_unslash( $_REQUEST['cloud_select'] ) );
return Cloud_API::fetch_search_results( $search_by, $search_query, $this->get_pagenum() - 1 );
}
// If no search results, then return empty object.
return new Cloud_Snippets();
}
/**
* Gets the current search result page number.
*
* @return integer
*/
public function get_pagenum(): int {
$page = isset( $_REQUEST['search_page'] ) ? absint( $_REQUEST['search_page'] ) : 0;
if ( isset( $this->_pagination_args['total_pages'] ) && $page > $this->_pagination_args['total_pages'] ) {
$page = $this->_pagination_args['total_pages'];
}
return max( 1, $page );
}
/**
* Display the table.
*
* @return void
*/
public function display() {
Cloud_API::render_cloud_snippet_thickbox();
parent::display();
}
/**
* Displays the pagination.
*
* @param string $which Context where the pagination will be displayed.
*
* @return void
*/
protected function pagination( $which ) {
$total_items = $this->_pagination_args['total_items'];
$total_pages = $this->_pagination_args['total_pages'];
$pagenum = $this->get_pagenum();
if ( 'top' === $which && $total_pages > 1 ) {
$this->screen->render_screen_reader_content( 'heading_pagination' );
}
$paginate = cloud_lts_pagination( $which, 'search', $total_items, $total_pages, $pagenum );
$page_class = $paginate['page_class'];
$output = $paginate['output'];
$this->_pagination = "<div class='tablenav-pages$page_class'>$output</div>";
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->_pagination;
// echo wp_kses_post( $this->_pagination ); TODO: This removes the top input box for page number.
}
}
cloud/class-cloud-snippet.php 0000644 00000005146 15153632426 0012271 0 ustar 00 <?php
namespace Code_Snippets\Cloud;
use Code_Snippets\Data_Item;
use function Code_Snippets\code_snippets_build_tags_array;
/**
* A snippet object as retrieved from the cloud API.
*
* @since 3.4.0
* @package Code_Snippets
*
* @property int $id The remote ID.
* @property string $name The snippet title.
* @property string $description The formatted description.
* @property string $code The executable code.
* @property array<string> $tags An array of the tags.
* @property string $scope The scope name.
* @property string $codevault Name of user codevault.
* @property string $total_votes The total number of votes.
* @property string $vote_count The number of actual votes.
* @property string $wp_tested Tested with WP version.
* @property string $status Snippet Status ID.
* @property string $created The date and time when the snippet data was first created, in ISO format.
* @property string $updated When the snippet was last updated, in ISO format.
* @property integer $revision The update revision number.
* @property bool $is_owner If user is owner or author of snippet.
*/
class Cloud_Snippet extends Data_Item {
/**
* Constructor function.
*
* @param array<string, mixed>|null $initial_data Initial snippet data.
*/
public function __construct( ?array $initial_data = null ) {
parent::__construct(
[
'id' => '',
'cloud_id' => '',
'name' => '',
'description' => '',
'code' => '',
'tags' => [],
'scope' => '',
'status' => '',
'codevault' => '',
'total_votes' => '',
'vote_count' => '',
'wp_tested' => '',
'created' => '',
'updated' => '',
'revision' => 0,
'is_owner' => false,
'shared_network' => false,
],
$initial_data
);
}
/**
* Prepare a value before it is stored.
*
* @param mixed $value Value to prepare.
* @param string $field Field name.
*
* @return mixed Value in the correct format.
*/
protected function prepare_field( $value, string $field ) {
switch ( $field ) {
case 'id':
case 'revision':
return absint( $value );
case 'is_owner':
return (bool) $value;
case 'description':
return ( null === $value ) ? '' : $value;
case 'tags':
return code_snippets_build_tags_array( $value );
default:
return $value;
}
}
}
cloud/class-cloud-snippets.php 0000644 00000004227 15153632426 0012453 0 ustar 00 <?php
namespace Code_Snippets\Cloud;
use Code_Snippets\Data_Item;
/**
* A list of snippets as retrieved from the cloud API.
*
* @since 3.4.0
* @package Code_Snippets
*
* @property Cloud_Snippet[] $snippets List of snippet items for the current page.
* @property integer $page Page of data that this data belongs to.
* @property integer $total_pages Total number of available pages of items.
* @property integer $total_snippets Total number of available snippet items.
* @property array $cloud_id_rev An array of all cloud snippet IDs and their revision numbers.
* @property bool $success If the request has any results.
*/
class Cloud_Snippets extends Data_Item {
/**
* Class constructor.
*
* @param array<string, Cloud_Snippet[]|integer> $initial_data Initial data.
*/
public function __construct( $initial_data = null ) {
parent::__construct(
[
'snippets' => [],
'total_snippets' => 0,
'total_pages' => 0,
'page' => 0,
'cloud_id_rev' => [],
],
$initial_data,
[
'items' => 'snippets',
'total_items' => 'total_snippets',
'page' => 'page',
'cloud_id_rev' => 'cloud_id_rev',
]
);
}
/**
* Prepare a value before it is stored.
*
* @param mixed $value Value to prepare.
* @param string $field Field name.
*
* @return mixed Value in the correct format.
*/
protected function prepare_field( $value, string $field ) {
switch ( $field ) {
case 'page':
case 'total_pages':
case 'total_snippets':
return absint( $value );
default:
return $value;
}
}
/**
* Prepare the `snippets` field by ensuring it is a list of Cloud_Snippets objects.
*
* @param mixed $snippets The field as provided.
*
* @return Cloud_Snippets[] The field in the correct format.
*/
protected function prepare_snippets( $snippets ): array {
$result = [];
$snippets = is_array( $snippets ) ? $snippets : [ $snippets ];
foreach ( $snippets as $snippet ) {
$result[] = $snippet instanceof Cloud_Snippet ? $snippet : new Cloud_Snippet( $snippet );
}
return $result;
}
}
cloud/list-table-shared-ops.php 0000644 00000020561 15153632426 0012501 0 ustar 00 <?php
/**
* Functions to perform snippet operations
*
* @package Code_Snippets
*/
namespace Code_Snippets\Cloud;
use function Code_Snippets\code_snippets;
/**
* Display a hidden input field for a certain column and snippet value.
*
* @param string $column_name Column name.
* @param Cloud_Snippet $snippet Column item.
*
* @return void
*/
function cloud_lts_display_column_hidden_input( string $column_name, Cloud_Snippet $snippet ) {
printf(
'<input id="cloud-snippet-%s-%s" class="cloud-snippet-item" type="hidden" name="%s" value="%s" />',
esc_attr( $column_name ),
esc_attr( $snippet->id ),
esc_attr( $column_name ),
esc_attr( $snippet->$column_name )
);
}
/**
* Process the download snippet action
*
* @param string $action Action - 'download' or 'update'.
* @param string $source Source - 'search' or 'cloud'.
* @param string $snippet Snippet ID.
* @param int $codevault_page The current page of the codevault.
*
* @return void
*/
function cloud_lts_process_download_action( string $action, string $source, string $snippet, int $codevault_page = 0 ) {
if ( 'download' === $action || 'update' === $action ) {
$result = code_snippets()->cloud_api->download_or_update_snippet( $snippet, $source, $action, $codevault_page );
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
*/
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 );
$additional_classes = 'search' === $source ? 'action-button-link' : '';
$is_licensed = code_snippets()->licensing->is_licensed();
$download = true;
$action_link = '';
if ( ! $is_licensed && in_array( $lang, [ 'css', 'js' ], true ) ) {
$download = false;
}
if ( $link ) {
if ( $is_licensed && $link->update_available ) {
$download = false;
$update_url = add_query_arg(
[
'action' => 'update',
'snippet' => $cloud_snippet->id,
'source' => $source,
]
);
$action_link = sprintf(
'<a class="cloud-snippet-update %s" href="%s">%s</a>',
$additional_classes,
esc_url( $update_url ),
esc_html__( 'Update Available', 'code-snippets' )
);
} else {
return sprintf(
'<a href="%s" class="cloud-snippet-downloaded %s">%s</a>',
esc_url( code_snippets()->get_snippet_edit_url( $link->local_id ) ),
$additional_classes,
esc_html__( 'View', 'code-snippets' )
);
}
}
if ( $download ) {
$download_url = add_query_arg(
[
'action' => 'download',
'snippet' => $cloud_snippet->id,
'source' => $source,
]
);
$action_link = $is_licensed ?
sprintf(
'<a class="cloud-snippet-download %s" href="%s">%s</a>',
$additional_classes,
esc_url( $download_url ),
esc_html__( 'Download', 'code-snippets' )
) :
sprintf(
'<a class="cloud-snippet-download %s" href="%s" target="_blank"><span class="go-pro-badge">%s</span>%s</a>',
$additional_classes,
'https://codesnippets.pro/pricing/',
esc_html_x( 'Pro', 'pro only', 'code-snippets' ),
esc_html_x( ' Only', 'pro only', 'code-snippets' )
);
}
$thickbox_url = '#TB_inline?&width=700&height=500&inlineId=show-code-preview';
$thickbox_link = sprintf(
'<a href="%s" title="%s" class="cloud-snippet-preview cloud-snippet-preview-style thickbox %s" data-snippet="%s" data-lang="%s">%s</a>',
esc_url( $thickbox_url ),
esc_attr( $cloud_snippet->name ),
$additional_classes,
esc_attr( $cloud_snippet->id ),
esc_attr( $lang ),
esc_html__( 'Preview', 'code-snippets' )
);
return $action_link . $thickbox_link;
}
/**
* Build the pagination functionality
*
* @param string $which Context where the pagination will be displayed.
* @param string $source Source - 'search' or 'cloud'.
* @param int $total_items Total number of items.
* @param int $total_pages Total number of pages.
* @param int $pagenum Current page number.
*
* @return array
*/
function cloud_lts_pagination( string $which, string $source, int $total_items, int $total_pages, int $pagenum ): array {
/* translators: %s: Number of items. */
$num = sprintf( _n( '%s item', '%s items', $total_items, 'code-snippets' ), number_format_i18n( $total_items ) );
$output = '<span class="displaying-num">' . $num . '</span>';
$current = isset( $_REQUEST['cloud_page'] ) ? (int) $_REQUEST['cloud_page'] : $pagenum;
$current_url = remove_query_arg( wp_removable_query_args() ) . '#' . $source;
$page_links = array();
$html_current_page = '';
$total_pages_before = '<span class="paging-input">';
$total_pages_after = '</span></span>';
$disable_first = false;
$disable_last = false;
$disable_prev = false;
$disable_next = false;
if ( 1 === $current ) {
$disable_first = true;
$disable_prev = true;
}
if ( $total_pages === $current ) {
$disable_last = true;
$disable_next = true;
}
if ( $disable_first ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>';
} else {
$page_links[] = sprintf(
'<a class="first-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">‹</span></a>',
esc_url( remove_query_arg( $source . '_page', $current_url ) ),
esc_html__( 'First page', 'code-snippets' )
);
}
if ( $disable_prev ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>';
} else {
$page_links[] = sprintf(
'<a class="prev-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">«</span></a>',
esc_url( add_query_arg( $source . '_page', max( 1, $current - 1 ), $current_url ) ),
esc_html__( 'Previous page', 'code-snippets' )
);
}
if ( 'bottom' === $which ) {
$html_current_page = $current;
$total_pages_before = sprintf( '<span class="screen-reader-text">%s</span><span id="table-paging" class="paging-input"><span class="tablenav-paging-text">', __( 'Current page', 'code-snippets' ) );
}
if ( 'top' === $which ) {
$html_current_page = sprintf(
'<label for="current-page-selector" class="screen-reader-text">%s</label><input class="current-page-selector" id="current-page-selector" type="text" name="%s_page" value="%s" size="%d" aria-describedby="table-paging" /><span class="tablenav-paging-text">',
__( 'Current page', 'code-snippets' ),
$source,
$current,
strlen( $total_pages )
);
}
$html_total_pages = sprintf( '<span class="total-pages">%s</span>', number_format_i18n( $total_pages ) );
/* translators: 1: Current page, 2: Total pages. */
$current_html = _x( '%1$s of %2$s', 'paging', 'code-snippets' );
$page_links[] = $total_pages_before . sprintf( $current_html, $html_current_page, $html_total_pages ) . $total_pages_after;
if ( $disable_next ) {
$page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>';
} else {
$page_links[] = sprintf(
'<a class="next-page button" href="%s"><span class="screen-reader-text">%s</span><span aria-hidden="true">%s</span></a>',
esc_url( add_query_arg( $source . '_page', min( $total_pages, $current + 1 ), $current_url ) ),
__( 'Next page' ),
'›'
);
}
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 ) ),
__( 'Last page', 'code-snippets' ),
'»'
);
}
$pagination_links_class = 'pagination-links';
if ( ! empty( $infinite_scroll ) ) {
$pagination_links_class .= ' hide-if-js';
}
$output .= "\n<span class='$pagination_links_class'>" . implode( "\n", $page_links ) . '</span>';
return [
'output' => $output,
'page_class' => $total_pages ? ( $total_pages < 2 ? ' one-page' : '' ) : ' no-pages',
];
}
deactivation-notice.php 0000644 00000003707 15153632426 0011224 0 ustar 00 <?php
/**
* File loaded when the plugin cannot be activated.
*
* All code in this file should be compatible with PHP 5.2 or later.
*
* @package Code_Snippets
*
* @noinspection PhpNestedDirNameCallsCanBeReplacedWithLevelParameterInspection
*
* phpcs:disable Modernize.FunctionCalls.Dirname.FileConstant
*/
if ( ! defined( 'ABSPATH' ) || function_exists( 'code_snippets_deactivation_notice' ) ) {
return;
}
/**
* Deactivate the plugin and display a notice informing the user that this has happened.
*
* @return void
*
* @since 3.3.0
*/
function code_snippets_deactivation_notice() {
$plugins = array();
$required_php_version = '7.4';
if ( version_compare( phpversion(), $required_php_version, '<' ) ) {
echo '<div class="error fade"><p><strong>';
// translators: %s: required PHP version number.
echo esc_html( sprintf( __( 'Code Snippets requires PHP %s or later.', 'code-snippets' ), $required_php_version ) );
echo '</strong><br>';
$update_url = function_exists( 'wp_get_default_update_php_url' ) ?
wp_get_default_update_php_url() :
'https://wordpress.org/support/update-php/';
// translators: %s: Update PHP URL.
$text = __( 'Please <a href="%s">upgrade your server to the latest version of PHP</a> to continue using Code Snippets.', 'code-snippets' );
echo wp_kses( sprintf( $text, $update_url ), array( 'a' => array( 'href' => array() ) ) );
echo '</p></div>';
$plugins[] = plugin_basename( dirname( dirname( __FILE__ ) ) . '/code-snippets.php' );
}
if ( defined( 'CODE_SNIPPETS_FILE' ) ) {
echo '<div class="error fade"><p>';
esc_html_e( 'Another version of Code Snippets appears to be installed. Deactivating this version.', 'code-snippets' );
echo '</p></div>';
$plugins[] = 'code-snippets/code-snippets.php';
}
if ( $plugins ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
deactivate_plugins( array_unique( $plugins ) );
}
}
add_action( 'admin_notices', 'code_snippets_deactivation_notice' );
editor.php 0000644 00000006320 15153632426 0006553 0 ustar 00 <?php
/**
* Functions for using the built-in code editor library
*
* @package Code_Snippets
*/
namespace Code_Snippets;
use function Code_Snippets\Settings\get_setting;
/**
* Register and load the CodeMirror library.
*
* @param string $type Type of code editor – either 'php', 'css', 'js', or 'html'.
* @param array<string, mixed> $extra_atts Pass a list of attributes to override the saved ones.
*/
function enqueue_code_editor( string $type, array $extra_atts = [] ) {
$plugin = code_snippets();
$modes = [
'css' => 'text/css',
'php' => 'php-snippet',
'js' => 'javascript',
'html' => 'application/x-httpd-php',
];
if ( ! isset( $modes[ $type ] ) ) {
$type = 'php';
}
$default_atts = [
'mode' => $modes[ $type ],
'inputStyle' => 'textarea',
'matchBrackets' => true,
'extraKeys' => [
'Alt-F' => 'findPersistent',
'Ctrl-Space' => 'autocomplete',
'Ctrl-/' => 'toggleComment',
'Cmd-/' => 'toggleComment',
'Alt-Up' => 'swapLineUp',
'Alt-Down' => 'swapLineDown',
],
'gutters' => [ 'CodeMirror-lint-markers', 'CodeMirror-foldgutter' ],
'lint' => 'css' === $type || 'php' === $type,
'direction' => 'ltr',
'colorpicker' => [ 'mode' => 'edit' ],
'foldOptions' => [ 'widget' => '...' ],
];
// Add relevant saved setting values to the default attributes.
$plugin_settings = Settings\get_settings_values();
$setting_fields = Settings\get_settings_fields();
foreach ( $setting_fields['editor'] as $field_id => $field ) {
// The 'codemirror' setting field specifies the name of the attribute.
$default_atts[ $field['codemirror'] ] = $plugin_settings['editor'][ $field_id ];
}
// Merge the default attributes with the ones passed into the function.
$atts = wp_parse_args( $default_atts, $extra_atts );
$atts = apply_filters( 'code_snippets_codemirror_atts', $atts );
// Ensure number values are not formatted as strings.
foreach ( [ 'indentUnit', 'tabSize' ] as $number_att ) {
$atts[ $number_att ] = intval( $atts[ $number_att ] );
}
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;
}
export/class-export-attachment.php 0000644 00000003246 15153632426 0013364 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Handles exporting snippets from the site to a downloadable file over HTTP.
*
* @package Code_Snippets
*/
class Export_Attachment extends Export {
/**
* Set up the current page to act like a downloadable file instead of being shown in the browser
*
* @param string $format File format. Used for file extension.
* @param string $mime_type File MIME type. Used for Content-Type header.
*/
private function do_headers( string $format, string $mime_type = 'text/plain' ) {
header( 'Content-Disposition: attachment; filename=' . sanitize_file_name( $this->build_filename( $format ) ) );
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() {
$mime_types = [
'php' => 'text/php',
'css' => 'text/css',
'js' => 'text/javascript',
];
$type = isset( $mime_types[ $this->snippets_list[0]->type ] ) ? $this->snippets_list[0]->type : 'php';
$this->do_headers( $type, $mime_types[ $type ] );
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo ( 'php' === $type || 'html' === $type ) ?
$this->export_snippets_php() :
$this->export_snippets_code( $type );
exit;
}
}
export/class-export.php 0000644 00000010170 15153632426 0011230 0 ustar 00 <?php
namespace Code_Snippets;
/**
* Handles exporting snippets from the site in various downloadable formats
*
* @package Code_Snippets
* @since 3.0.0
*/
class Export {
/**
* Array of snippet data fetched from the database
*
* @var Snippet[]
*/
protected array $snippets_list;
/**
* Class constructor
*
* @param array<int> $ids List of snippet IDs to export.
* @param boolean|null $network Whether to fetch snippets from local or network table.
*/
public function __construct( array $ids, ?bool $network = null ) {
$this->snippets_list = get_snippets( $ids, $network );
}
/**
* Build the export filename.
*
* @param string $format File format. Used for file extension.
*
* @return string
*/
public function build_filename( string $format ): string {
if ( 1 === count( $this->snippets_list ) ) {
// If there is only snippet to export, use its name instead of the site name.
$title = strtolower( $this->snippets_list[0]->name );
} else {
// Otherwise, use the site name as set in Settings > General.
$title = strtolower( get_bloginfo( 'name' ) );
}
$filename = "$title.code-snippets.$format";
return apply_filters( 'code_snippets/export/filename', $filename, $title, $this->snippets_list );
}
/**
* Bundle snippets together into JSON format.
*
* @return array<string, string|Snippet[]> Snippets as JSON object.
*/
public function create_export_object(): array {
$snippets = array();
foreach ( $this->snippets_list as $snippet ) {
$snippets[] = array_map(
function ( $value ) {
return is_string( $value ) ?
str_replace( "\r\n", "\n", $value ) :
$value;
},
$snippet->get_modified_fields()
);
}
return array(
'generator' => 'Code Snippets v' . code_snippets()->version,
'date_created' => gmdate( 'Y-m-d H:i' ),
'snippets' => $snippets,
);
}
/**
* Bundle a snippets into a PHP file.
*/
public function export_snippets_php(): string {
$result = "<?php\n";
foreach ( $this->snippets_list as $snippet ) {
$code = trim( $snippet->code );
if ( ( 'php' !== $snippet->type && 'html' !== $snippet->type ) || ! $code ) {
continue;
}
$result .= "\n/**\n * $snippet->display_name\n";
if ( ! empty( $snippet->desc ) ) {
// Convert description to PhpDoc.
$desc = wp_strip_all_tags( str_replace( "\n", "\n * ", $snippet->desc ) );
$result .= " *\n * $desc\n";
}
$result .= " */\n";
if ( 'content' === $snippet->scope ) {
$shortcode_tag = apply_filters( 'code_snippets_export_shortcode_tag', "code_snippets_export_$snippet->id", $snippet );
$code = sprintf(
"add_shortcode( '%s', function () {\n\tob_start();\n\t?>\n\n\t%s\n\n\t<?php\n\treturn ob_get_clean();\n} );",
$shortcode_tag,
str_replace( "\n", "\n\t", $code )
);
}
$result .= "$code\n";
}
return $result;
}
/**
* Export snippets in a generic JSON format that is not intended for importing.
*
* @return string
*/
public function export_snippets_basic_json(): string {
$snippet_data = array();
foreach ( $this->snippets_list as $snippet ) {
$snippet_data[] = $snippet->get_modified_fields();
}
return wp_json_encode( 1 === count( $snippet_data ) ? $snippet_data[0] : $snippet_data );
}
/**
* 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();
}
foreach ( $this->snippets_list as $snippet ) {
$snippet = new Snippet( $snippet );
if ( $snippet->type !== $type ) {
continue;
}
$result .= "\n/*\n";
if ( $snippet->name ) {
$result .= wp_strip_all_tags( $snippet->name ) . "\n\n";
}
if ( ! empty( $snippet->desc ) ) {
$result .= wp_strip_all_tags( $snippet->desc ) . "\n";
}
$result .= "*/\n\n$snippet->code\n\n";
}
return $result;
}
}
export/class-import.php 0000644 00000012324 15153632426 0011224 0 ustar 00 <?php
namespace Code_Snippets;
use DOMDocument;
/**
* Handles importing snippets from export files into the site
*
* @package Code_Snippets
* @since 3.0.0
*
* phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
*/
class Import {
/**
* Path to file to import.
*
* @var string
*/
private string $file;
/**
* Whether snippets should be imported into the network-wide or site-wide table.
*
* @var bool
*/
private bool $multisite;
/**
* Action to take if duplicate snippets are detected. Can be 'skip', 'ignore', or 'replace'.
*
* @var string
*/
private string $dup_action;
/**
* Class constructor.
*
* @param string $file The path to the file to import.
* @param bool|null $network Import into network-wide table (true) or site-wide table (false).
* @param string $dup_action Action to take if duplicate snippets are detected. Can be 'skip', 'ignore', or 'replace'.
*/
public function __construct( string $file, ?bool $network = null, string $dup_action = 'ignore' ) {
$this->file = $file;
$this->multisite = DB::validate_network_param( $network );
$this->dup_action = $dup_action;
}
/**
* Imports snippets from a JSON file.
*
* @return array<integer>|bool An array of imported snippet IDs on success, false on failure
*/
public function import_json() {
if ( ! file_exists( $this->file ) || ! is_file( $this->file ) ) {
return false;
}
$raw_data = file_get_contents( $this->file );
$data = json_decode( $raw_data, true );
$snippets = array();
// Reformat the data into snippet objects.
foreach ( $data['snippets'] as $snippet_data ) {
$snippet = new Snippet();
$snippet->network = $this->multisite;
$import_fields = [
'name',
'desc',
'description',
'code',
'tags',
'scope',
'priority',
'shared_network',
'modified',
'cloud_id',
];
foreach ( $import_fields as $field ) {
if ( isset( $snippet_data[ $field ] ) ) {
$snippet->set_field( $field, $snippet_data[ $field ] );
}
}
$snippets[] = $snippet;
}
$imported = $this->save_snippets( $snippets );
do_action( 'code_snippets/import/json', $this->file, $this->multisite );
return $imported;
}
/**
* Imports snippets from an XML file
*
* @return array<integer>|bool An array of imported snippet IDs on success, false on failure
*/
public function import_xml() {
if ( ! file_exists( $this->file ) || ! is_file( $this->file ) ) {
return false;
}
$dom = new DOMDocument( '1.0', get_bloginfo( 'charset' ) );
$dom->load( $this->file );
$snippets_xml = $dom->getElementsByTagName( 'snippet' );
$fields = array( 'name', 'description', 'desc', 'code', 'tags', 'scope' );
$snippets = array();
foreach ( $snippets_xml as $snippet_xml ) {
$snippet = new Snippet();
$snippet->network = $this->multisite;
// Build a snippet object by looping through the field names.
foreach ( $fields as $field_name ) {
// Fetch the field element from the document.
$field = $snippet_xml->getElementsByTagName( $field_name )->item( 0 );
// If the field element exists, add it to the snippet object.
if ( isset( $field->nodeValue ) ) {
$snippet->set_field( $field_name, $field->nodeValue );
}
}
// Get scope from attribute.
$scope = $snippet_xml->getAttribute( 'scope' );
if ( ! empty( $scope ) ) {
$snippet->scope = $scope;
}
$snippets[] = $snippet;
}
$imported = $this->save_snippets( $snippets );
do_action( 'code_snippets/import/xml', $this->file, $this->multisite );
return $imported;
}
/**
* Fetch a list of existing snippets for checking duplicates.
*
* @return array<string, integer>
*/
private function fetch_existing_snippets(): array {
$existing_snippets = array();
if ( 'replace' === $this->dup_action || 'skip' === $this->dup_action ) {
$all_snippets = get_snippets( array(), $this->multisite );
foreach ( $all_snippets as $snippet ) {
if ( $snippet->name ) {
$existing_snippets[ $snippet->name ] = $snippet->id;
}
}
}
return $existing_snippets;
}
/**
* Save imported snippets to the database
*
* @access private
*
* @param array<Snippet> $snippets List of snippets to save.
*
* @return array<integer> IDs of imported snippets.
*/
private function save_snippets( array $snippets ): array {
$existing_snippets = $this->fetch_existing_snippets();
$imported = array();
foreach ( $snippets as $snippet ) {
// Check if the snippet already exists.
if ( 'ignore' !== $this->dup_action && isset( $existing_snippets[ $snippet->name ] ) ) {
// If so, either overwrite the existing ID, or skip this import.
if ( 'replace' === $this->dup_action ) {
$snippet->id = $existing_snippets[ $snippet->name ];
} elseif ( 'skip' === $this->dup_action ) {
continue;
}
}
// Save the snippet and increase the counter if successful.
$saved_snippet = save_snippet( $snippet );
// Get ID of the saved snippet as save_snippet() returns complete snippet object.
$snippet_id = $saved_snippet->id;
if ( $snippet_id ) {
$imported[] = $snippet_id;
}
}
return $imported;
}
}
front-end/class-front-end.php 0000644 00000027176 15153632426 0012174 0 ustar 00 <?php
namespace Code_Snippets;
use WP_Post;
use WP_REST_Response;
use WP_REST_Server;
/**
* This class manages the shortcodes included with the plugin,
*
* @package Code_Snippets
*/
class Front_End {
/**
* Name of the shortcode tag for rendering the code source
*/
const SOURCE_SHORTCODE = 'code_snippet_source';
/**
* Name of the shortcode tag for rendering content snippets
*/
const CONTENT_SHORTCODE = 'code_snippet';
/**
* Handle to use for front-end scripts and styles.
*/
const PRISM_HANDLE = 'code-snippets-prism';
/**
* Maximum depth for shortcode recursion.
*/
const MAX_SHORTCODE_DEPTH = 5;
/**
* 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_style(
self::PRISM_HANDLE,
plugins_url( 'dist/prism.css', $plugin->file ),
array(),
$plugin->version
);
wp_register_script(
self::PRISM_HANDLE,
plugins_url( 'dist/prism.js', $plugin->file ),
array(),
$plugin->version,
true
);
}
/**
* 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;
}
/**
* 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;
}
/**
* Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet
* authors to use custom attributes.
*
* @phpcs:disable WordPress.PHP.DontExtract.extract_extract
*/
extract( $atts );
ob_start();
eval( "?>\n\n" . $snippet->code . "\n\n<?php" );
return ob_get_clean();
}
/**
* 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 = get_snippet( $id, (bool) $atts['network'] );
// 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 = __( '<strong>%1$s</strong> 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' );
$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 = get_snippet( $id, (bool) $atts['network'] );
return $this->render_snippet_source( $snippet, $atts );
}
}
front-end/mce-strings.php 0000644 00000003326 15153632426 0011417 0 ustar 00 <?php
/**
* For some reason, WordPress requires that TinyMCE translations be hosted in an external file. So that's what this is.
*
* @package Code_Snippets
*/
namespace Code_Snippets;
use _WP_Editors;
/**
* Variable types.
*
* @var array<string, string|array<string, Snippet[]>> $strings
*/
$strings = [
'insert_content_menu' => __( 'Content Snippet', 'code-snippets' ),
'insert_content_title' => __( 'Insert Content Snippet', 'code-snippets' ),
'snippet_label' => __( 'Snippet', 'code-snippets' ),
'php_att_label' => __( 'Run PHP code', 'code-snippets' ),
'format_att_label' => __( 'Apply formatting', 'code-snippets' ),
'shortcodes_att_label' => __( 'Enable shortcodes', 'code-snippets' ),
'insert_source_menu' => __( 'Snippet Source Code', 'code-snippets' ),
'insert_source_title' => __( 'Insert Snippet Source', 'code-snippets' ),
'show_line_numbers_label' => __( 'Show line numbers', 'code-snippets' ),
];
$strings = array_map( 'esc_js', $strings );
$snippets = get_snippets();
$strings['all_snippets'] = [];
$strings['content_snippets'] = [];
foreach ( $snippets as $snippet ) {
if ( 'content' === $snippet->scope ) {
$strings['content_snippets'][ $snippet->id ] = $snippet->display_name;
}
$strings['all_snippets'][ $snippet->id ] = sprintf(
'%s (%s)',
$snippet->display_name,
strtoupper( $snippet->type )
);
}
asort( $strings['all_snippets'], SORT_STRING | SORT_FLAG_CASE );
asort( $strings['content_snippets'], SORT_STRING | SORT_FLAG_CASE );
$strings = [ _WP_Editors::$mce_locale => [ 'code_snippets' => $strings ] ];
/** $strings is used by outer file. @noinspection PhpUnusedLocalVariableInspection */
$strings = 'tinyMCE.addI18n(' . wp_json_encode( $strings ) . ');';
load.php 0000644 00000002670 15153632426 0006210 0 ustar 00 <?php
/**
* Initialise and load the plugin under the proper namespace.
*
* @package Code_Snippets
*/
namespace Code_Snippets;
/**
* The version number for this release of the plugin.
* This will later be used for upgrades and enqueuing files.
*
* This should be set to the 'Plugin Version' value defined
* in the plugin header.
*
* @var string A PHP-standardized version number string.
*/
const PLUGIN_VERSION = CODE_SNIPPETS_VERSION;
/**
* The full path to the main file of this plugin.
*
* This can later be used with functions such as
* plugin_dir_path(), plugins_url() and plugin_basename()
* to retrieve information about plugin paths.
*
* @var string
*/
const PLUGIN_FILE = CODE_SNIPPETS_FILE;
/**
* Name of the group used for caching data.
*
* @var string
*/
const CACHE_GROUP = 'code_snippets';
/**
* Namespace used for REST API endpoints.
*
* @var string
*/
const REST_API_NAMESPACE = 'code-snippets/v';
// Load dependencies with Composer.
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
/**
* Retrieve the instance of the main plugin class.
*
* @return Plugin
* @since 2.6.0
*/
function code_snippets(): Plugin {
static $plugin;
if ( is_null( $plugin ) ) {
$plugin = new Plugin( PLUGIN_VERSION, PLUGIN_FILE );
}
return $plugin;
}
code_snippets()->load_plugin();
// Execute the snippets once the plugins are loaded.
add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets', 1 );
rest-api/class-snippets-rest-controller.php 0000644 00000036051 15153632426 0015121 0 ustar 00 <?php
namespace Code_Snippets\REST_API;
use Code_Snippets\Export;
use Code_Snippets\Snippet;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use function Code_Snippets\activate_snippet;
use function Code_Snippets\code_snippets;
use function Code_Snippets\deactivate_snippet;
use function Code_Snippets\delete_snippet;
use function Code_Snippets\get_snippet;
use function Code_Snippets\get_snippets;
use function Code_Snippets\save_snippet;
use const Code_Snippets\REST_API_NAMESPACE;
/**
* Allows fetching snippet data through the WordPress REST API.
*
* @since 3.4.0
* @package Code_Snippets
*/
final class Snippets_REST_Controller extends WP_REST_Controller {
/**
* Current API version.
*/
const VERSION = 1;
/**
* The base of this controller's route.
*/
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' ]
);
register_rest_route(
$this->namespace,
$route,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'get_items_permissions_check' ],
'args' => $network_args,
],
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'create_item' ],
'permission_callback' => [ $this, 'create_item_permissions_check' ],
'args' => $this->get_endpoint_args_for_item_schema( true ),
],
'schema' => [ $this, 'get_item_schema' ],
]
);
register_rest_route(
$this->namespace,
$id_route,
[
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
'args' => $network_args,
],
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'update_item' ],
'permission_callback' => [ $this, 'update_item_permissions_check' ],
'args' => $this->get_endpoint_args_for_item_schema( false ),
],
[
'methods' => WP_REST_Server::DELETABLE,
'callback' => [ $this, 'delete_item' ],
'permission_callback' => [ $this, 'delete_item_permissions_check' ],
'args' => $network_args,
],
'schema' => [ $this, 'get_item_schema' ],
]
);
register_rest_route(
$this->namespace,
$route . '/schema',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'get_public_item_schema' ],
'permission_callback' => '__return_true',
]
);
register_rest_route(
$this->namespace,
$id_route . '/activate',
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'activate_item' ],
'permission_callback' => [ $this, 'update_item_permissions_check' ],
'schema' => [ $this, 'get_item_schema' ],
'args' => $network_args,
]
);
register_rest_route(
$this->namespace,
$id_route . '/deactivate',
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'deactivate_item' ],
'permission_callback' => [ $this, 'update_item_permissions_check' ],
'schema' => [ $this, 'get_item_schema' ],
'args' => $network_args,
]
);
register_rest_route(
$this->namespace,
$id_route . '/export',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'export_item' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
'schema' => [ $this, 'get_item_schema' ],
'args' => $network_args,
]
);
register_rest_route(
$this->namespace,
$id_route . '/export-code',
[
'methods' => WP_REST_Server::READABLE,
'callback' => [ $this, 'export_item_code' ],
'permission_callback' => [ $this, 'get_item_permissions_check' ],
'schema' => [ $this, 'get_item_schema' ],
'args' => $network_args,
]
);
}
/**
* Retrieves a collection of snippets.
*
* @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 {
$snippets = get_snippets();
$snippets_data = [];
foreach ( $snippets as $snippet ) {
$snippet_data = $this->prepare_item_for_response( $snippet, $request );
$snippets_data[] = $this->prepare_response_for_collection( $snippet_data );
}
return rest_ensure_response( $snippets_data );
}
/**
* Retrieves one item from the collection.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response|WP_Error Response object on success.
*/
public function get_item( $request ) {
$snippet_id = $request->get_param( 'id' );
$item = get_snippet( $snippet_id, $request->get_param( 'network' ) );
if ( ! $item->id && 0 !== $snippet_id && '0' !== $snippet_id ) {
return new WP_Error(
'rest_cannot_get',
__( 'The snippet could not be found.', 'code-snippets' ),
[ 'status' => 500 ]
);
}
$data = $this->prepare_item_for_response( $item, $request );
return rest_ensure_response( $data );
}
/**
* Create one item from the collection
*
* @param WP_REST_Request|array $request Full data about the request.
*
* @return WP_REST_Response|WP_Error
*/
public function create_item( $request ) {
$snippet = $this->prepare_item_for_database( $request );
$result = $snippet ? save_snippet( $snippet ) : null;
return $result ?
$this->prepare_item_for_response( $result, $request ) :
new WP_Error(
'rest_cannot_create',
__( 'The snippet could not be created.', 'code-snippets' ),
[ 'status' => 500 ]
);
}
/**
* Update one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$snippet_id = absint( $request->get_param( 'id' ) );
$snippet = $snippet_id ? get_snippet( $snippet_id, $request->get_param( 'network' ) ) : null;
if ( ! $snippet_id || ! $snippet || ! $snippet->id ) {
return new WP_Error(
'rest_cannot_update',
__( 'Cannot update a snippet without a valid ID.', 'code-snippets' ),
[ 'status' => 400 ]
);
}
$item = $this->prepare_item_for_database( $request, $snippet );
$result = save_snippet( $item );
if ( $result ) {
$request->set_param( 'id', $result->id );
return $this->get_item( $request );
}
return new WP_Error(
'rest_cannot_update',
__( 'The snippet could not be updated.', 'code-snippets' ),
[ 'status' => 500 ]
);
}
/**
* Delete one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$item = $this->prepare_item_for_database( $request );
$result = delete_snippet( $item->id, $item->network );
return $result ?
new WP_REST_Response( null, 204 ) :
new WP_Error(
'rest_cannot_delete',
__( 'The snippet could not be deleted.', 'code-snippets' ),
[ 'status' => 500 ]
);
}
/**
* Activate one item in the collection.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function activate_item( WP_REST_Request $request ) {
$item = $this->prepare_item_for_database( $request );
$result = activate_snippet( $item->id, $item->network );
return $result instanceof Snippet ?
rest_ensure_response( $result ) :
new WP_Error(
'rest_cannot_activate',
$result,
[ 'status' => 500 ]
);
}
/**
* Deactivate one item in the collection.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function deactivate_item( WP_REST_Request $request ) {
$item = $this->prepare_item_for_database( $request );
$result = deactivate_snippet( $item->id, $item->network );
return $result instanceof Snippet ?
rest_ensure_response( $result ) :
new WP_Error(
'rest_cannot_activate',
__( 'The snippet could not be deactivated.', 'code-snippets' ),
[ 'status' => 500 ]
);
}
/**
* Prepare an instance of the Export class from a request.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return Export
*/
protected function build_export( WP_REST_Request $request ): Export {
$item = $this->prepare_item_for_database( $request );
return new Export( [ $item->id ], $item->network );
}
/**
* Retrieve one item in the collection in JSON export format.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function export_item( WP_REST_Request $request ) {
$export = $this->build_export( $request );
$result = $export->create_export_object();
return rest_ensure_response( $result );
}
/**
* Retrieve one item in the collection in the code export format.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function export_item_code( WP_REST_Request $request ) {
$export = $this->build_export( $request );
$result = $export->export_snippets_code();
return rest_ensure_response( $result );
}
/**
* Prepares one item for create or update operation.
*
* @param WP_REST_Request $request Request object.
* @param Snippet|null $item Existing item to augment.
*
* @return Snippet The prepared item.
*/
protected function prepare_item_for_database( $request, ?Snippet $item = null ): ?Snippet {
if ( ! $item instanceof Snippet ) {
$item = new Snippet();
}
foreach ( $item->get_allowed_fields() as $field ) {
if ( isset( $request[ $field ] ) ) {
$item->set_field( $field, $request[ $field ] );
}
}
return $item;
}
/**
* Prepare the item for the REST response.
*
* @param Snippet $item Snippet object.
* @param WP_REST_Request $request Request object.
*
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function prepare_item_for_response( $item, $request ) {
$schema = $this->get_item_schema();
$response = [];
foreach ( array_keys( $schema['properties'] ) as $property ) {
$response[ $property ] = $item->$property;
}
return rest_ensure_response( $response );
}
/**
* Check if a given request has access to get items.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return boolean
*/
public function get_items_permissions_check( $request ): bool {
return code_snippets()->current_user_can();
}
/**
* Check if a given request has access to get a specific item.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return boolean
*/
public function get_item_permissions_check( $request ): bool {
return $this->get_items_permissions_check( $request );
}
/**
* Check if a given request has access to create items.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return boolean
*/
public function create_item_permissions_check( $request ): bool {
return code_snippets()->current_user_can();
}
/**
* Check if a given request has access to update a specific item.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return boolean
*/
public function update_item_permissions_check( $request ): bool {
return $this->create_item_permissions_check( $request );
}
/**
* Check if a given request has access to delete a specific item.
*
* @param WP_REST_Request $request Full data about the request.
*
* @return boolean
*/
public function delete_item_permissions_check( $request ): bool {
return $this->create_item_permissions_check( $request );
}
/**
* Get our sample schema for a post.
*
* @return array<string, mixed> The sample schema for a post
*/
public function get_item_schema(): array {
if ( $this->schema ) {
return $this->schema;
}
$this->schema = [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'snippet',
'type' => 'object',
'properties' => [
'id' => [
'description' => esc_html__( 'Unique identifier for the snippet.', 'code-snippets' ),
'type' => 'integer',
'readonly' => true,
],
'name' => [
'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ),
'type' => 'string',
],
'desc' => [
'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ),
'type' => 'string',
],
'code' => [
'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ),
'type' => 'string',
],
'tags' => [
'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ),
'type' => 'array',
],
'scope' => [
'description' => esc_html__( 'Context in which the snippet is executable.', 'code-snippets' ),
'type' => 'string',
],
'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',
'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',
],
'modified' => [
'description' => esc_html__( 'Date and time when the snippet was last modified, in ISO format.', 'code-snippets' ),
'type' => 'string',
],
],
];
return $this->schema;
}
}
settings/class-setting-field.php 0000644 00000014635 15153632426 0012776 0 ustar 00 <?php
/**
* This file handles rendering the settings fields
*
* @since 2.0.0
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
/**
* Represents a single setting field
*
* @property-read string $desc Field description.
* @property-read string $label Field label.
* @property-read string $type Field type.
* @property-read string $name Setting name.
*
* @property-read int $min Minimum value (for numerical inputs).
* @property-read int $max Maximum value(for numerical inputs).
* @property-read array<string, string> $options List of options for a select or checkboxes field.
* @property-read callable $render_callback Custom function to use when rendering a callback field.
* @property-read callable $sanitize_callback Custom function to use when sanitize the setting value.
* @property-read mixed $default Default setting value.
*
* @property-read string $input_name Value of `name` HTML attribute on an input element.
* @property-read string $element_id
*/
class Setting_Field {
/**
* Input field identifier.
*
* @var string
*/
private string $field_id;
/**
* Settings section identifier.
*
* @var string
*/
private string $section;
/**
* List of possible arguments.
*
* @var array<string, mixed>
*/
private array $args = array(
'desc' => '',
'label' => '',
'min' => null,
'max' => null,
'options' => [],
);
/**
* Class constructor.
*
* @param string $section_id Settings section identifier.
* @param string $field_id Setting field identifier.
* @param array<string, mixed> $args The setting field attributes.
*/
public function __construct( string $section_id, string $field_id, array $args ) {
$this->field_id = $field_id;
$this->section = $section_id;
$this->args = array_merge( $this->args, $args );
}
/**
* Retrieve a single setting attribute.
*
* @param string $argument Attribute name.
*
* @return mixed Attribute value.
*/
public function __get( string $argument ) {
if ( 'input_name' === $argument ) {
return sprintf( '%s[%s][%s]', OPTION_NAME, $this->section, $this->field_id );
}
return $this->args[ $argument ];
}
/**
* Retrieve the saved value for this setting.
*
* @return mixed
*/
private function get_saved_value() {
return get_setting( $this->section, $this->field_id );
}
/**
* Render the setting field
*/
public function render() {
$method_name = 'render_' . $this->type . '_field';
if ( method_exists( $this, $method_name ) ) {
call_user_func( array( $this, $method_name ) );
} else {
// Error message, not necessary to translate.
printf( 'Cannot render a %s field.', esc_html( $this->type ) );
return;
}
if ( $this->desc ) {
echo '<p class="description">', wp_kses_post( $this->desc ), '</p>';
}
}
/**
* Render a callback field.
*/
public function render_callback_field() {
call_user_func( $this->render_callback );
}
/**
* Render a single checkbox field.
*
* @param string $input_name Input name.
* @param string $label Input label.
* @param boolean $checked Whether the checkbox should be checked.
*/
private static function render_checkbox( string $input_name, string $label, bool $checked ) {
$checkbox = sprintf(
'<input type="checkbox" name="%s"%s>',
esc_attr( $input_name ),
checked( $checked, true, false )
);
$kses = [
'input' => [
'type' => [],
'name' => [],
'checked' => [],
],
];
if ( $label ) {
printf(
'<label>%s %s</label>',
wp_kses( $checkbox, $kses ),
wp_kses_post( $label )
);
} else {
echo wp_kses( $checkbox, $kses );
}
}
/**
* Render a checkbox field for a setting
*
* @return void
* @since 2.0.0
*/
public function render_checkbox_field() {
$this->render_checkbox( $this->input_name, $this->label, $this->get_saved_value() ?? false );
}
/**
* Render a checkbox field for a setting
*
* @return void
* @since 2.0.0
*/
public function render_checkboxes_field() {
$saved_value = $this->get_saved_value();
$saved_value = is_array( $saved_value ) ? $saved_value : [];
echo '<fieldset>';
printf( '<legend class="screen-reader-text"><span>%s</span></legend>', esc_html( $this->name ) );
foreach ( $this->options as $option => $label ) {
$this->render_checkbox( $this->input_name . "[$option]", $label, in_array( $option, $saved_value, true ) );
echo '<br>';
}
echo '</fieldset>';
}
/**
* Render a basic text field for an editor setting.
*
* @return void
*/
private function render_text_field() {
printf(
'<input id="%s" type="text" name="%s" value="%s" class="regular-text %s">',
esc_attr( $this->element_id ),
esc_attr( $this->input_name ),
esc_attr( $this->get_saved_value() ),
esc_attr( $this->element_id )
);
if ( $this->label ) {
echo ' ' . wp_kses_post( $this->label );
}
}
/**
* Render a number select field for an editor setting
*
* @since 2.0.0
*/
private function render_number_field() {
printf(
'<input type="number" name="%s" value="%s"',
esc_attr( $this->input_name ),
esc_attr( $this->get_saved_value() )
);
if ( is_numeric( $this->min ) ) {
printf( ' min="%d"', intval( $this->min ) );
}
if ( is_numeric( $this->max ) ) {
printf( ' max="%d"', intval( $this->max ) );
}
echo '>';
if ( $this->label ) {
echo ' ' . wp_kses_post( $this->label );
}
}
/**
* Render a number select field for an editor setting.
*
* @since 3.0.0
*/
private function render_select_field() {
$saved_value = $this->get_saved_value();
printf( '<select name="%s">', esc_attr( $this->input_name ) );
foreach ( $this->options as $option => $option_label ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $option ),
selected( $option, $saved_value, false ),
esc_html( $option_label )
);
}
echo '</select>';
}
/**
* Render a button link.
*
* @since 3.5.1
*/
private function render_action_field() {
printf(
'<button type="submit" name="%s" class="button">%s</button>',
esc_attr( $this->input_name ),
esc_html( $this->label ? $this->label : $this->name )
);
}
}
settings/editor-preview.php 0000644 00000005620 15153632426 0012074 0 ustar 00 <?php
/**
* This file handles the editor preview setting
*
* @since 2.0.0
* @package Code_Snippets
*/
namespace Code_Snippets\Settings;
use function Code_Snippets\code_snippets;
use function Code_Snippets\enqueue_code_editor;
use function Code_Snippets\get_editor_themes;
/**
* Load the CSS and JavaScript for the editor preview field
*/
function enqueue_editor_preview_assets() {
$plugin = code_snippets();
enqueue_code_editor( 'php' );
// Enqueue all editor themes.
$themes = get_editor_themes();
foreach ( $themes as $theme ) {
wp_enqueue_style(
'code-snippets-editor-theme-' . $theme,
plugins_url( "dist/editor-themes/$theme.css", $plugin->file ),
[ 'code-editor' ],
$plugin->version
);
}
// Enqueue the menu scripts.
wp_enqueue_script(
'code-snippets-settings-menu',
plugins_url( 'dist/settings.js', $plugin->file ),
[ 'code-snippets-code-editor' ],
$plugin->version,
true
);
wp_set_script_translations( 'code-snippets-settings-menu', 'code-snippets' );
// Extract the CodeMirror-specific editor settings.
$setting_fields = get_settings_fields();
$editor_fields = array();
foreach ( $setting_fields['editor'] as $name => $field ) {
if ( empty( $field['codemirror'] ) ) {
continue;
}
$editor_fields[] = array(
'name' => $name,
'type' => $field['type'],
'codemirror' => addslashes( $field['codemirror'] ),
);
}
// Pass the saved options to the external JavaScript file.
$inline_script = 'var code_snippets_editor_settings = ' . wp_json_encode( $editor_fields ) . ';';
wp_add_inline_script( 'code-snippets-settings-menu', $inline_script, 'before' );
}
/**
* Retrieve the list of code editor themes.
*
* @return array<string, string> List of editor themes.
*/
function get_editor_theme_list(): array {
$themes = [
'default' => __( 'Default', 'code-snippets' ),
];
foreach ( get_editor_themes() as $theme ) {
// Skip mobile themes.
if ( '-mobile' === substr( $theme, -7 ) ) {
continue;
}
$themes[ $theme ] = ucwords( str_replace( '-', ' ', $theme ) );
}
return $themes;
}
/**
* Render the editor preview setting
*/
function render_editor_preview() {
$settings = get_settings_values();
$settings = $settings['editor'];
$indent_unit = absint( $settings['indent_unit'] );
$tab_size = absint( $settings['tab_size'] );
$n_tabs = $settings['indent_with_tabs'] ? floor( $indent_unit / $tab_size ) : 0;
$n_spaces = $settings['indent_with_tabs'] ? $indent_unit % $tab_size : $indent_unit;
$indent = str_repeat( "\t", $n_tabs ) . str_repeat( ' ', $n_spaces );
$code = "add_filter( 'admin_footer_text', function ( \$text ) {\n\n" .
$indent . "\$site_name = get_bloginfo( 'name' );\n\n" .
$indent . '$text = "Thank you for visiting $site_name.";' . "\n" .
$indent . 'return $text;' . "\n" .
"} );\n";
echo '<textarea id="code_snippets_editor_preview">', esc_textarea( $code ), '</textarea>';
}
settings/settings-fields.php 0000644 00000017074 15153632426 0012241 0 ustar 00 <?php
/**
* Manages the settings field definitions.
*
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
use function Code_Snippets\code_snippets;
/**
* Retrieve the default setting values
*
* @return array<string, array<string, mixed>>
*/
function get_default_settings(): array {
static $defaults;
if ( isset( $defaults ) ) {
return $defaults;
}
$defaults = [
'general' => [
'activate_by_default' => true,
'enable_tags' => true,
'enable_description' => true,
'visual_editor_rows' => 5,
'list_order' => 'priority-asc',
'disable_prism' => false,
'hide_upgrade_menu' => false,
'complete_uninstall' => false,
],
'editor' => [
'indent_with_tabs' => true,
'tab_size' => 4,
'indent_unit' => 4,
'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',
],
];
$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' ),
],
];
$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 Menu', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Hide the Upgrade button from the admin menu.', '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,
],
'wrap_lines' => [
'name' => __( 'Wrap Lines', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Soft-wrap long lines of code instead of horizontally scrolling.', 'code-snippets' ),
'codemirror' => 'lineWrapping',
],
'code_folding' => [
'name' => __( 'Code Folding', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Allow folding functions or other blocks into a single line.', 'code-snippets' ),
'codemirror' => 'foldGutter',
],
'line_numbers' => [
'name' => __( 'Line Numbers', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Show line numbers to the left of the editor.', 'code-snippets' ),
'codemirror' => 'lineNumbers',
],
'auto_close_brackets' => [
'name' => __( 'Auto Close Brackets', 'code-snippets' ),
'type' => 'checkbox',
'label' => __( 'Auto-close brackets and quotes when typed.', 'code-snippets' ),
'codemirror' => 'autoCloseBrackets',
],
'highlight_selection_matches' => [
'name' => __( 'Highlight Selection Matches', 'code-snippets' ),
'label' => __( 'Highlight all instances of a currently selected word.', 'code-snippets' ),
'type' => 'checkbox',
'codemirror' => 'highlightSelectionMatches',
],
'highlight_active_line' => [
'name' => __( 'Highlight Active Line', 'code-snippets' ),
'label' => __( 'Highlight the line that is currently being edited.', 'code-snippets' ),
'type' => 'checkbox',
'codemirror' => 'styleActiveLine',
],
'keymap' => [
'name' => __( 'Keymap', 'code-snippets' ),
'type' => 'select',
'desc' => __( 'The set of keyboard shortcuts to use in the code editor.', 'code-snippets' ),
'options' => [
'default' => __( 'Default', 'code-snippets' ),
'vim' => __( 'Vim', 'code-snippets' ),
'emacs' => __( 'Emacs', 'code-snippets' ),
'sublime' => __( 'Sublime Text', 'code-snippets' ),
],
'codemirror' => 'keyMap',
],
'theme' => [
'name' => __( 'Theme', 'code-snippets' ),
'type' => 'select',
'options' => get_editor_theme_list(),
'codemirror' => 'theme',
],
];
$fields = apply_filters( 'code_snippets_settings_fields', $fields );
return $fields;
}
settings/settings.php 0000644 00000022633 15153632426 0010772 0 ustar 00 <?php
/**
* This file registers the settings
*
* @package Code_Snippets
* @subpackage Settings
*/
namespace Code_Snippets\Settings;
use Code_Snippets\Welcome_API;
use function Code_Snippets\clean_snippets_cache;
use function Code_Snippets\code_snippets;
const CACHE_KEY = 'code_snippets_settings';
const OPTION_GROUP = 'code-snippets';
const OPTION_NAME = 'code_snippets_settings';
/**
* Add a new option for either the current site or the current network
*
* @param bool $network Whether to add a network-wide option.
* @param string $option Name of option to add. Expected to not be SQL-escaped.
* @param mixed $value Option value, can be anything. Expected to not be SQL-escaped.
*
* @return bool False if the option was not added. True if the option was added.
*/
function add_self_option( bool $network, string $option, $value ): bool {
return $network ? add_site_option( $option, $value ) : add_option( $option, $value );
}
/**
* Retrieves an option value based on an option name from either the current site or the current network
*
* @param bool $network Whether to get a network-wide option.
* @param string $option Name of option to retrieve. Expected to not be SQL-escaped.
* @param mixed $default_value Optional value to return if option doesn't exist. Default false.
*
* @return mixed Value set for the option.
*/
function get_self_option( bool $network, string $option, $default_value = false ) {
return $network ? get_site_option( $option, $default_value ) : get_option( $option, $default_value );
}
/**
* Update the value of an option that was already added on the current site or the current network
*
* @param bool $network Whether to update a network-wide option.
* @param string $option Name of option. Expected to not be SQL-escaped.
* @param mixed $value Option value. Expected to not be SQL-escaped.
*
* @return bool False if value was not updated. True if value was updated.
*/
function update_self_option( bool $network, string $option, $value ): bool {
return $network ? update_site_option( $option, $value ) : update_option( $option, $value );
}
/**
* Returns 'true' if plugin settings are unified on a multisite installation
* under the Network Admin settings menu
*
* This option is controlled by the "Enable administration menus" setting on the Network Settings menu
*
* @return bool
*/
function are_settings_unified(): bool {
if ( ! is_multisite() ) {
return false;
}
$menu_perms = get_site_option( 'menu_items', array() );
return empty( $menu_perms['snippets_settings'] );
}
/**
* Retrieve the setting values from the database.
* If a setting does not exist in the database, the default value will be returned.
*
* @return array<string, array<string, mixed>>
*/
function get_settings_values(): array {
$settings = wp_cache_get( CACHE_KEY );
if ( $settings ) {
return $settings;
}
$settings = get_default_settings();
$saved = get_self_option( are_settings_unified(), OPTION_NAME, array() );
foreach ( $settings as $section => $fields ) {
if ( isset( $saved[ $section ] ) ) {
$settings[ $section ] = array_replace( $fields, $saved[ $section ] );
}
}
wp_cache_set( CACHE_KEY, $settings );
return $settings;
}
/**
* Retrieve an individual setting field value
*
* @param string $section ID of the section the setting belongs to.
* @param string $field ID of the setting field.
*
* @return mixed
*/
function get_setting( string $section, string $field ) {
$settings = get_settings_values();
return $settings[ $section ][ $field ] ?? null;
}
/**
* Update a single setting to a new value.
*
* @param string $section ID of the section the setting belongs to.
* @param string $field ID of the setting field.
* @param mixed $new_value Setting value. Expected to not be SQL-escaped.
*
* @return bool False if value was not updated. True if value was updated.
*/
function update_setting( string $section, string $field, $new_value ): bool {
$settings = get_settings_values();
$settings[ $section ][ $field ] = $new_value;
wp_cache_set( CACHE_KEY, $settings );
return update_self_option( are_settings_unified(), OPTION_NAME, $settings );
}
/**
* Retrieve the settings sections
*
* @return array<string, string> Settings sections.
*/
function get_settings_sections(): array {
$sections = array(
'general' => __( 'General', 'code-snippets' ),
'editor' => __( 'Code Editor', 'code-snippets' ),
'debug' => __( 'Debug', 'code-snippets' ),
);
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.
foreach ( get_settings_fields() as $section_id => $fields ) {
foreach ( $fields as $field_id => $field ) {
$field_object = new Setting_Field( $section_id, $field_id, $field );
add_settings_field( $field_id, $field['name'], [ $field_object, 'render' ], 'code-snippets', $section_id );
}
}
// Add editor preview as a field.
add_settings_field(
'editor_preview',
__( 'Editor Preview', 'code-snippets' ),
__NAMESPACE__ . '\\render_editor_preview',
'code-snippets',
'editor'
);
}
add_action( 'admin_init', __NAMESPACE__ . '\\register_plugin_settings' );
/**
* Sanitize a single setting value.
*
* @param array<string, mixed> $field Setting field information.
* @param mixed $input_value User input setting value, or null if missing.
*
* @return mixed Sanitized setting value, or null if unset.
*/
function sanitize_setting_value( array $field, $input_value ) {
switch ( $field['type'] ) {
case 'checkbox':
return 'on' === $input_value;
case 'number':
return intval( $input_value );
case 'select':
$select_options = array_map( 'strval', array_keys( $field['options'] ) );
return in_array( strval( $input_value ), $select_options, true ) ? $input_value : null;
case 'checkboxes':
$results = [];
if ( ! empty( $input_value ) ) {
foreach ( $field['options'] as $option_id => $option_label ) {
if ( isset( $input_value[ $option_id ] ) && 'on' === $input_value[ $option_id ] ) {
$results[] = $option_id;
}
}
}
return $results;
case 'text':
case 'hidden':
return trim( sanitize_text_field( $input_value ) );
case 'callback':
return isset( $field['sanitize_callback'] ) && is_callable( $field['sanitize_callback'] ) ?
call_user_func( $field['sanitize_callback'], $input_value ) :
null;
default:
return null;
}
}
/**
* Process settings actions.
*
* @param array $input Provided settings input.
*
* @return array|null New $input value to return, or null to continue with settings update process.
*/
function process_settings_actions( array $input ): ?array {
if ( isset( $input['reset_settings'] ) ) {
add_settings_error(
OPTION_NAME,
'settings_reset',
__( 'All settings have been reset to their defaults.', 'code-snippets' ),
'updated'
);
delete_option( 'code_snippets_cloud_settings' );
return [];
}
if ( isset( $input['debug']['database_update'] ) ) {
code_snippets()->db->create_or_upgrade_tables();
add_settings_error(
OPTION_NAME,
'database_update_done',
__( 'Successfully performed database table upgrade.', 'code-snippets' ),
'updated'
);
}
if ( isset( $input['debug']['reset_caches'] ) ) {
Welcome_API::clear_cache();
clean_snippets_cache( code_snippets()->db->get_table_name( false ) );
if ( is_multisite() ) {
clean_snippets_cache( code_snippets()->db->get_table_name( true ) );
}
add_settings_error(
OPTION_NAME,
'snippet_caches_reset',
__( 'Successfully reset snippets caches.', 'code-snippets' ),
'updated'
);
}
return null;
}
/**
* Validate the settings
*
* @param array<string, array<string, mixed>> $input The received settings.
*
* @return array<string, array<string, mixed>> The validated settings.
*/
function sanitize_settings( array $input ): array {
wp_cache_delete( CACHE_KEY );
$result = process_settings_actions( $input );
if ( ! is_null( $result ) ) {
return $result;
}
$settings = get_settings_values();
$updated = false;
// Don't directly loop through $input as it does not include as deselected checkboxes.
foreach ( get_settings_fields() as $section_id => $fields ) {
foreach ( $fields as $field_id => $field ) {
// Fetch the corresponding input value from the posted data.
$input_value = $input[ $section_id ][ $field_id ] ?? null;
// Attempt to sanitize the setting value.
$sanitized_value = sanitize_setting_value( $field, $input_value );
if ( ! is_null( $sanitized_value ) && $settings[ $section_id ][ $field_id ] !== $sanitized_value ) {
$settings[ $section_id ][ $field_id ] = $sanitized_value;
$updated = true;
}
}
}
// Add an updated message.
if ( $updated ) {
add_settings_error(
OPTION_NAME,
'settings-saved',
__( 'Settings saved.', 'code-snippets' ),
'updated'
);
}
return $settings;
}
snippet-ops.php 0000644 00000051750 15153632426 0007555 0 ustar 00 <?php
/**
* Functions to perform snippet operations
*
* @package Code_Snippets
*/
namespace Code_Snippets;
use Code_Snippets\REST_API\Snippets_REST_Controller;
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_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 );
}
$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 );
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 active.
$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 );
$result = $wpdb->delete(
$table,
array( 'id' => $id ),
array( '%d' )
);
if ( $result ) {
do_action( 'code_snippets/delete_snippet', $id, $network );
clean_snippets_cache( $table );
code_snippets()->cloud_api->delete_snippet_from_transient_data( $id );
}
return (bool) $result;
}
/**
* Test snippet code for errors, augmenting the snippet object.
*
* @param Snippet $snippet Snippet object.
*/
function test_snippet_code( Snippet $snippet ) {
$snippet->code_error = null;
if ( 'php' !== $snippet->type ) {
return;
}
$validator = new Validator( $snippet->code );
$result = $validator->validate();
if ( $result ) {
$snippet->code_error = [ $result['message'], $result['line'] ];
}
if ( ! $snippet->code_error && 'single-use' !== $snippet->scope ) {
$result = execute_snippet( $snippet->code, $snippet->id, true );
if ( $result instanceof ParseError ) {
$snippet->code_error = [
ucfirst( rtrim( $result->getMessage(), '.' ) ) . '.',
$result->getLine(),
];
}
}
}
/**
* Saves a snippet to the database.
* Write operation.
*
* @param Snippet|array<string, mixed> $snippet The snippet to add/update to the database.
*
* @return Snippet|null Updated snippet.
*
* @since 2.0.0
*/
function save_snippet( $snippet ) {
global $wpdb;
$table = code_snippets()->db->get_table_name( $snippet->network );
if ( ! $snippet instanceof Snippet ) {
$snippet = new Snippet( $snippet );
}
// Update the last modification date if necessary.
$snippet->update_modified();
$snippet->increment_revision();
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;
}
}
}
// 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,
'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 ) {
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;
}
/**
* Run the active snippets.
* Read-write-execute operation.
*
* @return bool true on success, false on failure.
*
* @since 2.0.0
*/
function execute_active_snippets(): bool {
global $wpdb;
// Bail early if safe mode is active.
if ( ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ||
! apply_filters( 'code_snippets/execute_snippets', true ) ) {
return false;
}
$db = code_snippets()->db;
$scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' );
$data = $db->fetch_active_snippets( $scopes );
// Detect if a snippet is currently being edited, and if so, spare it from execution.
$edit_id = 0;
$edit_table = $db->table;
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'] ) ?
$db->ms_table : $db->table;
}
}
}
foreach ( $data as $table_name => $active_snippets ) {
// Loop through the returned snippets and execute the PHP code.
foreach ( $active_snippets as $snippet ) {
$snippet_id = intval( $snippet['id'] );
$code = $snippet['code'];
// If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens.
if ( 'single-use' === $snippet['scope'] ) {
$active_shared_ids = get_option( 'active_shared_network_snippets', array() );
if ( $table_name === $db->ms_table && is_array( $active_shared_ids ) && 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,
array( 'active' => '0' ),
array( 'id' => $snippet_id ),
array( '%d' ),
array( '%d' )
);
clean_snippets_cache( $table_name );
}
}
if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) &&
! ( $edit_id === $snippet_id && $table_name === $edit_table ) ) {
execute_snippet( $code, $snippet_id );
}
}
}
return true;
}
/**
* 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 );
}
strings.php 0000644 00000003470 15153632426 0006761 0 ustar 00 <?php
/**
* Additional strings needed for translation, but not currently present within code.
*
* @package Code_Snippets
*/
__( 'You can now safely remove the free version of Code Snippets', 'code-snippets' );
__( 'Success', 'code-snippets' );
__( 'Notice', 'code-snippets' );
__( 'Thanks', 'code-snippets' );
__( 'Okay', 'code-snippets' );
// settings-fields.php.
__( 'Minify Snippet Output', 'code-snippets' );
__( 'Minify snippet output by removing whitespace and optimising code to reduce load times.', 'code-snippets' );
// edit.php.
__( 'View Full Stylesheet', 'code-snippets' );
__( 'View Full Script', 'code-snippets' );
array(
'site-css' => __( 'Site front-end stylesheet', 'code-snippets' ),
'admin-css' => __( 'Administration area stylesheet', 'code-snippets' ),
'site-head-js' => __( 'JavaScript loaded in the site &lt;head&gt; section', 'code-snippets' ),
'site-footer-js' => __( 'JavaScript loaded just before the closing &lt;/body&gt; tag', 'code-snippets' ),
);
// class-content-widget.php.
__( 'Processing Options', 'code-snippets' );
__( 'Alignment', 'code-snippets' );
__( 'Left', 'code-snippets' );
__( 'Center', 'code-snippets' );
__( 'Right', 'code-snippets' );
__( 'Justified', 'code-snippets' );
__( 'Text Color', 'code-snippets' );
__( 'Select a snippet to show', 'code-snippets' );
// class-source-widget.php.
__( 'Code Snippet Source', 'code-snippets' );
__( 'Functions (PHP)', 'code-snippets' );
__( 'Content (Mixed)', 'code-snippets' );
__( 'Styles (CSS)', 'code-snippets' );
__( 'Scripts (JS)', 'code-snippets' );
__( 'Highlight Lines', 'code-snippets' );
__( 'Word Wrap', 'code-snippets' );
__( 'On', 'code-snippets' );
__( 'Off', 'code-snippets' );
__( 'Height', 'code-snippets' );
__( 'Font Size', 'code-snippets' );
__( 'Select a snippet to display', 'code-snippets' );
uninstall.php 0000644 00000004121 15153632426 0007273 0 ustar 00 <?php
/**
* Functions for cleaning data when the plugin is uninstalled.
*
* @package Code_Snippets
*
* phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
*/
namespace Code_Snippets\Uninstall;
/**
* Determine whether the option for allowing a complete uninstallation is enabled.
*
* @return boolean
*/
function complete_uninstall_enabled(): bool {
$unified = false;
if ( is_multisite() ) {
$menu_perms = get_site_option( 'menu_items', array() );
$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' );
}
/**
* Uninstall the Code Snippets plugin.
*
* @return void
*/
function uninstall_plugin() {
if ( complete_uninstall_enabled() ) {
if ( is_multisite() ) {
uninstall_multisite();
} else {
uninstall_current_site();
}
}
}
views/import.php 0000644 00000006764 15153632426 0007750 0 ustar 00 <?php
/**
* HTML for the Import Snippets page.
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* Loaded from import menu.
*
* @var Import_Menu $this
*/
if ( ! defined( 'ABSPATH' ) ) {
return;
}
$max_size_bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() );
?>
<div class="wrap">
<h1>
<?php
esc_html_e( 'Import Snippets', 'code-snippets' );
if ( code_snippets()->is_compact_menu() ) {
$this->render_page_title_actions( [ 'manage', 'add', 'settings' ] );
}
?>
</h1>
<?php $this->print_messages(); ?>
<div class="narrow">
<p><?php esc_html_e( 'Upload one or more Code Snippets export files and the snippets will be imported.', 'code-snippets' ); ?></p>
<p>
<?php
/* translators: %s: link to snippets admin menu */
$text = __( 'Afterward, you will need to visit the <a href="%s" >All Snippets</a> page to activate the imported snippets.', 'code-snippets' );
$url = esc_url( code_snippets()->get_menu_url( 'manage' ) );
echo wp_kses(
sprintf( $text, $url ),
array(
'a' => array(
'href' => array(),
'target' => array(),
)
)
);
?>
</p>
<form enctype="multipart/form-data" id="import-upload-form" method="post" class="wp-upload-form"
name="code_snippets_import">
<?php wp_nonce_field( 'import_code_snippets_file' ); ?>
<h2><?php esc_html_e( 'Duplicate Snippets', 'code-snippets' ); ?></h2>
<p class="description">
<?php esc_html_e( 'What should happen if an existing snippet is found with an identical name to an imported snippet?', 'code-snippets' ); ?>
</p>
<fieldset>
<p>
<label>
<input type="radio" name="duplicate_action" value="ignore" checked="checked">
<?php esc_html_e( 'Ignore any duplicate snippets: import all snippets from the file regardless and leave all existing snippets unchanged.', 'code-snippets' ); ?>
</label>
</p>
<p>
<label>
<input type="radio" name="duplicate_action" value="replace">
<?php esc_html_e( 'Replace any existing snippets with a newly imported snippet of the same name.', 'code-snippets' ); ?>
</label>
</p>
<p>
<label>
<input type="radio" name="duplicate_action" value="skip">
<?php esc_html_e( 'Do not import any duplicate snippets; leave all existing snippets unchanged.', 'code-snippets' ); ?>
</label>
</p>
</fieldset>
<h2><?php esc_html_e( 'Upload Files', 'code-snippets' ); ?></h2>
<p class="description">
<?php esc_html_e( 'Choose one or more Code Snippets (.xml or .json) files to upload, then click "Upload files and import".', 'code-snippets' ); ?>
</p>
<fieldset>
<p>
<label for="upload"><?php esc_html_e( 'Choose files from your computer:', 'code-snippets' ); ?></label>
<?php
/* translators: %s: size in bytes */
printf( esc_html__( '(Maximum size: %s)', 'code-snippets' ), esc_html( size_format( $max_size_bytes ) ) ); ?>
<input type="file" id="upload" name="code_snippets_import_files[]" size="25"
accept="application/json,.json,text/xml" multiple="multiple">
<input type="hidden" name="action" value="save">
<input type="hidden" name="max_file_size" value="<?php echo esc_attr( $max_size_bytes ); ?>">
</p>
</fieldset>
<?php
do_action( 'code_snippets/admin/import_form' );
submit_button( __( 'Upload files and import', 'code-snippets' ) );
?>
</form>
</div>
</div>
views/manage.php 0000644 00000007537 15153632426 0007665 0 ustar 00 <?php
/**
* HTML for the Manage Snippets page.
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* 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 feedback.', 'code-snippets' ),
[ 'span' => [ 'class' => [ 'highlight-yellow' ] ] ]
);
$feedback_url = __( 'mailto:team@codesnippets.pro?subject=Code Snippet Beta Test Feedback', 'code-snippets' );
printf( ' <a href="%s">%s</a>', esc_url( $feedback_url ), esc_html__( 'Click here to submit your 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
foreach ( $types as $type_name => $label ) {
Admin::render_snippet_type_tab( $type_name, $label, $current_type );
}
?>
<a class="button button-large nav-tab-button nav-tab-inactive go-pro-button"
href="https://codesnippets.pro/pricing/" target="_blank"
title="<?php esc_html_e( 'Find more about Pro (opens in external tab)', 'code-snippets' ); ?>">
<?php echo wp_kses( __( 'Upgrade to <span class="badge">Pro</span>', 'code-snippets' ), [ 'span' => [ 'class' => 'badge' ] ] ); ?>
<span class="dashicons dashicons-external"></span>
</a>
</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' ),
esc_html__( 'Learn more about style snippets →', 'code-snippets' ),
'https://codesnippets.pro/learn-css/',
],
'js' => [
__( 'Script snippets are loaded on the site front-end in a JavaScript file, either in the head or body sections.', 'code-snippets' ),
__( 'Learn more about javascript snippets →', 'code-snippets' ),
'https://codesnippets.pro/learn-js/',
],
'cloud' => [
__( 'See all your public and private snippets that are stored in your Code Snippet Cloud codevault.', 'code-snippets' ),
__( 'Learn more about Code Snippets Cloud →', 'code-snippets' ),
'https://codesnippets.cloud/getstarted/',
],
];
if ( isset( $type_info[ $current_type ] ) ) {
$info = $type_info[ $current_type ];
printf(
'<p class="snippet-type-description">%s <a href="%s" target="_blank">%s</a></p>',
esc_html( $info[0] ),
esc_url( $info[2] ),
esc_html( $info[1] )
);
}
do_action( 'code_snippets/admin/manage/before_list_table' );
$this->list_table->views();
switch ( $current_type ) {
case 'cloud_search':
include_once 'partials/cloud-search.php';
break;
default:
include_once 'partials/list-table.php';
break;
}
do_action( 'code_snippets/admin/manage', $current_type );
?>
</div>
views/partials/cloud-search.php 0000644 00000005003 15153632426 0012607 0 ustar 00 <?php
/**
* HTML for the cloud search tab
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* Loaded from manage menu.
*
* @var Manage_Menu $this
*/
$search_query = isset( $_REQUEST['cloud_search'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['cloud_search'] ) ) : '';
$cloud_select = sanitize_key( wp_unslash( $_REQUEST['cloud_select'] ?? '' ) );
?>
<p class="cloud-search-info">
<?php esc_html_e( 'Use the search bar below to search cloud snippets by entering either the name of a codevault or keywords.', 'code-snippets' ); ?>
<small>
<?php esc_html_e( '(Note: codevault name is case and spelling sensitive and only public snippets will be shown)', 'code-snippets' ); ?>
</small>
</p>
<form method="get" action="" id="cloud-search-form">
<?php List_Table::required_form_fields( 'search_box' ); ?>
<label class="screen-reader-text" for="cloud_search">
<?php esc_html_e( 'Search cloud snippets', 'code-snippets' ); ?>
</label>
<?php
if ( isset( $_REQUEST['type'] ) ) {
printf( '<input type="hidden" name="type" value="%s">', esc_attr( sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) ) );
}
?>
<div class="heading-box">
<p class="cloud-search-heading">
<label for="cloud-select-prepend"><?php esc_html_e( 'Search Cloud', 'code-snippets' ); ?></label>
</p>
</div>
<div class="input-group">
<select id="cloud-select-prepend" class="select-prepend" name="cloud_select">
<option value="term"<?php selected( $cloud_select, 'term' ); ?>>
<?php esc_html_e( 'Search by keyword(s)', 'code-snippets' ); ?>
</option>
<option value="codevault"<?php selected( $cloud_select, 'codevault' ); ?>>
<?php esc_html_e( 'Name of codevault', 'code-snippets' ); ?>
</option>
</select>
<input type="text" id="cloud_search" name="cloud_search" class="cloud_search"
value="<?php echo esc_html( $search_query ); ?>"
placeholder="<?php esc_html_e( 'e.g. Remove unused javascript…', 'code-snippets' ); ?>">
<button type="submit" id="cloud-search-submit" class="button">
<?php esc_html_e( 'Search Cloud', 'code-snippets' ); ?>
<span class="dashicons dashicons-search cloud-search"></span>
</button>
</div>
</form>
<form method="post" action="" id="cloud-search-results">
<input type="hidden"
id="code_snippets_ajax_nonce"
value="<?php echo esc_attr( wp_create_nonce( 'code_snippets_manage_ajax' ) ); ?>">
<?php
List_Table::required_form_fields();
if ( $search_query ) {
$this->cloud_search_list_table->display();
}
?>
</form>
views/partials/list-table-notices.php 0000644 00000004061 15153632426 0013743 0 ustar 00 <?php
/**
* HTML for displaying notices for the manage table.
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* Loaded from the manage menu.
*
* @var Manage_Menu $this
*/
if ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) {
?>
<div id="message" class="notice notice-error fade is-dismissible">
<p>
<?php echo wp_kses_post( __( '<strong>Warning:</strong> Safe mode is active and snippets will not execute! Remove the <code>CODE_SNIPPETS_SAFE_MODE</code> constant from <code>wp-config.php</code> to turn off safe mode.', 'code-snippets' ) ); ?>
<a href="https://help.codesnippets.pro/article/12-safe-mode" target="_blank">
<?php esc_html_e( 'Help', 'code-snippets' ); ?>
</a>
</p>
</div>
<?php
}
if ( empty( $_REQUEST['result'] ) ) {
return;
}
$result = sanitize_key( $_REQUEST['result'] );
$result_messages = apply_filters(
'code_snippets/manage/result_messages',
[
'executed' => __( 'Snippet <strong>executed</strong>.', 'code-snippets' ),
'activated' => __( 'Snippet <strong>activated</strong>.', 'code-snippets' ),
'activated-multi' => __( 'Selected snippets <strong>activated</strong>.', 'code-snippets' ),
'deactivated' => __( 'Snippet <strong>deactivated</strong>.', 'code-snippets' ),
'deactivated-multi' => __( 'Selected snippets <strong>deactivated</strong>.', 'code-snippets' ),
'deleted' => __( 'Snippet <strong>deleted</strong>.', 'code-snippets' ),
'deleted-multi' => __( 'Selected snippets <strong>deleted</strong>.', 'code-snippets' ),
'cloned' => __( 'Snippet <strong>cloned</strong>.', 'code-snippets' ),
'cloned-multi' => __( 'Selected snippets <strong>cloned</strong>.', 'code-snippets' ),
'cloud-refreshed' => __( 'Synced cloud data has been <strong>successfully</strong> refreshed.', 'code-snippets' ),
]
);
if ( isset( $result_messages[ $result ] ) ) {
printf(
'<div id="message" class="notice notice-success fade is-dismissible"><p>%s</p></div>',
wp_kses_post( $result_messages[ $result ] )
);
}
views/partials/list-table.php 0000644 00000001241 15153632426 0012276 0 ustar 00 <?php
/**
* HTML for the all snippets and codevault list table
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* Loaded from the manage menu.
*
* @var Manage_Menu $this
*/
?>
<form method="get" action="">
<?php
List_Table::required_form_fields( 'search_box' );
$this->list_table->search_box( __( 'Search Snippets', 'code-snippets' ), 'search_id' );
?>
</form>
<form method="post" action="">
<input type="hidden" id="code_snippets_ajax_nonce"
value="<?php echo esc_attr( wp_create_nonce( 'code_snippets_manage_ajax' ) ); ?>">
<?php
List_Table::required_form_fields();
$this->list_table->display();
?>
</form>
views/welcome.php 0000644 00000015547 15153632426 0010070 0 ustar 00 <?php
/**
* HTML for the welcome page.
*
* @package Code_Snippets
* @subpackage Views
*/
namespace Code_Snippets;
/**
* Loaded from the Welcome_Menu class.
*
* @var Welcome_Menu $this
*/
if ( ! defined( 'ABSPATH' ) ) {
return;
}
$hero = $this->api->get_hero_item();
$changelog_sections = [
'Added' => [
'title' => __( 'New features', 'code-snippets' ),
'icon' => 'lightbulb',
],
'Improved' => [
'title' => __( 'Improvements', 'code-snippets' ),
'icon' => 'chart-line',
],
'Fixed' => [
'title' => __( 'Bug fixes', 'code-snippets' ),
'icon' => 'buddicons-replies',
],
'Other' => [
'title' => __( 'Other', 'code-snippets' ),
'icon' => 'open-folder',
],
];
$plugin_types = [
'core' => _x( 'Core', 'badge label', 'code-snippets' ),
'pro' => _x( 'Pro', 'badge label', 'code-snippets' ),
];
?>
<div class="csp-welcome-wrap">
<div class="csp-welcome-header">
<header class="csp-welcome-logo">
<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>
<ul class="csp-welcome-nav">
<?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>
</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_html_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" href="https://wordpress.org/plugins/code-snippets/changelog" target="_blank"
title="<?php esc_html_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_html_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>
abstract-rules.php 0000644 00000006407 15154347153 0010227 0 ustar 00 <?php
namespace Contactable\SWV;
use WP_Error;
/**
* The base class of SWV rules.
*/
abstract class Rule {
protected $properties = array();
public function __construct( $properties = '' ) {
$this->properties = wp_parse_args( $properties, array() );
}
/**
* Returns true if this rule matches the given context.
*
* @param array $context Context.
*/
public function matches( $context ) {
$field = $this->get_property( 'field' );
if ( ! empty( $context['field'] ) ) {
if ( $field and ! in_array( $field, (array) $context['field'], true ) ) {
return false;
}
}
return true;
}
/**
* Validates with this rule's logic.
*
* @param array $context Context.
*/
public function validate( $context ) {
return true;
}
/**
* Converts the properties to an array.
*
* @return array Array of properties.
*/
public function to_array() {
$properties = (array) $this->properties;
if ( defined( 'static::rule_name' ) and static::rule_name ) {
$properties = array( 'rule' => static::rule_name ) + $properties;
}
return $properties;
}
/**
* Returns the property value specified by the given property name.
*
* @param string $name Property name.
* @return mixed Property value.
*/
public function get_property( $name ) {
if ( isset( $this->properties[$name] ) ) {
return $this->properties[$name];
}
}
/**
* Returns the default user input value from $_POST.
*
* @return mixed Default user input value.
*/
public function get_default_input() {
$field = $this->get_property( 'field' );
if ( isset( $_POST[$field] ) ) {
return wp_unslash( $_POST[$field] );
}
return '';
}
/**
* Creates an error object. Returns false if the error property is omitted.
*/
protected function create_error() {
$error_code = defined( 'static::rule_name' )
? sprintf( 'swv_%s', static::rule_name )
: 'swv';
return new WP_Error(
$error_code,
(string) $this->get_property( 'error' ),
$this
);
}
}
/**
* The base class of SWV composite rules.
*/
abstract class CompositeRule extends Rule {
protected $rules = array();
/**
* Adds a sub-rule to this composite rule.
*
* @param Rule $rule Sub-rule to be added.
*/
public function add_rule( $rule ) {
if ( $rule instanceof Rule ) {
$this->rules[] = $rule;
}
}
/**
* Returns an iterator of sub-rules.
*/
public function rules() {
foreach ( $this->rules as $rule ) {
yield $rule;
}
}
/**
* Returns true if this rule matches the given context.
*
* @param array $context Context.
*/
public function matches( $context ) {
return true;
}
/**
* Validates with this rule's logic.
*
* @param array $context Context.
*/
public function validate( $context ) {
foreach ( $this->rules() as $rule ) {
if ( $rule->matches( $context ) ) {
$result = $rule->validate( $context );
if ( is_wp_error( $result ) ) {
return $result;
}
}
}
return true;
}
/**
* Converts the properties to an array.
*
* @return array Array of properties.
*/
public function to_array() {
$rules_arrays = array_map(
static function ( $rule ) {
return $rule->to_array();
},
$this->rules
);
return array_merge(
parent::to_array(),
array(
'rules' => $rules_arrays,
)
);
}
}
rules/all.php 0000644 00000001114 15154347153 0007164 0 ustar 00 <?php
namespace Contactable\SWV;
class AllRule extends CompositeRule {
const rule_name = 'all';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
return true;
}
public function validate( $context ) {
foreach ( $this->rules() as $rule ) {
if ( $rule->matches( $context ) ) {
$result = $rule->validate( $context );
if ( is_wp_error( $result ) ) {
if ( $result->get_error_message() ) {
return $result;
} else {
return $this->create_error();
}
}
}
}
return true;
}
}
rules/any.php 0000644 00000000767 15154347153 0007220 0 ustar 00 <?php
namespace Contactable\SWV;
class AnyRule extends CompositeRule {
const rule_name = 'any';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
return true;
}
public function validate( $context ) {
foreach ( $this->rules() as $rule ) {
if ( $rule->matches( $context ) ) {
$result = $rule->validate( $context );
if ( ! is_wp_error( $result ) ) {
return true;
}
}
}
return $this->create_error();
}
}
rules/date.php 0000644 00000001076 15154347153 0007340 0 ustar 00 <?php
namespace Contactable\SWV;
class DateRule extends Rule {
const rule_name = 'date';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_date( $i ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/dayofweek.php 0000644 00000001750 15154347153 0010400 0 ustar 00 <?php
namespace Contactable\SWV;
class DayofweekRule extends Rule {
const rule_name = 'dayofweek';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$acceptable_values = (array) $this->get_property( 'accept' );
$acceptable_values = array_map( 'intval', $acceptable_values );
$acceptable_values = array_filter( $acceptable_values );
$acceptable_values = array_unique( $acceptable_values );
foreach ( $input as $i ) {
if ( wpcf7_is_date( $i ) ) {
$datetime = date_create_immutable( $i, wp_timezone() );
$dow = (int) $datetime->format( 'N' );
if ( ! in_array( $dow, $acceptable_values, true ) ) {
return $this->create_error();
}
}
}
return true;
}
}
rules/email.php 0000644 00000001101 15154347153 0007477 0 ustar 00 <?php
namespace Contactable\SWV;
class EmailRule extends Rule {
const rule_name = 'email';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_email( $i ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/enum.php 0000644 00000001613 15154347153 0007364 0 ustar 00 <?php
namespace Contactable\SWV;
class EnumRule extends Rule {
const rule_name = 'enum';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$acceptable_values = (array) $this->get_property( 'accept' );
$acceptable_values = array_map( 'strval', $acceptable_values );
$acceptable_values = array_unique( $acceptable_values );
$acceptable_values = array_filter( $acceptable_values,
static function ( $val ) {
return '' !== $val;
}
);
foreach ( $input as $i ) {
if ( ! in_array( $i, $acceptable_values, true ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/file.php 0000644 00000002437 15154347153 0007344 0 ustar 00 <?php
namespace Contactable\SWV;
class FileRule extends Rule {
const rule_name = 'file';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['file'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$field = $this->get_property( 'field' );
$input = $_FILES[$field]['name'] ?? '';
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$acceptable_filetypes = array();
foreach ( (array) $this->get_property( 'accept' ) as $accept ) {
if ( preg_match( '/^\.[a-z0-9]+$/i', $accept ) ) {
$acceptable_filetypes[] = strtolower( $accept );
} else {
foreach ( wpcf7_convert_mime_to_ext( $accept ) as $ext ) {
$acceptable_filetypes[] = sprintf(
'.%s',
strtolower( trim( $ext, ' .' ) )
);
}
}
}
$acceptable_filetypes = array_unique( $acceptable_filetypes );
foreach ( $input as $i ) {
$last_period_pos = strrpos( $i, '.' );
if ( false === $last_period_pos ) { // no period
return $this->create_error();
}
$suffix = strtolower( substr( $i, $last_period_pos ) );
if ( ! in_array( $suffix, $acceptable_filetypes, true ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/maxdate.php 0000644 00000001310 15154347153 0010035 0 ustar 00 <?php
namespace Contactable\SWV;
class MaxDateRule extends Rule {
const rule_name = 'maxdate';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_date( $threshold ) ) {
return true;
}
foreach ( $input as $i ) {
if ( wpcf7_is_date( $i ) and $threshold < $i ) {
return $this->create_error();
}
}
return true;
}
}
rules/maxfilesize.php 0000644 00000001305 15154347153 0010736 0 ustar 00 <?php
namespace Contactable\SWV;
class MaxFileSizeRule extends Rule {
const rule_name = 'maxfilesize';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['file'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$field = $this->get_property( 'field' );
$input = $_FILES[$field]['size'] ?? '';
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return true;
}
$threshold = $this->get_property( 'threshold' );
if ( $threshold < array_sum( $input ) ) {
return $this->create_error();
}
return true;
}
}
rules/maxitems.php 0000644 00000001243 15154347153 0010246 0 ustar 00 <?php
namespace Contactable\SWV;
class MaxItemsRule extends Rule {
const rule_name = 'maxitems';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_number( $threshold ) ) {
return true;
}
if ( (int) $threshold < count( $input ) ) {
return $this->create_error();
}
return true;
}
}
rules/maxlength.php 0000644 00000001364 15154347153 0010412 0 ustar 00 <?php
namespace Contactable\SWV;
class MaxLengthRule extends Rule {
const rule_name = 'maxlength';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return true;
}
$total = 0;
foreach ( $input as $i ) {
$total += wpcf7_count_code_units( $i );
}
$threshold = (int) $this->get_property( 'threshold' );
if ( $total <= $threshold ) {
return true;
} else {
return $this->create_error();
}
}
}
rules/maxnumber.php 0000644 00000001340 15154347153 0010413 0 ustar 00 <?php
namespace Contactable\SWV;
class MaxNumberRule extends Rule {
const rule_name = 'maxnumber';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_number( $threshold ) ) {
return true;
}
foreach ( $input as $i ) {
if ( wpcf7_is_number( $i ) and (float) $threshold < (float) $i ) {
return $this->create_error();
}
}
return true;
}
}
rules/mindate.php 0000644 00000001310 15154347153 0010033 0 ustar 00 <?php
namespace Contactable\SWV;
class MinDateRule extends Rule {
const rule_name = 'mindate';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_date( $threshold ) ) {
return true;
}
foreach ( $input as $i ) {
if ( wpcf7_is_date( $i ) and $i < $threshold ) {
return $this->create_error();
}
}
return true;
}
}
rules/minfilesize.php 0000644 00000001305 15154347153 0010734 0 ustar 00 <?php
namespace Contactable\SWV;
class MinFileSizeRule extends Rule {
const rule_name = 'minfilesize';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['file'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$field = $this->get_property( 'field' );
$input = $_FILES[$field]['size'] ?? '';
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return true;
}
$threshold = $this->get_property( 'threshold' );
if ( array_sum( $input ) < $threshold ) {
return $this->create_error();
}
return true;
}
}
rules/minitems.php 0000644 00000001243 15154347153 0010244 0 ustar 00 <?php
namespace Contactable\SWV;
class MinItemsRule extends Rule {
const rule_name = 'minitems';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_number( $threshold ) ) {
return true;
}
if ( count( $input ) < (int) $threshold ) {
return $this->create_error();
}
return true;
}
}
rules/minlength.php 0000644 00000001364 15154347153 0010410 0 ustar 00 <?php
namespace Contactable\SWV;
class MinLengthRule extends Rule {
const rule_name = 'minlength';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return true;
}
$total = 0;
foreach ( $input as $i ) {
$total += wpcf7_count_code_units( $i );
}
$threshold = (int) $this->get_property( 'threshold' );
if ( $threshold <= $total ) {
return true;
} else {
return $this->create_error();
}
}
}
rules/minnumber.php 0000644 00000001340 15154347153 0010411 0 ustar 00 <?php
namespace Contactable\SWV;
class MinNumberRule extends Rule {
const rule_name = 'minnumber';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
$threshold = $this->get_property( 'threshold' );
if ( ! wpcf7_is_number( $threshold ) ) {
return true;
}
foreach ( $input as $i ) {
if ( wpcf7_is_number( $i ) and (float) $i < (float) $threshold ) {
return $this->create_error();
}
}
return true;
}
}
rules/number.php 0000644 00000001104 15154347153 0007703 0 ustar 00 <?php
namespace Contactable\SWV;
class NumberRule extends Rule {
const rule_name = 'number';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_number( $i ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/required.php 0000644 00000001034 15154347153 0010235 0 ustar 00 <?php
namespace Contactable\SWV;
class RequiredRule extends Rule {
const rule_name = 'required';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return $this->create_error();
}
return true;
}
}
rules/requiredfile.php 0000644 00000001126 15154347153 0011077 0 ustar 00 <?php
namespace Contactable\SWV;
class RequiredFileRule extends Rule {
const rule_name = 'requiredfile';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['file'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$field = $this->get_property( 'field' );
$input = $_FILES[$field]['tmp_name'] ?? '';
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
if ( empty( $input ) ) {
return $this->create_error();
}
return true;
}
}
rules/tel.php 0000644 00000001073 15154347153 0007204 0 ustar 00 <?php
namespace Contactable\SWV;
class TelRule extends Rule {
const rule_name = 'tel';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_tel( $i ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/time.php 0000644 00000001076 15154347153 0007361 0 ustar 00 <?php
namespace Contactable\SWV;
class TimeRule extends Rule {
const rule_name = 'time';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_time( $i ) ) {
return $this->create_error();
}
}
return true;
}
}
rules/url.php 0000644 00000001073 15154347153 0007222 0 ustar 00 <?php
namespace Contactable\SWV;
class URLRule extends Rule {
const rule_name = 'url';
public function matches( $context ) {
if ( false === parent::matches( $context ) ) {
return false;
}
if ( empty( $context['text'] ) ) {
return false;
}
return true;
}
public function validate( $context ) {
$input = $this->get_default_input();
$input = wpcf7_array_flatten( $input );
$input = wpcf7_exclude_blank( $input );
foreach ( $input as $i ) {
if ( ! wpcf7_is_url( $i ) ) {
return $this->create_error();
}
}
return true;
}
}