1
2// multiorphan Admin Plugin Script
3(function($){
4
5    var canBeStopped = false, $orphanForm = null, $currentPagesAndMedia, $currentResults;
6
7    var ORPHANACTIONS = {
8
9        view : function(type) {
10            return {
11                label: 'View',
12                actionId: 'view',
13                click: function() {
14                    var $link = $(this);
15                    if (type === 'Page' || type === 'URL') {
16                        return true;
17                    }
18                    request({'do':'view'+type, 'link':decodeURIComponent($link.attr('elementid'))}, function(response){
19
20                        if ( response.dialogContent ) {
21                            $('<div/>').attr('id', 'multiorphan__preview_dialog').appendTo('body').dialog({
22                                title:'Preview',
23                                height: Math.min(700,$(window).height()-50),
24                                width: Math.min(700,$(window).width()-50),
25                                autoOpen:true,
26                                closeOnEscape:true,
27                                modal:true,
28                                close: function() {
29                                    $(this).dialog('close').remove();
30                                }
31                            }).html(response.dialogContent);
32                        }
33                    });
34                    return false;
35                }
36            }
37        },
38
39        delete : function(type) {
40            return {
41                label: 'Delete',
42                click: function() {
43                    var $link = $(this);
44                    request({'do':'delete'+type , 'link':decodeURIComponent($link.attr('elementid'))}, function(response){
45                        $link.parents('.entry[elementid="'+$link.attr('elementid')+'"]').addClass('deleted disabled');
46                        $link.parent('.actions').remove();
47                    });
48                    return false;
49                }
50            }
51        },
52
53        clear: function() {
54            return {
55                label: 'Clear',
56                click: function() {
57                    var $link = $(this);
58                    $link.parents('.entry[elementid="'+$link.attr('elementid')+'"]').remove();
59                    return false;
60                }
61            }
62        }
63    };
64
65    var init = function() {
66        $orphanForm = $('form#multiorphan').submit(loadpages);
67        $orphanForm.find( ".multiorphan__result_group" ).accordion({
68            collapsible: true,
69            active: false,
70            heightStyle: "content"
71        });
72        reset();
73    };
74
75    /**
76     * Load all pages and media
77     * Cycle the result.
78     */
79    var loadpages = function(event) {
80
81        if ( canBeStopped ) {
82
83            canBeStopped = false;
84            $currentPagesAndMedia.stop = true;
85			$orphanForm.find('button[type=submit]').text(getLang('start'));
86            errorLog(getLang('request-aborted'));
87            return false;
88        }
89
90        reset(true);
91        canBeStopped = true;
92        event.stopPropagation();
93        $orphanForm.find('button[type=submit]').text(getLang('stop'));
94        request({'do':'loadpages'}, function( $result ){
95
96            // Start cycling pages
97            $currentPagesAndMedia = $result;
98            $currentPagesAndMedia.interval = 1;//Math.floor($currentPagesAndMedia.pages.length / 10);
99            checkpagesandmedia($.makeArray($result.pages));
100        });
101
102        return false;
103    };
104
105    /**
106     * Time Check while running
107     */
108    var prevTime = 0;
109    var getTimeDifference = function(){
110        var newTime = Date.now();
111        var time = (newTime - (prevTime||newTime)) / 1000;
112        prevTime = newTime;
113        return time + "s";
114    };
115
116    /**
117     * Walk the current elements Tree
118     */
119    var checkpagesandmedia = function(elements) {
120
121		var throttleValue = 0;
122
123        // Cycle pages. Media is implicite.
124        var validateElement = function(result) {
125
126            // Check if we still have elements in the elements list (cycle-list) and in the resultList (could be stopped.)
127            if ( elements && elements.length && !($currentPagesAndMedia && $currentPagesAndMedia.stop) ) {
128                var element = elements.pop();
129
130                status(getLang('checking-page') + " ("+($currentPagesAndMedia.pages.length-elements.length)+"/"+($currentPagesAndMedia.pages.length)+" " + getTimeDifference() + ")" + (throttleValue > 0 ? ' <i>' + getLang('throttled') + '</i>' : '') + ":<br/>" + element);
131
132				window.setTimeout(function() {
133	                request({'do':'checkpage','id':element}, function(response) {
134	                    checkResponseForWantedAndLinked(response, element);
135
136	                    // Every 10 pages
137	                    //if ( elements && elements.length && elements.length % $currentPagesAndMedia.interval == 0 ) {
138	                        findOrphans();
139	                    //}
140	                }).always(validateElement);
141				}, Math.max(0, throttleValue) * 1000 );
142            } else {
143
144                // All done. Check for Orphans.
145                findOrphans(true);
146
147                // Now we can leave.
148                status(getLang('checking-done'));
149                reset();
150            }
151        };
152
153        validateElement();
154        throttleValue = parseInt( $orphanForm.find('input[name=throttle]').val() );
155    };
156
157    var buildUrl = function (id) {
158        var cleanedID = decodeURIComponent(id);
159        var schemeSepPos = cleanedID.indexOf('://');
160        if (schemeSepPos > -1) {
161            var scheme = cleanedID.substr(0, schemeSepPos);
162            if (JSINFO.schemes.indexOf(scheme) > -1) {
163                // we have an external url
164                return cleanedID;
165            }
166        }
167
168        return DOKU_BASE + 'doku.php?id=' + id;
169    };
170
171    var guiElementActions = function(actions, id, url, $insertPoint) {
172
173        // Add actions
174        var $buttonSet = $('<div/>').addClass('actions').appendTo($insertPoint);
175        $.each(actions||[], function(idx, action) {
176            const attrs = {
177                href: url || buildUrl(id),
178                elementid: id
179            };
180
181            if (action.actionId === 'view') {
182                attrs.target = '_blank';
183            }
184            var $link = $('<a>').attr(attrs).text(action.label).appendTo($buttonSet).click(action.click);
185            if ( action.process ) {
186                action.process($link);
187            }
188        })
189    };
190
191    /**
192     * Add an entry to the accordion of the according type.
193     */
194    var addGUIEntry = function($insertPoint, name, url, requestPage, requestPageURL, actions) {
195
196        var id = encodeURIComponent(name);
197        var $header = $insertPoint.prev('.header');
198        $header.attr('count', parseInt($header.attr('count')||0)+1);
199
200        var $appendTo = $insertPoint.find('.entry[elementid="'+id+'"] > ul');
201        if ( !$appendTo.length ) {
202            var $wrapper = $('<div/>').addClass('entry').attr('elementid', id).appendTo($insertPoint);
203            $('<span/>').text(name).appendTo($wrapper);
204
205            guiElementActions(actions.concat([ORPHANACTIONS.clear()]), id, url, $wrapper);
206
207            $appendTo = $('<ul/>').appendTo($wrapper);
208        }
209
210        if ( requestPage && requestPage.length ) {
211            var $pageId = $('<span>').text(requestPage);
212            var $entry = $('<li>').addClass('requestPage').append($pageId).appendTo($appendTo);
213            guiElementActions(actions, requestPage, requestPageURL, $entry);
214        }
215    };
216
217    /**
218     * Build up the structure for linked and wanted pages
219     */
220    var checkResponseForWantedAndLinked = function(response, requestPage) {
221
222        // Fill the $currentResults object with information.
223        var checkResponse = function( name, url, amount, object, $output, actions ) {
224
225            var checkId = name.split( '#', 2 ).shift();
226            var checkPoint = amount == 0 ? object.wanted : object.linked;
227            if ( !Array.isArray(checkPoint[checkId]) ) {
228                checkPoint[checkId] = [];
229            }
230
231            if ( checkPoint[checkId].indexOf(requestPage) == -1 ) {
232                checkPoint[checkId].push(requestPage);
233            }
234
235            addGUIEntry($output.find('.multiorphan__result.' + (amount == 0 ? 'wanted' : 'linked')), name, url, requestPage, (response||{}).href, actions);
236        };
237
238        var $pagesOut = $orphanForm.find('.multiorphan__result_group.pages');
239        var $mediaOut = $orphanForm.find('.multiorphan__result_group.media');
240        $.each((response||{}).pages||[], function(page, data){
241            checkResponse(page, data.href, data.amount, $currentResults.pages, $pagesOut, [ORPHANACTIONS.view('Page')]);
242        });
243        $.each((response||{}).urls||[], function(page, data){
244            checkResponse(page, data.href, data.amount, $currentResults.pages, $pagesOut, [ORPHANACTIONS.view('URL')]);
245        });
246        $.each((response||{}).media||[], function(media, data){
247            checkResponse(media, data.href, data.amount, $currentResults.media, $mediaOut, [ORPHANACTIONS.view('Media')]);
248        });
249    };
250
251    /**
252     * walk all linked pages and remove them from the ones that actually exist in the wiki
253     * assign the result to the array.
254     */
255    var findOrphans = function(processCompleted) {
256
257        // Sort out all not
258        var orphaned = function(linked, original) {
259
260            if ( !original || !original.length ) return [];
261            var orphaned = $.makeArray(original); // make copy
262
263            $.each(linked, function(link) {
264                if ( (idx = orphaned.indexOf(link)) > -1 ) {
265                    orphaned.splice(idx, 1);
266                }
267            });
268
269            return orphaned;
270        };
271
272        status(getLang('checking-orphans'));
273        $currentResults.pages.orphan = orphaned($currentResults.pages.linked, $currentPagesAndMedia.pages);
274        $currentResults.media.orphan = orphaned($currentResults.media.linked, $currentPagesAndMedia.media);
275
276        var $pagesOut = $orphanForm.find('.multiorphan__result_group.pages .multiorphan__result.orphan');
277        var $mediaOut = $orphanForm.find('.multiorphan__result_group.media .multiorphan__result.orphan');
278
279        $orphanForm.find('.multiorphan__result_group .orphan.header').attr('count', null);
280
281        if ( processCompleted == true ) {
282            $orphanForm.find('.multiorphan__result_group .multiorphan__result.orphan').html('');
283            $.each($currentResults.pages.orphan, function(idx, orphan){
284                addGUIEntry($pagesOut, orphan, null, null, null, [ORPHANACTIONS.view('Page'), ORPHANACTIONS.delete('Page')]);
285            });
286
287            $.each($currentResults.media.orphan, function(idx, orphan){
288                addGUIEntry($mediaOut, orphan, null, null, null, [ORPHANACTIONS.view('Media'), ORPHANACTIONS.delete('Media')]);
289            });
290        } else {
291            $orphanForm.find('.multiorphan__result_group .multiorphan__result.orphan').append($('<div/>').html(getLang('please-wait-orphan')));
292            $pagesOut.prev('.header').attr('count', $currentResults.pages.orphan.length);
293            $mediaOut.prev('.header').attr('count', $currentResults.media.orphan.length);
294        }
295    };
296
297    /**
298     * Send a request to the plugin.
299     */
300    var request = function(data, success) {
301        data['ns']     = $orphanForm.find('input[name=ns]').val();
302        data['filter'] = $orphanForm.find('input[name=filter]').val();
303        data['sectok'] = $orphanForm.find('input[name=sectok]').val();
304
305        if ( $orphanForm.find('input[name=purge]').is(':checked') ) {
306            data['purge'] = true
307        }
308
309        if ( $orphanForm.find('input[name=checkExternal]').is(':checked') ) {
310            data['checkExternal'] = true
311        }
312
313        if ( $orphanForm.find('input[name=includeWindowsShares]').is(':checked') ) {
314            data['includeWindowsShares'] = true
315        }
316
317        if ( $orphanForm.find('input[name=includeHidden]').is(':checked') ) {
318            data['includeHidden'] = true
319        }
320
321        // data['type']   = $orphanForm.find('select[name=type]').val() || 'both';
322        data['call']   = 'multiorphan';
323
324        throbber(true);
325        return $.post(DOKU_BASE + 'lib/exe/ajax.php', data, handleResponse(success)).always(function(){
326            throbber(false);
327        });
328    };
329
330    /**
331     * Handles Reponses with JSON from AJAX requests.
332     * Also takes care of errors.
333     */
334    var handleResponse = function( next ) {
335        return function( response ) {
336            // Check for errors
337            var $result;
338            try {
339                $result = $.parseJSON(response);
340            } catch( e ) {
341                throbber(false);
342                return errorLog( getLang('error-parsing') + "\n" + response + "\n\n" + e );
343            }
344
345            delete response;
346            if ( $result && $result.error ) {
347                reset();
348                return errorLog( $result.error );
349            } else {
350                return next( $result );
351            }
352        }
353    };
354
355    /**
356     * Set text for status
357     */
358    var status = function(text) {
359        $('#multiorphan__out').html(text).removeClass('error');
360    };
361
362    /**
363     * Log errors into container
364     */
365    var errorLog = function(text) {
366
367        if (!text || !text.length) {
368            return;
369        }
370
371        if (!$('#multiorphan__errorlog').size()) {
372            $('#multiorphan__out').parent().append($('<div id="multiorphan__errorlog"/>'));
373        }
374
375        var msg = text.split("\n");
376        for ( var int = 0; int < msg.length; int++) {
377
378            var txtMsg = msg[int];
379            txtMsg = txtMsg.replace(new RegExp("^runtime error:", "i"), "");
380
381            if (txtMsg.length == 0) {
382                continue;
383            }
384
385            $('#multiorphan__errorlog').append($('<p/>').text(txtMsg.replace(new RegExp("</?.*?>", "ig"), "")));
386        }
387    };
388
389    var resetErrorLog = function() {
390        $('#multiorphan__errorlog').remove();
391    };
392
393    /**
394     * Display the loading gif
395     */
396    var throbberCount = 0;
397    var throbber = function(on) {
398        throbberCount = Math.max(0, throbberCount + (on?1:-1));
399        $('#multiorphan__throbber').css('visibility', throbberCount>0 ? 'visible' : 'hidden');
400    };
401
402    var reset = function(fullReset) {
403        canBeStopped = false;
404
405        // Result Object
406        $currentResults = {
407
408            pages: {
409                linked: {},
410                wanted: {},
411                orphan: []
412            },
413            media: {
414                linked: {},
415                wanted: {},
416                orphan: []
417            }
418        };
419
420        // All pages and Media from DW
421        $currentPagesAndMedia = {};
422
423        throbber(false);
424        $orphanForm.find('button[type=submit]').text(getLang('start'));
425
426        if ( fullReset === true ) {
427            resetErrorLog();
428            $orphanForm.find('.multiorphan__result_group .header').attr('count', null);
429            $orphanForm.find('.multiorphan__result_group .multiorphan__result').html('');
430        }
431    };
432
433    var getLang = function(key) {
434        return LANG.plugins.multiorphan ? LANG.plugins.multiorphan[key] : key;
435    };
436
437    $(document).ready(init);
438})(jQuery);
439