thibaultmarin has submitted this change and it was merged.

Change subject: PGFTikZ extension: initial import
......................................................................


PGFTikZ extension: initial import

Tag extension (<PGFTIkZ> tag) used to input and render PGF/TikZ code.
The code is placed in a temporary LaTeX file, compiled using the
following sequence of commands:
-latex (generates a .dvi from the generate .tex file)
-dvips (generates a .eps file from the .dvi file)
-epstool (extracts a minimal bounding box from the .eps file)
-convert (ImageMagick converts to the final file type)
The generated image is uploaded to the wiki, and the source used to
generate it is saved in the image's wikipage.  When parsing the tag,
the source code is tested against the saved code to skip compilation
if it is not needed (see mediawiki.org/wiki/Extension:PGFTikZ).
Update after review.

Change-Id: I261a697d687c8bde84dfd4d3d5c3c28962ea6b65
---
A PGFTikZ.compiler.php
A PGFTikZ.hooks.php
A PGFTikZ.i18n.php
A PGFTikZ.parser.php
A PGFTikZ.php
5 files changed, 952 insertions(+), 0 deletions(-)

Approvals:
  thibaultmarin: Verified; Looks good to me, approved
  Siebrand: Looks good to me, but someone else must approve



diff --git a/PGFTikZ.compiler.php b/PGFTikZ.compiler.php
new file mode 100644
index 0000000..844e729
--- /dev/null
+++ b/PGFTikZ.compiler.php
@@ -0,0 +1,225 @@
+<?php
+
+class PGFTikZCompiler {
+
+       /**
+        * Temporary folder name
+        */
+       private $_foldName = "";
+
+       /**
+        * Error message
+        */
+       private $_errorMsg = "";
+
+       /**
+        * Destructor (delete temporary folder)
+        */
+       public function __destruct() {
+               self::rmTempFiles( $this->_foldName );
+       }
+
+       /**
+        * Return the temporary folder name (to get the final image)
+        */
+       public function getFolder() {
+               return $this->_foldName;
+       }
+
+       /**
+        * Delete temporary files and folder used for LaTeX compilation
+        */
+       private static function rmTempFiles ($dir) {
+               if ( is_dir( $dir ) ) {
+                       wfRecursiveRemoveDir( $dir );
+               }
+       }
+
+       /**
+        * Return latest error message
+        */
+       public function getError() {
+               return $this->_errorMsg;
+       }
+
+       /**
+        * Report error with content of log
+        */
+       private function errorMsgLog( $msg, $log, $nLines = -1 ) {
+               $log = explode( PHP_EOL, $log );
+               if ( $nLines != -1 ) {
+                       $nLinesLog = count( $log );
+                       $log = array_slice( $log, $nLinesLog - $nLines + 1, 
$nLinesLog);
+               }
+               $log = preg_replace( "#" . $this->_foldName . "#", "", $log );
+               $log = implode ( "<br />", $log );
+               return htmlspecialchars( $msg ) . "<br />" . $log;
+       }
+
+       public function generateImage( $preambleStr, $latexContent, $imgFname,
+                                      $dpi, $TEXLR ) {
+
+               // Global parameters
+               global $wgPGFTikZDefaultDPI;
+               global $wgPGFTikZLaTeXPath;
+               global $wgPGFTikZLaTeXOpts;
+               global $wgPGFTikZdvipsPath;
+               global $wgPGFTikZepstoolPath;
+               global $wgImageMagickConvertCommand;
+
+               // 1 - Check ability to compile LaTeX file
+               // ---------------------------------------
+               // Check if latex is present and if the desired file format can 
be
+               // generated (might require imagemagick/epstool for tight 
bounding box).
+// TODO
+               // Commands
+               $LATEX      = $wgPGFTikZLaTeXPath;
+               $LATEX_OPTS = $wgPGFTikZLaTeXOpts;
+               $DVIPS      = $wgPGFTikZdvipsPath;
+               $EPSTOOL    = $wgPGFTikZepstoolPath;
+               $CONVERT    = $wgImageMagickConvertCommand;
+
+               // 2 - Create .tex file
+               // --------------------
+               // Store in temporary location (ensure writeable)
+
+               // Build latex string
+               // (see http://heinjd.wordpress.com/2010/04/28/
+               // creating-eps-figures-using-tikz/)
+               $latexStr  = '\documentclass{article}' . $TEXLR;
+               $latexStr .= '\def\pgfsysdriver{pgfsys-dvips.def}' . $TEXLR;
+               //$latexStr .= '\usepackage{nopageno}' . $TEXLR;
+               $latexStr .= '\usepackage[usenames]{color}' . $TEXLR;
+               $latexStr .= $preambleStr . $TEXLR;
+               $latexStr .= '\begin{document}' . $TEXLR;
+               $latexStr .= '\thispagestyle{empty}' . $TEXLR;
+               $latexStr .= $latexContent;
+               $latexStr .= '\end{document}' . $TEXLR;
+
+               // Write to file
+               $latexTmpDir = wfTempDir() . "/tmp_latex_" . rand(1,999999999);
+               $this->_foldName = $latexTmpDir;
+               if ( !is_dir( $latexTmpDir ) ) {
+                       if ( !mkdir( $latexTmpDir, 0700, true ) ) {
+                               $this->_errorMsg = errorMsg(
+                                   wfMessage ( 'pgftikz-error-tmpdircreate' ) 
);
+                               return false;
+                       }
+               }
+               $latexBaseFname = $latexTmpDir . "/tikz";
+               $latexFname = $latexBaseFname . ".tex";
+               $latexWriteRet = file_put_contents( $latexFname, $latexStr );
+               if ( !$latexWriteRet ) {
+                       $this->_errorMsg = errorMsg(
+                           wfMessage( 'pgftikz-error-texfilecreate' ) );
+                       return false;
+               }
+
+               // 3 - Generate image (compilation)
+               // --------------------------------
+
+               // External calls
+               $cmd_latex = "$LATEX $LATEX_OPTS -output-directory=$latexTmpDir 
" .
+                   wfEscapeShellArg( $latexFname ) . " &> 
$latexTmpDir/latex_log.txt";
+               //print ("Running latex on tikz code\n(<$cmd_latex>)..." . 
"\n");
+               wfShellExec( $cmd_latex, $latexRetVal );
+               if ( !file_exists( "$latexBaseFname.dvi" ) || $latexRetVal != 0 
) {
+                       if ( file_exists( "$latexTmpDir/latex_log.txt" ) ) {
+                               $retLatex = file_get_contents( 
"$latexTmpDir/latex_log.txt" );
+                               if ( empty( $retLatex ) ) {
+                                       $this->_errorMsg = errorMsg(
+                                           wfMessage( 
'pgftikz-error-latexnoout' ) );
+                                       return false;
+                               }
+                               $this->_errorMsg = $this->errorMsgLog(
+                                   wfMessage( 'pgftikz-error-latexcompil' ), 
$retLatex, 10 );
+                               return false;
+                       } else {
+                               $this->_errorMsg = errorMsg(
+                                   wfMessage( 'pgftikz-error-latexnoout' ) );
+                               return false;
+                       }
+               }
+
+               // Generate EPS
+               $cmd_dvips = "$DVIPS -R -K0 -E " .
+                   wfEscapeShellArg( $latexBaseFname ) . ".dvi " .
+                   "-o $latexTmpDir/out.eps &> $latexTmpDir/dvips_log.txt";
+               //print ("Running dvips on dvi\n(<$cmd_dvips>)..." . "\n");
+               wfShellExec( $cmd_dvips, $dvipsRetVal );
+               if ( !file_exists( "$latexTmpDir/out.eps" ) || $dvipsRetVal != 
0 ) {
+                       if ( file_exists( "$latexTmpDir/dvips_log.txt" ) ) {
+                               $retDvips = file_get_contents( 
"$latexTmpDir/dvips_log.txt" );
+                               if ( empty( $retDvips ) ) {
+                                       $this->_errorMsg = errorMsg(
+                                           wfMessage( 
'pgftikz-error-dvipsnoout' ) );
+                                       return false;
+                               }
+                               $this->_errorMsg = $this->errorMsgLog(
+                                   wfMessage( 'pgftikz-error-dvipscompil' ), 
$retDvips, 10 );
+                               return false;
+                       } else {
+                               $this->_errorMsg = errorMsg(
+                                   wfMessage( 'pgftikz-error-dvipsnoout' ) );
+                               return false;
+                       }
+               }
+
+               // Fix bounding box
+               $cmd_eps = "$EPSTOOL --copy --bbox $latexTmpDir/out.eps " .
+                   "$latexTmpDir/out_bb.eps &> $latexTmpDir/epstool_log.txt";
+               //print ("Fixing bounding box\n(<$cmd_eps>)..." . "\n");
+               wfShellExec( $cmd_eps, $epstoolRetVal );
+               if ( !file_exists( "$latexTmpDir/out_bb.eps" ) ||
+                    $epstoolRetVal != 0 ) {
+                       if ( file_exists( "$latexTmpDir/epstool_log.txt" ) ) {
+                               $retEpstool = file_get_contents(
+                                   "$latexTmpDir/epstool_log.txt" );
+                               if ( empty( $retEpstool ) ) {
+                                       $this->_errorMsg = errorMsg(
+                                           wfMessage( 
'pgftikz-error-epstoolnoout' ) );
+                                       return false;
+                               }
+                               $this->_errorMsg = $this->errorMsgLog(
+                                   wfMessage( 'pgftikz-error-epstoolrun' ),
+                                   $retEpstool, 10 );
+                               return false;
+                       } else {
+                               $this->_errorMsg = errorMsg(
+                                       wfMessage( 'pgftikz-error-epstoolnoout' 
) );
+                               return false;
+                       }
+               }
+
+               // Convert to desired output
+               $cmd_convert = "$CONVERT -density $dpi $latexTmpDir/out_bb.eps 
" .
+                   "$latexTmpDir/" . wfEscapeShellArg( $imgFname ) .
+                   " &> $latexTmpDir/convert_log.txt";
+               //print ("Converting file\n(<$cmd_convert>)..." . "\n");
+               wfShellExec( $cmd_convert, $convertRetVal );
+               if ( !file_exists( "$latexTmpDir/$imgFname" ) ||
+                    $convertRetVal != 0 ) {
+                       if ( file_exists( "$latexTmpDir/convert_log.txt" ) ) {
+                               $retConvert =
+                                   file_get_contents( 
"$latexTmpDir/convert_log.txt" );
+                               if ( empty( $retConvert ) ) {
+                                       $this->_errorMsg = errorMsg(
+                                           wfMessage( 
'pgftikz-error-convertnoout' ) );
+                                       return false;
+                               }
+                               $this->_errorMsg = $this->errorMsgLog(
+                                   wfMessage( 'pgftikz-error-convertrun' ),
+                                   $retConvert, 10 );
+                               return false;
+                       } else {
+                               $this->_errorMsg = errorMsg(
+                                   wfMessage( 'pgftikz-error-convertnoout' ) );
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+
+}
+
diff --git a/PGFTikZ.hooks.php b/PGFTikZ.hooks.php
new file mode 100644
index 0000000..aea188d
--- /dev/null
+++ b/PGFTikZ.hooks.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * PGFTikZ parser hooks
+ */
+class PGFTikZHooks {
+
+       /**
+        * Register PGFTikZ hook
+        */
+       public static function onPGFTikZParserInit( $parser ) {
+               $parser->setHook( 'PGFTikZ', 'PGFTikZParser::PGFTikZParse' );
+               return true;
+       }
+
+       /**
+        * After tidy hook - restore content
+        */
+       public static function onPGFTikZAfterTidy(&$parser, &$text) {
+               // find markers in $text
+               // replace markers with actual output
+               global $markerList;
+               for ( $i = 0; $i < count( $markerList ); $i++ ) {
+                       $text = preg_replace( '/xx-marker' . $i . '-xx/',
+                                             $markerList[$i], $text );
+               }
+               return true;
+       }
+
+}
+
diff --git a/PGFTikZ.i18n.php b/PGFTikZ.i18n.php
new file mode 100644
index 0000000..2c07773
--- /dev/null
+++ b/PGFTikZ.i18n.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Internationalisation for PGFTikZ
+ *
+ * @PGFTikZ.i18n.php
+ * @ingroup Extensions
+ */
+$messages = array();
+
+/** English
+ * @author thibault marin
+ */
+$messages['en'] = array(
+
+       // Description
+       'pgftikz-desc' => "Render PGF/TikZ graphs in wiki",
+
+       // Error messages
+       'pgftikz-error-title' => 'PGFTikZ parser error:',
+       'pgftikz-error-emptyinput' => "Empty input.",
+       'pgftikz-error-imagelineparse' => "Could not parse image line, expected 
something like ([[Image:image_file_name.ext|image link]]).",
+       'pgftikz-error-apigetpagecontent' => "Could not get image page content 
for comparision.",
+       'pgftikz-error-apiedit' => "Could not update file wikipage.",
+       'pgftikz-error-apidelete' => "Could not delete preview wikipage.",
+       'pgftikz-error-nonpgffile' => "Existing file is not PGFTikZ-generated, 
not overwriting.",
+       'pgftikz-error-preambleparse' => "Error parsing LaTeX preamble.",
+       'pgftikz-error-tmpdircreate' => "Error creating temporary folder.",
+       'pgftikz-error-texfilecreate' => "Error creating LaTeX source file.",
+       'pgftikz-error-latexnoout' => "Error when running LaTeX (no output, is 
latex present?).",
+       'pgftikz-error-latexcompil' => "Error when running LaTeX (compilation 
error).",
+       'pgftikz-error-dvipsnoout' => "Error when running dvips (no output, is 
dvips present?).",
+       'pgftikz-error-dvipscompil' => "Error when running dvips (runtime 
error).",
+       'pgftikz-error-epstoolnoout' => "Error when running epstool (no output, 
is epstool present?).",
+       'pgftikz-error-epstoolrun' => "Error when running epstool (runtime 
error).",
+       'pgftikz-error-convertnoout' => "Error when running convert (no output, 
is convert present?).",
+       'pgftikz-error-convertrun' => "Error when running convert (runtime).",
+
+       'pgftikz-error-uploadlocal_error_empty' => "Error during upload (empty 
file).",
+       'pgftikz-error-uploadlocal_error_missing' => "Error during upload 
(filetype missing).",
+       'pgftikz-error-uploadlocal_error_badtype' => "Error during upload (bad 
filetype).",
+       'pgftikz-error-uploadlocal_error_tooshort' => "Error during upload 
(filename too short).",
+       'pgftikz-error-uploadlocal_error_illegal' => "Error during upload 
(illegal filename).",
+       'pgftikz-error-uploadlocal_error_overwrite' => "Error during upload 
(overwrite).",
+       'pgftikz-error-uploadlocal_error_verify' => "Error during upload 
(verification error).",
+       'pgftikz-error-uploadlocal_error_hook' => "Error during upload (hook 
aborted).",
+       'pgftikz-error-uploadlocal_error_unknown' => "Error during upload 
(unknown error)."
+
+);
+
+/** Message documentation
+ * @author thibault marin
+ */
+$messages['qqq'] = array(
+       'pgftikz' => "PGFTikZ parser",
+       'pgftikz-desc' => "{{desc}}",
+
+       'pgftikz-error-title' => "Generic title message displayed before the 
actual reported error",
+       // Errors
+       'pgftikz-error-emptyinput' => "Displayed when parsing input and finding 
no text between <PGFTikZ> tags.",
+       'pgftikz-error-imagelineparse' => "Displayed when extraction of 
filename from image entry line (e.g. [[Image:filename.png]]) failed.",
+       'pgftikz-error-apigetpagecontent' => "Displayed when API call to get 
existing file failed.",
+       'pgftikz-error-apiedit' => "Displayed when API call failed to edit 
wikipage associated with uploaded image.",
+       'pgftikz-error-apidelete' => "Displayed when API call to delete preview 
wikipage.",
+       'pgftikz-error-nonpgffile' => "Displayed when a file with the target 
name was found, but the PGFTikZ header was not found (possibly a non 
PGFTikZ-generated file).",
+       'pgftikz-error-preambleparse' => "<PGFTikZPreamble> tags not found in 
input.",
+       'pgftikz-error-tmpdircreate' => "Displayed when temporary folder 
creation failed.",
+       'pgftikz-error-texfilecreate' => "Error creating LaTeX source file.",
+       'pgftikz-error-latexnoout' => "Error when running LaTeX (no output).",
+       'pgftikz-error-latexcompil' => "Error when running LaTeX (compilation 
error).",
+       'pgftikz-error-dvipsnoout' => "Error when running dvips (no output).",
+       'pgftikz-error-dvipscompil' => "Error when running dvips (runtime 
error).",
+       'pgftikz-error-epstoolnoout' => "Error when running epstool (no 
output).",
+       'pgftikz-error-epstoolrun' => "Error when running epstool (runtime 
error).",
+       'pgftikz-error-convertnoout' => "Error when running convert (no 
output).",
+       'pgftikz-error-convertrun' => "Error when running convert (runtime).",
+
+       'pgftikz-error-uploadlocal_error_empty' => "Error during upload (empty 
file).",
+       'pgftikz-error-uploadlocal_error_missing' => "Error during upload 
(filetype missing).",
+       'pgftikz-error-uploadlocal_error_badtype' => "Error during upload (bad 
filetype).",
+       'pgftikz-error-uploadlocal_error_tooshort' => "Error during upload 
(filename too short).",
+       'pgftikz-error-uploadlocal_error_illegal' => "Error during upload 
(illegal filename).",
+       'pgftikz-error-uploadlocal_error_overwrite' => "Error during upload 
(overwrite).",
+       'pgftikz-error-uploadlocal_error_verify' => "Error during upload 
(verification error).",
+       'pgftikz-error-uploadlocal_error_hook' => "Error during upload (hook 
aborted).",
+       'pgftikz-error-uploadlocal_error_unknown' => "Error during upload 
(unknown error)."
+
+);
+
diff --git a/PGFTikZ.parser.php b/PGFTikZ.parser.php
new file mode 100644
index 0000000..7f8f05b
--- /dev/null
+++ b/PGFTikZ.parser.php
@@ -0,0 +1,529 @@
+<?php
+
+class PGFTikZParser {
+
+       /**
+        * Report error to wikipage
+        */
+       public static function errorMsg( $msg ) {
+               return "<div><pre><tt>" . wfMessage( 'pgftikz-error-title' ) .
+                      "</tt>" . $msg . "</pre></div>";
+       }
+
+       /**
+        * Report error with content of log
+        */
+       private static function errorMsgLog( $msg, $log, $nLines = -1 ) {
+               $log = explode( PHP_EOL, $log );
+               if ( $nLines != -1 ) {
+                       $nLinesLog = count( $log );
+                       $log = array_slice( $log, $nLinesLog - $nLines + 1, 
$nLinesLog);
+               }
+               $log = implode ( "<br />", $log );
+               return self::errorMsg( htmlspecialchars( $msg ) . "<br />" .
+                                      htmlspecialchars( $log ) );
+       }
+
+       /**
+        * Delete wikipage created for preview (if it exists)
+        */
+       private static function deletePreviewPage ( $fname, $previewSuffix,
+                                                   $token = null ) {
+
+               global $wgRequest, $wgUser;
+
+               // Get filename for preview
+               $fname_preview = preg_replace( "/\.(\w+)$/", $previewSuffix . 
".$1",
+                                              $fname );
+
+               // Check page existence (using API)
+               $params = new DerivativeRequest(
+                   $wgRequest,
+                   array(
+                       'action' => 'query',
+                       'titles' => 'File:' . urlencode( $fname_preview ),
+                       'prop'   => 'info' )
+               );
+               $api = new ApiMain( $params );
+               try {
+                       $api->execute();
+               } catch ( Exception $e ) {
+                       self::errorMsgLog( wfMessage( 
'pgftikz-error-apigetpagecontent' ),
+                                          $e->getMessage() );
+               }
+               $apiResult = $api->getResultData();
+               $apiPagePreview = array_values( $apiResult['query']['pages'] );
+
+               if ( count( $apiPagePreview ) > 0 ) {
+                       $previewPageExists = array_key_exists( 'pageid',
+                                                              
$apiPagePreview[0] );
+                       if ( $previewPageExists ) {
+                               // If page exists, delete it (API call)
+
+                               if ( $token === null ) {
+                                       // Get edit token if not provided
+                                       $token = $wgUser->getEditToken();
+                               }
+
+                               $reqParams = array(
+                                   'action'     => 'delete',
+                                   'title'      => 'File:' . urlencode( 
$fname_preview ),
+                                   'token'      => $token );
+                               $api = new ApiMain(
+                                   new DerivativeRequest( $wgRequest, 
$reqParams, false ),
+                                   true // enable write?
+                               );
+                               try {
+                                       $api->execute();
+                               } catch ( Exception $e ) {
+                                       self::errorMsgLog( wfMessage( 
'pgftikz-error-apidelete' ),
+                                                          $e->getMessage() );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Main parser function
+        */
+       public static function PGFTikZParse( $input, array $args,
+                                            Parser $parser, PPFrame $frame) {
+
+               // Header / footer for image pages (better if not configurable 
by user?)
+               $wgPGFTikZTextMarker = "PGFTikZ-file-text -- DO NOT EDIT";
+               // Suffix appended to filename to create preview page
+               $wgPGFTikZPreviewSuffix = "__PGFTikZpreview";
+
+               // Local parameters
+               $flagDebug = true;
+               $eol = PHP_EOL; // "\n"
+
+               // Global variables
+               global $wgRequest;
+               global $wgServer;
+               global $wgScriptPath;
+               global $wgUser;
+               global $wgParser;
+               global $wgParserConf;
+
+               // Global parameters
+               global $wgPGFTikZDefaultDPI;
+
+               // Found this to avoid 'Invalid marker' problem
+               // 
http://www.cityofsteamwiki.com/extensions/ArticleComments/.svn/
+               // pristine/39/39caa7e912faa5b2e8ae5d3418086bc95a7a7e91.svn-base
+               if ( $parser === $wgParser ) {
+                       // Needed since r82645. Workaround the 'Invalid marker' 
problem by
+                       // giving a new parser to wfMsgExt().
+                       $wgParser = new StubObject( 'wgParser', 
$wgParserConf['class'],
+                                                    array( $wgParserConf ) );
+               }
+
+               // Detect preview mode (use another filename for upload if it 
is the
+               // case).  This is to avoid edit conflicts when saving the page.
+               $flagIsPreview = $parser->getOptions()->getIsPreview();
+
+// DEBUG-disable cache
+               //$parser->disableCache();
+
+               // Create text page for input (saved in file's wikipage)
+               $imgPageText  = "<pre>\n";
+               $imgPageText .= $wgPGFTikZTextMarker . "\n";
+               $imgPageText .= htmlspecialchars( $input ) . "\n";
+               $imgPageText .= $wgPGFTikZTextMarker . "\n";
+               $imgPageText .= "</pre>\n";
+
+               // User can force update of image file by passing an "update" 
argument
+               // (the value of the parameter is discarded)
+               $flagForceUpdate = isset( $args['update'] );
+
+               // Get dpi parameter
+               if ( isset( $args['dpi'] ) ) {
+                       $dpi = $args['dpi'];
+               } else {
+                       $dpi = $wgPGFTikZDefaultDPI;
+               }
+
+               // Split input line by line
+               $lines = explode( "\n", $input );
+               // Remove empty lines, reindex to 0:N-1
+               $lines = array_values( array_filter( $lines, 'trim' ) );
+               $nLines = count( $lines );
+               if ( count( $nLines ) == 0 ) {
+                       return self::errorMsg ( wfMessage( 
'pgftikz-error-emptyinput' ) );
+               }
+
+               // Get image entry (should be the first line)
+               // Require a filename to use for output image
+               $imgFname = "";
+               $firstLineIdx = 0;
+               while ( strlen( $lines[$firstLineIdx] ) == 0 &&
+                       $firstLineIdx < $nLines ) {
+                       $firstLineIdx++;
+               }
+               if ( $firstLineIdx >= $nLines ) {
+                       return self::errorMsg( wfMessage( 
'pgftikz-error-emptyinput' ) );
+               }
+
+               // Extract filename from first (non-empty) line
+               // $imageEntryLine is an image inclusion line
+               // (e.g. [[File:graph.png|title]])
+               $imageEntryLine = $lines[$firstLineIdx];
+               if ( preg_match( "/\s*\[\[\s*File\s*:\s*([^|\]]*)/",
+                                $imageEntryLine, $matches ) ) {
+                       if ( count( $matches ) > 1 ) {
+                               $imgFname = $matches[1];
+                       } else {
+                               // DEBUG
+                               return self::errorMsg(
+                                   wfMessage( 'pgftikz-error-imagelineparse' ) 
);
+                       }
+               } else {
+                       return self::errorMsg(
+                           wfMessage( 'pgftikz-error-imagelineparse' ) );
+               }
+               if ( $flagDebug ) {
+                       wfDebugLog( "", "PGF-image entry line found: 
$imageEntryLine\n" );
+               }
+               if ( $flagIsPreview ) {
+                       // Append suffix to filename for preview
+                       $imgFnameOld = $imgFname;
+                       $imgFname = preg_replace( "/\.(\w+)$/",
+                                                 $wgPGFTikZPreviewSuffix . 
".$1",
+                                                 $imgFname );
+                       // Update image entry line
+                       $imageEntryLine = str_replace( $imgFnameOld, $imgFname,
+                                                      $imageEntryLine );
+               }
+               if ($flagDebug) {
+                       wfDebugLog( "", "PGF-image fname: $imgFname - 
$imageEntryLine\n" );
+               }
+
+               // 1 - Check existence of image file
+               // ---------------------------------
+               // Note: there might be a better way using 
Image::newFromTitle() or
+               // Image::newFromName() (see
+               // 
http://www.ehartwell.com/TechNotes/MediaWikiSideTrips.htm#Load_image)
+
+               // Check if a file with the same name exists using API
+               $flagFoundImage = false;
+               $flagNeedUpdate = false;
+               if ( !$flagForceUpdate ) {
+                       $params = new DerivativeRequest(
+                               $wgRequest,
+                               array(
+                                       'action' => 'query',
+                                       'titles' => 'File:' . urlencode( 
$imgFname ),
+                                       'prop'   => 'revisions',
+                                       'rvprop' => 'content' )
+                               );
+                       $api = new ApiMain( $params );
+                       try {
+                               $api->execute();
+                       } catch ( Exception $e ) {
+                               self::errorMsgLog(
+                                   wfMessage( 
'pgftikz-error-apigetpagecontent' ),
+                                   $e->getMessage() );
+                       }
+                       $apiResult = $api->getResultData();
+                       $apiPagesResult = array_values( 
$apiResult['query']['pages'] );
+
+                       if ( count( $apiPagesResult ) > 0 ) {
+                               $flagFoundImage = array_key_exists( 'revisions',
+                                                                   
$apiPagesResult[0] );
+                               if ( $flagFoundImage ) {
+//DEBUG
+                                       if ( $flagDebug ) {
+                                               wfDebugLog( "", "PGF-File 
$imgFname already exists, " .
+                                                           "checking if 
different\n". "\n");
+                                       }
+                                       if ( count( 
$apiPagesResult[0]['revisions'] ) > 0 ) {
+                                               // File already exists, compare 
content
+                                               $imgPageTextRef =
+                                                   
$apiPagesResult[0]['revisions'][0]['*'];
+                                               $textNewArray = explode( 
PHP_EOL, $imgPageText );
+                                               $textRefArray = explode( 
PHP_EOL, $imgPageTextRef );
+                                               $nLinesNew = count( 
$textNewArray );
+                                               $nLinesRef = count( 
$textRefArray );
+                                               // Compare strings between 
$wgPGFTikZTextMarker lines
+                                               $liNew = 1;
+                                               $liRef = 0;
+                                               $foundFirst = false;
+                                               while ( $liRef < $nLinesRef && 
!$foundFirst ) {
+                                                       $lineRef = 
$textRefArray[$liRef];
+                                                       if ( strcmp( $lineRef,
+                                                                    
$wgPGFTikZTextMarker ) == 0 ) {
+                                                               $foundFirst = 
true;
+                                                       } else {
+                                                               $liRef++;
+                                                       }
+                                               }
+                                               if ( !$foundFirst ) {
+                                                       return self::errorMsg(
+                                                           wfMessage( 
'pgftikz-error-nonpgffile' ) );
+                                               }
+                                               $foundDiff = false;
+                                               while ( $liNew < $nLinesNew && 
$liRef < $nLinesRef &&
+                                                       !$foundDiff ) {
+                                                       $lineRef = 
$textRefArray[$liRef];
+                                                       $lineNew = 
$textNewArray[$liRef];
+                                                       if ( strcmp( $lineRef, 
$lineNew ) != 0 ) {
+                                                               $foundDiff = 
true;
+                                                       } else {
+                                                               $liRef++;
+                                                               $liNew++;
+                                                       }
+                                               }
+                                               $flagNeedUpdate = $foundDiff;
+                                       } else {
+                                               return self::errorMsg(
+                                                   wfMessage( 
'pgftikz-error-apigetpagecontent' ) );
+                                       }
+                               } else {
+                                       if ( $flagDebug ) {
+                                               wfDebugLog( "", "PGF-image not 
found\n" );
+                                       }
+                               }
+                       } else {
+                               return self::errorMsg(
+                                   wfMessage( 
'pgftikz-error-apigetpagecontent' ) );
+                       }
+               }
+// DEBUG
+               if ( $flagDebug ) {
+                       wfDebugLog( "", "PGF-Found  = " . ( $flagFoundImage  
?1:0) . "\n");
+                       wfDebugLog( "", "PGF-Update = " . ( $flagForceUpdate 
?1:0) . "\n");
+                       wfDebugLog( "", "PGF-NeedUp = " . ( $flagNeedUpdate  
?1:0) . "\n");
+               }
+
+               // If the file exists and update is not needed or forced, 
simply render
+               // the image.
+               if ( $flagFoundImage && !$flagNeedUpdate && !$flagForceUpdate ) 
{
+//DEBUG
+                       if ( $flagDebug ) {
+                               wfDebugLog( "","PGF-no need to update, display 
image\n");
+                       }
+                       if ( !$flagIsPreview ) {
+                               self::deletePreviewPage( $imgFname, 
$wgPGFTikZPreviewSuffix );
+                       }
+                       return $parser->recursiveTagParse( $imageEntryLine, 
$frame );
+               }
+//DEBUG
+               if ( $flagDebug ) {
+                       wfDebugLog( "","PGF-Continue with compilation". "\n");
+               }
+
+               // End-of-line character for temporary tex file
+               $TEXLR = PHP_EOL; //"\n";
+
+               // Extract preamble
+               $preambleStr = "";
+               $li = $firstLineIdx + 1;
+               $foundPreambleStart = false;
+               $foundPreambleEnd = false;
+               while ( $li < $nLines && !$foundPreambleEnd )
+               {
+                       $line = $lines[$li];
+                       if ( !$foundPreambleStart ) {
+                               if ( preg_match("#<PGFTikZPreamble>#", $line ) 
) {
+                                       $foundPreambleStart = true;
+                               }
+                       } else {
+                               if ( preg_match( "#</PGFTikZPreamble>#", $line 
) ) {
+                                       $foundPreambleEnd = true;
+                               } else {
+                                       // Append preamble line to latex string
+                                       $preambleStr .= $line . $TEXLR;
+                               }
+                       }
+                       $li++;
+               }
+               if ( !$foundPreambleStart || !$foundPreambleEnd ) {
+                       return self::errorMsg(
+                           wfMessage ( 'pgftikz-error-preambleparse' ) );
+               }
+
+               // Extract tex input
+               $latexContent = "";
+               while ( $li < $nLines ) {
+                       $latexContent .= $lines[$li] . $TEXLR;
+                       $li++;
+               }
+
+               // Instantiate compiler module
+               $compiler = new PGFTikZCompiler();
+
+               // Create latex file, compile and convert
+               if ( !$compiler->generateImage( $preambleStr, $latexContent, 
$imgFname,
+                                               $dpi, $TEXLR ) ) {
+                       return self::errorMsg( $compiler->getError() );
+               }
+
+//DEBUG
+               if ( $flagDebug ) {
+                       wfDebugLog( "", "PGF-done compiling, upload" . "\n");
+               }
+
+               // 5 - Upload output image to wiki
+               // -------------------------------
+
+               // File location
+               $filename = $compiler->getFolder() . "/" . $imgFname;
+
+               // Get edit token
+               $token = $wgUser->getEditToken();
+
+               // Request parameters
+               $comment = 'Automatically uploaded by PGFTikZ extension';
+               $params = array(
+                   'filename'        => $imgFname,
+                   'comment'         => $comment,
+                   'text'            => $imgPageText,
+                   'file'            => '@'.$filename,
+                   'ignorewarnings'  => '1',
+                   'token'           => $token
+               );
+
+//DEBUG
+               if ($flagDebug) {
+                       wfDebugLog( "", "PGF-upload from file\n");
+               }
+
+               $upload = new UploadFromFile();
+               $upload->initializeFromRequest( $wgRequest );
+               $upload->initializePathInfo( $imgFname, $filename,
+                                            filesize( $filename ) );
+
+               $title = $upload->getTitle();
+               if( $title == null ) {
+                       wfDebugLog( '', 'PGF-title empty' );
+                       return;
+               }
+               $warnings = $upload->checkWarnings();
+               if ( $flagDebug ) {
+                       $var = var_export( $warnings, true );
+                       wfDebugLog( '', 'PGF-warnings' . $var );
+               }
+
+               $verification = $upload->verifyUpload();
+               if ( $flagDebug ) {
+                       $var = var_export( $verification, true );
+                       wfDebugLog( '', 'PGF-verification' . $var );
+               }
+               if ( $verification['status'] === UploadBase::OK ) {
+                       $upload->performUpload( $comment, $imgPageText, false, 
$wgUser );
+               } else {
+                       switch( $verification['status'] ) {
+                       case UploadBase::EMPTY_FILE:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_empty' ) );
+                               break;
+                       case UploadBase::FILETYPE_MISSING:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_missing' ) );
+                               break;
+                       case UploadBase::FILETYPE_BADTYPE:
+                               global $wgFileExtensions;
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_badtype' ) );
+                               break;
+                       case UploadBase::MIN_LENGTH_PARTNAME:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_tooshort' ) );
+                               break;
+                       case UploadBase::ILLEGAL_FILENAME:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_illegal' ) );
+                               break;
+                       case UploadBase::OVERWRITE_EXISTING_FILE:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_overwrite' ) );
+                               break;
+                       case UploadBase::VERIFICATION_ERROR:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_verify',
+                                              $verification['details'][0] ) );
+                               break;
+                       case UploadBase::HOOK_ABORTED:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_hook' ) );
+                               break;
+                       default:
+                               return errorMsg(
+                                   wfMessage( 
'pgftikz-error-uploadlocal_error_unknown' ) );
+                               break;
+                       }
+               }
+
+               // Still need to update page text
+               if ( $flagFoundImage || $flagForceUpdate ) {
+//DEBUG
+                       if ($flagDebug) {
+                               wfDebugLog( "", "PGF-update page content\n");
+                       }
+                       // Note: ignoring edit conflicts (for now)
+                       $reqParams = array(
+                               'action'     => 'edit',
+                               'title'      => 'File:' . urlencode( $imgFname),
+                               'text'       => $imgPageText,
+                               'summary'    => wfMsgForContent( 'update from 
PGFTikZ' ),
+                               'notminor'   => true,
+                               'recreate'   => true,
+                               'bot'        => true,
+                               'token'      => $token
+                               );
+                       $api = new ApiMain(
+                               new DerivativeRequest( $wgRequest, $reqParams, 
false ),
+                               true // enable write?
+                               );
+                       try {
+                               $api->execute();
+                       } catch ( Exception $e ) {
+                               self::errorMsgLog( wfMessage( 
'pgftikz-error-apiedit' ),
+                                                  $e->getMessage() );
+                       }
+                       $apiResult = $api->getResultData();
+                       if ( array_key_exists ( 'edit', $apiResult ) ) {
+                               if ( array_key_exists( 'result', 
$apiResult['edit'] ) ) {
+                                       if ( !strcasecmp( 
$apiResult['edit']['result'],
+                                                         'Success' ) ) {
+                                               self::errorMsg( wfMessage( 
'pgftikz-error-apiedit' ) );
+                                       }
+                               } else {
+                                       self::errorMsg( wfMessage( 
'pgftikz-error-apiedit' ) );
+                               }
+                       } else {
+                               self::errorMsg( wfMessage( 
'pgftikz-error-apiedit' ) );
+                       }
+               }
+//DEBUG
+               if ($flagDebug) {
+                       wfDebugLog( "", "PGF-done updating page content\n");
+               }
+
+               // Delete intermediate files
+//DEBUG
+               if ($flagDebug) {
+                       wfDebugLog( "", "PGF-deleting temporary data\n");
+               }
+
+               // Delete file used for preview
+               if ( !$flagIsPreview ) {
+                       self::deletePreviewPage( $imgFname, 
$wgPGFTikZPreviewSuffix,
+                                                $token );
+               }
+
+               // 6 - Insert <img ...> code in page
+               // ---------------------------------
+               // Use recursiveTagParse()
+               // (http://www.mediawiki.org/wiki/Manual:
+               // Tag_extensions#Since_version_1.16)
+//DEBUG
+               if ($flagDebug) {
+                       wfDebugLog( "", "PGF-render image:\n" . 
$imageEntryLine. "\n" );
+               }
+
+               return $parser->recursiveTagParse( $imageEntryLine, $frame );
+       }
+}
+
diff --git a/PGFTikZ.php b/PGFTikZ.php
new file mode 100644
index 0000000..e2186b3
--- /dev/null
+++ b/PGFTikZ.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * PGFTikZ - this extension creates images from PGF/TikZ input (requires LaTeX
+ * on the server).
+ *
+ * To activate this extension, add the following into your LocalSettings.php 
file:
+ * require_once('$IP/extensions/PGFTikZ.php');
+ *
+ * @ingroup Extensions
+ * @author Thibault Marin
+ * @version 0.1
+ * @link http://www.mediawiki.org/wiki/Extension:PGFTikZ
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
+ *
+ * @revision
+ * 0.1
+ *  Initial version.
+ */
+
+/**
+ * Protect against register_globals vulnerabilities.
+ * This line must be present before any global variable is referenced.
+ */
+if( !defined( 'MEDIAWIKI' ) )
+{
+        echo( "This is an extension to the MediaWiki package and cannot be run 
standalone.\n" );
+        die( -1 );
+}
+
+// Extension credits that will show up on Special:Version
+$wgExtensionCredits['validextensionclass'][] = array(
+       'path'           => __FILE__,
+       'name'           => 'PGFTikZ',
+       'version'        => '0.1',
+       'author'         => 'Thibault Marin',
+       'url'            => 'http://www.mediawiki.org/wiki/Extension:PGFTikZ',
+       'descriptionmsg' => 'pgftikz-desc'
+       );
+
+/**
+ * Extension class
+ */
+$wgAutoloadClasses['PGFTikZHooks'] = dirname( __FILE__ ) . 
'/PGFTikZ.hooks.php';
+$wgAutoloadClasses['PGFTikZParser'] = dirname( __FILE__ ) . 
'/PGFTikZ.parser.php';
+$wgAutoloadClasses['PGFTikZCompiler'] = dirname( __FILE__ ) . 
'/PGFTikZ.compiler.php';
+
+/**
+ * Register hooks
+ */
+$wgHooks['ParserFirstCallInit'][] = 'PGFTikZHooks::onPGFTikZParserInit';
+$wgHooks['ParserAfterTidy'][]     = 'PGFTikZHooks::onPGFTikZAfterTidy';
+
+/**
+ * Internationalization
+ */
+$wgExtensionMessagesFiles['PGFTikZ'] = dirname( __FILE__ ) . 
'/PGFTikZ.i18n.php';
+
+/**
+ * Parameters (modify in LocalSettings.php)
+ */
+
+// Default resolution for generated images
+$wgPGFTikZDefaultDPI = 300;
+
+// Full path to latex executable (assume in path if empty)
+$wgPGFTikZLaTeXPath = 'latex';
+
+// Extra options to latex executable (e.g. --shell-escape for special TikZ
+// rendering)
+$wgPGFTikZLaTeXOpts = '-no-shell-escape';
+
+// Full path to 'dvips' executable
+$wgPGFTikZdvipsPath = 'dvips';
+
+// Full path to 'epstool' executable
+$wgPGFTikZepstoolPath = 'epstool';
+
+

-- 
To view, visit https://gerrit.wikimedia.org/r/38487
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I261a697d687c8bde84dfd4d3d5c3c28962ea6b65
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/PGFTikZ
Gerrit-Branch: master
Gerrit-Owner: thibaultmarin <thibault.ma...@gmail.com>
Gerrit-Reviewer: Nemo bis <federicol...@tiscali.it>
Gerrit-Reviewer: Platonides <platoni...@gmail.com>
Gerrit-Reviewer: Siebrand <siebr...@wikimedia.org>
Gerrit-Reviewer: thibaultmarin <thibault.ma...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to