286 lines
11 KiB
PHP
286 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Maple Code Blocks
|
|
* Plugin URI: https://sspmedia.ca/wordpress/
|
|
* Description: Display code files from GitHub, GitLab, Bitbucket, and Codeberg repositories in a beautiful, safe terminal/IDE-style view
|
|
* Version: 2.0.0
|
|
* Author: SSP Media
|
|
* Author URI: https://sspmedia.ca/wordpress/
|
|
* License: GPL v2 or later
|
|
* Text Domain: maple-code-blocks
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('WPINC')) {
|
|
die('Direct access not permitted.');
|
|
}
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Define plugin constants
|
|
define('MCB_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
define('MCB_PLUGIN_PATH', plugin_dir_path(__FILE__));
|
|
define('MCB_PLUGIN_VERSION', '2.0.0');
|
|
define('MCB_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
|
|
|
// Include required files
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-security.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-security-fixes.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-privacy-manager.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-github-api.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-code-renderer.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-shortcode.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/class-simple-block.php';
|
|
require_once MCB_PLUGIN_PATH . 'includes/basic-block.php';
|
|
// require_once MCB_PLUGIN_PATH . 'includes/class-block-editor.php';
|
|
// require_once MCB_PLUGIN_PATH . 'includes/class-block-patterns.php';
|
|
|
|
// Initialize plugin
|
|
class Maple_Code_Blocks {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct() {
|
|
add_action('init', array($this, 'init'));
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
|
add_action('wp_ajax_mcb_load_file', array($this, 'ajax_load_file'));
|
|
add_action('wp_ajax_nopriv_mcb_load_file', array($this, 'ajax_load_file'));
|
|
add_action('wp_ajax_mcb_get_repo_files', array($this, 'ajax_get_repo_files'));
|
|
add_action('wp_ajax_nopriv_mcb_get_repo_files', array($this, 'ajax_get_repo_files'));
|
|
}
|
|
|
|
public function init() {
|
|
// Initialize shortcode
|
|
MCB_Shortcode::init();
|
|
|
|
// Simple block is self-initializing via class-simple-block.php
|
|
|
|
// Add admin menu if needed
|
|
if (is_admin()) {
|
|
add_action('admin_menu', array($this, 'add_admin_menu'));
|
|
}
|
|
}
|
|
|
|
public function enqueue_scripts() {
|
|
// Load styles
|
|
wp_enqueue_style('prism-css', MCB_PLUGIN_URL . 'assets/css/prism.css', array(), MCB_PLUGIN_VERSION);
|
|
wp_enqueue_style('mcb-styles', MCB_PLUGIN_URL . 'assets/css/mcb-styles.css', array(), MCB_PLUGIN_VERSION);
|
|
|
|
// Load scripts
|
|
wp_enqueue_script('prism-core', MCB_PLUGIN_URL . 'assets/js/prism.js', array(), MCB_PLUGIN_VERSION, true);
|
|
wp_enqueue_script('mcb-script', MCB_PLUGIN_URL . 'assets/js/mcb-script.js', array('jquery'), MCB_PLUGIN_VERSION, true);
|
|
|
|
// Localize script for AJAX
|
|
wp_localize_script('mcb-script', 'mcb_ajax', array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('mcb_ajax_nonce')
|
|
));
|
|
}
|
|
|
|
public function ajax_load_file() {
|
|
// Check request method
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
wp_die('Invalid request method', 405);
|
|
}
|
|
|
|
// Rate limiting check
|
|
if (class_exists('MCB_Privacy_Manager') && MCB_Privacy_Manager::is_privacy_mode()) {
|
|
// Use nonce-based rate limiting in privacy mode
|
|
$rate_limit_key = 'mcb_rate_' . wp_get_current_user()->ID . '_' . wp_create_nonce('mcb_rate');
|
|
} else {
|
|
// IP-based rate limiting
|
|
$user_ip = $this->get_client_ip();
|
|
$rate_limit_key = 'mcb_rate_' . md5($user_ip);
|
|
}
|
|
|
|
$requests = get_transient($rate_limit_key);
|
|
|
|
if ($requests !== false && $requests > 30) { // 30 requests per minute
|
|
wp_die('Rate limit exceeded. Please try again later.', 429);
|
|
}
|
|
|
|
set_transient($rate_limit_key, ($requests ? $requests + 1 : 1), 60);
|
|
|
|
// Verify nonce
|
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mcb_ajax_nonce')) {
|
|
wp_die('Security check failed', 403);
|
|
}
|
|
|
|
// Note: We allow public viewing of public repositories by default
|
|
// Site admins can restrict access if needed via filter
|
|
$require_login = apply_filters('mcb_require_login_for_viewing', false);
|
|
|
|
if ($require_login && !is_user_logged_in()) {
|
|
wp_die('Please log in to view code repositories', 403);
|
|
}
|
|
|
|
$repo = isset($_POST['repo']) ? sanitize_text_field($_POST['repo']) : '';
|
|
$file_path = isset($_POST['file_path']) ? sanitize_text_field($_POST['file_path']) : '';
|
|
|
|
// Additional validation using security class
|
|
if (!MCB_Security::validate_repo_format($repo)) {
|
|
MCB_Security::log_security_event('invalid_repo_format', array('repo' => $repo));
|
|
wp_send_json_error('Invalid repository format');
|
|
return;
|
|
}
|
|
|
|
if (!MCB_Security::validate_file_path($file_path)) {
|
|
MCB_Security::log_security_event('invalid_file_path', array('path' => $file_path));
|
|
wp_send_json_error('Invalid file path');
|
|
return;
|
|
}
|
|
|
|
$github_api = new MCB_GitHub_API();
|
|
$content = $github_api->get_file_content($repo, $file_path);
|
|
|
|
if (is_wp_error($content)) {
|
|
wp_send_json_error($content->get_error_message());
|
|
return;
|
|
}
|
|
|
|
// Validate content before sending
|
|
$renderer = new MCB_Code_Renderer();
|
|
$validation = $renderer->validate_content($content);
|
|
|
|
if (is_wp_error($validation)) {
|
|
wp_send_json_error($validation->get_error_message());
|
|
return;
|
|
}
|
|
|
|
// Return raw content - JavaScript will handle escaping and rendering
|
|
// This is more secure as it avoids double-escaping issues
|
|
wp_send_json_success(array(
|
|
'content' => $content, // Send raw content, not rendered HTML
|
|
'filename' => basename($file_path)
|
|
));
|
|
}
|
|
|
|
public function ajax_get_repo_files() {
|
|
// Check request method
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
wp_die('Invalid request method', 405);
|
|
}
|
|
|
|
// Rate limiting check
|
|
$user_ip = $this->get_client_ip();
|
|
$rate_limit_key = 'mcb_rate_' . md5($user_ip);
|
|
$requests = get_transient($rate_limit_key);
|
|
|
|
if ($requests !== false && $requests > 30) { // 30 requests per minute
|
|
wp_die('Rate limit exceeded. Please try again later.', 429);
|
|
}
|
|
|
|
set_transient($rate_limit_key, ($requests ? $requests + 1 : 1), 60);
|
|
|
|
// Verify nonce
|
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mcb_ajax_nonce')) {
|
|
wp_die('Security check failed', 403);
|
|
}
|
|
|
|
// Note: We allow public viewing of public repositories by default
|
|
// Site admins can restrict access if needed via filter
|
|
$require_login = apply_filters('mcb_require_login_for_viewing', false);
|
|
|
|
if ($require_login && !is_user_logged_in()) {
|
|
wp_die('Please log in to view code repositories', 403);
|
|
}
|
|
|
|
$repo = isset($_POST['repo']) ? sanitize_text_field($_POST['repo']) : '';
|
|
$path = isset($_POST['path']) ? sanitize_text_field($_POST['path']) : '';
|
|
|
|
// Validate repository format
|
|
if (!MCB_Security::validate_repo_format($repo)) {
|
|
wp_send_json_error('Invalid repository format');
|
|
return;
|
|
}
|
|
|
|
$github_api = new MCB_GitHub_API();
|
|
$files = $github_api->get_repository_files($repo, $path);
|
|
|
|
if (is_wp_error($files)) {
|
|
wp_send_json_error($files->get_error_message());
|
|
return;
|
|
}
|
|
|
|
wp_send_json_success($files);
|
|
}
|
|
|
|
public function add_admin_menu() {
|
|
add_options_page(
|
|
'GitHub Code Viewer Settings',
|
|
'GitHub Code Viewer',
|
|
'manage_options',
|
|
'maple-code-blocks',
|
|
array($this, 'admin_page')
|
|
);
|
|
}
|
|
|
|
public function admin_page() {
|
|
include MCB_PLUGIN_PATH . 'admin/settings-page.php';
|
|
}
|
|
|
|
/**
|
|
* Get client IP address for rate limiting
|
|
*/
|
|
private function get_client_ip() {
|
|
$ip_keys = array('HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
|
|
foreach ($ip_keys as $key) {
|
|
if (array_key_exists($key, $_SERVER) === true) {
|
|
$ips = explode(',', $_SERVER[$key]);
|
|
foreach ($ips as $ip) {
|
|
$ip = trim($ip);
|
|
if (filter_var($ip, FILTER_VALIDATE_IP,
|
|
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
|
return $ip;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
|
}
|
|
}
|
|
|
|
// Initialize the plugin
|
|
Maple_Code_Blocks::get_instance();
|
|
|
|
// Register deactivation hook to clean up scheduled events
|
|
register_deactivation_hook(__FILE__, 'mcb_deactivate');
|
|
function mcb_deactivate() {
|
|
// Remove scheduled cleanup event
|
|
$timestamp = wp_next_scheduled('mcb_privacy_cleanup');
|
|
if ($timestamp) {
|
|
wp_unschedule_event($timestamp, 'mcb_privacy_cleanup');
|
|
}
|
|
|
|
// Clear all MCB transients
|
|
global $wpdb;
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mcb_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mcb_%'");
|
|
}
|
|
|
|
// Register uninstall hook for complete cleanup
|
|
register_uninstall_hook(__FILE__, 'mcb_uninstall');
|
|
function mcb_uninstall() {
|
|
// Remove all plugin options
|
|
delete_option('mcb_settings');
|
|
delete_option('mcb_github_token_encrypted');
|
|
delete_option('mcb_gitlab_token_encrypted');
|
|
delete_option('mcb_bitbucket_token_encrypted');
|
|
delete_option('mcb_codeberg_token_encrypted');
|
|
|
|
// Clear all transients
|
|
global $wpdb;
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mcb_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mcb_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_site_transient_mcb_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_site_transient_timeout_mcb_%'");
|
|
}
|