~ubuntu-branches/ubuntu/saucy/mediawiki-extensions/saucy

« back to all changes in this revision

Viewing changes to include/SyntaxHighlight_GeSHi.class.php

  • Committer: Bazaar Package Importer
  • Author(s): Romain Beauxis
  • Date: 2010-05-04 15:13:35 UTC
  • mfrom: (0.1.1 experimental)
  • Revision ID: james.westby@ubuntu.com-20100504151335-54qeucg3ec108q28
Tags: 2.2
* Added Replaces:/Conflicts: to allow a proper upgrade.
Closes: #580066
* Fixed package descriptions.
Closes: #579667
* Patched mediawiki-extensions-fckeditor to make it work with
  php 5.3. The fix may not be perfect but at least it work.
  Not closing the bug (#579822) for now..

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<?php
2
 
 
3
 
class SyntaxHighlight_GeSHi {
4
 
 
5
 
        /**
6
 
         * Has GeSHi been initialised this session?
7
 
         */
8
 
        private static $initialised = false;
9
 
 
10
 
        /**
11
 
         * List of languages available to GeSHi
12
 
         */
13
 
        private static $languages = null;
14
 
 
15
 
        /**
16
 
         * Parser hook
17
 
         *
18
 
         * @param string $text
19
 
         * @param array $args
20
 
         * @param Parser $parser
21
 
         * @return string
22
 
         */
23
 
        public static function parserHook( $text, $args = array(), $parser ) {
24
 
                self::initialise();
25
 
                $text = rtrim( $text );
26
 
                // Don't trim leading spaces away, just the linefeeds
27
 
                $text = preg_replace( '/^\n+/', '', $text );
28
 
                // Validate language
29
 
                if( isset( $args['lang'] ) ) {
30
 
                        $lang = strtolower( $args['lang'] );
31
 
                } else {
32
 
                        return self::formatError( htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-language' ) ) );
33
 
                }
34
 
                if( !preg_match( '/^[a-z_0-9-]*$/', $lang ) )
35
 
                        return self::formatError( htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-language' ) ) );
36
 
                $geshi = self::prepare( $text, $lang );
37
 
                if( !$geshi instanceof GeSHi )
38
 
                        return self::formatError( htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-language' ) ) );
39
 
 
40
 
                $enclose = self::getEncloseType( $args );
41
 
 
42
 
                // Line numbers
43
 
                if( isset( $args['line'] ) ) {
44
 
                        $geshi->enable_line_numbers( GESHI_FANCY_LINE_NUMBERS );
45
 
                }
46
 
                // Highlighting specific lines
47
 
                if( isset( $args['highlight'] ) ) {
48
 
                        $lines = self::parseHighlightLines( $args['highlight'] );
49
 
                        if ( count($lines) ) $geshi->highlight_lines_extra( $lines );
50
 
                }
51
 
                // Starting line number
52
 
                if( isset( $args['start'] ) )
53
 
                        $geshi->start_line_numbers_at( $args['start'] );
54
 
                $geshi->set_header_type( $enclose );
55
 
                // Strict mode
56
 
                if( isset( $args['strict'] ) )
57
 
                        $geshi->enable_strict_mode();
58
 
                // Format
59
 
                $out = $geshi->parse_code();
60
 
                $err = $geshi->error();
61
 
                if( $err ) {
62
 
                        // Error!
63
 
                        return self::formatError( $err );
64
 
                } else {
65
 
                        // Armour for Parser::doBlockLevels()
66
 
                        if( $enclose === GESHI_HEADER_DIV )
67
 
                                $out = str_replace( "\n", '', $out );
68
 
                        // Register CSS
69
 
                        $parser->mOutput->addHeadItem( self::buildHeadItem( $geshi ), "source-{$lang}" );
70
 
                        if ( $enclose === GESHI_HEADER_NONE ) {
71
 
                                return '<span class="'.$lang.' source-'.$lang.'"> '.$out . '</span>';
72
 
                        } else {
73
 
                                return '<div dir="ltr" style="text-align: left;">' . $out . '</div>';
74
 
                        }
75
 
                }
76
 
        }
77
 
        
78
 
        /**
79
 
         * Take an input specifying a list of lines to highlight, returning
80
 
         * a raw list of matching line numbers.
81
 
         *
82
 
         * Input is comma-separated list of lines or line ranges.
83
 
         *
84
 
         * @input string
85
 
         * @return array of ints
86
 
         */
87
 
        protected static function parseHighlightLines( $arg ) {
88
 
                $lines = array();
89
 
                $values = array_map( 'trim', explode( ',', $arg ) );
90
 
                foreach ( $values as $value ) {
91
 
                        if ( ctype_digit($value) ) {
92
 
                                $lines[] = (int) $value;
93
 
                        } elseif ( strpos( $value, '-' ) !== false ) {
94
 
                                list( $start, $end ) = array_map( 'trim', explode( '-', $value ) );
95
 
                                if ( self::validHighlightRange( $start, $end ) ) {
96
 
                                        for ($i = intval( $start ); $i <= $end; $i++ ) {
97
 
                                                $lines[] = $i;
98
 
                                        }
99
 
                                } else {
100
 
                                        wfDebugLog( 'geshi', "Invalid range: $value\n" );
101
 
                                }
102
 
                        } else {
103
 
                                wfDebugLog( 'geshi', "Invalid line: $value\n" );
104
 
                        }
105
 
                }
106
 
                return $lines;
107
 
        }
108
 
        
109
 
        /**
110
 
         * Validate a provided input range
111
 
         */
112
 
        protected static function validHighlightRange( $start, $end ) {
113
 
                // Since we're taking this tiny range and producing a an
114
 
                // array of every integer between them, it would be trivial
115
 
                // to DoS the system by asking for a huge range.
116
 
                // Impose an arbitrary limit on the number of lines in a
117
 
                // given range to reduce the impact.
118
 
                $arbitrarilyLargeConstant = 10000;
119
 
                return
120
 
                        ctype_digit($start) &&
121
 
                        ctype_digit($end) &&
122
 
                        $start > 0 &&
123
 
                        $start < $end &&
124
 
                        $end - $start < $arbitrarilyLargeConstant;
125
 
        }
126
 
 
127
 
        static function getEncloseType( $args ) {
128
 
                // Since version 1.0.8 geshi can produce valid pre, but we need to check for it
129
 
                if ( defined('GESHI_HEADER_PRE_VALID') ) {
130
 
                        $pre = GESHI_HEADER_PRE_VALID;
131
 
                } else {
132
 
                        $pre = GESHI_HEADER_PRE;
133
 
                }
134
 
 
135
 
                // "Enclose" parameter
136
 
                $enclose = $pre;
137
 
                if ( isset( $args['enclose'] ) ) {
138
 
                        if ( $args['enclose'] === 'div' ) {
139
 
                                $enclose = GESHI_HEADER_DIV;
140
 
                        } elseif ( $args['enclose'] === 'none' ) {
141
 
                                $enclose = GESHI_HEADER_NONE;
142
 
                        }
143
 
                }
144
 
 
145
 
                if( isset( $args['line'] ) && $pre === GESHI_HEADER_PRE ) {
146
 
                        // Force <div> mode to maintain valid XHTML, see
147
 
                        // http://sourceforge.net/tracker/index.php?func=detail&aid=1201963&group_id=114997&atid=670231
148
 
                        $enclose = GESHI_HEADER_DIV;
149
 
                }
150
 
 
151
 
                return $enclose;
152
 
        }
153
 
 
154
 
        /**
155
 
         * Hook into Article::view() to provide syntax highlighting for
156
 
         * custom CSS and JavaScript pages
157
 
         *
158
 
         * @param string $text
159
 
         * @param Title $title
160
 
         * @param OutputPage $output
161
 
         * @return bool
162
 
         */
163
 
        public static function viewHook( $text, $title, $output ) {
164
 
                // Determine the language
165
 
                preg_match( '!\.(css|js)$!u', $title->getText(), $matches );
166
 
                $lang = $matches[1] == 'css' ? 'css' : 'javascript';
167
 
                // Attempt to format
168
 
                $geshi = self::prepare( $text, $lang );
169
 
                if( $geshi instanceof GeSHi ) {
170
 
                        $out = $geshi->parse_code();
171
 
                        if( !$geshi->error() ) {
172
 
                                // Done
173
 
                                $output->addHeadItem( "source-$lang", self::buildHeadItem( $geshi ) );
174
 
                                $output->addHTML( "<div dir=\"ltr\">{$out}</div>" );
175
 
                                return false;
176
 
                        }
177
 
                }
178
 
                // Bottle out
179
 
                return true;
180
 
        }
181
 
 
182
 
        /**
183
 
         * Initialise a GeSHi object to format some code, performing
184
 
         * common setup for all our uses of it
185
 
         *
186
 
         * @param string $text
187
 
         * @param string $lang
188
 
         * @return GeSHi
189
 
         */
190
 
        private static function prepare( $text, $lang ) {
191
 
                self::initialise();
192
 
                $geshi = new GeSHi( $text, $lang );
193
 
                if( $geshi->error() == GESHI_ERROR_NO_SUCH_LANG )
194
 
                        return null;
195
 
                $geshi->set_encoding( 'UTF-8' );
196
 
                $geshi->enable_classes();
197
 
                $geshi->set_overall_class( "source-$lang" );
198
 
                $geshi->enable_keyword_links( false );
199
 
                return $geshi;
200
 
        }
201
 
 
202
 
        /**
203
 
         * Prepare a CSS snippet suitable for use as a ParserOutput/OutputPage
204
 
         * head item
205
 
         *
206
 
         * @param GeSHi $geshi
207
 
         * @return string
208
 
         */
209
 
        private static function buildHeadItem( $geshi ) {
210
 
                global $wgUseSiteCss, $wgSquidMaxage;
211
 
                $lang = $geshi->language;
212
 
                $css[] = '<style type="text/css">/*<![CDATA[*/';
213
 
                $css[] = ".source-$lang {line-height: normal;}";
214
 
                $css[] = ".source-$lang li, .source-$lang pre {";
215
 
                $css[] = "\tline-height: normal; border: 0px none white;";
216
 
                $css[] = "}";
217
 
                $css[] = $geshi->get_stylesheet( false );
218
 
                $css[] = '/*]]>*/';
219
 
                $css[] = '</style>';
220
 
                if( $wgUseSiteCss ) {
221
 
                        $title = Title::makeTitle( NS_MEDIAWIKI, 'Geshi.css' );
222
 
                        $q = "usemsgcache=yes&action=raw&ctype=text/css&smaxage={$wgSquidMaxage}";
223
 
                        $css[] = '<style type="text/css">/*<![CDATA[*/';
224
 
                        $css[] = '@import "' . $title->getLocalUrl( $q ) . '";';
225
 
                        $css[] = '/*]]>*/';
226
 
                        $css[] = '</style>';
227
 
                }
228
 
                return implode( "\n", $css );
229
 
        }
230
 
 
231
 
        /**
232
 
         * Format an error message
233
 
         *
234
 
         * @param string $error
235
 
         * @return string
236
 
         */
237
 
        private static function formatError( $error = '' ) {
238
 
                $html = '';
239
 
                if( $error )
240
 
                        $html .= "<p>{$error}</p>";
241
 
                $html .= '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-specify' ) )
242
 
                        . ' <samp>&lt;source lang=&quot;html4strict&quot;&gt;...&lt;/source&gt;</samp></p>'
243
 
                        . '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-supported' ) ) . '</p>'
244
 
                        . self::formatLanguages();
245
 
                return "<div style=\"border: solid red 1px; padding: .5em;\">{$html}</div>";
246
 
        }
247
 
 
248
 
        /**
249
 
         * Format the list of supported languages
250
 
         *
251
 
         * @return string
252
 
         */
253
 
        private static function formatLanguages() {
254
 
                $langs = self::getSupportedLanguages();
255
 
                $list = array();
256
 
                if( count( $langs ) > 0 ) {
257
 
                        foreach( $langs as $lang ) {
258
 
                                $list[] = '<samp>' . htmlspecialchars( $lang ) . '</samp>';
259
 
                        }
260
 
                        return '<p style="padding: 0em 1em;">' . implode( ', ', $list ) . '</p>';
261
 
                } else {
262
 
                        return '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-loading' ) ) . '</p>';
263
 
                }
264
 
        }
265
 
 
266
 
        /**
267
 
         * Get the list of supported languages
268
 
         *
269
 
         * @return array
270
 
         */
271
 
        private static function getSupportedLanguages() {
272
 
                if( !is_array( self::$languages ) ) {
273
 
                        self::initialise();
274
 
                        self::$languages = array();
275
 
                        foreach( glob( GESHI_LANG_ROOT . "/*.php" ) as $file ) {
276
 
                                self::$languages[] = basename( $file, '.php' );
277
 
                        }
278
 
                        sort( self::$languages );
279
 
                }
280
 
                return self::$languages;
281
 
        }
282
 
 
283
 
        /**
284
 
         * Initialise messages and ensure the GeSHi class is loaded
285
 
         */
286
 
        private static function initialise() {
287
 
                if( !self::$initialised ) {
288
 
                        wfLoadExtensionMessages( 'SyntaxHighlight_GeSHi' );
289
 
                        if( !class_exists( 'GeSHi' ) )
290
 
                                require( 'geshi/geshi.php' );
291
 
                        self::$initialised = true;
292
 
                }
293
 
                return true;
294
 
        }
295
 
}