*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* --------------------------------------------------------------------
*
*/
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
if(!defined('PLUGIN_TEXIT')) define('PLUGIN_TEXIT',DOKU_PLUGIN.'texit/');
if(!defined('PLUGIN_TEXIT_CONF')) define('PLUGIN_TEXIT_CONF',PLUGIN_TEXIT.'conf/');
require_once(PLUGIN_TEXIT.'texitrender.php');
class config_plugin_texit {
var $id;
var $ns;
var $namespace_mode;
var $nsbpc;
var $conf;
var $mediadir;
var $texitdir;
var $prfix;
var $all_files;
var $texit_render_obj; // not initialized by constructor, done only if needed
var $bibfn;
/*
* I didn't use a helper plugin because I needed a constructor.
* This basically sets up the environment by computing base the filenames, etc.
*
*/
function __construct($id, $namespace_mode, $conf, $nsbpc_obj) {
$this->id = cleanID($id);
$this->ns = getNS(cleanID($id));
$this->namespace_mode = $namespace_mode;
$this->nsbpc = $nsbpc_obj;
$this->conf = $conf;
$this->set_prefix();
$this->_set_texit_dir();
$this->_set_media_dir();
$this->bibfn = $this->generate_bib();
$this->get_all_files();
$this->conf['latexentities'] = false; // we generate it at compile time
$this->texit_render_obj = false;
}
/*
* This function sets $this->latexentities to an array where keys are
* the initial characters and values are the characters escaped in
* LaTeX (ex: _ => \_). It gets in by calling conftohash on conf/entities.cfg
* in the plugin's directory.
*/
function get_entities() {
$basefn = PLUGIN_TEXIT_CONF.'entities.cfg';
return $this->confToHash($basefn);
}
/**
* Builds a hash from a configfile
*
* If $lower is set to true all hash keys are converted to
* lower case.
*
* This is a modified version of Dokuwiki's function
* that doesn't consider # as a comment character (we
* need it for LaTeX entities).
*/
function confToHash($file) {
$conf = array();
$lines = @file( $file );
if ( !$lines ) return false;
foreach ( $lines as $line ) {
$line = trim($line);
if(empty($line)) continue;
$line = preg_split('/[\s\t]+/',$line,2);
// Build the associative array
$conf[$line[0]] = $line[1];
}
return $conf;
}
/*
* This function (eventually) generates the file texit.bib, in the directory
* of the namespace pointed by the "reference-db-enable" configuration option
* of the refnotes plugin.
*
* To do so, it merges:
* - the BibTeX parts of all pages in the refnotes's database namespace
* ("refnotes" by default)
* - the conf/bibliography.bib in the texit plugin directory
*/
function generate_bib() {
global $conf;
$bibtext = ''; // we merge all the files in this string
$basefn = PLUGIN_TEXIT_CONF.'bibliography.bib';
if (!is_callable("refnotes_configuration::getSetting")) {
// case where refnotes isn't available. In this case the
// file to include is just $basefn.
return $basefn;
}
// code coming from refnotes' syntax.php
$refnotes_nsdir = refnotes_configuration::getSetting('reference-db-namespace');
$refnotes_nsdir = str_replace(':', '/', $refnotes_nsdir);
$refnotes_nsdir = trim($refnotes_nsdir, '/ ');
$destfn = $conf['datadir'].'/'.$refnotes_nsdir.'/texit.bib';
$all_refnotes_pages = Array();
$opts = array('listdirs' => false,
'listfiles' => true,
'pagesonly' => true,
'skipacl' => false, // to check for read right
'sneakyacl' => true,
'showhidden'=> false,
);
// we cannot use $opts in search_list or in search_namespaces, see
// https://bugs.dokuwiki.org/index.php?do=details&task_id=2858
search($all_refnotes_pages,$conf['datadir'],'search_universal',$opts,$refnotes_nsdir);
// now all_refnotes_pages contains all the configuration pages of refnotes, that
// we'll have to merge...
// First step here is to see if we need to recompile anything:
if (is_readable($destfn)) {
// if the file is readable, then it might be up-to-date?
$needsupdate = false;
if (is_readable($basefn)) {
$needsupdate = $this->_needs_update($basefn, $destfn);
}
foreach ($all_refnotes_pages as $page) {
// A problem here: if the refnote page doesn't contain
// any bibtex code, the update will take place anyway,
// but it doesn't sound critical.
if ($this->_needs_update(wikiFN($page['id']), $destfn)) {
$needsupdate = true;
}
}
// if the file doesn't need update, we just return.
if (!$needsupdate) {
return $destfn;
}
}
if (is_readable($basefn)) {
$bibtext = file_get_contents($basefn);
}
foreach($all_refnotes_pages as $page) {
$fn = wikiFN($page['id']);
$bibtext .= $this->parse_refnotes_page($fn);
}
if (empty($bibtext)) {
return false;
}
file_put_contents($destfn, $bibtext);
// we return the filename where the bibliography is saved
return $destfn;
}
function parse_refnotes_page ($fn) {
$filestr = file_get_contents($fn);
preg_match_all('#(?<=)(((?!
).)*)(?=)#ms', $filestr, $matches);
$return = '';
foreach($matches[0] as $match) {
$return .= $match."\n";
}
return $return;
}
function set_prefix() {
if (!$this->conf['use_prefix']) {
$this->prefix = '';
return;
} else {
if (!empty($this->conf['pre_prefix'])) {
$this->prefix = $this->conf['pre_prefix'].":";
}
$this->prefix .= $this->ns;
if ($this->conf['prefix_separator']) {
$this->prefix = str_replace(':', $this->conf['prefix_separator'], $this->prefix);
$this->prefix .= $this->conf['prefix_separator'];
} // else we keep it this way
}
}
function _create_dir($path) {
global $conf;
$res = init_path($path);
if(empty($res)) {
// let's create it, recursively
$res = io_mkdir_p($path);
//$res = mkdir($path, $conf['dmode'], true);
if(!$res){
die("Unable to create directory $path, please create it.");
}
}
}
// This function escapes a filename so that it doesn't contain _ character:
function _escape_fn($fn) {
$bn = basename($fn);
$bn = str_replace('_', '-', $bn);
$dn = dirname($fn);
if ($dn == ".") {
return $bn;
}
return dirname($fn).'/'.$bn;
}
function _set_media_dir() {
global $conf;
$path = $conf['mediadir'];
$path .= '/'.str_replace(':','/',$this->ns);
// taken from init_paths in inc/init.php
$this->_create_dir($path);
$this->mediadir = $path;
}
function _set_texit_dir() {
global $conf;
$path = $this->conf['texitdir'];
// taken from init_paths in inc/init.php
$path = empty($path) ? $conf['datadir'].'/../texit' : $path;
$path .= '/'.str_replace(':','/',$this->ns);
$this->_create_dir($path);
$path = realpath($path);
$this->texitdir = $path;
}
function get_zip_fn() {
return $this->mediadir.'/'.$this->get_common_basename().".zip";
}
function get_base_bib_fn() {
return $this->bibfn;
}
function get_dest_bib_fn() {
// we always call it texit.bib for practical reasons, this may
// change in the future
return $this->texitdir.'/'.'texit.bib';
}
function get_pdf_media_fn() {
return $this->mediadir.'/'.$this->prefix.$this->get_common_basename().".pdf";
}
function get_pdf_media_id() {
return $this->ns.':'.$this->prefix.$this->get_common_basename().".pdf";
}
function get_pdf_texit_fn() {
return $this->texitdir.'/'.$this->get_common_basename().".pdf";
}
/* This returns 'all' if in namespace-mode, or the escaped ID, without extension.
*
*/
function get_common_basename() {
if ($this->namespace_mode) {
return "all";
} else {
return $this->_escape_fn(noNS($this->id));
}
}
/* This returns the full path of the base header file we take as reference
* for this compilation. In case nothing is found, false is returned.
*/
function get_base_header_fn() {
// first we look for nsbpc headers
// the names are 'texit-namespace' or 'texit-page'
$header_name = "texit-page";
if ($this->namespace_mode) {
$header_name = "texit-namespace";
}
$found = $this->nsbpc->getConfFN($header_name, $this->ns);
if ($found) {
return $found;
}
// No nsbpc configuration was found, now looking in the conf/ directory of
// the plugin. Names are different here...
$header_name = "header-page.tex";
if ($this->namespace_mode) {
$header_name = "header-namespace.tex";
}
if (is_readable(PLUGIN_TEXIT_CONF.$header_name)) {
return PLUGIN_TEXIT_CONF.$header_name;
}
return false;
}
/* This returns the full path of the header file we want in the destination
* texit namespace.
*/
function get_dest_header_fn() {
if ($this->namespace_mode) {
return $this->texitdir."/all.tex";
} else {
return $this->texitdir.'/'.$this->get_common_basename().".tex";
}
}
/* This returns the full path of the base footer file we take as reference
* for this compilation, or false if there is no such file.
*/
function get_base_footer_fn() {
// first we look through nsbpc
$found = $this->nsbpc->getConfFN("texit-footer", $this->ns);
if ($found) {
return $found;
}
// No nsbpc configuration was found, now looking in the conf/ directory of
// the plugin.
if (is_readable(PLUGIN_TEXIT_CONF."footer.tex")) {
return PLUGIN_TEXIT_CONF."footer.tex";
}
return false;
}
/* This returns the full path of the commands file we want in the destination
* texit namespace.
*/
function get_dest_footer_fn() {
return $this->texitdir."/footer.tex";
}
/* This returns the full path of the base coommands file we take as reference
* for this compilation.
*/
function get_base_commands_fn() {
// first we look through nsbpc
$found = $this->nsbpc->getConfFN("texit-commands", $this->ns);
if ($found) {
return $found;
}
// No nsbpc configuration was found, now looking in the conf/ directory of
// the plugin.
if (is_readable(PLUGIN_TEXIT_CONF."commands.tex")) {
return PLUGIN_TEXIT_CONF."commands.tex";
}
return false;
}
/* This returns the full path of the commands file we want in the destination
* texit namespace.
*/
function get_dest_commands_fn() {
return $this->texitdir."/commands.tex";
}
/* This function returns an array of all IDs of pages to be rendered by TeXit.
*
*/
function get_all_IDs() {
global $conf;
if ($this->namespace_mode) {
$list = array();
$nsdir = str_replace(':', '/', $this->ns);
$opts = array('listdirs' => false,
'listfiles' => true,
'pagesonly' => true,
'depth' => 1,
'skipacl' => false, // to check for read right
'sneakyacl' => true,
'showhidden'=> false,
);
if ($this->conf['includestart'] == false) {
$opts['idmatch'] = "^((?!start$).)+$";
}
search($list,$conf['datadir'],'search_universal',$opts,$nsdir);
return $list;
} else {
return array(array('id' => $this->id));
}
}
/* Returns an array with base and destination filenames. Works with full paths.
*
* The returned array has the following structure:
* [base] => (type, fn)
* where:
* * base is the base filename (like /path/to/dkwiki/pages/ns/id.txt)
* * type is either "header", "commands", "tex" or "bib".
* * fn is the absolute destination filename (prefix included)
*/
function get_all_files() {
// this gives us all the page ids that need txt->tex conversion:
$id_array = $this->get_all_IDs();
$result = array();
// now we put them all in the $result array
foreach($id_array as $value) {
if (!is_array($value) || !$value['id']) { // I did'nt find any more elegant way to do so
continue;
}
$fn = wikiFN($value['id']);
$dest = $this->texitdir.'/'.noNS($value['id'])."-content.tex";
$dest = $this->_escape_fn($dest);
$result[$fn] = array('type' => 'tex', 'fn' => $dest);
}
// and we add the header and command
$base = $this->get_base_header_fn();
if (!$base) {
nice_die("TeXit: Unable to find a header file!");
}
$result[$base] = array('type' => 'header', 'fn' => $this->get_dest_header_fn());
$base = $this->get_base_commands_fn();
if (!$base) {
nice_die("TeXit: Unable to find a commands file!");
}
$result[$base] = array('type' => 'commands', 'fn' => $this->get_dest_commands_fn());
$bib = $this->get_base_bib_fn();
if ($bib) { // not mandatory
$result[$bib] = array('type' => 'bib', 'fn' => $this->get_dest_bib_fn());
}
$footer = $this->get_base_footer_fn();
if ($footer) { // not mandatory
$result[$footer] = array('type' => 'footer', 'fn' => $this->get_dest_footer_fn());
}
$this->all_files = $result;
}
/* This function takes three arguments:
* * base is the full path of the base header file
* (for instance /path/to/dkwiki/lib/plugin/texit/conf/header-page.tex)
* * dest is the full path of the destination header file
* * all_files is the table returned by get_all_files()
*
* It reads $base, adds \input lines for $all_files and writes the result in
* $dest.
*/
function compile_header($base, $dest, $all_files) {
// first we simply copy the file
$this->simple_copy($base, $dest);
// we prepare a string to append at the end:
$toappend = "\n";
// we spot the last value:
$beginning = 1;
$footer = false;
foreach($this->all_files as $value) {
switch($value['type']) {
case 'tex':
// between two different files, we call the \dokuinternspagedo
// macro, doing nothing by default.
if (!$beginning) {
$toappend .= "\\dokuinternspagedo\n\n";
}
$toappend .= '\dokuinclude{'.basename($value['fn'], '.tex')."}\n\n";
break;
case 'footer':
$footer = basename($value['fn'], '.tex');
default:
break;
}
$beginning = 0;
}
if ($footer) {
$toappend .= "\dokuinclude{".$footer."}\n";
}
$toappend .= "\n\\end{document}";
// the we open it in append mode to write things at the end:
file_put_contents($dest, $toappend, FILE_APPEND);
}
/* This function takes two arguments:
* * base is the full path of the base page file
* (for instance /path/to/dkwiki/data/pages/ns/id.txt)
* * dest is the full path of the destination tex file
*
* It reads $base, renders it into TeX and writes $dest.
*/
function compile_tex($base, $dest) {
if (!$this->conf['latexentities'])
{
$this->conf['latexentities'] = $this->get_entities();
}
if (!$this->texit_render_obj)
{
$this->texit_render_obj = new texitrender_plugin_texit($this);
}
$this->texit_render_obj->process($base, $dest);
}
/* This function takes two arguments:
* * base is the full path of the base file
* * dest is the full path of the destination tex file
*
* It copies $base into $dest.
*/
function simple_copy($base, $dest) {
if (!copy($base, $dest)) {
nice_die("TeXit: unable to copy $base into $dest.");
}
}
/*
* This functions returns true if $base is more recent that $dest, and
* false otherwise. If $dest doesn't exist, then we consider it needs
* update and thus return true.
*/
function _needs_update($base, $dest) {
if (!file_exists($dest) || !file_exists($dest)) {
return true;
}
return filemtime($base) > filemtime($dest);
}
/* This function sets the TeX compilation environment up by copying the files
* in the good folders and renames them. It uses file modification timestamps
* to evaluate if files need to be recompiled or recopied.
*
* The returned value is a boolean: true if something has been updated, and
* false otherwise.
*/
function setup_files() {
if (!is_array($this->all_files)) {
$this->get_all_files();
}
if (!is_array($this->all_files)) {
die("TeXit: cannot analyze files");
}
$needsupdate = false;
foreach($this->all_files as $base => $dest) {
$destfn = $dest['fn'];
if ($this->_needs_update($base, $destfn)) {
$needsupdate = true;
switch($dest['type']) {
case "header":
$this->compile_header($base, $destfn, $this->all_files);
break;
case "commands":
$this->simple_copy($base, $destfn);
break;
case "bib":
$this->simple_copy($base, $destfn);
break;
case "footer":
$this->simple_copy($base, $destfn);
break;
case "tex":
$this->compile_tex($base, $destfn);
break;
default:
break;
}
}
}
return $needsupdate;
}
/* This function calls latexmk with the good options on the good files.
*/
function _do_latexmk() {
if (!is_dir($this->texitdir)) {
die("TeXit: directory $this->texitdir doesn't exit");
}
chdir($this->texitdir);
$basecmdline = '';
if (isset($this->conf['latexmk_path'])
&& trim($this->conf['latexmk_path']) != "") {
$basecmdline = $this->conf['latexmk_path'] . DIRECTORY_SEPARATOR;
} else {
$basecmdline = '';
}
$cmdline = $basecmdline."latexmk -f ";
if ($this->bibfn) {
$cmdline .= "-bibtex ";
}
switch ($this->conf['latex_mode'])
{
case "latex":
// TODO: test, comes from http://users.phys.psu.edu/~collins/software/latexmk-jcc/
$cmdline .= "-e '\$dvipdf = \"dvipdfm %O -o %D %S\";' -pdfdvi ";
break;
case "pdflatex":
$cmdline .= "-pdf ";
break;
case "lualatex":
$cmdline .= "-pdf -pdflatex=lualatex ";
break;
case "xelatex":
$cmdline .= "-latex=xelatex -e '\$dvipdf = \"dvipdfmx %O -o %D %S\";' -pdfdvi ";
break;
default:
// error
break;
}
$file = basename($this->get_dest_header_fn());
$cmdline .= $file . ' 2>&1 ';
$ret = 0;
@exec($cmdline, $output, $ret);
if ($ret) {
print("
TeXit error: latexmk returned error code ".$ret."
\n
Log:
\n");
print_r(implode("
\n", $output));
}
// at the end, we clean temporary files. There is currently no way to tell
// latexmk to clean at the end of the compilation... quite a shame...
// An email has been written to the author in this sense.
$cmdline = $basecmdline."latexmk -c 2>&1";
$log = @exec($cmdline, $output, $ret);
if ($ret) {
print("
TeXit error: latexmk -c returned error code ".$ret."
\n
Log:
\n");
print_r(implode("
\n", $output));
}
}
/* This function zips the good files in the texit namespace in a .zip archive
* in the media namespace.
*/
function compile_zip() {
$zipfn = $this->get_zip_fn();
// if the file already exists and needs update, remove it.
if (@file_exists($zipfn)) {
unlink($zipfn);
}
$zip = new ZipArchive();
if ($zip->open($zipfn, ZipArchive::CREATE) !== true) {
exit("Unable to create $zipfn\n");
}
// First argument of addFile is the absolute, second is the name we want
// in the archive (in our case, the basename).
$zip->addFile($this->get_pdf_texit_fn(), basename($this->get_pdf_texit_fn()));
foreach($this->all_files as $base => $dest) {
$zip->addFile($dest['fn'], basename($dest['fn']));
}
$zip->close();
}
/* My mind is too used to C programming and thus this is a bit too
* iterative and not object-oriented enough...
*
* This function processes everything when the user asks for a PDF.
*/
function process() {
$needsupdate = $this->setup_files();
$pdftexitfn = $this->get_pdf_texit_fn();
$pdfmediafn = $this->get_pdf_media_fn();
$pdfmediaid = $this->get_pdf_media_id();
$zipfn = $this->get_zip_fn();
if ($needsupdate || !@file_exists($pdftexitfn)) {
$this->_do_latexmk();
}
// then copy the pdf to media
if ($needsupdate || !@file_exists($pdfmediafn)) {
$this->simple_copy($pdftexitfn, $pdfmediafn);
}
if ($this->conf['use_zip'] && ($needsupdate || !@file_exists($zipfn))) {
$this->compile_zip();
}
return $this->id_to_url($pdfmediaid);
}
/* This returns an absolute URL from a media ID.
*/
function id_to_url($pdfmediaid) {
// internal dokuwiki function, defined in inc/common.php
return ml($pdfmediaid, '', true, '&', true);
}
}
?>