* * 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); } } ?>