backup de julho
[h2N7SspZmY.git] / lib / plugins / tag / helper.php
blobd17b8a9dd226956ca1e9ce6586beffbd27f0c549
1 <?php
3 /**
4 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author Esther Brunner <wikidesign@gmail.com>
6 */
8 // must be run within Dokuwiki
9 if (!defined('DOKU_INC')) die();
11 if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
12 if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14 require_once(DOKU_INC.'inc/indexer.php');
16 class helper_plugin_tag extends DokuWiki_Plugin {
18 var $namespace = ''; // namespace tag links point to
20 var $doc = ''; // the final output XHTML string
21 var $references = array(); // $meta['relation']['references'] data for metadata renderer
23 var $sort = ''; // sort key
24 var $idx_dir = ''; // directory for index files
25 var $topic_idx = array();
27 /**
28 * Constructor gets default preferences and language strings
30 function helper_plugin_tag() {
31 global $ID, $conf;
33 $this->namespace = $this->getConf('namespace');
34 if (!$this->namespace) $this->namespace = getNS($ID);
35 $this->sort = $this->getConf('sortkey');
37 // determine where index files are saved
38 if (@file_exists($conf['indexdir'].'/page.idx')) { // new word length based index
39 $this->idx_dir = $conf['indexdir'];
40 if (!@file_exists($this->idx_dir.'/topic.idx')) $this->_importTagIndex();
41 } else { // old index
42 $this->idx_dir = $conf['cachedir'];
43 if (!@file_exists($this->idx_dir.'/topic.idx')) $this->_generateTagIndex();
46 // load page and tag index
47 $this->topic_idx = unserialize(io_readFile($this->idx_dir.'/topic.idx', false));
50 function getInfo() {
51 return array(
52 'author' => 'Gina Häußge, Michael Klier, Esther Brunner',
53 'email' => 'dokuwiki@chimeric.de',
54 'date' => @file_get_contents(DOKU_PLUGIN.'tag/VERSION'),
55 'name' => 'Tag Plugin (helper class)',
56 'desc' => 'Functions to return tag links and topic lists',
57 'url' => 'http://www.dokuwiki.org/plugin:tag',
61 function getMethods() {
62 $result = array();
63 $result[] = array(
64 'name' => 'th',
65 'desc' => 'returns the header for the tags column for pagelist',
66 'return' => array('header' => 'string'),
68 $result[] = array(
69 'name' => 'td',
70 'desc' => 'returns the tag links of a given page',
71 'params' => array('id' => 'string'),
72 'return' => array('links' => 'string'),
74 $result[] = array(
75 'name' => 'tagLinks',
76 'desc' => 'generates tag links for given words',
77 'params' => array('tags' => 'array'),
78 'return' => array('links' => 'string'),
80 $result[] = array(
81 'name' => 'getTopic',
82 'desc' => 'returns a list of pages tagged with the given keyword',
83 'params' => array(
84 'namespace (optional)' => 'string',
85 'number (not used)' => 'integer',
86 'tag (required)' => 'string'),
87 'return' => array('pages' => 'array'),
89 $result[] = array(
90 'name' => 'tagRefine',
91 'desc' => 'refines an array of pages with tags',
92 'params' => array(
93 'pages to refine' => 'array',
94 'refinement tags' => 'string'),
95 'return' => array('pages' => 'array'),
97 return $result;
101 * Returns the column header for th Pagelist Plugin
103 function th() {
104 return $this->getLang('tags');
108 * Returns the cell data for the Pagelist Plugin
110 function td($id) {
111 $subject = p_get_metadata($id, 'subject');
112 return $this->tagLinks($subject);
116 * Returns the links for given tags
118 function tagLinks($tags) {
119 global $conf;
121 if (!is_array($tags)) $tags = explode(' ', $tags);
122 if (empty($tags) || ($tags[0] == '')) return '';
124 foreach ($tags as $tag) {
125 $title = str_replace('_', ' ', noNS($tag));
126 resolve_pageid($this->namespace, $tag, $exists); // resolve shortcuts
127 if ($exists) {
128 $class = 'wikilink1';
129 $url = wl($tag);
130 if ($conf['useheading']) {
131 // important: set sendond param to false to prevent recursion!
132 $heading = p_get_first_heading($tag, false);
133 if ($heading) $title = $heading;
135 } else {
136 $class = 'wikilink1';
137 $url = wl($tag, array('do'=>'showtag', 'tag'=>$tag));
139 $links[] = '<a href="'.$url.'" class="'.$class.'" title="'.hsc($tag).
140 '" rel="tag">'.hsc($title).'</a>';
141 $this->references[$tag] = $exists;
144 return implode(','.DOKU_LF.DOKU_TAB, $links);
148 * Returns a list of pages with a certain tag; very similar to ft_backlinks()
150 * @author Esther Brunner <wikidesign@gmail.com>
152 function getTopic($ns = '', $num = NULL, $tag = '') {
153 if (!$tag) $tag = $_REQUEST['tag'];
154 $tag = $this->_parseTagList($tag, true);
155 $result = array();
157 $docs = $this->_tagIndexLookup($tag);
158 $docs = array_filter($docs, 'isVisiblePage'); // discard hidden pages
159 if (!count($docs)) return $result;
161 // check metadata for matching subject
162 foreach ($docs as $match) {
164 // filter by namespace
165 if ($ns && (strpos(':'.getNS($match), ':'.$ns) !== 0)) continue;
167 // check ACL permission; if okay, then add the page
168 $perm = auth_quickaclcheck($match);
169 if ($perm < AUTH_READ) continue;
171 // get metadata
172 $meta = array();
173 $meta = p_get_metadata($match);
175 // skip drafts unless for users with create priviledge
176 $draft = ($meta['type'] == 'draft');
177 if ($draft && ($perm < AUTH_CREATE)) continue;
179 $title = $meta['title'];
180 $tags = $meta['subject'];
181 $date = ($this->sort == 'mdate' ? $meta['date']['modified'] : $meta['date']['created'] );
182 if (!is_array($tags)) $tags = explode(' ', $tags);
183 $taglinks = $this->tagLinks($tags);
185 // determine the sort key
186 if ($this->sort == 'id') $key = $match;
187 elseif ($this->sort == 'pagename') $key = noNS($match);
188 elseif ($this->sort == 'title') $key = $title;
189 else $key = $date;
191 // make sure that the key is unique
192 $key = $this->_uniqueKey($key, $result);
194 // is the page really tagged with one of our tags?
195 foreach ($tags as $word) {
196 if (in_array(utf8_strtolower($word), $tag)) {
197 $result[$key] = array(
198 'id' => $match,
199 'title' => $title,
200 'date' => $date,
201 'user' => $meta['creator'],
202 'desc' => $meta['description']['abstract'],
203 'cat' => $tags[0],
204 'tags' => $taglinks,
205 'perm' => $perm,
206 'exists' => true,
207 'draft' => $draft,
209 break;
213 // if not, the tag index was out of date: refresh it!
214 if (!is_array($result[$key]))
215 $this->_refreshTagIndex($match, $tag);
218 // finally sort by sort key
219 if ($this->getConf('sortorder') == 'ascending') ksort($result);
220 else krsort($result);
222 return $result;
226 * Refine found pages with tags (+tag: AND, -tag: (AND) NOT)
228 function tagRefine($pages, $refine) {
229 if (!is_array($pages)) return $pages; // wrong data type
230 $tags = $this->_parseTagList($refine, true);
231 foreach ($tags as $tag) {
232 if (!(($tag{0} == '+') || ($tag{0} == '-'))) continue;
233 $cleaned_tag = substr($tag, 1);
234 $tagpages = $this->topic_idx[$cleaned_tag];
235 $and = ($tag{0} == '+');
236 foreach ($pages as $key => $page) {
237 $cond = in_array($page['id'], $tagpages);
238 if ($and) $cond = (!$cond);
239 if ($cond) unset($pages[$key]);
242 return $pages;
246 * Refresh tag index
247 * Deletes all tags of page id which are not defined in the page's metadata
248 * as well.
250 * @param id the page id
251 * @param tags tags as defined in the index to double-check
253 function _refreshTagIndex($id, $tags) {
254 if (!is_array($tags) || empty($tags)) return false;
255 $changed = false;
257 // clean array first
258 $c = count($tags);
259 for ($i = 0; $i <= $c; $i++) {
260 $tags[$i] = utf8_strtolower($tags[$i]);
263 // get actual tags as saved in metadata
264 $meta = p_get_metadata($id);
265 $metatags = $meta['subject'];
266 if (!is_array($metatags)) $metatags = array();
268 foreach ($tags as $tag) {
269 if (!$tag) continue; // skip empty tags
270 if (in_array($tag, $metatags)) continue; // tag is still there
271 if (is_array($this->topic_idx[$tag])) {
272 $this->topic_idx[$tag] = array_diff($this->topic_idx[$tag], array($id));
273 } else {
274 $this->topic_idx[$tag] = array($id);
276 $changed = true;
279 if ($changed) return $this->_saveIndex();
280 else return true;
284 * Update tag index
286 function _updateTagIndex($id, $tags) {
287 global $ID, $INFO;
289 if (!is_array($tags) || empty($tags)) return false;
290 $changed = false;
292 // clean array first
293 $c = count($tags);
294 for ($i = 0; $i <= $c; $i++) {
295 $tags[$i] = utf8_strtolower($tags[$i]);
298 // clear no longer used tags
299 if ($ID == $id) {
300 $oldtags = $INFO['meta']['subject'];
301 if (!is_array($oldtags)) $oldtags = explode(' ', $oldtags);
302 foreach ($oldtags as $oldtag) {
303 if (!$oldtag) continue; // skip empty tags
304 $oldtag = utf8_strtolower($oldtag);
305 if (in_array($oldtag, $tags)) continue; // tag is still there
306 if (!is_array($this->topic_idx[$oldtag]))
307 $this->topic_idx[$oldtag] = array();
308 $this->topic_idx[$oldtag] = array_diff($this->topic_idx[$oldtag], array($id));
309 $changed = true;
313 // fill tag in
314 foreach ($tags as $tag) {
315 if (!$tag) continue; // skip empty tags
316 if (!is_array($this->topic_idx[$tag])) $this->topic_idx[$tag] = array();
317 if (!in_array($id, $this->topic_idx[$tag])) {
318 $this->topic_idx[$tag][] = $id;
319 $changed = true;
323 // save tag index
324 if ($changed) return $this->_saveIndex();
325 else return true;
329 * Save tag or page index
331 function _saveIndex() {
332 return io_saveFile($this->idx_dir.'/topic.idx', serialize($this->topic_idx));
336 * Import old creation date index
338 function _importTagIndex() {
339 global $conf;
341 $old = $conf['indexdir'].'/tag.idx';
342 $new = $conf['indexdir'].'/topic.idx';
344 if (!@file_exists($old)) return $this->_generateTagIndex();
346 $tag_index = @file($this->idx_dir.'/tag.idx');
347 $topic_index = array();
349 if (is_array($tag_index)) {
350 foreach ($tag_index as $idx_line) {
351 list($key, $value) = explode(' ', $idx_line, 2);
352 $topic_index[$key] = $this->_numToID(explode(':', trim($value)));
354 return io_saveFile($new, serialize($topic_index));
357 return false;
361 * Generates the tag index
363 function _generateTagIndex() {
364 global $conf;
366 require_once (DOKU_INC.'inc/search.php');
368 $pages = array();
369 search($pages, $conf['datadir'], 'search_allpages', array());
370 foreach ($pages as $page) {
371 $this->_generateTagData($page);
373 return true;
377 * Generates the tag data for a single page.
379 function _generateTagData($page) {
380 $tags = p_get_metadata($page['id'], 'subject');
381 if (!is_array($tags)) $tags = explode(' ', $tags);
382 $this->_updateTagIndex($page['id'], $tags);
386 * Tag index lookup
388 function _tagIndexLookup($tags) {
389 $result = array(); // array of line numbers in the page index
391 // get the line numbers in page index
392 foreach ($tags as $tag) {
393 if (($tag{0} == '+') || ($tag{0} == '-'))
394 $t = substr($tag, 1);
395 else
396 $t = $tag;
397 if (!is_array($this->topic_idx[$t])) $this->topic_idx[$t] = array();
399 if ($tag{0} == '+') { // AND: add only if in both arrays
400 $result = array_intersect($result, $this->topic_idx[$t]);
401 } elseif ($tag{0} == '-') { // NOT: remove array from docs
402 $result = array_diff($result, $this->topic_idx[$t]);
403 } else { // OR: add array to docs
404 $result = array_unique(array_merge($result, $this->topic_idx[$t]));
408 // now convert to page IDs and return
409 return $result;
413 * Converts an array of page numbers to IDs
415 function _numToID($nums) {
416 $page_index = idx_getIndex('page', '');
417 if (is_array($nums)) {
418 $docs = array();
419 foreach ($nums as $num) {
420 $docs[] = trim($page_index[$num]);
422 return $docs;
423 } else {
424 return trim($page_index[$nums]);
429 * Splits a string into an array of tags
431 function _parseTagList($tags, $clean = false) {
433 // support for "quoted phrase tags"
434 if (preg_match_all('#".*?"#', $tags, $matches)) {
435 foreach ($matches[0] as $match) {
436 $replace = str_replace(' ', '_', substr($match, 1, -1));
437 $tags = str_replace($match, $replace, $tags);
441 if ($clean) $tags = utf8_strtolower($this->_applyMacro($tags));
442 return explode(' ', $tags);
446 * Makes user or date dependent topic lists possible
448 function _applyMacro($id) {
449 global $INFO, $auth;
451 $user = $_SERVER['REMOTE_USER'];
452 // .htaccess auth doesn't provide the auth object
453 if($auth) {
454 $userdata = $auth->getUserData($user);
455 $group = $userdata['grps'][0];
458 $replace = array(
459 '@USER@' => cleanID($user),
460 '@NAME@' => cleanID($INFO['userinfo']['name']),
461 '@GROUP@' => cleanID($group),
462 '@YEAR@' => date('Y'),
463 '@MONTH@' => date('m'),
464 '@DAY@' => date('d'),
466 return str_replace(array_keys($replace), array_values($replace), $id);
470 * Non-recursive function to check whether an array key is unique
472 * @author Esther Brunner <wikidesign@gmail.com>
473 * @author Ilya S. Lebedev <ilya@lebedev.net>
475 function _uniqueKey($key, &$result) {
477 // increase numeric keys by one
478 if (is_numeric($key)) {
479 while (array_key_exists($key, $result)) $key++;
480 return $key;
482 // append a number to literal keys
483 } else {
484 $num = 0;
485 $testkey = $key;
486 while (array_key_exists($testkey, $result)) {
487 $testkey = $key.$num;
488 $num++;
490 return $testkey;
494 function _notEmpty($val) {
495 return !empty($val);
499 // vim:ts=4:sw=4:et:enc=utf-8: