1<?php
2
3use dokuwiki\Extension\ActionPlugin;
4use dokuwiki\Extension\EventHandler;
5use dokuwiki\Extension\Event;
6
7/**
8 * DokuWiki Plugin networkgraph (Action Component)
9 *
10 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
11 * @author Feralheart <dokuwiki@feralheart.dev>
12 */
13class action_plugin_networkgraph_grapher extends ActionPlugin
14{
15    /** @inheritDoc */
16    public function register(EventHandler $controller)
17    {
18        $controller->register_hook('INDEXER_PAGE_ADD', 'AFTER', $this, 'rebuildGraphData');
19    }
20
21    /**
22     * Event handler for INDEXER_TASKS_RUN
23     *
24     * @see https://www.dokuwiki.org/devel:events:INDEXER_TASKS_RUN
25     * @param Event $event Event object
26     * @param mixed $param optional parameter passed when event was registered
27     * @return void
28     */
29    public function rebuildGraphData(Event $event, $param)
30    {
31        global $conf;
32
33        $namespaces = [''];
34        $depth = 0;
35        $incmedia = 'ns';
36        $pages = [];
37        $media = [];
38        $nodes = [];
39        $links = [];
40        foreach($namespaces as $ns) {
41            // find media
42            if($incmedia == 'ns') {
43                $data = [];
44                search(
45                    $data,
46                    $conf['mediadir'],
47                    'search_universal',
48                    [
49                        'depth' => $depth,
50                        'listfiles' => true,
51                        'listdirs' => false,
52                        'pagesonly' => false,
53                        'skipacl' => true,
54                        'keeptxt' => true,
55                        'meta' => true,
56                    ],
57                    str_replace(':', '/', $ns)
58                );
59
60                // go through all those media files
61                while($item = array_shift($data)) {
62                    $media[$item['id']] = [
63                        'title' => noNS($item['id']),
64                        'size' => $item['size'],
65                        'ns' => getNS($item['id']),
66                        'time' => $item['mtime'],
67                    ];
68                }
69            }
70
71            // find pages
72            $data = [];
73            search(
74                $data,
75                $conf['datadir'],
76                'search_universal',
77                [
78                    'depth' => $depth,
79                    'listfiles' => true,
80                    'listdirs' => false,
81                    'pagesonly' => true,
82                    'skipacl' => true,
83                    'firsthead' => true,
84                    'meta' => true,
85                ],
86                str_replace(':', '/', $ns)
87            );
88
89            // ns start page
90            if($ns && page_exists($ns)) {
91                $data[] = [
92                    'id' => $ns,
93                    'ns' => getNS($ns),
94                    'title' => p_get_first_heading($ns, false),
95                    'size' => filesize(wikiFN($ns)),
96                    'mtime' => filemtime(wikiFN($ns)),
97                    'perm' => 16,
98                    'type' => 'f',
99                    'level' => 0,
100                    'open' => 1,
101                ];
102            }
103
104            // go through all those pages
105            while($item = array_shift($data)) {
106                $time = (int) p_get_metadata($item['id'], 'date created', false);
107                if(!$time) $time = $item['mtime'];
108
109                $pages[$item['id']] = [
110                    'title' => $item['title'],
111                    'ns' => $item['ns'],
112                    'size' => $item['size'],
113                    'time' => $time,
114                    'links' => [],
115                    'media' => [],
116                ];
117            }
118        }
119
120        // now get links and media
121        foreach($pages as $pid => $item) {
122            // get instructions
123            $ins = p_cached_instructions(wikiFN($pid), false, $pid);
124            // find links and media usage
125            foreach($ins as $i) {
126                $mid = null;
127
128                if($i[0] == 'internallink') {
129                    $id = $i[1][0];
130                    $exists = true;
131                    resolve_pageid($item['ns'], $id, $exists);
132                    list($id) = explode('#', $id, 2);
133                    if($id == $pid) continue; // skip self references
134                    if($exists && isset($pages[$id])) {
135                        $pages[$pid]['links'][] = $id;
136                    }
137                    if(is_array($i[1][1]) && $i[1][1]['type'] == 'internalmedia') {
138                        $mid = $i[1][1]['src']; // image link
139                    } else {
140                        continue; // we're done here
141                    }
142                }
143
144                if($i[0] == 'internalmedia') {
145                    $mid = $i[1][0];
146                }
147
148                if(is_null($mid)) continue;
149                if($incmedia == 'none') continue; // no media wanted
150
151                $exists = true;
152                resolve_mediaid($item['ns'], $mid, $exists);
153                list($mid) = explode('#', $mid, 2);
154                $mid = cleanID($mid);
155
156                if($exists) {
157                    if($incmedia == 'all') {
158                        if(!isset($media[$mid])) { //add node
159                            $media[$mid] = [
160                                'size' => filesize(mediaFN($mid)),
161                                'time' => filemtime(mediaFN($mid)),
162                                'ns' => getNS($mid),
163                                'title' => noNS($mid),
164                            ];
165                        }
166                        $pages[$pid]['media'][] = $mid;
167                    } elseif(isset($media[$mid])) {
168                        $pages[$pid]['media'][] = $mid;
169                    }
170                }
171            }
172
173	        $backlinks = ft_backlinks($pid, true);
174            foreach($backlinks as $backlink) {
175                $pages[$backlink]['links'][] = $pid;
176            }
177        }
178
179        // clean up duplicates then create nodes
180        foreach($pages as $pid => $item) {
181            $pages[$pid]['links'] = array_unique($pages[$pid]['links']);
182            $pages[$pid]['media'] = array_unique($pages[$pid]['media']);
183
184            //create nodes
185            $nodes[] = [
186                'id' => $pid,
187                'group' => 'page',
188                'label' => $pid."\n".$item['title']
189            ];
190
191            //create links
192            foreach($pages[$pid]['links'] as $link) {
193                $links[] = [
194                    'source' => $pid,
195                    'target' => $link
196                ];
197            }
198
199            foreach($pages[$pid]['media'] as $mediaLink) {
200                $links[] = [
201                    'source' => $pid,
202                    'target' => $mediaLink
203                ];
204            }
205        }
206
207        foreach($media as $mid => $item) {
208            $nodes[] = [
209                'id' => $mid,
210                'group' => 'media',
211                'label' => $mid."\n".$item['title']
212            ];
213        }
214
215        $graphData = json_encode(['nodes' => $nodes, 'links' => $links]);
216        $saveFile = metaFN('networkgraph_data', '.json');
217        io_saveFile($saveFile, json_encode([
218            'nodes' => $nodes,
219            'links' => $links
220        ]));
221
222    }
223}
224