<?php
// =============================================================================
// Synchi
//
// Released under the GNU General Public Licence v2
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
//
// CodeMirror library is released under a MIT-style license
// http://codemirror.net/LICENSE
//
// Please refer all questions/requests to: hide@address.com
//
// This is an add-on for WordPress
// http://wordpress.org/
// =============================================================================
// =============================================================================
// This piece of software is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
// =============================================================================
/*
Plugin Name: Synchi
Plugin URI: http://projects.djekic.net/synchi
Description: A full IDE inside your Wordpress! Syntax highlighting and powerfull IDE features in WP plugin editor, themes editor and article HTML editor.
Version: 5.0
Author: MiloÅ¡ ÄekiÄ
Author URI: http://milos.djekic.net
*/
// check if direct access attempted
if(preg_match('#' . basename(__FILE__) . '#', $_SERVER['PHP_SELF'])) {
header('HTTP/1.1 403 Forbidden');
exit('Direct access not alowed.');
}
// synchi version
define("SYNCHI",'5.0');
// define paths
if(!defined('WP_PLUGIN_URL')) define('WP_PLUGIN_URL', WP_CONTENT_URL. '/plugins');
if(!defined('WP_PLUGIN_DIR')) define('WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins');
if(!defined('WP_THEME_DIR')) define('WP_THEME_DIR', WP_CONTENT_DIR . '/themes');
if(!defined('WP_ADMIN_URL')) define('WP_ADMIN_URL', get_bloginfo('wpurl') . '/wp-admin');
// define settings page unique name
define("SYNCHI_SETTINGS_PAGE",'synchi-settings');
// define themes
$synchi_themes = array('default','ambiance','blackboard','cobalt','eclipse','elegant','erlang-dark','lesser-dark','monokai','neat','night','rubyblue','xq-dark');
// define supported modes
$synchi_modes = array('plugin-editor','theme-editor','post','post-new','widgets');
// define supported extensions
$synchi_extensions = array("php","js","css","sql","html","htm","txt","xml");
// define supported image extensions
$synchi_image_extensions = array('gif','jpg','png','bmp');
// define bad filename characters
$synchi_bad_chars = array('[',']','/','\\','=','+','<','>',':',';','"',',','*');
// ===================================================== Utility Functions =====
/**
* Clears $_GET and $_POST
*/
function sychi_clearRequest() {
$_GET = array();
$_POST = array();
}
/**
* Echoes a CSS include with versioning
*
* @param string $filepath
*/
function synchi_echoCSSinclude($filepath) { ?>
<link rel="stylesheet" href="<?php echo WP_PLUGIN_URL; ?>/synchi/<?php echo $filepath; ?>.css?version=<?php echo SYNCHI; ?>" />
<?php }
/**
* Echoes a JS include with versioning
*
* @param string $filepath
*/
function synchi_echoJSinclude($filepath) { ?>
<script src="<?php echo WP_PLUGIN_URL; ?>/synchi/<?php echo $filepath; ?>.js?version=<?php echo SYNCHI; ?>"></script>
<?php }
/**
* Returns the name of the script currently accessed
*
* @return string $script_name
*/
function synchi_get_script() {
$script_name = explode('/', $_SERVER['SCRIPT_NAME']);
return end($script_name);
}
/**
* Echos an ajax response
*
* @param mixed $result
*/
function synchi_ajax_response($result) {
$response = new stdClass();
$response->status = 1;
$response->result = $result;
header('Content-type: application/json');
echo json_encode($response);
die;
}
/**
* Echos an ajax error
*
* @param string $error error description
*/
function synchi_ajax_error($error) {
$response = new stdClass();
$response->status = 0;
$response->error = $error;
header('Content-type: application/json');
echo json_encode($response);
die;
}
/**
* Deletes a directory with all files inside
*
* @param string $dirname
* @return bool true if delete is success
*/
function synchi_delete_directory($dirname) {
if (is_dir($dirname)) $dir_handle = opendir($dirname);
if (!$dir_handle) return false;
while ($file = readdir($dir_handle)) {
if ($file != "." && $file != "..") {
if (!is_dir($dirname . "/" . $file)) unlink($dirname . "/" . $file);
else synchi_delete_directory($dirname . '/' . $file);
}
}
closedir($dir_handle);
rmdir($dirname);
return true;
}
/**
* Ensure that the string ends with the specified character
*
* @param string $string
* @return string
*/
function synhci_includeTrailingCharacter($string, $character) {
if (strlen($string) > 0) {
if (substr($string, -1) !== $character) return $string . $character;
else return $string;
} else return $character;
}
/**
* Copies files or directories with entire structure
*
* @param string $source
* @param string $target
*/
function synchi_full_copy($source, $target) {
if (is_dir($source)) {
@mkdir($target);
$d = dir($source);
while (($entry = $d->read()) !== false) {
if ($entry == '.' || $entry == '..') continue;
$Entry = synhci_includeTrailingCharacter($source,'/') . $entry;
if($Entry == $target) continue;
if (is_dir($Entry)) {
synchi_full_copy($Entry, $target . '/' . $entry);
continue;
}
copy($Entry, $target . '/' . $entry);
}
$d->close();
} else copy($source, $target);
}
/**
* Calculates and returns file size
*
* @param string $file file path
*
* @return string
*/
function synchi_file_size($file) {
$bytes = @filesize($file);
$precision = 2;
$units = array('B', 'KB', 'MB', 'GB', 'TB');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
// $bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}
// ====================================================== Action Functions =====
/**
* Renders editor controls
*/
function synchi_action_get_editor_controls() {
ob_start();
include(WP_PLUGIN_DIR . '/synchi/php/editor_controls.php');
$html = ob_get_contents();
ob_clean();
synchi_ajax_response($html);
}
/**
* Updates synchi settings
*
* @global $synchi_themes
*/
function synchi_action_update_settings() {
global $synchi_themes;
/* Global Settings */
// handle flag:plugins
if (!isset($_POST['synchi_option_flag_plugins'])) return;
$flag_plugins = $_POST['synchi_option_flag_plugins'];
if ($flag_plugins == 0 || $flag_plugins == 1) update_option('synchi_option_flag_plugins', $flag_plugins);
// handle flag:themes
if (!isset($_POST['synchi_option_flag_themes'])) return;
$flag_themes = $_POST['synchi_option_flag_themes'];
if ($flag_themes == 0 || $flag_themes == 1) update_option('synchi_option_flag_themes', $flag_themes);
// handle flag:articles
if (!isset($_POST['synchi_option_flag_articles'])) return;
$flag_articles = $_POST['synchi_option_flag_articles'];
if ($flag_articles == 0 || $flag_articles == 1) update_option('synchi_option_flag_articles', $flag_articles);
// handle flag:widgets
if (!isset($_POST['synchi_option_flag_widgets'])) return;
$flag_widgets = $_POST['synchi_option_flag_widgets'];
if ($flag_widgets == 0 || $flag_widgets == 1) update_option('synchi_option_flag_widgets', $flag_widgets);
/* Editing Settings */
// handle theme
if (!isset($_POST['synchi_option_theme'])) return;
$theme = $_POST['synchi_option_theme'];
if (!isset($theme) || empty($theme) || !in_array($theme, $synchi_themes)) $theme = 'default';
update_option('synchi_option_theme', $theme);
// handle line numbers
if (!isset($_POST['synchi_option_lineNumbers'])) return;
$lineNumbers = $_POST['synchi_option_lineNumbers'];
if ($lineNumbers == 0 || $lineNumbers == 1) update_option('synchi_option_lineNumbers', $lineNumbers);
// handle match brackets
if (!isset($_POST['synchi_option_matchBrackets'])) return;
$matchBrackets = $_POST['synchi_option_matchBrackets'];
if ($matchBrackets == 0 || $matchBrackets == 1) update_option('synchi_option_matchBrackets', $matchBrackets);
// handle font size
if (!isset($_POST['synchi_option_fontSize'])) return;
$fontSize = $_POST['synchi_option_fontSize'];
if ($fontSize >= 10 && $fontSize <= 16) update_option('synchi_option_fontSize', $fontSize);
// handle tab size
if (!isset($_POST['synchi_option_tabSize'])) return;
$tabSize = $_POST['synchi_option_tabSize'];
if ($tabSize >= 2 && $tabSize <= 5) update_option('synchi_option_tabSize', $tabSize);
// handle indent with tabs
if (!isset($_POST['synchi_option_indentWithTabs'])) return;
$indentWithTabs = $_POST['synchi_option_indentWithTabs'];
if ($indentWithTabs == 0 || $indentWithTabs == 1) update_option('synchi_option_indentWithTabs', $indentWithTabs);
// redirect to settings page
header('Location: ' . WP_ADMIN_URL . '/options-general.php?page=' . SYNCHI_SETTINGS_PAGE . '&updated=true');
die;
}
/**
* Fetches file contents
*
* @global $synchi_extensions
*/
function synchi_action_get_file_contents() {
global $synchi_extensions;
global $synchi_image_extensions;
// get filename
$filename = $_REQUEST['file'];
$filename = str_replace("\\\\", "/", $filename);
// check if file exists
if(!file_exists($filename)) synchi_ajax_error("File not found!");
// file is image flag
$file_is_image = false;
// check if extension is supported
$clean_filename = end(explode('/',$filename));
$extension = end(explode('.',$clean_filename));
if(!in_array($extension, $synchi_extensions)) {
// check if file is image
if(!in_array($extension, $synchi_image_extensions)) synchi_ajax_error("File not supported!");
else $file_is_image = true;
}
// get contents
if($file_is_image) {
$image_info = getimagesize($filename);
ob_start();
include(WP_PLUGIN_DIR . '/synchi/php/image.php');
$contents = ob_get_clean();
}
else {
$contents = @file_get_contents($filename);
if($contents == "") $contents = " ";
}
// form result
$result = new stdClass();
$result->contents = $contents;
$result->file_is_image = $file_is_image;
// respond
synchi_ajax_response($result);
}
/**
* Renders and returns IDE HTML in result
*/
function synchi_action_get_ide() {
ob_start();
$editor_mode = isset($_REQUEST['editor_mode']) ? substr($_REQUEST['editor_mode'],0,-1) : 'Files';
include(WP_PLUGIN_DIR . '/synchi/php/synchi_ide.php');
$html = ob_get_contents();
ob_clean();
synchi_ajax_response($html);
}
/**
* Saves last opened tabs
*/
function synchi_action_serialize_tabs() {
$files = $_REQUEST['files'];
if(!is_array($files)) synchi_ajax_response(false);
$mode = str_replace('/', '', $_REQUEST['mode']);
$serialized_files = array();
foreach ($files as $filename) $serialized_files[] = str_replace("\\\\", "/", $filename);
update_option('synchi_option_serializedTabs_' . $mode, $serialized_files);
synchi_ajax_response(true);
}
/**
* Saves a file
*/
function synchi_action_save_file() {
// get filename
$filename = $_REQUEST['file'];
$filename = str_replace("\\\\", "/", $filename);
// check if file exists
if(!file_exists($filename)) synchi_ajax_error("File not found!");
// get contents
$contents = $_REQUEST['contents'];
$contents = stripslashes($contents);
// save contents
@file_put_contents($filename, $contents);
synchi_ajax_response(true);
}
/**
* Creates a file
*
* global $synchi_bad_chars
*/
function synchi_action_create_file() {
global $synchi_bad_chars;
// get filename
$filename = $_REQUEST['filename'];
// check filename
foreach($synchi_bad_chars as $bad_char) {
if(strpos($filename, $bad_char) !== false)
synchi_ajax_error("File name can not contain: " . implode(' ',$synchi_bad_chars));
}
if(strlen($filename) > 32) synchi_ajax_error("Name must fit in 32 characters.");
$extension = end(explode(".",$filename));
// get parent
$dir = $_REQUEST['file'];
if(!is_dir($dir)) $dir = dirname($dir);
$path = "$dir/$filename";
if(file_exists($path)) synchi_ajax_error("File already exists!");
// create file
$handle = fopen($path, 'w') or synchi_ajax_error("Unable to create file!");
fclose($handle);
synchi_ajax_response(true);
}
/**
* Creates a folder
*
* @global $synchi_bad_chars
*/
function synchi_action_create_folder() {
global $synchi_bad_chars;
// get dirname
$dirname = $_REQUEST['dirname'];
// check dirname
foreach($synchi_bad_chars as $bad_char) {
if(strpos($dirname, $bad_char) !== false)
synchi_ajax_error("Folder name can not contain: " . implode(' ',$synchi_bad_chars));
}
if(strlen($dirname) > 32) synchi_ajax_error("Name must fit in 32 characters.");
// get parent
$dir = $_REQUEST['file'];
if(!is_dir($dir)) $dir = dirname($dir);
$path = "$dir/$dirname";
if(file_exists($path)) synchi_ajax_error("Foler already exists!");
// create directory
mkdir($path) or synchi_ajax_error("Unable to create folder!");
synchi_ajax_response(true);
}
/**
* Delete a file
*/
function synchi_action_delete_file() {
// get filename
$filename = $_REQUEST['filename'];
if(is_dir($filename)) $success = synchi_delete_directory($filename);
else $success = unlink($filename);
if($success) synchi_ajax_response(true);
else synchi_ajax_error("Unable to delete file/folder!");
}
/**
* Performs copy/paste and cut/paste
*/
function synchi_action_paste_file() {
// get data
$source = $_REQUEST['source'];
$mode = $_REQUEST['mode'];
$file = $_REQUEST['file'];
// get parent
$dir = $file;
if(!is_dir($dir)) $dir = dirname($dir);
if(is_dir($source)) {
// get dirname
$parts = explode('/',$source);
$dirname = $parts[count($parts)-1];
if($dirname == "") $dirname = $parts[count($parts)-2];
// make sure dirname is unique
$i = 0;
$path = "{$dir}{$dirname}";
while(file_exists($path)) {
$path = "{$dir}{$dirname}_" . (++$i);
}
// copy
synchi_full_copy($source, $path);
// if cut delete
if($mode == "cut") synchi_delete_directory($source);
synchi_ajax_response($path);
}
else {
// get filename
$parts = explode('/',$source);
$filename = $parts[count($parts)-1];
// make sure filename is unique
$i = 0;
$path = "{$dir}/{$filename}";
$parts = explode('.',$filename);
$extension = $parts[count($parts)-1];
unset($parts[count($parts)-1]);
$name = implode('.',$parts);
while(file_exists($path)) {
$path = "{$dir}/{$name}_" . (++$i) . ".$extension";
}
// copy
synchi_full_copy($source, $path);
// if cut delete
if($mode == "cut") unlink($source);
synchi_ajax_response($path);
}
}
/**
* Fetches file tree for filetree script. Added by request from Eduardo Alberto
* as a workaround for problems with server settings concerning direct access to
* scripts other than WP entry points.
*/
function synchi_action_file_tree() {
ob_start();
include(WP_PLUGIN_DIR . '/synchi/php/tree.php');
$html = ob_get_contents();
ob_clean();
synchi_ajax_response($html);
}
// ====================================================== Plugin Functions =====
/**
* Request handler intercepts requests to admin.php and checks if synchi should
* perform any actions ('synchi_action' parameter is in the request array)
*/
function synchi_request_handler() {
// check if synchi action is to be performed
if(empty($_REQUEST['synchi_action'])) return;
// check if user is admin
if(!is_admin()) return;
// perform action
switch($_REQUEST['synchi_action']) {
case 'update_settings': synchi_action_update_settings(); break;
case 'get_editor_controls': synchi_action_get_editor_controls(); break;
case 'get_ide': synchi_action_get_ide(); break;
case 'get_file_contents' : synchi_action_get_file_contents(); break;
case 'serialize_tabs' : synchi_action_serialize_tabs(); break;
case 'save_file' : synchi_action_save_file(); break;
case 'create_file' : synchi_action_create_file(); break;
case 'create_folder' : synchi_action_create_folder(); break;
case 'delete_file' : synchi_action_delete_file(); break;
case 'paste_file' : synchi_action_paste_file(); break;
case 'file_tree': synchi_action_file_tree(); break;
default: return;
}
}
/**
* Returns synchi settings in an array
*
* @return array $settings
* @global $synchi_themes
*/
function synchi_get_settings() {
global $synchi_themes;
$theme = get_option('synchi_option_theme');
if(!isset($theme) || empty($theme) || !in_array($theme, $synchi_themes)) $theme = 'default';
return array(
'flag_plugins' => get_option('synchi_option_flag_plugins') == 1,
'flag_themes' => get_option('synchi_option_flag_themes') == 1,
'flag_articles' => get_option('synchi_option_flag_articles') == 1,
'flag_widgets' => get_option('synchi_option_flag_widgets') == 1,
'theme' => $theme,
'lineWrapping' => true,
'lineNumbers' => get_option('synchi_option_lineNumbers') == 1,
'matchBrackets' => get_option('synchi_option_matchBrackets') == 1,
'fontSize' => get_option('synchi_option_fontSize'),
'tabSize' => get_option('synchi_option_tabSize'),
'indentWithTabs' => get_option('synchi_option_indentWithTabs') == 1,
);
}
/**
* Initializes the plugin
*
* @since Synchi 4.5
*/
function synchi_init() {
// load language support
load_plugin_textdomain('synchi', false, '/synchi/lang');
}
/**
* Initializes synchi plugin by adding JavaScript
* and CSS file includes to admin head
*
* @global $synchi_modes
*/
function synchi_initAdminHead() {
global $synchi_modes;
// determine mode
$synchi_mode = str_replace(".php", "", synchi_get_script());
// do nothing for unsupported modes
if(!in_array($synchi_mode, $synchi_modes)) return;
// init settings
$synchi_settings = synchi_get_settings();
// determine editor root
switch($synchi_mode) {
case "plugin-editor":
if(!current_user_can('edit_plugins')) return;
if(!$synchi_settings['flag_plugins']) return;
sychi_clearRequest();
$editor_mode = 'plugins';
$editor_root = addslashes(WP_PLUGIN_DIR);
$serialized_tabs_unprocessed = get_option("synchi_option_serializedTabs_plugins");
// get and parse serialized tabs
$serialized_tabs = array();
foreach ($serialized_tabs_unprocessed as $filename)
if (file_exists($filename)) $serialized_tabs[] = $filename;
// include head
include(WP_PLUGIN_DIR . '/synchi/php/head/ide.php');
break;
case 'theme-editor':
if(!current_user_can('edit_themes')) return;
if(!$synchi_settings['flag_themes']) return;
sychi_clearRequest();
$editor_mode = 'themes';
$editor_root = addslashes(WP_THEME_DIR);
$serialized_tabs_unprocessed = get_option("synchi_option_serializedTabs_themes");
// get and parse serialized tabs
$serialized_tabs = array();
foreach ($serialized_tabs_unprocessed as $filename)
if (file_exists($filename)) $serialized_tabs[] = $filename;
// include head
include(WP_PLUGIN_DIR . '/synchi/php/head/ide.php');
break;
case 'widgets':
//if(!current_user_can('edit_widgets')) return;
if(!$synchi_settings['flag_widgets']) return;
sychi_clearRequest();
// include head
include(WP_PLUGIN_DIR . '/synchi/php/head/widget.php');
break;
case 'post': case 'post-new':
if(!$synchi_settings['flag_articles']) return;
include(WP_PLUGIN_DIR . '/synchi/php/head/editor.php');
break;
}
}
/**
* Adds a menu item for synchi settings
*/
function synchi_menu() {
add_submenu_page(
'options-general.php', // parent
'Synchi Settings', // page title
'Synchi', // menu item title
'administrator', // permission
SYNCHI_SETTINGS_PAGE, // unique page name
'synchi_render_settings' // rendering function
);
}
/**
* Renders settings HTML
*
* @global $synchi_themes
*/
function synchi_render_settings() {
global $synchi_themes;
$theme = get_option('synchi_option_theme');
if(!in_array($theme, $synchi_themes)) $theme = 'default';
$synchi_settings = synchi_get_settings();
include(WP_PLUGIN_DIR . '/synchi/php/settings.php');
}
// ================================================= Plugin Initialization =====
// init plugin
add_action('plugins_loaded', 'synchi_init');
// register request handler
add_action('init', 'synchi_request_handler', 9999);
// register action handles
add_action('admin_head','synchi_initAdminHead');
// register global options
add_option('synchi_option_flag_plugins', 1);
add_option('synchi_option_flag_themes', 1);
add_option('synchi_option_flag_articles', 1);
add_option('synchi_option_flag_widgets', 1);
// register editing options
add_option('synchi_option_serializedTabs_plugins',array());
add_option('synchi_option_serializedTabs_themes',array());
add_option('synchi_option_theme', 'default');
add_option('synchi_option_lineNumbers', 1);
add_option('synchi_option_matchBrackets', 1);
add_option('synchi_option_fontSize', 12);
add_option('synchi_option_tabSize', 2);
add_option('synchi_option_indentWithTabs', 0);
// register menu item
add_action('admin_menu', 'synchi_menu');