1<?php
2
3/**
4 * DokuWiki Plugin latexit (Renderer Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Adam Kučera <adam.kucera@wrent.cz>
8 */
9// must be run within Dokuwiki
10if (!defined('DOKU_INC'))
11    die();
12
13/**
14 * Latexit plugin extends default renderer class in this file
15 */
16require_once DOKU_INC . 'inc/parser/renderer.php';
17
18/**
19 * includes additional plugin classes
20 */
21require_once DOKU_INC . 'lib/plugins/latexit/classes/Package.php';
22require_once DOKU_INC . 'lib/plugins/latexit/classes/RowspanHandler.php';
23require_once DOKU_INC . 'lib/plugins/latexit/classes/BibHandler.php';
24require_once DOKU_INC . 'lib/plugins/latexit/classes/LabelHandler.php';
25require_once DOKU_INC . 'lib/plugins/latexit/classes/RecursionHandler.php';
26
27/**
28 * includes default DokuWiki files containing functions used by latexit plugin
29 */
30require_once DOKU_INC . 'inc/parserutils.php';
31require_once DOKU_INC . 'inc/pageutils.php';
32require_once DOKU_INC . 'inc/pluginutils.php';
33require_once DOKU_INC . 'inc/confutils.php';
34
35/**
36 * Main latexit class, specifies how will be latex rendered
37 */
38class renderer_plugin_latexit extends Doku_Renderer {
39
40    /**
41     * Singleton helper plugin to store data for multiple renderer instances
42     *
43     * @var helper_plugin_latexit
44     */
45    protected $store;
46
47    /**
48     * Stores the information about last list level
49     * @var int
50     */
51    protected $last_level;
52
53    /**
54     * Is true when the renderer is in a list
55     * @var boolean
56     */
57    protected $list_opened;
58
59    /**
60     * Stores the information about the level of recursion.
61     * It stores the depth of current recusively added file.
62     * @var int
63     */
64    protected $recursion_level;
65
66    /**
67     * Used in recursively inserted files, stores information about headers level.
68     * @var int
69     */
70    protected $headers_level;
71
72    /**
73     * Is TRUE when recursive inserting should be used.
74     * @var bool
75     */
76    protected $recursive;
77
78    /**
79     * Stores the information about the headers level increase in last recursive insertion.
80     * @var int
81     */
82    protected $last_level_increase;
83
84    /**
85     * Stores the information about the number of cells found in a table row.
86     * @var int
87     */
88    protected $cells_count;
89
90    /**
91     * Stores the information about the number a table cols.
92     * @var int
93     */
94    protected $table_cols;
95
96    /**
97     * Stores the last colspan in a table.
98     * @var int
99     */
100    protected $last_colspan;
101
102    /**
103     * Stores the last rowspan in a table.
104     * @var int
105     */
106    protected $last_rowspan;
107
108    /**
109     * Stores the last align of a cell in a table.
110     * @var int
111     */
112    protected $last_align;
113
114    /**
115     * Is TRUE when renderer is inside a table.
116     * @var bool
117     */
118    protected $in_table;
119
120    /**
121     * An instance of a RowspanHandler class.
122     * @var RowspanHandler
123     */
124    protected $rowspan_handler;
125
126    /**
127     * Is set on true if the document contains media.
128     * @var boolean
129     */
130    protected $media;
131
132    /**
133     * Stores the instance of BibHandler
134     * @var BibHandler
135     */
136    protected $bib_handler;
137
138    /**
139     * This handler makes all the header labels unique
140     * @var LabelHandler
141     */
142    protected $label_handler;
143
144    /**
145     * This handler prevents recursive inserting of subpages to be an unending loop.
146     * @var RecursionHandler
147     */
148    protected $recursion_handler;
149
150    /**
151     * @var bool
152     */
153    protected $bibliography;
154
155    /**
156     * Constructor
157     *
158     * Initializes the storage helper
159     */
160    public function __construct() {
161        $this->_initializeStore();
162    }
163
164    /**
165     * Make available as LaTeX renderer
166     */
167    public function canRender($format) {
168        if ($format == 'latex') {
169            return true;
170        }
171        return false;
172    }
173
174    /**
175     * Return the rendering format of the renderer - latex
176     */
177    public function getFormat() {
178        return 'latex';
179    }
180
181    /**
182     * Renderer is always created as a new instance.
183     * It is required for recursive export.
184     */
185    public function isSingleton() {
186        return false;
187    }
188
189    /**
190     * Allow overwriting options from within the document
191     *
192     * @param string $setting
193     * @param bool   $notset
194     * @return mixed
195     */
196    function getConf($setting, $notset = false) {
197        global $ID;
198        $opts = p_get_metadata($ID, 'plugin_latexit');
199        if($opts && isset($opts[$setting])) return $opts[$setting];
200
201        return parent::getConf($setting, $notset);
202    }
203
204    /**
205     * function is called, when a document is started to being rendered.
206     * It inicializes variables, adds headers to the LaTeX document and
207     * sets the browser headers of the exported file.
208     */
209    function document_start() {
210        //register global variables used for recursive rendering
211        global $latexit_level;
212        global $latexit_headers;
213        global $zip;
214        //ID stores the current page id with namespaces, required for recursion prevention
215        global $ID;
216
217        if(is_null($this->store)) {
218            $this->_initializeStore();
219            echo "aaa";
220        }
221
222        //initialize variables
223        $this->list_opened = FALSE;
224        $this->recursive = FALSE;
225        $this->in_table = FALSE;
226        $this->last_level_increase = 0;
227        $this->rowspan_handler = new RowspanHandler();
228        $this->media = FALSE;
229        $this->bibliography = FALSE;
230        if (!plugin_isdisabled('zotero')) {
231            $this->bib_handler = BibHandler::getInstance();
232        } else {
233            $this->bib_handler = NULL;
234        }
235        $this->label_handler = LabelHandler::getInstance();
236        $this->recursion_handler = RecursionHandler::getInstance();
237
238        //is this recursive export calling on a subpage?
239        if (!isset($latexit_level) || is_null($latexit_level)) {
240            $this->recursion_level = 0;
241        } else {
242            $this->recursion_level = $latexit_level;
243        }
244        if (!isset($latexit_headers) || is_null($latexit_headers)) {
245            $this->headers_level = 0;
246        } else {
247            $this->headers_level = $latexit_headers;
248        }
249
250        //export of the main document
251        if (!$this->_immersed()) {
252            //the parent documented cannot be recursively inserted somewhere
253            $this->recursion_handler->insert(wikifn($ID));
254
255            //prepare ZIP archive (will not be created, if it isn't necessary)
256            $zip = new ZipArchive();
257            $this->_prepareZIP();
258
259            // configure language
260            $document_lang = $this->getConf('document_lang');
261            $pckg = new Package('babel');
262            $pckg->addParameter($document_lang);
263            $this->store->addPackage($pckg);
264
265            // encoding is always UTF-8
266            $pckg = new Package('inputenc');
267            $pckg->addParameter('utf8x');
268            $this->store->addPackage($pckg);
269
270            // add metadata to preamble
271            $this->store->addPreamble(array('date', '\today')); // FIXME use the document's date instead
272            $this->store->addPreamble(array('title', $this->getConf('title')));
273            $this->store->addPreamble(array('author', $this->getConf('author')));
274
275
276            // start document
277            $this->_c('begin', 'document', 2);
278
279            //if title or author or date is set, it prints it
280            if ($this->getConf('date') || $this->getConf('title') != "" || $this->getConf('author') != "") {
281                $this->_c('maketitle');
282            }
283            //if table of contents should be displayed, it prints it
284            if ($this->getConf('table_of_content')) {
285                $this->_c('tableofcontents', NULL, 2);
286            }
287        }
288    }
289
290    /**
291     * Prefix the created document with the pramble and packages
292     */
293    protected function document_prefix() {
294        // copy current doc and reset
295        $doc = $this->doc;
296        $this->doc = '';
297
298        //get document settings
299        $params = array(
300            $this->getConf('paper_size'),
301            $this->getConf('output_format'),
302            $this->getConf('font_size') . 'pt',);
303        if ($this->getConf('landscape')) {
304            $params[] = 'landscape';
305        }
306        if ($this->getConf('draft')) {
307            $params[] = 'draft';
308        }
309
310        // print document settings
311        $this->_c('documentclass', $this->getConf('document_class'), 1, $params);
312
313        // print the packages
314        $packages = $this->store->getPackages();
315        foreach ($packages as $package) {
316            /** @var  Package $package */
317            $this->doc .= $package->printUsePackage();
318        }
319
320        // print the preamble
321        $preamble = $this->store->getPreamble();
322        foreach($preamble as $command) {
323            if(is_array($command)) {
324                $this->_c($command[0], $command[1], $command[2], $command[3]);
325            }else {
326                $this->doc .= $command;
327            }
328        }
329
330        // add custom document header
331        $this->doc .= $this->getConf('document_header');
332
333        // finally readd the previously created document
334        $this->doc .= $doc;
335    }
336
337    /**
338     * function is called, when a document ends its rendering to finish the document
339     * It finalizes the document.
340     *
341     */
342    function document_end() {
343        /** @var ZipArchive $zip */
344        global $zip;
345
346        //if a media were inserted in a recursively added file, we have to push this information up
347        $this->_checkMedia();
348
349        //this is MAIN PAGE of exported file, we can finalize document
350        if (!$this->_immersed()) {
351            $this->_n(2);
352
353            if ($this->_useBibliography() && !$this->bib_handler->isEmpty()) {
354                $this->_c('bibliographystyle', $this->getConf('bibliography_style'));
355                $this->_c('bibliography', $this->getConf('bibliography_name'), 2);
356            }
357
358            $this->doc .= $this->getConf('document_footer');
359            $this->_c('end', 'document');
360
361            // the document is done, add the prefix
362            $this->document_prefix();
363
364            $this->_deleteMediaSyntax();
365            //finalize rendering of few entities
366            $this->_highlightFixme();
367            $this->_removeEntities();
368            $this->_fixImageRef();
369
370
371            $output = "output" . time() . ".latex";
372
373            //file to download will be ZIP archive
374            if ($this->media || ($this->_useBibliography() && !$this->bib_handler->isEmpty())) {
375                $filename = $zip->filename;
376                if ($this->_useBibliography() && !$this->bib_handler->isEmpty()) {
377                    $zip->addFromString($this->getConf('bibliography_name') . '.bib', $this->bib_handler->getBibtex());
378                }
379                $zip->addFromString($output, $this->doc);
380                //zip archive is created when this function is called,
381                //so if no ZIP is needed, nothing is created
382                $zip->close();
383
384                header("Content-type: application/zip");
385                header("Content-Disposition: attachment; filename=output" . time() . ".zip");
386                header("Content-length: " . filesize($filename));
387                header("Pragma: no-cache");
388                header("Expires: 0");
389                readfile($filename);
390                //delete temporary zip file
391                unlink($filename);
392            }
393            //file to download will be ordinary LaTeX file
394            else {
395                //set the headers, so the browsers knows, this is not the HTML file
396                header('Content-Type: application/x-latex');
397                header("Content-Disposition: attachment; filename=$output;");
398            }
399        }
400        //this is RECURSIVELY added file
401        else {
402            //signal to the upper document, that we inserted media to ZIP archive
403            if ($this->media) {
404                $this->doc .= '%///MEDIA///';
405            }
406        }
407    }
408
409    /**
410     * Function is called, when renderer finds a new header.
411     * It calls the LaTeX command for an appropriate level.
412     * @param string $text Text of the header
413     * @param int $level Level of the header.
414     * @param int $pos Not used in LaTeX
415     */
416    function header($text, $level, $pos) {
417        //package hyperref will enable PDF bookmarks
418        $package = new Package('hyperref');
419        $package->addParameter('unicode');
420        $this->store->addPackage($package);
421
422        //set the types of headers to be used depending on configuration
423        $levels = array();
424        if ($this->getConf('header_title')) {
425            $levels[] = 'title';
426        }
427        if ($this->getConf('header_part')) {
428            $levels[] = 'part';
429        }
430        if ($this->getConf('header_chapter') && $this->getConf('document_class') != 'article') {
431            $levels[] = 'chapter';
432        }
433        array_push($levels, 'section', 'subsection', 'subsubsection', 'paragraph', 'subparagraph');
434
435        if ($this->_immersed()) {
436            //when document is recursively inserted, it will continue from previous headers level
437            $level += $this->headers_level;
438        }
439        $this->_n(2);
440
441        //the array of levels is indexed from 0
442        $level--;
443
444        //such a level exists in the array
445        if (isset($levels[$level])) {
446            $this->_header($levels[$level], $text);
447        }
448        //level not in array, use default
449        else {
450            //to force a newline in latex, there has to be some empty char before, e.g. ~
451            $this->doc .= '~';
452            $this->_c('newline');
453            $this->_c('textbf', $this->_latexSpecialChars($text));
454        }
455        //add a label, so each section can be referenced
456        $label = $this->label_handler->newLabel($this->_createLabel($text));
457        $this->_c('label', 'sec:' . $label);
458    }
459
460    /**
461     * Basic funcion called, when a text not from DokuWiki syntax is read
462     * It adds the data to the document, potentionally dangerous characters for
463     * LaTeX are escaped or removed.
464     * @param string $text Text to be inserted.
465     */
466    function cdata($text) {
467        $this->doc .= $this->_latexSpecialChars($text);
468    }
469
470    /**
471     * Function is called, when renderer finds a new paragraph.
472     * It makes new paragraph in LaTeX Document.
473     */
474    function p_open() {
475        $this->_n(2);
476    }
477
478    /**
479     * Function is called, when renderer finds a linebreak.
480     * It adds new line in LaTeX Document.
481     */
482    function linebreak() {
483        if ($this->in_table) {
484            //in tables in LaTeX there is different syntax
485            $this->doc .= "\\newline ";
486        } else {
487            $this->doc .= "\\\\";
488        }
489    }
490
491    /**
492     * Function is called, when renderer finds a horizontal line.
493     * It adds centered horizontal line in LaTeX Document.
494     */
495    function hr() {
496        $this->_n(2);
497        $this->_c('begin', 'center');
498        $this->doc .= "\line(1,0){250}\n";
499        $this->_c('end', 'center', 2);
500    }
501
502    /**
503     * function is called, when renderer finds a strong text
504     * It calls command for strong text in LaTeX Document.
505     */
506    function strong_open() {
507        $this->_open('textbf');
508    }
509
510    /**
511     * function is called, when renderer finds the end of a strong text
512     */
513    function strong_close() {
514        $this->_close();
515    }
516
517    /**
518     * function is called, when renderer finds an emphasised text
519     * It calls command for emphasised text in LaTeX Document.
520     */
521    function emphasis_open() {
522        $this->_open('emph');
523    }
524
525    /**
526     * function is called, when renderer finds the end of an emphasised text
527     */
528    function emphasis_close() {
529        $this->_close();
530    }
531
532    /**
533     * function is called, when renderer finds an underlined text
534     * It calls command for underlined text in LaTeX Document.
535     */
536    function underline_open() {
537        $this->_open('underline');
538    }
539
540    /**
541     * function is called, when renderer finds the end of an underlined text
542     */
543    function underline_close() {
544        $this->_close();
545    }
546
547    /**
548     * function is called, when renderer finds a monospace text
549     * (all letters have same width)
550     * It calls command for monospace text in LaTeX Document.
551     */
552    function monospace_open() {
553        $this->_open('texttt');
554    }
555
556    /**
557     * function is called, when renderer finds the end of a monospace text
558     * (all letters have same width)
559     */
560    function monospace_close() {
561        $this->_close();
562    }
563
564    /**
565     * function is called, when renderer finds a subscript
566     * It adds needed package and calls command for subscript in LaTeX Document.
567     */
568    function subscript_open() {
569        $package = new Package('fixltx2e');
570        $this->store->addPackage($package);
571        $this->_open('textsubscript');
572    }
573
574    /**
575     * function is called, when renderer finds the end of a subscript
576     */
577    function subscript_close() {
578        $this->_close();
579    }
580
581    /**
582     * function is called, when renderer finds a superscript
583     * It adds needed package and calls command for superscript in LaTeX Document.
584     */
585    function superscript_open() {
586        $package = new Package('fixltx2e');
587        $this->store->addPackage($package);
588        $this->_open('textsuperscript');
589    }
590
591    /**
592     * function is called, when renderer finds the end of a superscript
593     */
594    function superscript_close() {
595        $this->_close();
596    }
597
598    /**
599     * function is called, when renderer finds a deleted text
600     * It adds needed package and calls command for deleted text in LaTeX Document.
601     */
602    function deleted_open() {
603        $package = new Package('ulem');
604        $package->addParameter('normalem');
605        $this->store->addPackage($package);
606        $this->_open('sout');
607    }
608
609    /**
610     * function is called, when renderer finds the end of a deleted text
611     */
612    function deleted_close() {
613        $this->_close();
614    }
615
616    /**
617     * function is called, when renderer finds a footnote
618     * It calls footnote command in LaTeX Document.
619     */
620    function footnote_open() {
621        $this->_open('footnote');
622    }
623
624    /**
625     * function is called, when renderer finds the end of a footnote
626     */
627    function footnote_close() {
628        $this->_close();
629    }
630
631    /**
632     * function is called, when renderer finds start of an unordered list
633     * It calls command for an unordered list in latex, even with right indention
634     */
635    function listu_open() {
636        $this->_list_open("itemize");
637    }
638
639    /**
640     * function is called, when renderer finds the end of an unordered list
641     * It calls command for the end of an unordered list in latex, even with right indention
642     */
643    function listu_close() {
644        $this->_list_close("itemize");
645    }
646
647    /**
648     * function is called, when renderer finds start of an ordered list
649     * It calls command for an ordered list in latex, even with right indention
650     */
651    function listo_open() {
652        $this->_list_open("enumerate");
653    }
654
655    /**
656     * function is called, when renderer finds the end of an ordered list
657     * It calls command for the end of an ordered list in latex, even with right indention
658     */
659    function listo_close() {
660        $this->_list_close("enumerate");
661    }
662
663    /**
664     * function is called, when renderer finds start of a list item
665     * It calls command for a list item in latex, even with right indention
666     * @param int $level Level of indention.
667     */
668    function listitem_open($level) {
669        $this->last_level = $level;
670        $this->_indent_list();
671        $this->doc .= "  ";
672        $this->_c('item', NULL, 0);
673    }
674
675    /**
676     * function is called, when renderer finds the end of a list item content
677     * It adds newline to the latex file.
678     */
679    function listcontent_close() {
680        $this->_n();
681    }
682
683    /**
684     * Original text is not formatted by DW, so this function just inserts the text as it is.
685     * It just escapes special characters.
686     * @param string $text Unformatted text.
687     */
688    function unformatted($text) {
689        $this->doc .= $this->_latexSpecialChars($text);
690    }
691
692    /**
693     * Inserts PHP code to the document.
694     * @param string $text PHP code.
695     */
696    function php($text) {
697        $this->code($text, "PHP");
698    }
699
700    /**
701     * Inserts block of PHP code to the document.
702     * @param string $text PHP code.
703     */
704    function phpblock($text) {
705        $this->code($text, "PHP");
706    }
707
708    /**
709     * Inserts HTML code to the document.
710     * @param string $text HTML code.
711     */
712    function html($text) {
713        $this->code($text, "HTML");
714    }
715
716    /**
717     * Inserts block of HTML code to the document.
718     * @param string $text HTML code.
719     */
720    function htmlblock($text) {
721        $this->code($text, "HTML");
722    }
723
724    /**
725     * Inserts preformatted text (with all whitespaces)
726     * @param string $text Preformatted text.
727     */
728    function preformatted($text) {
729        $this->_n();
730        $this->_c('begin', 'verbatim');
731        $this->doc .= $text;
732        $this->_n();
733        $this->_c('end', 'verbatim');
734    }
735
736    /**
737     * Opens the quote environment.
738     */
739    function quote_open() {
740        $this->_n();
741        $this->_c('begin', 'quote');
742    }
743
744    /**
745     * Closes the quote environment.
746     */
747    function quote_close() {
748        $this->_n();
749        $this->_c('end', 'quote');
750    }
751
752    /**
753     * File tag is almost the same like the code tag, but it enables to download
754     * the code directly from DW.
755     * Therefore we just add the filename to the top of code.
756     * @param string $text The code itself.
757     * @param string $lang Programming language.
758     * @param string $file The code will be exported from DW as a file.
759     */
760    function file($text, $lang = null, $file = null) {
761        $this->code($text, $lang, $file);
762    }
763
764    /**
765     * Function adds a block of programming language code to LaTeX file
766     * using the listings package.
767     * @param string $text The code itself.
768     * @param string $lang Programming language.
769     * @param string $file The code can be inserted to DokuWiki as a file.
770     */
771    function code($text, $lang = null, $file = null) {
772        $pckg = new Package('listings');
773        $this->store->addPackage($pckg);
774
775        //start code block
776        $this->_open('lstset');
777        $this->doc .= 'frame=single';
778        if (!is_null($lang)) {
779            //if language name is specified, insert it to LaTeX
780            $this->doc .= ', language=';
781            $this->doc .= $this->_latexSpecialChars($lang);
782        }
783        //insert filename
784        if (!is_null($file)) {
785            $this->doc .= ', title=';
786            $this->doc .= $this->_latexSpecialChars($file);
787        }
788        $this->_close();
789        $this->_n();
790        //open the code block
791        $this->_c('begin', 'lstlisting');
792
793        //get rid of some non-standard characters
794        $text = str_replace('”', '"', $text);
795        $text = str_replace('–', '-', $text);
796        $this->doc .= $text;
797        //close the code block
798        $this->_c('end', 'lstlisting', 2);
799    }
800
801    /**
802     * This function is called when an acronym is found. It just inserts it as a classic text.
803     * I decided not to implement the mouse over text, although it is possible, but
804     * it does not work in all PDF browsers.
805     * http://tex.stackexchange.com/questions/32314/is-there-an-easy-way-to-add-hover-text-to-all-incidents-of-math-mode-where-the-h
806     * @param string $acronym The Acronym.
807     */
808    function acronym($acronym) {
809        $this->doc .= $this->_latexSpecialChars($acronym);
810    }
811
812    /**
813     * This function is called when a smiley is found.
814     * LaTeX does not support smileys, so they are inserted as a normal text.
815     * FIXME and DELETEME are exceptions, they are highlited (in the end of exporting).
816     * @param string $smiley Smiley chars.
817     */
818    function smiley($smiley) {
819        if ($smiley == 'FIXME' || $smiley == 'DELETEME') {
820            $pckg = new Package('soul');
821            $this->store->addPackage($pckg);
822            $this->doc .= $smiley;
823        } else {
824            $this->doc .= $this->_latexSpecialChars($smiley);
825        }
826    }
827
828    /**
829     * DocuWiki can represent some characters as they typograficaly correct entities.
830     * Most of them exist in LaTeX as well, but some only in math mode.
831     * @param string $entity An entity.
832     */
833    function entity($entity) {
834        //this text is removed after exporting
835        //it is here to disallow double escaping of some math characters
836        $this->doc .= '///ENTITYSTART///';
837        switch ($entity) {
838            case '->':
839                $this->doc .= '$\rightarrow$';
840                break;
841            case '<-':
842                $this->doc .= '$\leftarrow$';
843                break;
844            case '<->':
845                $this->doc .= '$\leftrightarrow$';
846                break;
847            case '=>':
848                $this->doc .= '$\Rightarrow$';
849                break;
850            case '<=':
851                $this->doc .= '$\Leftarrow$';
852                break;
853            case '<=>':
854                $this->doc .= '$\Leftrightarrow$';
855                break;
856            case '(c)':
857                $this->doc .= '\copyright ';
858                break;
859            case '(tm)':
860                $this->doc .= '\texttrademark ';
861                break;
862            case '(r)':
863                $this->doc .= '\textregistered ';
864                break;
865            default:
866                $this->doc .= $this->_latexSpecialChars($entity);
867                break;
868        }
869        $this->doc .= '///ENTITYEND///';
870    }
871
872    /**
873     * Inserts multiply entity (eg. 640x480) to LaTeX file.
874     * @param int $x First number
875     * @param int $y Second number
876     */
877    function multiplyentity($x, $y) {
878        $this->doc .= '///ENTITYSTART///';
879        $this->doc .= '$';
880        $this->doc .= $this->_latexSpecialChars($x);
881        $this->doc .= ' \times ';
882        $this->doc .= $this->_latexSpecialChars($y);
883        $this->doc .= '$';
884        $this->doc .= '///ENTITYEND///';
885    }
886
887    /**
888     * Inserts single quote opening to LaTeX depending on set language.
889     */
890    function singlequoteopening() {
891        $this->doc .= '`';
892    }
893
894    /**
895     * Inserts single quote closing to LaTeX depending on set language.
896     */
897    function singlequoteclosing() {
898        $this->doc .= '\'';
899    }
900
901    /**
902     * Inserts apostrophe to LaTeX depending on set language.
903     */
904    function apostrophe() {
905        $this->doc .= '\'';
906    }
907
908    /**
909     * Inserts double quote opening to LaTeX depending on set language.
910     * Support for only English and Czech is implemented.
911     */
912    function doublequoteopening() {
913        switch ($this->getConf('document_lang')) {
914            /* This is bugging, DW parses it strangely... FIXME consultation
915             * case 'czech':
916              $this->_open('uv');
917              break; */
918            default :
919                $this->doc .= ',,';
920                break;
921        }
922    }
923
924    /**
925     * Inserts double quote closing to LaTeX depending on set language.
926     * Support for only English and Czech is implemented.
927     */
928    function doublequoteclosing() {
929        switch ($this->getConf('document_lang')) {
930            /* This is bugging, DW parses it strangely... FIXME consultation
931              case 'czech':
932              $this->_close();
933              break; */
934            default :
935                $this->doc .= '"';
936                break;
937        }
938    }
939
940    /**
941     * Function is called, when renderer finds a link written in text like CamelCase.
942     * It just calls the common link function.
943     * @param string $link Internal link to a wiki page.
944     */
945    function camelcaselink($link) {
946        $this->internallink($link, $link);
947    }
948
949    /**
950     * This function handles the links on the page itself (#something at the end of URL)
951     * It inserts reference to LaTeX document
952     * @param string $hash Label of a section
953     * @param string $name Text of the original link
954     */
955    function locallink($hash, $name = NULL) {
956        $this->_insertLinkPackages();
957        if (!is_null($name)) {
958            $this->doc .= $this->_latexSpecialChars($name);
959        } else {
960            $this->doc .= $this->_latexSpecialChars($hash);
961        }
962        $this->doc .= ' (';
963        $this->_c('autoref', "sec:" . $hash, 0);
964        $this->doc .= ')';
965    }
966
967    /**
968     * function is called, when renderer finds an internal link
969     * It resolves the internal link (namespaces, URL)
970     * Depending on the configuration (inside the document):
971     *     It handles link as an external and calls proper function in LaTeX depending on the title
972     *     It recursively adds the linked page to the exported LaTeX file
973     * This feature is not in classic plugin configuration.
974     * If you want to have a link recursively inserted, add ~~RECURSIVE~~ just before it.
975     * The count of ~ means the same as = for headers. It will determine the
976     * level of first header used in recursively inserted text.
977     * @param string $link Internal link (can be without proper namespace)
978     * @param string/array $title Title, can be null or array (if it is media)
979     */
980    function internallink($link, $title = NULL) {
981        //register globals
982        global $ID; //in this global var DokuWiki stores the current page id with namespaces
983        global $latexit_level;
984        global $latexit_headers;
985
986        //escape link title
987        if (!is_array($title)) {
988            $title = $this->_latexSpecialChars($title);
989        }
990        $link_original = $link;
991
992        //get current namespace from current page
993        $current_namespace = getNS($ID);
994        //get the page ID with right namespaces
995        //$exists stores information, if the page exists.
996        resolve_pageid($current_namespace, $link, $exists);
997
998        //if the page does not exist, just insert it as common text
999        if (!$exists) {
1000            $this->doc .= $title;
1001            return;
1002        }
1003
1004        $params = '';
1005        $absoluteURL = true;
1006        //get the whole URL
1007        $url = wl($link, $params, $absoluteURL);
1008        $url = $this->_secureLink($url);
1009        if ($this->recursive) {
1010            //check if it can continue with recursive inserting of this page
1011            if ($this->recursion_handler->disallow(wikifn($link))) {
1012                $this->_n(2);
1013                //warn the user about unending recursion
1014                $this->doc .= "%!!! RECURSION LOOP HAS BEEN PREVENTED !!!";
1015                $this->_n(2);
1016            } else {
1017                //insert this page to RecursionHandler
1018                $this->recursion_handler->insert(wikifn($link));
1019                //the level of recursion is increasing
1020                $latexit_level = $this->recursion_level + 1;
1021                $latexit_headers = $this->headers_level;
1022
1023                //start parsing linked page - call the latexit plugin again
1024                $data = p_cached_output(wikifn($link), 'latexit');
1025
1026                $this->_n(2);
1027                //insert comment to LaTeX
1028                $this->doc .= "%RECURSIVELY INSERTED FILE START";
1029                $this->_n(2);
1030                //insert parsed data
1031                $this->doc .= $data;
1032                $this->_n(2);
1033                //insert comment to LaTeX
1034                $this->doc .= "%RECURSIVELY INSERTED FILE END";
1035                $this->_n(2);
1036                //get headers level to previous level
1037                $this->headers_level -= $this->last_level_increase;
1038                //remove this page from RecursionHandler
1039                $this->recursion_handler->remove(wikifn($link));
1040            }
1041        }
1042        //handle internal links as they were external
1043        else {
1044            $this->_insertLink($url, $title, "internal", $link_original);
1045        }
1046        $this->recursive = FALSE;
1047    }
1048
1049    /**
1050     * function is called, when renderer finds an external link
1051     * It calls proper function in LaTeX depending on the title
1052     * @param string $link External link
1053     * @param string/array $title Title, can be null or array (if it is media)
1054     */
1055    function externallink($link, $title = NULL) {
1056        if (!is_array($title)) {
1057            $title = $this->_latexSpecialChars($title);
1058        }
1059        $link = $this->_secureLink($link);
1060        $this->_insertLink($link, $title, "external");
1061    }
1062
1063    /**
1064     * InterWiki links lead to another wikis and they can be written in special syntax.
1065     * This resolves the link and inserts it as normal external link.
1066     * @param string $link Original link in DW syntax
1067     * @param string $title Title of link, can also be image
1068     * @param string $wikiName Name of wiki (according to configuration)
1069     * @param string $wikiUri Text in link after wiki address
1070     */
1071    function interwikilink($link, $title = NULL, $wikiName, $wikiUri) {
1072        $url = $this->_resolveInterWiki($wikiName, $wikiUri);
1073        if (is_null($title)) {
1074            $name = $wikiUri;
1075        } else {
1076            $name = $title;
1077        }
1078        $this->externallink($url, $name);
1079    }
1080
1081    /**
1082     * Inserts a link to a file on local filesystem.
1083     * It just handles the link as an external link.
1084     * @param string $link Link to a file.
1085     * @param string $title Title of the link, can be image.
1086     */
1087    function filelink($link, $title = NULL) {
1088        $this->externallink($link, $title);
1089    }
1090
1091    /**
1092     * Inserts a link to a Windows share intranet server.
1093     * It just handles the link as an external link.
1094     * @param string $link Link to a file.
1095     * @param string $title Title of the link, can be image.
1096     */
1097    function windowssharelink($link, $title = NULL) {
1098        $this->externallink($link, $title);
1099    }
1100
1101    /**
1102     * function is called, when renderer finds an email link
1103     * It calls proper function in LaTeX depending on the name and sets mailto
1104     * @param string $address Email address
1105     * @param string/array $name Name, can be null or array (if it is media)
1106     */
1107    function emaillink($address, $name = NULL) {
1108        if (!is_array($name)) {
1109            $name = $this->_latexSpecialChars($name);
1110        }
1111        $this->_insertLink($address, $name, "email");
1112    }
1113
1114    /**
1115     * This function is called when an image is uploaded to DokuWiki and inserted to a page.
1116     * It adds desired commands to the LaTeX file and also downloads the image with LaTeX
1117     * file in the ZIP archive.
1118     *
1119     * @param string      $src     DokuWiki source of the media.
1120     * @param string|null $title   Mouseover title of image, we dont use this param (use imagareference plugin for correct labeling)
1121     * @param string|null $align   Align of the media.
1122     * @param int|null    $width   Width of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it.
1123     * @param int|null    $height  Height of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it.
1124     * @param string      $cache   We delete cache, so we don't use this param.
1125     * @param bool        $linking Not used.
1126     */
1127    function internalmedia($src, $title = NULL, $align = NULL, $width = NULL, $height = NULL, $cache = NULL, $linking = NULL) {
1128        /** @var ZipArchive $zip */
1129        global $zip;
1130
1131        $media_folder = $this->getConf('media_folder');
1132
1133        if (strpos($src,':') !== false){
1134        //the namespace structure is kept in folder structure in ZIP archive
1135            $namespaces = explode(':', $src);
1136            $path = '';
1137            for ($i = 1; $i < count($namespaces); $i++) {
1138                if ($i != 1) {
1139                    $path .= "/";
1140                }
1141                $path .= $namespaces[$i];
1142            }
1143        }else{
1144            $path = $src;
1145        }
1146
1147        //find media on FS
1148        $location = mediaFN($src);
1149        $exists = file_exists($location);
1150        if ($exists) {
1151            //exported file will be ZIP archive
1152            $this->media = TRUE;
1153            //add media to ZIP archive
1154            $zip->addFile($location, $media_folder . "/" . $path);
1155        }
1156
1157        $mime = mimetype($src);
1158        if (substr($mime[1], 0, 5) == "image") {
1159            $this->_insertImage($path, $align, $media_folder);
1160        } else {
1161            $this->_insertFile($path, $title, $media_folder);
1162        }
1163    }
1164
1165    /**
1166     * This function is called when an image from the internet is inserted to a page.
1167     * It adds desired commands to the LaTeX file and also downloads the image with LaTeX
1168     * file in the ZIP archive.
1169     *
1170     * @param string      $src     URL source of the media.
1171     * @param string|null $title   Mouseover title of image, we dont use this param (use imagareference plugin for correct labeling)
1172     * @param string      $align   Align of the media.
1173     * @param int|null    $width   Width of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it.
1174     * @param int|null    $height  Height of the media. But DW uses pixels, LaTeX does not. Therefore we dont use it.
1175     * @param string|null $cache   We delete cache, so we don't use this param.
1176     * @param bool|null   $linking Not used.
1177     */
1178    function externalmedia($src, $title = NULL, $align = NULL, $width = NULL, $height = NULL, $cache = NULL, $linking = NULL) {
1179        global $conf;
1180        /** @var ZipArchive $zip */
1181        global $zip;
1182
1183        $this->media = TRUE;
1184        $media_folder = $this->getConf('media_folder');
1185
1186        //get just the name of file without path
1187        $filename = basename($src);
1188        //download the file to the DokuWiki TEMP folder
1189        $location = $conf["tmpdir"] . "/" . $filename;
1190        file_put_contents($location, file_get_contents($src));
1191        //add file to the ZIP archive
1192        $path = $media_folder . "/" . $filename;
1193        $zip->addFile($location, $path);
1194
1195        $mime = mimetype($filename);
1196
1197        if (substr($mime[1], 0, 5) == "image") {
1198            $this->_insertImage($filename, $align, $media_folder);
1199        } else {
1200            $this->_insertFile($filename, $title, $media_folder);
1201        }
1202    }
1203
1204    /**
1205     * Function is called, when a renderer finds a start of an table.
1206     * It inserts needed packages and the header of the table.
1207     * @param int $maxcols Maximum of collumns in the table
1208     * @param int $numrows Number of rows in table (not required in LaTeX)
1209     * @param int $pos This parameter is not required by LaTeX.
1210     */
1211    function table_open($maxcols = null, $numrows = null, $pos = null) {
1212        $this->table_cols = $maxcols;
1213
1214        //set environment to tables
1215        $this->in_table = true;
1216        $pckg = new Package('longtable');
1217        $this->store->addPackage($pckg);
1218
1219        //print the header
1220        $this->_c('begin', 'longtable', 0);
1221        $this->doc .= "{|";
1222        for ($i = 0; $i < $maxcols; $i++) {
1223            $this->doc .= $this->getConf('default_table_align') . "|";
1224        }
1225        $this->_close();
1226        $this->_n();
1227        $this->_c('hline');
1228    }
1229
1230    /**
1231     * Function is called in the end of every table.
1232     * It prints the footer of the table.
1233     * @param int $pos Not required in LaTeX.
1234     */
1235    function table_close($pos = null) {
1236        //close the table environment
1237        $this->in_table = false;
1238        //print the footer
1239        $this->_c('end', 'longtable', 2);
1240    }
1241
1242    /**
1243     * Function is called at start of every row in a table.
1244     */
1245    function tablerow_open() {
1246        //set the number of cells printed
1247        $this->cells_count = 0;
1248    }
1249
1250    /**
1251     * Function is called at the end of every row in a table
1252     */
1253    function tablerow_close() {
1254        //add syntax for end of a row
1255        $this->doc .= " \\\\ ";
1256        $this->_n();
1257        //add line
1258        $this->_c('hline');
1259        $this->doc .= " ";
1260        $this->_n();
1261    }
1262
1263    /**
1264     * Function is called when the header row is reached.
1265     * It just prints regular row in bold.
1266     *
1267     * @param int         $colspan
1268     * @param string|null $align
1269     * @param int         $rowspan
1270     */
1271    function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1) {
1272        $this->tablecell_open($colspan, $align, $rowspan);
1273        $this->_open('textbf');
1274    }
1275
1276    /**
1277     * Function is called at the end of the header row.
1278     */
1279    function tableheader_close() {
1280        $this->_close();
1281        $this->tablecell_close();
1282    }
1283
1284    /**
1285     * Function handling exporting of each cell in a table.
1286     * @param int $colspan Sets collspan of the cell.
1287     * @param string $align Sets align of the cell.
1288     * @param int $rowspan Sets rows[am of the cell.
1289     */
1290    function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1) {
1291        if (is_null($align)) {
1292            $align = $this->getConf('default_table_align');
1293        } else {
1294            //in DW align is left, right, center, in LaTeX just first letter
1295            $align = substr($align, 0, 1);
1296        }
1297        //if anything is not standard, we will have to use different closing of a cell
1298        $this->last_colspan = $colspan;
1299        $this->last_rowspan = $rowspan;
1300        $this->last_align = $align;
1301
1302        //RowspanHandler stores information about the number of cells to be rowspanned
1303        if ($this->rowspan_handler->getRowspan($this->cells_count) != 0) {
1304            $this->doc .= ' & ';
1305            $this->rowspan_handler->decreaseRowspan($this->cells_count);
1306            $this->cells_count++;
1307        }
1308
1309        //colspan or not default align
1310        if ($colspan != 1 || $align != $this->getConf('default_table_align')) {
1311            $this->doc .= "\\multicolumn{" . $colspan . "}{|$align|}{";
1312        }
1313        //start a new rowspan using RowspanHandler
1314        if ($rowspan != 1) {
1315            $pckg = new Package('multirow');
1316            $this->store->addPackage($pckg);
1317            $this->rowspan_handler->insertRowspan($rowspan - 1, $this->cells_count);
1318            $this->doc .= "\\multirow{" . $rowspan . "}{*}{";
1319        }
1320    }
1321
1322    /**
1323     * Function is called at the end of every cell.
1324     */
1325    function tablecell_close() {
1326        //colspan or align different from default has been set in this cell
1327        if ($this->last_colspan != 1 || $this->last_align != $this->getConf('default_table_align')) {
1328            $this->doc .= "}";
1329        }
1330        //rowspan has been set in this cell
1331        if ($this->last_rowspan != 1) {
1332            $this->doc .= "}";
1333        }
1334
1335        //are there any cells left in this row?
1336        $this->cells_count += $this->last_colspan;
1337        if ($this->table_cols != $this->cells_count) {
1338            $this->doc .= " & ";
1339        }
1340    }
1341
1342    /**
1343     * Syntax of almost every basic LaTeX command is always the same.
1344     * @param string $command The name of a LaTeX command.
1345     * @param array $params Array of parameters of the command
1346     * @param bool $brackets Tells if the brackets should be used.
1347     */
1348    protected function _open($command, $params = NULL, $brackets = true) {
1349        $this->doc .= "\\" . $command;
1350        //if params are set, print them all
1351        if (!is_null($params)) {
1352            $this->doc .= '[';
1353            $i = 0;
1354            foreach ($params as $p) {
1355                if ($i++ > 0) {
1356                    $this->doc .= ', ';
1357                }
1358                $this->doc .= $p;
1359            }
1360            $this->doc .= ']';
1361        }
1362        if ($brackets) {
1363            $this->doc .= "{";
1364        }
1365    }
1366
1367    /**
1368     * Closing tag of a lot of LaTeX commands is always same and will be called
1369     * in almost every close function.
1370     */
1371    protected function _close() {
1372        $this->doc .= '}';
1373    }
1374
1375    /**
1376     * Helper function for printing almost all regular commands in LaTeX.
1377     * It can also print newlines after command and it supports parameters.
1378     * @param string $command Name of the command.
1379     * @param string $text Text to insert into the brackets.
1380     * @param int $newlines How many newlines after the command to insert.
1381     * @param array $params Array of parameters to be inserted.
1382     */
1383    protected function _c($command, $text = NULL, $newlines = 1, $params = NULL) {
1384        //if there is no text, there will be no brackets
1385        if (is_null($text)) {
1386            $brackets = false;
1387        } else {
1388            $brackets = true;
1389        }
1390        $this->_open($command, $params, $brackets);
1391        //if there is no text, there is nothing to be closed
1392        if (!is_null($text)) {
1393            $this->doc .= $text;
1394            $this->_close();
1395        }
1396        $this->_n($newlines);
1397    }
1398
1399    /**
1400     * Function inserting new lines in the LaTeX file.
1401     * @param int $cnt How many new lines to insert.
1402     */
1403    protected function _n($cnt = 1) {
1404        for ($i = 0; $i < $cnt; $i++) {
1405            $this->doc .= "\n";
1406        }
1407    }
1408
1409    /**
1410     * Function checks, if there were media added in a subfile.
1411     */
1412    protected function _checkMedia() {
1413        //check
1414        if (preg_match('#%///MEDIA///#si', $this->doc)) {
1415            $this->media = TRUE;
1416        }
1417        //and delete any traces
1418        $this->_deleteMediaSyntax();
1419    }
1420
1421    /**
1422     * Function removes %///MEDIA/// from document
1423     */
1424    protected function _deleteMediaSyntax() {
1425        str_replace('%///MEDIA///', '', $this->doc);
1426    }
1427
1428    /**
1429     * Function inserts package used for hyperlinks.
1430     */
1431    protected function _insertLinkPackages() {
1432        $package = new Package('hyperref');
1433        //fixes the encoding warning
1434        $package->addParameter('unicode');
1435        $this->store->addPackage($package);
1436    }
1437
1438    /**
1439     * Function used for exporting lists, they differ only by command.
1440     * @param string $command Proper LaTeX list command
1441     */
1442    protected function _list_open($command) {
1443        $this->_n();
1444        if ($this->list_opened) {
1445            for ($i = 1; $i < $this->last_level + 1; $i++) {
1446                //indention
1447                $this->doc .= '  ';
1448            }
1449        } else {
1450            $this->list_opened = TRUE;
1451        }
1452        $this->_indent_list();
1453        $this->_c('begin', $command);
1454    }
1455
1456    /**
1457     * Function used for exporting the end of lists, they differ only by command.
1458     * @param string $command Proper LaTeX list command
1459     */
1460    protected function _list_close($command) {
1461        if ($this->last_level == 1) {
1462            $this->list_opened = FALSE;
1463        }
1464        $this->_indent_list();
1465        $this->_c('end', $command);
1466    }
1467
1468    /**
1469     * Indents the list according to the last seen level.
1470     */
1471    protected function _indent_list() {
1472        for ($i = 1; $i < $this->last_level; $i++) {
1473            $this->doc .= '  ';
1474        }
1475    }
1476
1477    /**
1478     * This function highlights fixme DW command.
1479     * This format is used in some DokuWiki instances.
1480     * FIXME insert into documentation
1481     * format is: FIXME[author](description of a thing to fix)
1482     * (this feature comes from CCM at FIT CVUT, for whom I write the plugin)
1483     */
1484    protected function _highlightFixme() {
1485        $this->doc = str_replace('FIXME', '\hl{FIXME}', $this->doc);
1486        $this->doc = str_replace('DELETEME', '\hl{DELETEME}', $this->doc);
1487        $this->doc = preg_replace_callback('#{FIXME}\[(.*?)\]\((.*?)\)#si', array(&$this, '_highlightFixmeHandler'), $this->doc);
1488    }
1489
1490    /**
1491     * Function handling parsing of the fix me DW command.
1492     *
1493     * @param array $matches of strings $matches strings from the regex
1494     * @return string regex result replacement
1495     */
1496    protected function _highlightFixmeHandler($matches) {
1497        $matches[1] = $this->_stripDiacritics($matches[1]);
1498        $matches[2] = $this->_stripDiacritics($matches[2]);
1499        return '{FIXME[' . $matches[1] . '](' . $matches[2] . ')}';
1500    }
1501
1502    /**
1503     * Insert header to the LaTeX document with right level command.
1504     * @param string $level LaTeX command for header on right level.
1505     * @param string $text Text of the Header.
1506     */
1507    protected function _header($level, $text) {
1508        $this->_open($level);
1509        //pdflatex can have problems with special chars while making bookmarks
1510        //this is the fix
1511        $this->_open('texorpdfstring');
1512        $text = str_replace("\"", "", $text);
1513        $this->doc .= $this->_latexSpecialChars($text);
1514        $this->_close();
1515        $this->doc .= '{';
1516        $this->doc .= $this->_pdfString($text);
1517        $this->_close();
1518        $this->_close();
1519        $this->_n();
1520    }
1521
1522    /**
1523     * This function finds out, if the current renderer is immersed in recursion.
1524     * @return boolean Is immersed in recursion?
1525     */
1526    protected function _immersed() {
1527        if ($this->recursion_level > 0) {
1528            return true;
1529        }
1530        return false;
1531    }
1532
1533    /**
1534     * Escapes LaTeX special chars.
1535     * Entities are in the middle of special tags so eg. MathJax texts are not escaped, but entities are.
1536     * @param string $text Text to be escaped.
1537     * @return string Escaped text.
1538     */
1539    public function _latexSpecialChars($text) {
1540        return helper_plugin_latexit::escape($text);
1541    }
1542
1543    /**
1544     * Function replaces entities, which have not been replaced using _latexSpecialChars function
1545     */
1546    protected function _removeEntities() {
1547        $this->doc = preg_replace('#///ENTITYSTART///(.*?)///ENTITYEND///#si', '$1', $this->doc);
1548    }
1549
1550    /**
1551     * Functions fixes few problems which come from imagereference plugin.
1552     */
1553    protected function _fixImageRef() {
1554        $this->doc = str_replace('[h!]{\centering}', '[!ht]{\centering}', $this->doc);
1555        $this->doc = str_replace('\\ref{', '\autoref{', $this->doc);
1556    }
1557
1558    /**
1559     * Function sets, if the next link will be inserted to the file recursively.
1560     * @param bool $recursive Will next link be added recursively?
1561     */
1562    public function _setRecursive($recursive) {
1563        $this->recursive = $recursive;
1564    }
1565
1566    /**
1567     * Function increases header level of a given number.
1568     * @param int $level Size of the increase.
1569     */
1570    public function _increaseLevel($level) {
1571        $this->last_level_increase = $level;
1572        $this->headers_level += $level;
1573    }
1574
1575    /**
1576     * function replacing some characters in MathJax mode
1577     * @param string $data Parsed text.
1578     */
1579    public function _mathMode($data) {
1580        $data = str_replace('<=>', '\Leftrightarrow', $data);
1581        $data = str_replace('<->', '\leftrightarrow', $data);
1582        $data = str_replace('->', '\rightarrow', $data);
1583        $data = str_replace('<-', '\leftarrow', $data);
1584        $data = str_replace('=>', '\Rightarrow', $data);
1585        $data = str_replace('<=', '\Leftarrow', $data);
1586        $data = str_replace('...', '\ldots', $data);
1587        $data = str_replace('−', '-', $data);
1588
1589        $this->doc .= $data;
1590    }
1591
1592    /**
1593     * Function creates label from a header name.
1594     * @param string $text A header name.
1595     * @return string Label
1596     */
1597    protected function _createLabel($text) {
1598        $text = preg_replace('#///ENTITYSTART///(.*?)///ENTITYEND///#si', '$1', $text);
1599        $text = $this->_stripDiacritics($text);
1600        $text = strtolower($text);
1601        $text = str_replace(" ", "_", $text);
1602        $text = $this->_removeMathAndSymbols($text);
1603        return $text;
1604    }
1605
1606    /**
1607     * Escapes some characters in the URL.
1608     * @param string $link The URL.
1609     * @return string Escaped URL.
1610     */
1611    protected function _secureLink($link) {
1612        $link = str_replace("\\", "\\\\", $link);
1613        $link = str_replace("#", "\#", $link);
1614        $link = str_replace("%", "\%", $link);
1615        $link = str_replace("&", "\&", $link);
1616        return $link;
1617    }
1618
1619    /**
1620     * Prepares the ZIP archive.
1621     * @global string $conf global dokuwiki configuration
1622     * @global ZipArchive $zip pointer to our zip archive
1623     */
1624    protected function _prepareZIP() {
1625        global $conf;
1626        /** @var ZipArchive $zip */
1627        global $zip;
1628
1629        //generate filename
1630        $filename = $conf["tmpdir"] . "/output" . time() . ".zip";
1631        //create ZIP archive
1632        if ($zip->open($filename, ZipArchive::CREATE) !== TRUE) {
1633            exit("LaTeXit was not able to open <$filename>, check access rights.\n");
1634        }
1635    }
1636
1637    /**
1638     * Function prints the image command into the LaTeX file.
1639     * @param string $path relative path of the image.
1640     * @param string $align image align
1641     * @param string $media_folder path to the media folder.
1642     */
1643    protected function _insertImage($path, $align, $media_folder) {
1644        $pckg = new Package('graphicx');
1645        $pckg->addCommand('\\graphicspath{{' . $media_folder . '/}}');
1646        $this->store->addPackage($pckg);
1647
1648
1649        //http://stackoverflow.com/questions/2395882/how-to-remove-extension-from-string-only-real-extension
1650        $path = preg_replace("/\\.[^.\\s]{3,4}$/", "", $path);
1651
1652        //print align command
1653        if (!is_null($align)) {
1654            switch ($align) {
1655                case "center":
1656                    $this->_c('centering', NULL, 0);
1657                    break;
1658                case "left":
1659                    $this->_c('raggedleft', NULL, 0);
1660                    break;
1661                case "right":
1662                    $this->_c('raggedright', NULL, 0);
1663                    break;
1664                default :
1665                    break;
1666            }
1667        }
1668        //insert image with params from config.
1669        $this->_c('includegraphics', $path, 1, array($this->getConf('image_params')));
1670    }
1671
1672    /**
1673     * Inserts a link to media file other from an image.
1674     * @param string $path Relative path to the file.
1675     * @param string $title Title of the link.
1676     * @param string $media_folder Location of media folder.
1677     */
1678    protected function _insertFile($path, $title, $media_folder) {
1679        $path = $media_folder . "/" . $path;
1680        $this->filelink($path, $title);
1681    }
1682
1683    /**
1684     * General function for inserting links
1685     * @param string $url Link URL.
1686     * @param string $title Link title.
1687     * @param string $type Link type (internal/external/email)
1688     * @param string $link_original Original link (for internal links it is used as a title)
1689     */
1690    protected function _insertLink($url, $title, $type, $link_original = NULL) {
1691        $this->_insertLinkPackages();
1692
1693        if ($type == "email") {
1694            $mailto = "mailto:";
1695        } else {
1696            $mailto = "";
1697        }
1698
1699        //no title was specified
1700        if (is_null($title) || (!is_array($title) && trim($title) == '')) {
1701            //for internal links, original DW link is inserted as a title
1702            if ($type == "internal") {
1703                $this->doc .= '\\href{' . $mailto . $url . '}{' . $link_original . '}';
1704            }
1705            //email links have to contain mailto and address is used as text
1706            elseif ($type == "email") {
1707                $this->doc .= '\\href{' . $mailto . $url . '}{' . $url . '}';
1708            }
1709            //reqular external link inserts the whole URL
1710            else {
1711                $this->doc .= '\\url{' . $mailto . $url . '}';
1712            }
1713        } else {
1714            //is title an image?
1715            if (is_array($title)) {
1716                $this->doc .= '\\href{' . $mailto . $url . '}{';
1717                if ($title["type"] == "internalmedia") {
1718                    $this->internalmedia($title["src"], $title["title"], $title["align"]);
1719                } else {
1720                    $this->externalmedia($title["src"], $title["title"], $title["align"]);
1721                }
1722                $this->doc .= '}';
1723            } else {
1724                $this->doc .= '\\href{' . $mailto . $url . '}{' . $title . '}';
1725            }
1726        }
1727    }
1728
1729    /**
1730     * Handle a new BibEntry
1731     * @param string $entry
1732     */
1733    public function _bibEntry($entry) {
1734        if ($this->_useBibliography()) {
1735            $this->bib_handler->insert($entry);
1736        }
1737    }
1738
1739    /**
1740     * Escape the text, so it can be used as an pdf string for headers
1741     * @param string $text
1742     * @return string
1743     */
1744    protected function _pdfString($text) {
1745        $text = $this->_stripDiacritics($this->_latexSpecialChars($text));
1746        $text = $this->_removeMathAndSymbols($text);
1747        return $text;
1748    }
1749
1750    /**
1751     * Removes all math and symbols from the text.
1752     * @param string $text
1753     * @return string
1754     */
1755    protected function _removeMathAndSymbols($text) {
1756        $text = preg_replace("#\$(.*)\$#", "", $text);
1757        //next regex comes from this site:
1758        //http://stackoverflow.com/questions/5199133/function-to-return-only-alpha-numeric-characters-from-string
1759        $text = preg_replace("/[^a-zA-Z0-9_ ]+/", "", $text);
1760        return $text;
1761    }
1762
1763    public function _useBibliography() {
1764        if (is_null($this->bib_handler)) {
1765            return false;
1766        } else {
1767            return true;
1768        }
1769    }
1770
1771    /**
1772     * Initializes store variable.
1773     */
1774    protected function _initializeStore() {
1775        $this->store = $this->loadHelper('latexit', true);
1776    }
1777
1778    /**
1779     * Function removing diacritcs from a text.
1780     * From http://cs.wikibooks.org/wiki/PHP_prakticky/Odstran%C4%9Bn%C3%AD_diakritiky
1781     * @param string $data Text with diacritics
1782     * @return string Text withou diacritics
1783     */
1784    protected function _stripDiacritics($data) {
1785        $table = Array(
1786            'ä' => 'a',
1787            'Ä' => 'A',
1788            'á' => 'a',
1789            'Á' => 'A',
1790            'à' => 'a',
1791            'À' => 'A',
1792            'ã' => 'a',
1793            'Ã' => 'A',
1794            'â' => 'a',
1795            'Â' => 'A',
1796            'č' => 'c',
1797            'Č' => 'C',
1798            'ć' => 'c',
1799            'Ć' => 'C',
1800            'ď' => 'd',
1801            'Ď' => 'D',
1802            'ě' => 'e',
1803            'Ě' => 'E',
1804            'é' => 'e',
1805            'É' => 'E',
1806            'ë' => 'e',
1807            'Ë' => 'E',
1808            'è' => 'e',
1809            'È' => 'E',
1810            'ê' => 'e',
1811            'Ê' => 'E',
1812            'í' => 'i',
1813            'Í' => 'I',
1814            'ï' => 'i',
1815            'Ï' => 'I',
1816            'ì' => 'i',
1817            'Ì' => 'I',
1818            'î' => 'i',
1819            'Î' => 'I',
1820            'ľ' => 'l',
1821            'Ľ' => 'L',
1822            'ĺ' => 'l',
1823            'Ĺ' => 'L',
1824            'ń' => 'n',
1825            'Ń' => 'N',
1826            'ň' => 'n',
1827            'Ň' => 'N',
1828            'ñ' => 'n',
1829            'Ñ' => 'N',
1830            'ó' => 'o',
1831            'Ó' => 'O',
1832            'ö' => 'o',
1833            'Ö' => 'O',
1834            'ô' => 'o',
1835            'Ô' => 'O',
1836            'ò' => 'o',
1837            'Ò' => 'O',
1838            'õ' => 'o',
1839            'Õ' => 'O',
1840            'ő' => 'o',
1841            'Ő' => 'O',
1842            'ř' => 'r',
1843            'Ř' => 'R',
1844            'ŕ' => 'r',
1845            'Ŕ' => 'R',
1846            'š' => 's',
1847            'Š' => 'S',
1848            'ś' => 's',
1849            'Ś' => 'S',
1850            'ť' => 't',
1851            'Ť' => 'T',
1852            'ú' => 'u',
1853            'Ú' => 'U',
1854            'ů' => 'u',
1855            'Ů' => 'U',
1856            'ü' => 'u',
1857            'Ü' => 'U',
1858            'ù' => 'u',
1859            'Ù' => 'U',
1860            'ũ' => 'u',
1861            'Ũ' => 'U',
1862            'û' => 'u',
1863            'Û' => 'U',
1864            'ý' => 'y',
1865            'Ý' => 'Y',
1866            'ž' => 'z',
1867            'Ž' => 'Z',
1868            'ź' => 'z',
1869            'Ź' => 'Z'
1870        );
1871
1872        return strtr($data, $table);
1873    }
1874
1875}
1876