423 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			13 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;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| ?>
 | 
