This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php namespace Myth\Docs; |
||
2 | /** |
||
3 | * Sprint |
||
4 | * |
||
5 | * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow. |
||
6 | * |
||
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
||
8 | * of this software and associated documentation files (the "Software"), to deal |
||
9 | * in the Software without restriction, including without limitation the rights |
||
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
11 | * copies of the Software, and to permit persons to whom the Software is |
||
12 | * furnished to do so, subject to the following conditions: |
||
13 | * |
||
14 | * The above copyright notice and this permission notice shall be included in |
||
15 | * all copies or substantial portions of the Software. |
||
16 | * |
||
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||
23 | * THE SOFTWARE. |
||
24 | * |
||
25 | * @package Sprint |
||
26 | * @author Lonnie Ezell |
||
27 | * @copyright Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com) |
||
28 | * @license http://opensource.org/licenses/MIT (MIT) |
||
29 | * @link http://sprintphp.com |
||
30 | * @since Version 1.0 |
||
31 | */ |
||
32 | |||
33 | use Myth\Docs\DocSearchInterface; |
||
34 | |||
35 | /** |
||
36 | * Class Search |
||
37 | * |
||
38 | * Implements basic search capabilities for Bonfire docs. Includes application, |
||
39 | * core bonfire, and module docs. |
||
40 | * |
||
41 | * @package Myth\Docs |
||
42 | */ |
||
43 | class Search implements DocSearchInterface |
||
44 | { |
||
45 | |||
46 | /** |
||
47 | * Minimum characters that can be submitted for a search. |
||
48 | * |
||
49 | * @var int |
||
50 | */ |
||
51 | protected $min_chars = 3; |
||
52 | |||
53 | /** |
||
54 | * Maximum characters that can be submitted for a search. |
||
55 | * |
||
56 | * @var int |
||
57 | */ |
||
58 | protected $max_chars = 30; |
||
59 | |||
60 | /** |
||
61 | * Valid file extensions we can search in. |
||
62 | * |
||
63 | * @var string |
||
64 | */ |
||
65 | protected $allowed_file_types = 'html|htm|php|php4|php5|txt|md'; |
||
66 | |||
67 | /** |
||
68 | * Which files should we skip over during our search? |
||
69 | * |
||
70 | * @var array |
||
71 | */ |
||
72 | protected $skip_files = ['.', '..', '_404.md', '_toc.ini']; |
||
73 | |||
74 | /** |
||
75 | * How much of each file should we read. |
||
76 | * Use lower values for faster searches. |
||
77 | * |
||
78 | * @var int |
||
79 | */ |
||
80 | protected $byte_size = 51200; |
||
81 | |||
82 | /** |
||
83 | * Number of words long (approximately) |
||
84 | * the result excerpt should be. |
||
85 | * |
||
86 | * @var int |
||
87 | */ |
||
88 | protected $excerpt_length = 60; |
||
89 | |||
90 | /** |
||
91 | * The maximum number of results allowed from a single file. |
||
92 | * |
||
93 | * @var int |
||
94 | */ |
||
95 | protected $max_per_file = 1; |
||
96 | |||
97 | protected $doc_folders = array(); |
||
98 | |||
99 | protected $formatters = array(); |
||
100 | |||
101 | //-------------------------------------------------------------------- |
||
102 | |||
103 | /** |
||
104 | * The entry point for performing a search of the documentation. |
||
105 | * |
||
106 | * @param null $terms |
||
107 | * @param array $folders |
||
108 | * |
||
109 | * @return array|null |
||
110 | */ |
||
111 | public function search($terms = null, $folders = []) |
||
112 | { |
||
113 | if (empty($terms) || empty($folders)) { |
||
114 | return null; |
||
115 | } |
||
116 | |||
117 | $results = []; |
||
118 | $this->doc_folders = $folders; |
||
119 | |||
120 | foreach ($folders as $group => $folder) { |
||
121 | $results = array_merge($results, $this->searchFolder($terms, $folder, $group)); |
||
122 | } |
||
123 | |||
124 | return $results; |
||
125 | } |
||
126 | |||
127 | //-------------------------------------------------------------------- |
||
128 | |||
129 | //-------------------------------------------------------------------- |
||
130 | // Private Methods |
||
131 | //-------------------------------------------------------------------- |
||
132 | |||
133 | |||
134 | /** |
||
135 | * Searches a single directory worth of files. |
||
136 | * |
||
137 | * @param $term |
||
138 | * @param $folder |
||
139 | * @param $group_name |
||
140 | * |
||
141 | * @return array The results. |
||
142 | */ |
||
143 | protected function searchFolder($term, $folder, $group_name) |
||
144 | { |
||
145 | $results = []; |
||
146 | |||
147 | $map = $this->directory_map($folder, 2); |
||
148 | |||
149 | $map = $this->flattenMap($map); |
||
150 | |||
151 | // Make sure we have something to work with. |
||
152 | if (! is_array($map) || (is_array($map) && ! count($map))) { |
||
153 | return []; |
||
154 | } |
||
155 | |||
156 | // Loop over each file and search the contents for our term. |
||
157 | foreach ($map as $dir => $file) { |
||
158 | $file_count = 0; |
||
159 | |||
160 | if (in_array($file, $this->skip_files)) { |
||
161 | continue; |
||
162 | } |
||
163 | |||
164 | // Is it a folder? |
||
165 | if (is_array($file) && count($file)) { |
||
166 | $results = array_merge($results, $this->searchFolder($term, $folder . '/' . $dir, $group_name)); |
||
167 | continue; |
||
168 | } |
||
169 | |||
170 | // Make sure it's the right file type... |
||
171 | if (! preg_match("/({$this->allowed_file_types})/i", $file)) { |
||
172 | continue; |
||
173 | } |
||
174 | |||
175 | $path = is_string($dir) ? $folder . '/' . $dir . '/' . $file : $folder . '/' . $file; |
||
176 | $term_html = htmlentities($term); |
||
177 | |||
178 | // Read in the file text |
||
179 | $handle = fopen($path, 'r'); |
||
180 | $text = fread($handle, $this->byte_size); |
||
181 | |||
182 | // Do we have a match in here somewhere? |
||
183 | $found = stristr($text, $term) || stristr($text, $term_html); |
||
184 | |||
185 | if (! $found) { |
||
186 | continue; |
||
187 | } |
||
188 | |||
189 | // Escape our terms to safely use in a preg_match |
||
190 | $excerpt = strip_tags($text); |
||
191 | $term = preg_quote($term); |
||
192 | $term = str_replace("/", "\/", "{$term}"); |
||
193 | $term_html = preg_quote($term_html); |
||
194 | $term_html = str_replace("/", "\/", "{$term_html}"); |
||
195 | |||
196 | // Add the item to our results with extracts. |
||
197 | if (preg_match_all( |
||
198 | "/((\s\S*){0,3})($term|$term_html)((\s?\S*){0,3})/i", |
||
199 | $excerpt, |
||
200 | $matches, |
||
201 | PREG_OFFSET_CAPTURE | PREG_SET_ORDER |
||
202 | )) { |
||
203 | foreach ($matches as $match) { |
||
204 | if ($file_count >= $this->max_per_file) { |
||
205 | continue; |
||
206 | } |
||
207 | $result_url = '/docs/' . $group_name . '/' . str_replace('.md', '', $file); |
||
208 | |||
209 | foreach ($this->doc_folders as $alias => $folder) { |
||
210 | $result_url = str_replace($folder, $alias, $result_url); |
||
211 | } |
||
212 | |||
213 | $results[] = [ |
||
214 | 'title' => $this->extractTitle($excerpt, $file), |
||
215 | 'file' => $folder . '/' . $file, |
||
216 | 'url' => $result_url, |
||
217 | 'extract' => $this->buildExtract($excerpt, $term, $match[0][0]) |
||
218 | ]; |
||
219 | |||
220 | $file_count++; |
||
221 | } |
||
222 | } |
||
223 | } |
||
224 | |||
225 | return $results; |
||
226 | } |
||
227 | |||
228 | //-------------------------------------------------------------------- |
||
229 | |||
230 | /** |
||
231 | * Stores the name of the callback method to run to convert the source |
||
232 | * files to viewable files. By default, this should be used to register |
||
233 | * a Mardown Extended formatter with the system, but could be used to |
||
234 | * extend the |
||
235 | * |
||
236 | * @param string $callback_name |
||
237 | * @param bool $cascade // If FALSE the formatting of a component ends here. If TRUE, will be passed to next formatter. |
||
238 | * @return $this |
||
239 | */ |
||
240 | public function registerFormatter($callback_name = '', $cascade = false) |
||
241 | { |
||
242 | if (empty($callback_name)) return; |
||
243 | |||
244 | $this->formatters[] = array($callback_name => $cascade); |
||
245 | |||
246 | return $this; |
||
247 | } |
||
248 | |||
249 | //-------------------------------------------------------------------- |
||
250 | |||
251 | /** |
||
252 | * Runs the text through the registered formatters. |
||
253 | * |
||
254 | * @param $str |
||
255 | * @return mixed |
||
256 | */ |
||
257 | View Code Duplication | public function format($str) |
|
258 | { |
||
259 | if (! is_array($this->formatters)) return $str; |
||
260 | |||
261 | foreach ($this->formatters as $formatter) { |
||
262 | $method = key($formatter); |
||
263 | $cascade = $formatter[$method]; |
||
264 | |||
265 | $str = call_user_func($method, $str); |
||
266 | |||
267 | if (! $cascade) return $str; |
||
268 | } |
||
269 | |||
270 | return $str; |
||
271 | } |
||
272 | |||
273 | //-------------------------------------------------------------------- |
||
274 | |||
275 | |||
276 | //-------------------------------------------------------------------- |
||
277 | // Protected Methods |
||
278 | //-------------------------------------------------------------------- |
||
279 | |||
280 | /** |
||
281 | * Converts an array generated by directory_map into a flat array of |
||
282 | * folders, removing any nested folders and adding them to the path. |
||
283 | * |
||
284 | * @param $map |
||
285 | * @param $prefix Used to recursively add the folder name... |
||
286 | * @return mixed |
||
287 | */ |
||
288 | protected function flattenMap($map, $prefix = '') |
||
289 | { |
||
290 | if (! is_array($map) || ! count($map)) { |
||
291 | return $map; |
||
292 | } |
||
293 | |||
294 | $return = []; |
||
295 | |||
296 | foreach ($map as $folder => $files) { |
||
297 | |||
298 | // If it's a folder name and an array of files |
||
299 | // then call this method recursively to flatten it out. |
||
300 | if (is_array($files)) { |
||
301 | $return = array_merge($return, $this->flattenMap($files, $prefix . $folder)); |
||
302 | continue; |
||
303 | } |
||
304 | |||
305 | // Else, add our prefix (if any) to the filename... |
||
306 | $return[] = $prefix . $files; |
||
307 | } |
||
308 | |||
309 | return $return; |
||
310 | } |
||
311 | |||
312 | //-------------------------------------------------------------------- |
||
313 | |||
314 | /** |
||
315 | * Handles extracting the text surrounding our match and basic match formatting. |
||
316 | * |
||
317 | * @param $excerpt |
||
318 | * @param $term |
||
319 | * @param $match_string |
||
320 | * |
||
321 | * @return string |
||
322 | */ |
||
323 | protected function buildExtract($excerpt, $term, $match_string) |
||
324 | { |
||
325 | // Find the character positions within the string that our match was found at. |
||
326 | // That way we'll know from what positions before and after this we want to grab it in. |
||
327 | $start_offset = stripos($excerpt, $match_string); |
||
328 | |||
329 | // Modify the start and end positions based on $this->excerpt_length / 2. |
||
330 | $buffer = floor($this->excerpt_length / 2); |
||
331 | |||
332 | // Adjust our start position |
||
333 | $start_offset = $start_offset - $buffer; |
||
334 | if ($start_offset < 0) { |
||
335 | $start_offset = 0; |
||
336 | } |
||
337 | |||
338 | $extract = substr($excerpt, $start_offset); |
||
339 | |||
340 | $extract = strip_tags($this->format($extract)); |
||
341 | |||
342 | $extract = $this->firstXWords($extract, $this->excerpt_length); |
||
343 | |||
344 | // Wrap the search term in a span we can style. |
||
345 | $extract = str_ireplace($term, '<span class="term-hilight">' . $term . '</span>', $extract); |
||
346 | |||
347 | return $extract; |
||
348 | } |
||
349 | |||
350 | //-------------------------------------------------------------------- |
||
351 | |||
352 | /** |
||
353 | * Extracts the title from a bit of markdown formatted text. If it doesn't |
||
354 | * have an h1 or h2, then it uses the filename. |
||
355 | * |
||
356 | * @param $excerpt |
||
357 | * @param $file |
||
358 | * @return string |
||
359 | */ |
||
360 | protected function extractTitle($excerpt, $file) |
||
361 | { |
||
362 | $title = ''; |
||
363 | |||
364 | // Easiest to work if this is split into lines. |
||
365 | $lines = explode("\n", $excerpt); |
||
366 | |||
367 | if (is_array($lines) && count($lines)) { |
||
368 | foreach ($lines as $line) { |
||
369 | if (strpos($line, '# ') === 0 || strpos($line, '## ') === 0) { |
||
370 | $title = trim(str_replace('#', '', $line)); |
||
371 | break; |
||
372 | } |
||
373 | } |
||
374 | } |
||
375 | |||
376 | // If it's empty, we'll use the filename. |
||
377 | if (empty($title)) { |
||
378 | $title = str_replace('_', ' ', $file); |
||
379 | $title = str_replace('.md', ' ', $title); |
||
380 | $title = ucwords($title); |
||
381 | } |
||
382 | |||
383 | return $title; |
||
384 | } |
||
385 | //-------------------------------------------------------------------- |
||
386 | |||
387 | /** |
||
388 | * Create a Directory Map |
||
389 | * |
||
390 | * Reads the specified directory and builds an array |
||
391 | * representation of it. Sub-folders contained with the |
||
392 | * directory will be mapped as well. |
||
393 | * |
||
394 | * @param string $source_dir Path to source |
||
395 | * @param int $directory_depth Depth of directories to traverse |
||
396 | * (0 = fully recursive, 1 = current dir, etc) |
||
397 | * @param bool $hidden Whether to show hidden files |
||
398 | * @return array |
||
399 | */ |
||
400 | View Code Duplication | protected function directory_map($source_dir, $directory_depth = 0, $hidden = FALSE) |
|
401 | { |
||
402 | if ($fp = @opendir($source_dir)) { |
||
403 | $filedata = array(); |
||
404 | $new_depth = $directory_depth - 1; |
||
405 | $source_dir = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
||
406 | |||
407 | while (FALSE !== ($file = readdir($fp))) { |
||
408 | // Remove '.', '..', and hidden files [optional] |
||
0 ignored issues
–
show
|
|||
409 | if ($file === '.' OR $file === '..' OR ($hidden === FALSE && $file[0] === '.')) { |
||
410 | continue; |
||
411 | } |
||
412 | |||
413 | is_dir($source_dir . $file) && $file .= DIRECTORY_SEPARATOR; |
||
414 | |||
415 | if (($directory_depth < 1 OR $new_depth > 0) && is_dir($source_dir . $file)) |
||
416 | { |
||
417 | $filedata[$file] = $this->directory_map($source_dir . $file, $new_depth, $hidden); |
||
418 | } else |
||
419 | { |
||
420 | // Replace the directory separator here with a forward slash since |
||
421 | // Windows uses backward slashes and not all browsers will auto-replace |
||
422 | // those slashes in URLs. |
||
423 | $filedata[] = str_replace(DIRECTORY_SEPARATOR, '/', $file); |
||
424 | } |
||
425 | } |
||
426 | |||
427 | closedir($fp); |
||
428 | return $filedata; |
||
429 | } |
||
430 | |||
431 | return FALSE; |
||
432 | } |
||
433 | |||
434 | //-------------------------------------------------------------------- |
||
435 | |||
436 | /** |
||
437 | * Gets the first 'X' words of a string. |
||
438 | * |
||
439 | * @param $str |
||
440 | * @param int $wordCount |
||
441 | * @return string |
||
442 | */ |
||
443 | protected function firstXWords($str, $wordCount = 10) |
||
444 | { |
||
445 | return implode( |
||
446 | '', |
||
447 | array_slice( |
||
448 | preg_split( |
||
449 | '/([\s,\.;\?\!]+)/', |
||
450 | $str, |
||
451 | $wordCount * 2 + 1, |
||
452 | PREG_SPLIT_DELIM_CAPTURE |
||
453 | ), |
||
454 | 0, |
||
455 | $wordCount * 2 - 1 |
||
456 | ) |
||
457 | ); |
||
458 | } |
||
459 | |||
460 | //-------------------------------------------------------------------- |
||
461 | |||
462 | } |
||
463 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.