1<?php
2
3/**
4 * $Id: syntax.php 18 2017-06-19 15:22:35Z denis $
5 * @based on Source Plugin by  Chris Smith <chris@jalakai.co.uk>
6 * DokuWiki Plugin src (Syntax Component)
7 * $Id: syntax.php 18 2017-06-19 15:22:35Z denis $
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  DenisVS <deniswebcomm@gmail.com>
10 * @link    https://www.dokuwiki.org/plugin:src
11 */
12// must be run within Dokuwiki
13if (!defined('DOKU_INC'))
14  die();
15
16class syntax_plugin_src extends DokuWiki_Syntax_Plugin {
17
18  /**
19   * @return string Syntax mode type
20   */
21  public function getType() {
22    return 'substition';
23  }
24
25  /**
26   * @return string Paragraph type
27   */
28  public function getPType() {
29    return 'block';
30  }
31
32  /**
33   * @return int Sort order - Low numbers go before high numbers
34   */
35  public function getSort() {
36    return 200;
37    //return 321;
38  }
39
40  /**
41   * Connect lookup pattern to lexer.
42   *
43   * @param string $mode Parser mode
44   */
45  public function connectTo($mode) {
46    $this->Lexer->addSpecialPattern('\{\{src.+?\}\}', $mode, 'plugin_src');
47//        $this->Lexer->addEntryPattern('<FIXME>',$mode,'plugin_src');
48  }
49
50//    public function postConnect() {
51//        $this->Lexer->addExitPattern('</FIXME>','plugin_src');
52//    }
53
54  /**
55   * Handle matches of the src syntax
56   *
57   * @param string $match The match of the syntax
58   * @param int    $state The state of the handler
59   * @param int    $pos The position in the document
60   * @param Doku_Handler    $handler The handler
61   * @return array Data for the renderer
62   */
63  public function handle($match, $state, $pos, Doku_Handler &$handler) {
64    $match = (preg_replace("/\s+/", " ", substr($match, 5, -2))); // Чистим от повторяющихся пробелов
65    $t = explode(' -', $match); //бьём строку параметров на отдельные
66    foreach ($t as $key => $value) {
67      if (!empty($value)) {
68        $k = explode(' ', $value);  //если параметр есть, разбить на праметр-значение через пробел
69        $tempData[$k['0']] = trim($k['1']);
70        $data[$k['0']] = trim($k['1']);
71      }
72    }
73    if (isset($tempData['p'])) {
74      $tempArray = explode(':', $tempData['p']);
75      $data['start'] = $tempArray[0];
76      $data['end'] = $tempArray[1];
77      unset($data['p']);
78      unset($tempData);
79      unset($tempArray);
80    }
81    return $data;
82  }
83
84  /**
85   * Render xhtml output or metadata
86   *
87   * @param string         $mode      Renderer mode (supported modes: xhtml)
88   * @param Doku_Renderer  $renderer  The renderer
89   * @param array          $data      The data from the handler() function
90   * @return bool If rendering was successful.
91   */
92  public function render($mode, Doku_Renderer $renderer, $data) {
93    global $INFO;
94    global $conf;
95
96    $namespaceTypecasted = $this->_mb_str_replace(':', '/', $INFO['namespace']); //относительный путь в пространстве имён
97    $file = $this->_wikiPathFileToAbsolute($data['f'], $namespaceTypecasted, $conf['mediadir']);
98    $fileName = pathinfo($file, PATHINFO_BASENAME);
99
100    if ($mode != 'xhtml') {
101      return false;
102    }
103
104    $code = $this->_assembling($this->_fetchFile($file, $data['start'], $data['end']));
105
106    if (empty($data['l'])) {
107      $lang = pathinfo($file, PATHINFO_EXTENSION); //синтаксис по расширению
108    }
109    else {
110      $lang = $data['l'];
111    }
112
113    if (!empty($data['e'])) {
114      $code = mb_convert_encoding($code, "utf-8", $data['e']); //применяем перекодировку
115    }
116
117    // highlighting by Geshi
118    $geshi = new GeSHi($code, $lang, DOKU_INC . 'inc/geshi');
119    $geshi->set_encoding('utf-8');
120    $geshi->enable_classes();
121    $geshi->set_header_type(GESHI_HEADER_PRE);
122    $geshi->set_link_target($conf['target']['extern']);
123    // when you like to use your own wrapper, remove GeSHi's wrapper element
124    // we need to use a GeSHi wrapper to avoid <BR> throughout the highlighted text
125    $code = trim(preg_replace('!^<pre[^>]*>|</pre>$!', '', $geshi->parse_code()), "\n\r");
126    $code = $this->_mb_str_replace("&nbsp;\n", "", $code);
127//    var_dump($data);
128    $renderer->doc .= '<dt><a href="_media/'.$INFO['namespace'].':'.$fileName.'" title="Download '.$fileName.'" class="mediafile mf_' . $lang . '">' . $fileName . '</a></dt>';
129    $renderer->doc .= '<dt>';
130    $renderer->doc .= '<pre class="code">';
131    $renderer->doc .= $code;
132    $renderer->doc .= '</pre>';
133    $renderer->doc .= '</dt>';
134
135    return true;
136  }
137
138  function _fetchFile($fileName, $start = 0, $end = 0) {
139    $content = file($fileName);
140    if ($end != 0) {
141      for ($i = 0; $i < $end + 1; $i++) {
142        $content2[] = $content[$i];
143      }
144    }
145    else {
146      $content2 = $content;
147    }
148    unset($content);
149    if ($start != 0) {
150      for ($i = $start; $i < sizeof($content2); $i++) {
151        $content3[] = $content2[$i];
152      }
153    }
154    else {
155      $content3 = $content2;
156    }
157    unset($content2);
158    return $content3;
159  }
160
161  /**
162   * Преобразование массива в строку
163   * @param array $contentFromArray
164   * Массив с контентом построчно
165   * @return string
166   * Неформатированный текст на выходе
167   */
168  function _assembling($contentFromArray) {
169    $this->contentFromArray = $contentFromArray;
170    $this->txtContent = FALSE;
171    foreach ($this->contentFromArray as $key => $val) {
172      $this->txtContent .= $val . "\n";
173    }
174    $this->txtContent = substr($this->txtContent, 0, -1);  ///< Отрубаем последний перенос
175    return $this->txtContent;
176  }
177
178  /**
179   * Аналог str_replace для мультибайтных строк
180   * @param string $needle Вхождение
181   * @param string $replacement Замена
182   * @param string $haystack Строка на входе
183   * @return string Строка на выходе
184   */
185  function _mb_str_replace($needle, $replacement, $haystack) {
186    $needle_len = mb_strlen($needle);
187    $replacement_len = mb_strlen($replacement);
188    $pos = mb_strpos($haystack, $needle);
189    while ($pos !== false) {
190      $haystack = mb_substr($haystack, 0, $pos) . $replacement
191          . mb_substr($haystack, $pos + $needle_len);
192      $pos = mb_strpos($haystack, $needle, $pos + $replacement_len);
193    }
194    return $haystack;
195  }
196
197  /**
198   * Функция подъёма по директориям выше на заданное количество ступеней (слэшей).
199   * Обрубает лишние низлежащие. Дефолтно 1 уровень.
200   * @param string $url Исходный URL
201   * @param int $level Уровень, на который надо подняться
202   * @return string Результирующий URL
203   */
204  function _dirUp($url, $level = 1) {
205    $i = 0;
206    do {
207      $pos = mb_strrpos($url, '/');
208      $url = mb_substr($url, 0, $pos);
209      $i++;
210    } while ($level > $i);
211    return $url;
212  }
213
214  /**
215   * Преобразование пути к файлу из викиформата в абсолютный стантдартный.
216   * @param string $filePath путь к файлу в Wiki формате
217   * @param string $namespace текущий namespace
218   * @param string $startDir Директория, от которой строить путь.
219   */
220  function _wikiPathFileToAbsolute($filePath, $namespace, $startDir) {
221    // From current level
222    if (preg_match('/^(((\.:{0,1}){0,1}(\w+\.*\-*)+)|\.(\:)?(\w+\-*\:*)+)\z/smu', $filePath)) {
223      $filePath = preg_replace('/^(\.)(\:)?((\w+\-*\:*)+\z)/sm', '$3', $filePath);
224      $filePath = preg_replace('/^((\.:{0,1}){0,1}((\w+\.*\-*)+)\z)/smu', '$3', $filePath);
225      $filePath = $this->_mb_str_replace(':', '/', $filePath); //путь к файлу (запись согласно namespaces) в пространстве имён
226      $filePath = $startDir . '/' . $namespace . '/' . $filePath;
227    }
228    // From root level
229    else if (preg_match('/^(\w+\-*\.*\:{0,5})+(\:+)(\w+\.*\-*\:{0,5})*\z|^(\:(\w+\.*\-*\:{0,5})*)\z/smu', $filePath)) {
230      $filePath = preg_replace('/^(\:*)((\w+\.*\-*\:{0,5})*\z)/smu', '$2', $filePath);
231      $filePath = $this->_mb_str_replace(':', '/', $filePath); //путь к файлу (запись согласно namespaces) в пространстве имён
232      $filePath = $startDir . '/' . '' . $filePath;
233    }
234    // From parent level
235    else if (preg_match('/^(\.:)?(\.\.):?(\w+\.*\-*\:{0,5})*\z/smu', $filePath)) {
236      $filePath = preg_replace('/^(\.:)?(\.\.):?((\w+\.*\-*\:{0,5})*\z)/smu', '$3', $filePath);
237      $filePath = $this->_mb_str_replace(':', '/', $filePath); //путь к файлу (запись согласно namespaces) в пространстве имён
238      if ($this->dirUp($namespace) != '')
239        $midSlash = '/';
240      $filePath = $startDir . '/' . $this->dirUp($namespace) . $midSlash . $filePath;
241    }
242    return $filePath;
243  }
244
245}
246