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;
 | |
|         }
 | |
|     }
 | |
| }
 |