Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

WantedPagesCLI::setup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4285
cc 1
eloc 7
nc 1
nop 1
1
#!/usr/bin/php
2
<?php
3
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
4
define('NOSESSION', 1);
5
require_once(DOKU_INC.'inc/init.php');
6
7
/**
8
 * Find wanted pages
9
 */
10
class WantedPagesCLI extends DokuCLI {
11
12
    const DIR_CONTINUE = 1;
13
    const DIR_NS       = 2;
14
    const DIR_PAGE     = 3;
15
16
    /**
17
     * Register options and arguments on the given $options object
18
     *
19
     * @param DokuCLI_Options $options
20
     * @return void
21
     */
22
    protected function setup(DokuCLI_Options $options) {
23
        $options->setHelp(
24
            'Outputs a list of wanted pages (pages which have internal links but do not yet exist).'
25
        );
26
        $options->registerArgument(
27
            'namespace',
28
            'The namespace to lookup. Defaults to root namespace',
29
            false
30
        );
31
    }
32
33
    /**
34
     * Your main program
35
     *
36
     * Arguments and options have been parsed when this is run
37
     *
38
     * @param DokuCLI_Options $options
39
     * @return void
40
     */
41
    protected function main(DokuCLI_Options $options) {
42
43
        if($options->args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options->args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
44
            $startdir = dirname(wikiFN($options->args[0].':xxx'));
45
        } else {
46
            $startdir = dirname(wikiFN('xxx'));
47
        }
48
49
        $this->info("searching $startdir");
50
51
        $wanted_pages = array();
52
53
        foreach($this->get_pages($startdir) as $page) {
54
            $wanted_pages = array_merge($wanted_pages, $this->internal_links($page));
55
        }
56
        $wanted_pages = array_unique($wanted_pages);
57
        sort($wanted_pages);
58
59
        foreach($wanted_pages as $page) {
60
            print $page."\n";
61
        }
62
    }
63
64
    /**
65
     * Determine directions of the search loop
66
     *
67
     * @param string $entry
68
     * @param string $basepath
69
     * @return int
70
     */
71
    protected function dir_filter($entry, $basepath) {
72
        if($entry == '.' || $entry == '..') {
73
            return WantedPagesCLI::DIR_CONTINUE;
74
        }
75
        if(is_dir($basepath.'/'.$entry)) {
76
            if(strpos($entry, '_') === 0) {
77
                return WantedPagesCLI::DIR_CONTINUE;
78
            }
79
            return WantedPagesCLI::DIR_NS;
80
        }
81
        if(preg_match('/\.txt$/', $entry)) {
82
            return WantedPagesCLI::DIR_PAGE;
83
        }
84
        return WantedPagesCLI::DIR_CONTINUE;
85
    }
86
87
    /**
88
     * Collects recursively the pages in a namespace
89
     *
90
     * @param string $dir
91
     * @return array
92
     * @throws DokuCLI_Exception
93
     */
94
    protected function get_pages($dir) {
95
        static $trunclen = null;
96
        if(!$trunclen) {
97
            global $conf;
98
            $trunclen = strlen($conf['datadir'].':');
99
        }
100
101
        if(!is_dir($dir)) {
102
            throw new DokuCLI_Exception("Unable to read directory $dir");
103
        }
104
105
        $pages = array();
106
        $dh    = opendir($dir);
107
        while(false !== ($entry = readdir($dh))) {
108
            $status = $this->dir_filter($entry, $dir);
109
            if($status == WantedPagesCLI::DIR_CONTINUE) {
110
                continue;
111
            } else if($status == WantedPagesCLI::DIR_NS) {
112
                $pages = array_merge($pages, $this->get_pages($dir.'/'.$entry));
113
            } else {
114
                $page    = array(
115
                    'id'   => pathID(substr($dir.'/'.$entry, $trunclen)),
116
                    'file' => $dir.'/'.$entry,
117
                );
118
                $pages[] = $page;
119
            }
120
        }
121
        closedir($dh);
122
        return $pages;
123
    }
124
125
    /**
126
     * Parse instructions and returns the non-existing links
127
     *
128
     * @param array $page array with page id and file path
129
     * @return array
130
     */
131
    function internal_links($page) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
132
        global $conf;
133
        $instructions = p_get_instructions(file_get_contents($page['file']));
134
        $links        = array();
135
        $cns          = getNS($page['id']);
136
        $exists       = false;
137
        foreach($instructions as $ins) {
0 ignored issues
show
Bug introduced by
The expression $instructions of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
138
            if($ins[0] == 'internallink' || ($conf['camelcase'] && $ins[0] == 'camelcaselink')) {
139
                $mid = $ins[1][0];
140
                resolve_pageid($cns, $mid, $exists);
0 ignored issues
show
Security Bug introduced by
It seems like $cns defined by getNS($page['id']) on line 135 can also be of type false; however, resolve_pageid() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
141
                if(!$exists) {
142
                    list($mid) = explode('#', $mid); //record pages without hashs
143
                    $links[] = $mid;
144
                }
145
            }
146
        }
147
        return $links;
148
    }
149
}
150
151
// Main
152
$cli = new WantedPagesCLI();
153
$cli->run();