1<?php
2
3namespace dokuwiki\plugin\xref;
4
5/**
6 * Figure out what to send to Grok to hopefully show the right, single hit
7 */
8class Heuristics
9{
10    /** @var string the definition to search */
11    protected $def = '';
12    /** @var string the path to use */
13    protected $path = '';
14    /** @var array deprecated classes and their replacements */
15    protected $deprecations;
16
17    /**
18     * Try to gues what the given reference means and how to best search for it
19     *
20     * @param string $reference
21     */
22    public function __construct($reference)
23    {
24        $this->loadDeprecations();
25
26        if ($reference !== '') $reference = $this->checkDeprecation($reference);
27        if ($reference !== '') $reference = $this->checkHash($reference);
28        if ($reference !== '') $reference = $this->checkFilename($reference);
29        if ($reference !== '') $reference = $this->checkNamespace($reference);
30        if ($reference !== '') $reference = $this->checkClassPrefix($reference);
31        if ($reference !== '') $reference = $this->checkVariable($reference);
32        if ($reference !== '') $reference = $this->checkFunction($reference);
33        if ($reference !== '') $reference = $this->checkPSRClass($reference);
34        if ($reference !== '') $this->def = $reference;
35    }
36
37    /**
38     * @return string
39     */
40    public function getDef()
41    {
42        return trim(preg_replace('/[^\w]+/', '', $this->def));
43    }
44
45    /**
46     * @return string
47     */
48    public function getPath()
49    {
50        return trim(preg_replace('/[^\w.]+/', ' ', $this->path));
51    }
52
53    /**
54     * @return array
55     */
56    public function getDeprecations()
57    {
58        return $this->deprecations;
59    }
60
61    /**
62     * Replace deprecated classes
63     *
64     * @param string $reference
65     * @return string
66     */
67    protected function checkDeprecation($reference)
68    {
69        return $this->deprecations[$reference] ?? $reference;
70    }
71
72    /**
73     * Handle things in the form path#symbol
74     *
75     * @param string $reference
76     * @return string
77     */
78    protected function checkHash($reference)
79    {
80        if (!str_contains($reference, '#')) return $reference;
81        [$this->path, $this->def] = explode('#', $reference, 2);
82        return '';
83    }
84
85    /**
86     * Known file extension?
87     *
88     * @param string $reference
89     * @return mixed|string
90     */
91    protected function checkFilename($reference)
92    {
93        if (preg_match('/\.(php|js|css|html)$/', $reference)) {
94            $this->def = '';
95            $this->path = $reference;
96            return '';
97        }
98        return $reference;
99    }
100
101    /**
102     * Namespaces are paths
103     *
104     * @param string $reference
105     * @return string
106     */
107    protected function checkNamespace($reference)
108    {
109        if (!str_contains($reference, '\\')) return $reference;
110
111        $parts = explode('\\', $reference);
112        $parts = array_values(array_filter($parts));
113
114        $reference = array_pop($parts); // last part may be more than a class
115
116        // our classes are in inc
117        if ($parts[0] == 'dokuwiki') $parts[0] = 'inc';
118
119        $this->path = implode(' ', $parts);
120
121        return $reference;
122    }
123
124    /**
125     * Is there something called on a class?
126     */
127    protected function checkClassPrefix($reference)
128    {
129        if (
130            !str_contains($reference, '::') &&
131            !str_contains($reference, '->')
132        ) {
133            return $reference;
134        }
135        [$class, $reference] = preg_split('/(::|->)/', $reference, 2);
136
137        $this->path .= ' ' . $class;
138        $this->def = $reference;
139        return '';
140    }
141
142    /**
143     * Clearly a variable
144     *
145     * @param string $reference
146     * @return string
147     */
148    protected function checkVariable($reference)
149    {
150        if ($reference[0] == '$') {
151            $this->def = $reference;
152            return '';
153        }
154        return $reference;
155    }
156
157    /**
158     * It's a function
159     *
160     * @param string $reference
161     * @return string
162     */
163    protected function checkFunction($reference)
164    {
165        if (str_ends_with($reference, '()')) {
166            $this->def = $reference;
167            return '';
168        }
169        if (preg_match('/\(.+?\)$/', $reference)) {
170            [$reference, /* $arguments */] = explode('(', $reference, 2);
171            $this->def = $reference;
172            return '';
173        }
174        return $reference;
175    }
176
177    /**
178     * Upercase followed by lowercase letter, must be a class
179     *
180     * Those are in their own files, so add it to the path
181     * @param $reference
182     * @return mixed|string
183     */
184    protected function checkPSRClass($reference)
185    {
186        if (preg_match('/^[A-Z][a-z]/', $reference)) {
187            $this->def = $reference;
188            $this->path .= ' ' . $reference;
189            return '';
190        }
191        return $reference;
192    }
193
194    /**
195     * Load deprecated classes info
196     */
197    protected function loadDeprecations()
198    {
199        $this->deprecations = [];
200
201        // class aliases
202        $legacy = file_get_contents(DOKU_INC . 'inc/legacy.php');
203        if (
204            preg_match_all(
205                '/class_alias\(\'([^\']+)\', *\'([^\']+)\'\)/',
206                $legacy,
207                $matches,
208                PREG_SET_ORDER
209            )
210        ) {
211            foreach ($matches as $match) {
212                $this->deprecations[$match[2]] = $match[1];
213            }
214        }
215
216        // deprecated classes
217        $deprecations = file_get_contents(DOKU_INC . 'inc/deprecated.php');
218        if (
219            preg_match_all(
220                '/class (.+?) extends (\\\\dokuwiki\\\\.+?)(\s|$|{)/',
221                $deprecations,
222                $matches,
223                PREG_SET_ORDER
224            )
225        ) {
226            foreach ($matches as $match) {
227                $this->deprecations[$match[1]] = $match[2];
228            }
229        }
230    }
231}
232