1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Otto Vainio <oiv-plugins@valjakko.net>
5 *             Ideas "borrowed" from Esther Brunners tag plugin.
6 * Version 8.3.2007 Fixed replace patter to use back reference to keep case of replaced text
7 * Version 11.10.2011 Quote regular expression characters
8 */
9
10// must be run within Dokuwiki
11if (!defined('DOKU_INC')) die();
12
13class helper_plugin_autolink2 extends DokuWiki_Plugin {
14
15  var $idx_dir = '';      // directory for index files
16  var $page_idx = array(); // array of existing pages
17  var $autolink_idx = array(); // array of anchors and index in which pages they are found
18
19  /**
20   * Constructor gets default preferences and language strings
21   */
22  function helper_plugin_autolink2(){
23    global $ID, $conf;
24
25    // determine where index files are saved
26    if (@file_exists($conf['indexdir'].'/page.idx')){ // new word length based index
27      $this->idx_dir = $conf['indexdir'];
28      $this->page_idx = @file($this->idx_dir.'/page.idx');
29      if (!@file_exists($this->idx_dir.'/autolink.idx')) $this->_importOldAutolinkIndex('index');
30    } else {                                          // old index
31      $this->idx_dir = $conf['cachedir'];
32      $this->page_idx = @file($this->idx_dir.'/page.idx');
33      if (!@file_exists($this->idx_dir.'/autolink.idx')) $this->_importOldAutolinkIndex('cache');
34    }
35
36    // load page and tag index
37    $autolink_index      = @file($this->idx_dir.'/autolink.idx');
38    usort($autolink_index,array("helper_plugin_autolink2","lensort"));
39
40    if (is_array($autolink_index)){
41      foreach ($autolink_index as $idx_line){
42        list($key, $value) = explode("\t", $idx_line, 2);
43        if ($value) {
44          $this->autolink_idx[$key]=trim($value);
45        }
46      }
47    }
48  }
49
50  function getInfo() {
51    return confToHash(dirname(__FILE__).'/plugin.info.txt');
52  }
53
54  function getMethods(){
55    $result = array();
56    $result[] = array(
57      'name'   => 'getAnchors',
58      'desc'   => 'returns pattern and replace arrays for replace',
59      'return' => array('{pattern,replace}' => 'array'),
60    );
61    return $result;
62  }
63
64
65  function getAnchors() {
66    $result = array(); // array of line numbers in the page index
67//    $result = array_merge($result, $this->autolink_idx);
68    $result = $this->autolink_idx;
69    $res=$this->_numToID($result);
70    if (is_array($res)){
71    $l=0;
72    $sr="-anchorlink-";
73    $er="-knilrohcna-";
74    $sp="-anchorlink-";
75    $ep="-knilrohcna-";
76    $hasCustom=false;
77    $customStart="";
78    $customEnd="";
79    if ($this->getConf('customfilter_start') && $this->getConf('customfilter_end')) {
80      $customStart=$this->getConf('customfilter_start');
81      $customEnd=$this->getConf('customfilter_end');
82      $hasCustom=true;
83    }
84    foreach ($res as $anchor => $page){
85      // Mark everything close to possible as a potential autolink anchor
86      $pattern[$l]="/(?<= |\\n|\\t|\|)(".$anchor.")(?=( |,|\.|:|\\n|\\t|\|))/msi";
87      //    $replace[$l++]=$sr.$anchor.$er;
88      $replace[$l++]=$sr."\\1".$er;
89      // Remove anchor from headings
90      $pattern[$l]="/(={1,6}.*?)".$sp."(".$anchor.")".$ep."(.*?={1,6})/i";
91      //    $replace[$l++]="$1".$anchor."$3";
92      $replace[$l++]="$1$2$3";
93      // Remove anchor from media (and some plugin) refs = {{something}}
94      $pattern[$l]="/(\{\{.*?)".$sp."(".$anchor.")".$ep."(.*?\}\})/i";
95      $replace[$l++]="$1$2$3";
96      // Remove anchor from links refs = [[something]]
97      $pattern[$l]="/(\[\[.*)".$sp."(".$anchor.")".$ep."(.*\]\])/Ui";
98      $replace[$l++]="$1$2$3";
99      // Remove anchor from links refs = <something>
100      $pattern[$l]="/(\<.*)".$sp."(".$anchor.")".$ep."(.*\>)/Ui";
101      $replace[$l++]="$1$2$3";
102      // Remove custom pattern links.
103      if ($hasCustom==true) {
104        $pattern[$l]=$customStart.$sp."(".$anchor.")".$ep.$customEnd;
105        $replace[$l++]="$1$2$3";
106      }
107
108      // Finally change all that's left to links
109      $pattern[$l]="/".$sp."(".$anchor.")".$ep."/i";
110      //    $replace[$l++]="[[:".$page."|".$anchor."]]";
111      $replace[$l++]="[[:".$page."|$1]]";
112
113    }
114    if (is_array($pattern)) ksort($pattern);
115    if (is_array($replace)) ksort($replace);
116  }
117
118    // now convert to page IDs and return
119    return array($pattern,$replace);
120  }
121
122  function lensort($a,$b){
123    return strlen($b)-strlen($a);
124  }
125
126  /**
127   * Update Autolink index
128   */
129  function _updateAutolinkIndex($id, $autolinks){
130    global $ID, $INFO;
131    if (!is_array($autolinks) || empty($autolinks)) return false;
132    usort($autolinks,array("helper_plugin_autolink2","lensort"));
133    $changed = false;
134    // get page id (this is the linenumber in page.idx)
135    $pid = array_search("$id\n", $this->page_idx);
136    if (!is_int($pid)){
137      $this->page_idx[] = "$id\n";
138      $pid = count($this->page_idx) - 1;
139      // page was new - write back
140      $this->_saveIndex('page');
141    }
142
143    // clean array first
144    $c = count($autolinks);
145    for ($i = 0; $i <= $c; $i++){
146      $autolinks[$i] = utf8_strtolower(preg_quote($autolinks[$i], '/'));
147    }
148    // clear no longer used autolinks
149    if ($ID == $id){
150      $oldautolinks = $INFO['meta']['anchors'];
151      $oldautolinks = $this->getoldautolinks($pid);
152      if (!is_array($oldautolinks)) $oldautolinks = explode(' ', $oldautolinks);
153      foreach ($oldautolinks as $oldtag){
154        if (!$oldtag) continue;                 // skip empty autolinks
155        $oldtag = utf8_strtolower($oldtag);
156        if (in_array($oldtag, $autolinks)) continue; // tag is still there
157        $this->autolink_idx[$oldtag]="";
158        $changed = true;
159      }
160    }
161
162    // fill tag in
163    foreach ($autolinks as $autolink){
164      if (!$autolink) continue; // skip empty autolinks
165      if ($this->autolink_idx[$autolink]!=$pid){
166        $this->autolink_idx[$autolink] = $pid;
167        $changed = true;
168      }
169    }
170
171    // save tag index
172    if ($changed) return $this->_saveIndex('autolink');
173    else return true;
174  }
175
176    /**
177   * Remove Autolink index
178   */
179  function _removeAutolinkIndex($id){
180    global $ID, $INFO;
181
182    // get page id (this is the linenumber in page.idx)
183    $pid = array_search("$id\n", $this->page_idx);
184    if (!is_int($pid)){
185      return;
186    }
187
188    // clear no longer used autolinks
189    if ($ID == $id){
190      $oldautolinks = $INFO['meta']['anchors'];
191      if (!is_array($oldautolinks)) $oldautolinks = explode(' ', $oldautolinks);
192      foreach ($oldautolinks as $oldtag){
193        if (!$oldtag) continue;                 // skip empty autolinks
194        $oldtag = utf8_strtolower($oldtag);
195//        if (in_array($oldtag, $autolinks)) continue; // tag is still there
196        $this->autolink_idx[$oldtag]="";
197        $changed = true;
198      }
199
200    }
201/*
202    // fill tag in
203    foreach ($autolinks as $autolink){
204      if (!$autolink) continue; // skip empty autolinks
205      if ($this->autolink_idx[$autolink]!=$pid){
206        $this->autolink_idx[$autolink] = $pid;
207        $changed = true;
208      }
209    }
210  */
211    // save tag index
212    if ($changed) return $this->_saveIndex('autolink');
213    else return true;
214  }
215
216  function getoldautolinks($thispid) {
217    $ali = $this->autolink_idx;
218    $retlinks = array();
219    foreach($ali as $key=>$ind) {
220      if ($ind==$thispid) {
221        $retlinks[]=$key;
222      }
223    }
224    return $retlinks;
225  }
226
227  /**
228   * Save tag or page index
229   */
230  function _saveIndex($idx = 'autolink'){
231    $fh = fopen($this->idx_dir.'/'.$idx.'.idx', 'w');
232    if (!$fh) return false;
233    if ($idx == 'page'){
234      fwrite($fh, join('', $this->page_idx));
235    } else {
236      $autolink_index = array();
237      foreach ($this->autolink_idx as $key => $value){
238        if ($value=="") continue;                 // skip empty autolinks
239        $autolink_index[] = $key."\t".$value."\n";
240      }
241      fwrite($fh, join('', $autolink_index));
242    }
243    fclose($fh);
244    return true;
245  }
246
247  /**
248   * Generates the autolink index
249   */
250  function _generateAutolinkIndex(){
251    global $conf;
252    require_once (DOKU_INC.'inc/search.php');
253    $pages = array();
254    search($pages, $conf['datadir'], 'search_allpages', array());
255    foreach ($pages as $page){
256      $anchors = p_get_metadata($page['id'], 'anchors');
257      if (!is_array($anchors)) $anchors = explode('|', $anchors);
258      $this->_updateAutolinkIndex($page['id'], $anchors);
259    }
260    return true;
261  }
262
263
264  /**
265   * Converts an array of pages numbers to IDs
266   */
267  function _numToID($nums){
268    if (is_array($nums)){
269      $docs = array();
270      foreach ($nums as $page=>$num){
271        $docs[$page] = trim($this->page_idx[$num]);
272      }
273      return $docs;
274    } else {
275      return trim($this->page_idx[$nums]);
276    }
277  }
278
279  /**
280   * Import old Autolink index
281   */
282  function _importOldAutolinkIndex($to){
283    global $conf;
284    $old=DOKU_PLUGIN.'autolink/data/links.php';
285    $cache = $conf['cachedir'].'/autolink.idx';
286    $index = $conf['indexdir'].'/autolink.idx';
287
288    if ($to=='index') {
289      if (@file_exists($cache)) {
290        if (@copy($cache, $index)){
291          @unlink($cache);
292          return true;
293        }
294      } else if (@file_exists($old)) {
295        return $this->_buildindexFromOld($index);
296      } else {
297        return $this->_generateAutolinkIndex();
298      }
299    } else {
300      if (@file_exists($old)) {
301        return $this->_buildindexFromOld($cache);
302      } else {
303        $this->_generateAutolinkIndex();
304      }
305    }
306    return false;
307  }
308
309
310  function _buildindexFromOld($to) {
311    global $conf;
312    $old=DOKU_PLUGIN.'autolink/data/links.php';
313    require_once($old);
314    $changed = false;
315    foreach ($replace as $value){
316      $page=substr($value,3,-2);
317      list ($pageid,$anchor) = explode('|',$page);
318      $pid = array_search("$pageid\n", $this->page_idx);
319      $this->autolink_idx[$anchor] = $pid;
320      $changed = true;
321    }
322    // save autolink index
323    if ($changed) return $this->_saveIndex('autolink');
324    else return true;
325  }
326
327
328
329}
330