1<?php
2/**
3 *
4 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author     Christoph Clausen <christoph.clausen@unige.ch>
6 */
7
8// must be run within Dokuwiki
9if(!defined('DOKU_INC')) die();
10
11class datatemplate_cache {
12
13    /**
14     * Remove any metadata that might have been stored by previous versions
15     * of the plugin.
16     * @param Doku_Renderer_metadata $renderer an instance of the dokuwiki renderer.
17     */
18    public function removeMeta(&$renderer) {
19        global $ID;
20        foreach(array_keys($renderer->persistent) as $key) {
21            if(substr($key, 0, 12) == 'datatemplate') {
22                unset($renderer->meta[$key]);
23                unset($renderer->persistent[$key]);
24            }
25        }
26    }
27
28    /**
29     * Check and generation of cached data to avoid
30     * repeated use of the SQLite queries, which can end up
31     * being quite slow.
32     *
33     * @param array $data from the handle function
34     * @param string $sql stripped SQL for hash generation
35     * @param syntax_plugin_datatemplate_list $dtlist reference, the calling datatemplate list instance
36     */
37    public function checkAndBuildCache($data, $sql, &$dtlist) {
38        // We know that the datatemplate list has a datahelper.
39        /** @var $sqlite helper_plugin_sqlite */
40        $sqlite = $dtlist->dthlp->_getDB();
41
42        // Build minimalistic data array for checking the cache
43        $dtcc = array();
44        $dtcc['cols'] = array(
45            '%pageid%' => array(
46                    'multi' => '',
47                    'key' => '%pageid%',
48                    'title' => 'Title',
49                    'type' => 'page',
50        ));
51        // Apply also filters. Note, though, that (probably) only filters with respect
52        // to the pageid are actually considered.
53        $dtcc['filter'] = $data['filter'];
54        $sqlcc = $dtlist->_buildSQL($dtcc);
55        $res = $sqlite->query($sqlcc);
56        $pageids = $sqlite->res2arr($res, $assoc = false);
57
58        // Ask dokuwiki for cache file name
59        $cachefile = getCacheName($sql, '.datatemplate');
60        if(file_exists($cachefile))
61            $cachedate = filemtime($cachefile);
62        else
63            $cachedate = 0;
64
65        $latest = 0;
66        if($cachedate) {
67            // Check for newest page in SQL result
68            foreach($pageids as $pageid) {
69                $modified = filemtime(wikiFN($pageid[0]));
70                $latest = ($modified > $latest) ? $modified : $latest;
71            }
72        }
73        if(!$cachedate || $latest > (int) $cachedate  || isset($_REQUEST['purge'])) {
74            $res = $sqlite->query($sql);
75            $rows = $sqlite->res2arr($res, $assoc = false);
76            file_put_contents($cachefile, serialize($rows), LOCK_EX);
77        } else {
78            // We arrive here when the cache seems up-to-date. However,
79            // it is possible that the cache contains items which should
80            // no longer be there. We need to find those and remove those.
81
82            // $pageids is an array of arrays, where the latter only contain
83            // one entry, the pageid. We need get rid of the second level of arrays:
84            foreach($pageids as $k => $v) {
85                $pageids[$k] = trim($v[0]);
86            }
87
88            // Then create map id->index for the pages that should be there.
89            $dataitems = array_flip($pageids);
90
91            // Do the same things for the pages that ARE there.
92            // Figure out which row-element is the page id.
93
94            $idx = 0;
95            foreach($data['cols'] as $key=>$value) {
96                if($key == '%pageid%') break;
97                $idx++;
98            }
99            $cache = $this->getData($sql);
100            $cacheitems = array();
101            foreach($cache as $num=>$row) {
102                $cacheitems[trim($row[$idx])] = $num;
103            }
104            // Now calculate the difference and update cache if necessary.
105            $diff = array_diff_key($cacheitems, $dataitems);
106            if(count($diff) > 0) {
107                foreach($diff as $key => $num) {
108                    unset($cache[$num]);
109                }
110                file_put_contents($cachefile, serialize($cache), LOCK_EX);
111            }
112        }
113    }
114
115    /**
116     * Retrieve cached data.
117     * @param string $sql the stripped sql for the data request
118     * @return Array containing the rows of the cached sql result
119     */
120    public function getData($sql) {
121        $cachefile = getCacheName($sql, '.datatemplate');
122        $datastr = file_get_contents($cachefile);
123        $data = unserialize($datastr);
124        return $data;
125    }
126}