450 lines
16 KiB
PHP
450 lines
16 KiB
PHP
<?php
|
|
// Class includes
|
|
require_once('global.php');
|
|
require_once(CRAYON_PARSER_PHP);
|
|
require_once(CRAYON_FORMATTER_PHP);
|
|
require_once(CRAYON_SETTINGS_PHP);
|
|
require_once(CRAYON_LANGS_PHP);
|
|
|
|
/* The main class for managing the syntax highlighter */
|
|
|
|
class CrayonHighlighter
|
|
{
|
|
// Properties and Constants ===============================================
|
|
private $id = '';
|
|
// URL is initially NULL, meaning none provided
|
|
private $url = NULL;
|
|
private $code = '';
|
|
private $formatted_code = '';
|
|
private $title = '';
|
|
private $line_count = 0;
|
|
private $marked_lines = array();
|
|
private $range = NULL;
|
|
private $error = '';
|
|
// Determine whether the code needs to be loaded, parsed or formatted
|
|
private $needs_load = FALSE;
|
|
private $needs_format = FALSE;
|
|
// Record the script run times
|
|
private $runtime = array();
|
|
// Whether the code is mixed
|
|
private $is_mixed = FALSE;
|
|
// Inline code on a single floating line
|
|
private $is_inline = FALSE;
|
|
private $is_highlighted = TRUE;
|
|
|
|
// Objects
|
|
// Stores the CrayonLang being used
|
|
private $language = NULL;
|
|
// A copy of the current global settings which can be overridden
|
|
private $settings = NULL;
|
|
|
|
// Methods ================================================================
|
|
function __construct($url = NULL, $language = NULL, $id = NULL)
|
|
{
|
|
if ($url !== NULL) {
|
|
$this->url($url);
|
|
}
|
|
|
|
if ($language !== NULL) {
|
|
$this->language($language);
|
|
}
|
|
// Default ID
|
|
$id = $id !== NULL ? $id : uniqid();
|
|
$this->id($id);
|
|
}
|
|
|
|
/* Tries to load the code locally, then attempts to load it remotely */
|
|
private function load()
|
|
{
|
|
if (empty($this->url)) {
|
|
$this->error('The specified URL is empty, please provide a valid URL.');
|
|
return;
|
|
}
|
|
// Try to replace the URL with an absolute path if it is local, used to prevent scripts
|
|
// from executing when they are loaded.
|
|
$url = $this->url;
|
|
if ($this->setting_val(CrayonSettings::DECODE_ATTRIBUTES)) {
|
|
$url = CrayonUtil::html_entity_decode($url);
|
|
}
|
|
$url = CrayonUtil::pathf($url);
|
|
$site_http = CrayonGlobalSettings::site_url();
|
|
$scheme = parse_url($url, PHP_URL_SCHEME);
|
|
// Try to replace the site URL with a path to force local loading
|
|
if (empty($scheme)) {
|
|
// No url scheme is given - path may be given as relative
|
|
$url = CrayonUtil::path_slash($site_http) . CrayonUtil::path_slash($this->setting_val(CrayonSettings::LOCAL_PATH)) . $url;
|
|
}
|
|
$http_code = 0;
|
|
// If available, use the built in wp remote http get function.
|
|
if (function_exists('wp_remote_get')) {
|
|
$url_uid = 'crayon_' . CrayonUtil::str_uid($url);
|
|
$cached = get_transient($url_uid, 'crayon-syntax');
|
|
CrayonSettingsWP::load_cache();
|
|
if ($cached !== FALSE) {
|
|
$content = $cached;
|
|
$http_code = 200;
|
|
} else {
|
|
$response = @wp_remote_get($url, array('sslverify' => false, 'timeout' => 20));
|
|
$content = wp_remote_retrieve_body($response);
|
|
$http_code = wp_remote_retrieve_response_code($response);
|
|
$cache = $this->setting_val(CrayonSettings::CACHE);
|
|
$cache_sec = CrayonSettings::get_cache_sec($cache);
|
|
if ($cache_sec > 1 && $http_code >= 200 && $http_code < 400) {
|
|
set_transient($url_uid, $content, $cache_sec);
|
|
CrayonSettingsWP::add_cache($url_uid);
|
|
}
|
|
}
|
|
} else if (in_array(parse_url($url, PHP_URL_SCHEME), array('ssl', 'http', 'https'))) {
|
|
// Fallback to cURL. At this point, the URL scheme must be valid.
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_HEADER, FALSE);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
|
|
// For https connections, we do not require SSL verification
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
|
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
|
|
curl_setopt($ch, CURLOPT_FRESH_CONNECT, FALSE);
|
|
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
|
|
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
|
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
|
|
}
|
|
$content = curl_exec($ch);
|
|
$error = curl_error($ch);
|
|
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
}
|
|
if ($http_code >= 200 && $http_code < 400) {
|
|
$this->code($content);
|
|
} else {
|
|
if (empty($this->code)) {
|
|
// If code is also given, just use that
|
|
$this->error("The provided URL ('$this->url'), parsed remotely as ('$url'), could not be accessed.");
|
|
}
|
|
}
|
|
$this->needs_load = FALSE;
|
|
}
|
|
|
|
/* Central point of access for all other functions to update code. */
|
|
public function process()
|
|
{
|
|
$tmr = new CrayonTimer();
|
|
$this->runtime = NULL;
|
|
if ($this->needs_load) {
|
|
$tmr->start();
|
|
$this->load();
|
|
$this->runtime[CRAYON_LOAD_TIME] = $tmr->stop();
|
|
}
|
|
if (!empty($this->error) || empty($this->code)) {
|
|
// Disable highlighting for errors and empty code
|
|
return;
|
|
}
|
|
|
|
if ($this->language === NULL) {
|
|
$this->language_detect();
|
|
}
|
|
if ($this->needs_format) {
|
|
$tmr->start();
|
|
try {
|
|
// Parse before hand to read modes
|
|
$code = $this->code;
|
|
// If inline, then combine lines into one
|
|
if ($this->is_inline) {
|
|
$code = preg_replace('#[\r\n]+#ms', '', $code);
|
|
if ($this->setting_val(CrayonSettings::TRIM_WHITESPACE)) {
|
|
$code = trim($code);
|
|
}
|
|
}
|
|
// Decode html entities (e.g. if using visual editor or manually encoding)
|
|
if ($this->setting_val(CrayonSettings::DECODE)) {
|
|
$code = CrayonUtil::html_entity_decode($code);
|
|
}
|
|
// Save code so output is plain output is the same
|
|
$this->code = $code;
|
|
|
|
// Allow mixed if langauge supports it and setting is set
|
|
CrayonParser::parse($this->language->id());
|
|
if (!$this->setting_val(CrayonSettings::MIXED) || !$this->language->mode(CrayonParser::ALLOW_MIXED)) {
|
|
// Format the code with the generated regex and elements
|
|
$this->formatted_code = CrayonFormatter::format_code($code, $this->language, $this);
|
|
} else {
|
|
// Format the code with Mixed Highlighting
|
|
$this->formatted_code = CrayonFormatter::format_mixed_code($code, $this->language, $this);
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->error($e->message());
|
|
return;
|
|
}
|
|
$this->needs_format = FALSE;
|
|
$this->runtime[CRAYON_FORMAT_TIME] = $tmr->stop();
|
|
}
|
|
}
|
|
|
|
/* Used to format the glue in between code when finding mixed languages */
|
|
private function format_glue($glue, $highlight = TRUE)
|
|
{
|
|
// TODO $highlight
|
|
return CrayonFormatter::format_code($glue, $this->language, $this, $highlight);
|
|
}
|
|
|
|
/* Sends the code to the formatter for printing. Apart from the getters and setters, this is
|
|
the only other function accessible outside this class. $show_lines can also be a string. */
|
|
function output($show_lines = TRUE, $print = TRUE)
|
|
{
|
|
$this->process();
|
|
if (empty($this->error)) {
|
|
// If no errors have occured, print the formatted code
|
|
$ret = CrayonFormatter::print_code($this, $this->formatted_code, $show_lines, $print);
|
|
} else {
|
|
$ret = CrayonFormatter::print_error($this, $this->error, '', $print);
|
|
}
|
|
// Reset the error message at the end of the print session
|
|
$this->error = '';
|
|
// If $print = FALSE, $ret will contain the output
|
|
return $ret;
|
|
}
|
|
|
|
// Getters and Setters ====================================================
|
|
function code($code = NULL)
|
|
{
|
|
if ($code === NULL) {
|
|
return $this->code;
|
|
} else {
|
|
// Trim whitespace
|
|
if ($this->setting_val(CrayonSettings::TRIM_WHITESPACE)) {
|
|
$code = preg_replace("#(?:^\\s*\\r?\\n)|(?:\\r?\\n\\s*$)#", '', $code);
|
|
}
|
|
|
|
if ($this->setting_val(CrayonSettings::TRIM_CODE_TAG)) {
|
|
$code = preg_replace('#^\s*<\s*code[^>]*>#msi', '', $code);
|
|
$code = preg_replace('#</\s*code[^>]*>\s*$#msi', '', $code);
|
|
}
|
|
|
|
$before = $this->setting_val(CrayonSettings::WHITESPACE_BEFORE);
|
|
if ($before > 0) {
|
|
$code = str_repeat("\n", $before) . $code;
|
|
}
|
|
$after = $this->setting_val(CrayonSettings::WHITESPACE_AFTER);
|
|
if ($after > 0) {
|
|
$code = $code . str_repeat("\n", $after);
|
|
}
|
|
|
|
if (!empty($code)) {
|
|
$this->code = $code;
|
|
$this->needs_format = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
function language($id = NULL)
|
|
{
|
|
if ($id === NULL || !is_string($id)) {
|
|
return $this->language;
|
|
}
|
|
|
|
if (($lang = CrayonResources::langs()->get($id)) != FALSE || ($lang = CrayonResources::langs()->alias($id)) != FALSE) {
|
|
// Set the language if it exists or look for an alias
|
|
$this->language = $lang;
|
|
} else {
|
|
$this->language_detect();
|
|
}
|
|
|
|
// Prepare the language for use, even if we have no code, we need the name
|
|
CrayonParser::parse($this->language->id());
|
|
}
|
|
|
|
function language_detect()
|
|
{
|
|
// Attempt to detect the language
|
|
if (!empty($id)) {
|
|
$this->log("The language '$id' could not be loaded.");
|
|
}
|
|
$this->language = CrayonResources::langs()->detect($this->url, $this->setting_val(CrayonSettings::FALLBACK_LANG));
|
|
}
|
|
|
|
function url($url = NULL)
|
|
{
|
|
if ($url === NULL) {
|
|
return $this->url;
|
|
} else {
|
|
$this->url = $url;
|
|
$this->needs_load = TRUE;
|
|
}
|
|
}
|
|
|
|
function title($title = NULL)
|
|
{
|
|
if (!CrayonUtil::str($this->title, $title)) {
|
|
return $this->title;
|
|
}
|
|
}
|
|
|
|
function line_count($line_count = NULL)
|
|
{
|
|
if (!CrayonUtil::num($this->line_count, $line_count)) {
|
|
return $this->line_count;
|
|
}
|
|
}
|
|
|
|
function marked($str = NULL)
|
|
{
|
|
if ($str === NULL) {
|
|
return $this->marked_lines;
|
|
}
|
|
// If only an int is given
|
|
if (is_int($str)) {
|
|
$array = array($str);
|
|
return CrayonUtil::arr($this->marked_lines, $array);
|
|
}
|
|
// A string with ints separated by commas, can also contain ranges
|
|
$array = CrayonUtil::trim_e($str);
|
|
$array = array_unique($array);
|
|
$lines = array();
|
|
foreach ($array as $line) {
|
|
// Check for ranges
|
|
if (strpos($line, '-') !== FALSE) {
|
|
$ranges = CrayonUtil::range_str($line);
|
|
$lines = array_merge($lines, $ranges);
|
|
} else {
|
|
// Otherwise check the string for a number
|
|
$line = intval($line);
|
|
if ($line !== 0) {
|
|
$lines[] = $line;
|
|
}
|
|
}
|
|
}
|
|
return CrayonUtil::arr($this->marked_lines, $lines);
|
|
}
|
|
|
|
function range($str = NULL)
|
|
{
|
|
if ($str === NULL) {
|
|
return $this->range;
|
|
} else {
|
|
$range = CrayonUtil::range_str_single($str);
|
|
if ($range) {
|
|
$this->range = $range;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
function log($var)
|
|
{
|
|
if ($this->setting_val(CrayonSettings::ERROR_LOG)) {
|
|
CrayonLog::log($var);
|
|
}
|
|
}
|
|
|
|
function id($id = NULL)
|
|
{
|
|
if ($id == NULL) {
|
|
return $this->id;
|
|
} else {
|
|
$this->id = strval($id);
|
|
}
|
|
}
|
|
|
|
function error($string = NULL)
|
|
{
|
|
if (!$string) {
|
|
return $this->error;
|
|
}
|
|
$this->error .= $string;
|
|
$this->log($string);
|
|
// Add the error string and ensure no further processing occurs
|
|
$this->needs_load = FALSE;
|
|
$this->needs_format = FALSE;
|
|
}
|
|
|
|
// Set and retreive settings
|
|
// TODO fix this, it's too limiting
|
|
function settings($mixed = NULL)
|
|
{
|
|
if ($this->settings == NULL) {
|
|
$this->settings = CrayonGlobalSettings::get_obj();
|
|
}
|
|
|
|
if ($mixed === NULL) {
|
|
return $this->settings;
|
|
} else if (is_string($mixed)) {
|
|
return $this->settings->get($mixed);
|
|
} else if (is_array($mixed)) {
|
|
$this->settings->set($mixed);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* Retrieve a single setting's value for use in the formatter. By default, on failure it will
|
|
* return TRUE to ensure FALSE is only sent when a setting is found. This prevents a fake
|
|
* FALSE when the formatter checks for a positive setting (Show/Enable) and fails. When a
|
|
* negative setting is needed (Hide/Disable), $default_return should be set to FALSE. */
|
|
// TODO fix this (see above)
|
|
function setting_val($name = NULL, $default_return = TRUE)
|
|
{
|
|
if (is_string($name) && $setting = $this->settings($name)) {
|
|
return $setting->value();
|
|
} else {
|
|
// Name not valid
|
|
return (is_bool($default_return) ? $default_return : TRUE);
|
|
}
|
|
}
|
|
|
|
// Set a setting value
|
|
// TODO fix this (see above)
|
|
function setting_set($name = NULL, $value = TRUE)
|
|
{
|
|
$this->settings->set($name, $value);
|
|
}
|
|
|
|
// Used to find current index in dropdown setting
|
|
function setting_index($name = NULL)
|
|
{
|
|
$setting = $this->settings($name);
|
|
if (is_string($name) && $setting->is_array()) {
|
|
return $setting->index();
|
|
} else {
|
|
// Returns -1 to avoid accidentally selecting an item in a dropdown
|
|
return CrayonSettings::INVALID;
|
|
}
|
|
}
|
|
|
|
function formatted_code()
|
|
{
|
|
return $this->formatted_code;
|
|
}
|
|
|
|
function runtime()
|
|
{
|
|
return $this->runtime;
|
|
}
|
|
|
|
function is_highlighted($highlighted = NULL)
|
|
{
|
|
if ($highlighted === NULL) {
|
|
return $this->is_highlighted;
|
|
} else {
|
|
$this->is_highlighted = $highlighted;
|
|
}
|
|
}
|
|
|
|
function is_mixed($mixed = NULL)
|
|
{
|
|
if ($mixed === NULL) {
|
|
return $this->is_mixed;
|
|
} else {
|
|
$this->is_mixed = $mixed;
|
|
}
|
|
}
|
|
|
|
function is_inline($inline = NULL)
|
|
{
|
|
if ($inline === NULL) {
|
|
return $this->is_inline;
|
|
} else {
|
|
$inline = CrayonUtil::str_to_bool($inline, FALSE);
|
|
$this->is_inline = $inline;
|
|
}
|
|
}
|
|
}
|