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 |
||
2 | /** |
||
3 | * Copyright (c) 2013-2017 |
||
4 | * |
||
5 | * @category Library |
||
6 | * @package Dwoo\Template |
||
7 | * @author Jordi Boggiano <[email protected]> |
||
8 | * @author David Sanchez <[email protected]> |
||
9 | * @copyright 2008-2013 Jordi Boggiano |
||
10 | * @copyright 2013-2017 David Sanchez |
||
11 | * @license http://dwoo.org/LICENSE LGPLv3 |
||
12 | * @version 1.4.0 |
||
13 | * @date 2017-03-16 |
||
14 | * @link http://dwoo.org/ |
||
15 | */ |
||
16 | |||
17 | namespace Dwoo\Template; |
||
18 | |||
19 | use Dwoo\Core; |
||
20 | use Dwoo\Compiler; |
||
21 | use Dwoo\ITemplate; |
||
22 | use Dwoo\ICompiler; |
||
23 | use Dwoo\Exception; |
||
24 | |||
25 | /** |
||
26 | * Represents a Dwoo template contained in a string. |
||
27 | * This software is provided 'as-is', without any express or implied warranty. |
||
28 | * In no event will the authors be held liable for any damages arising from the use of this software. |
||
29 | */ |
||
30 | class Str implements ITemplate |
||
31 | { |
||
32 | /** |
||
33 | * Template name. |
||
34 | * |
||
35 | * @var string |
||
36 | */ |
||
37 | protected $name; |
||
38 | |||
39 | /** |
||
40 | * Template compilation id. |
||
41 | * |
||
42 | * @var string |
||
43 | */ |
||
44 | protected $compileId; |
||
45 | |||
46 | /** |
||
47 | * Template cache id, if not provided in the constructor, it is set to |
||
48 | * the md4 hash of the request_uri. it is however highly recommended to |
||
49 | * provide one that will fit your needs. |
||
50 | * in all cases, the compilation id is prepended to the cache id to separate |
||
51 | * templates with similar cache ids from one another |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | protected $cacheId; |
||
56 | |||
57 | /** |
||
58 | * Validity duration of the generated cache file (in seconds). |
||
59 | * set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time |
||
60 | * |
||
61 | * @var int |
||
62 | */ |
||
63 | protected $cacheTime; |
||
64 | |||
65 | /** |
||
66 | * Boolean flag that defines whether the compilation should be enforced (once) or |
||
67 | * not use this if you have issues with the compiled templates not being updated |
||
68 | * but if you do need this it's most likely that you should file a bug report. |
||
69 | * |
||
70 | * @var bool |
||
71 | */ |
||
72 | protected $compilationEnforced; |
||
73 | |||
74 | /** |
||
75 | * Caches the results of the file checks to save some time when the same |
||
76 | * templates is rendered several times. |
||
77 | * |
||
78 | * @var array |
||
79 | */ |
||
80 | protected static $cache = array( |
||
81 | 'cached' => array(), |
||
82 | 'compiled' => array() |
||
83 | ); |
||
84 | |||
85 | /** |
||
86 | * Holds the compiler that built this template. |
||
87 | * |
||
88 | * @var ICompiler |
||
89 | */ |
||
90 | protected $compiler; |
||
91 | |||
92 | /** |
||
93 | * Chmod value for all files written (cached or compiled ones). |
||
94 | * set to null if you don't want any chmod operation to happen |
||
95 | * |
||
96 | * @var int |
||
97 | */ |
||
98 | protected $chmod = 0777; |
||
99 | |||
100 | /** |
||
101 | * Containing template string. |
||
102 | * |
||
103 | * @var string |
||
104 | */ |
||
105 | protected $template; |
||
106 | |||
107 | /** |
||
108 | * Creates a template from a string. |
||
109 | * |
||
110 | * @param string $templateString the template to use |
||
111 | * @param int $cacheTime duration of the cache validity for this template, |
||
112 | * if null it defaults to the Dwoo instance that will |
||
113 | * render this template, set to -1 for infinite cache or 0 to disable |
||
114 | * @param string $cacheId the unique cache identifier of this page or anything else that |
||
115 | * makes this template's content unique, if null it defaults |
||
116 | * to the current url |
||
117 | * @param string $compileId the unique compiled identifier, which is used to distinguish this |
||
118 | * template from others, if null it defaults to the md4 hash of the template |
||
119 | */ |
||
120 | public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null) |
||
121 | { |
||
122 | $this->template = $templateString; |
||
123 | $this->name = hash('md4', $templateString); |
||
124 | $this->cacheTime = $cacheTime; |
||
125 | |||
126 | View Code Duplication | if ($compileId !== null) { |
|
0 ignored issues
–
show
|
|||
127 | $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;' . PATH_SEPARATOR, '/-------')); |
||
128 | } else { |
||
129 | $this->compileId = $templateString; |
||
130 | } |
||
131 | |||
132 | View Code Duplication | if ($cacheId !== null) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
133 | $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------')); |
||
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Returns the cache duration for this template. |
||
139 | * defaults to null if it was not provided |
||
140 | * |
||
141 | * @return int|null |
||
142 | */ |
||
143 | public function getCacheTime() |
||
144 | { |
||
145 | return $this->cacheTime; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Sets the cache duration for this template. |
||
150 | * can be used to set it after the object is created if you did not provide |
||
151 | * it in the constructor |
||
152 | * |
||
153 | * @param int $seconds duration of the cache validity for this template, if |
||
154 | * null it defaults to the Dwoo instance's cache time. 0 = disable and |
||
155 | * -1 = infinite cache |
||
156 | */ |
||
157 | public function setCacheTime($seconds = null) |
||
158 | { |
||
159 | $this->cacheTime = $seconds; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Returns the chmod value for all files written (cached or compiled ones). |
||
164 | * defaults to 0777 |
||
165 | * |
||
166 | * @return int|null |
||
167 | */ |
||
168 | public function getChmod() |
||
169 | { |
||
170 | return $this->chmod; |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Set the chmod value for all files written (cached or compiled ones). |
||
175 | * set to null if you don't want to do any chmod() operation |
||
176 | * |
||
177 | * @param int $mask new bitmask to use for all files |
||
178 | */ |
||
179 | public function setChmod($mask = null) |
||
180 | { |
||
181 | $this->chmod = $mask; |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Returns the template name. |
||
186 | * |
||
187 | * @return string |
||
188 | */ |
||
189 | public function getName() |
||
190 | { |
||
191 | return $this->name; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Returns the resource name for this template class. |
||
196 | * |
||
197 | * @return string |
||
198 | */ |
||
199 | public function getResourceName() |
||
200 | { |
||
201 | return 'string'; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Returns the resource identifier for this template, false here as strings don't have identifiers. |
||
206 | * |
||
207 | * @return false |
||
208 | */ |
||
209 | public function getResourceIdentifier() |
||
210 | { |
||
211 | return false; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Returns the template source of this template. |
||
216 | * |
||
217 | * @return string |
||
218 | */ |
||
219 | public function getSource() |
||
220 | { |
||
221 | return $this->template; |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * Returns an unique value identifying the current version of this template, |
||
226 | * in this case it's the md4 hash of the content. |
||
227 | * |
||
228 | * @return string |
||
229 | */ |
||
230 | public function getUid() |
||
231 | { |
||
232 | return $this->name; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Returns the compiler used by this template, if it was just compiled, or null. |
||
237 | * |
||
238 | * @return ICompiler |
||
239 | */ |
||
240 | public function getCompiler() |
||
241 | { |
||
242 | return $this->compiler; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Marks this template as compile-forced, which means it will be recompiled even if it |
||
247 | * was already saved and wasn't modified since the last compilation. do not use this in production, |
||
248 | * it's only meant to be used in development (and the development of dwoo particularly). |
||
249 | */ |
||
250 | public function forceCompilation() |
||
251 | { |
||
252 | $this->compilationEnforced = true; |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Returns the cached template output file name, true if it's cache-able but not cached |
||
257 | * or false if it's not cached. |
||
258 | * |
||
259 | * @param Core $core the dwoo instance that requests it |
||
260 | * |
||
261 | * @return string|bool |
||
262 | */ |
||
263 | public function getCachedTemplate(Core $core) |
||
0 ignored issues
–
show
getCachedTemplate uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
Loading history...
|
|||
264 | { |
||
265 | $cacheLength = $core->getCacheTime(); |
||
266 | if ($this->cacheTime !== null) { |
||
267 | $cacheLength = $this->cacheTime; |
||
268 | } |
||
269 | |||
270 | // file is not cacheable |
||
271 | if ($cacheLength == 0) { |
||
272 | return false; |
||
273 | } |
||
274 | |||
275 | $cachedFile = $this->getCacheFilename($core); |
||
276 | |||
277 | if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) { |
||
278 | // already checked, return cache file |
||
279 | return $cachedFile; |
||
280 | } elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === - 1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($core))) { |
||
281 | // cache is still valid and can be loaded |
||
282 | self::$cache['cached'][$this->cacheId] = true; |
||
283 | |||
284 | return $cachedFile; |
||
285 | } |
||
286 | |||
287 | // file is cacheable |
||
288 | return true; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Caches the provided output into the cache file. |
||
293 | * |
||
294 | * @param Core $core the dwoo instance that requests it |
||
295 | * @param string $output the template output |
||
296 | * |
||
297 | * @return mixed full path of the cached file or false upon failure |
||
298 | */ |
||
299 | public function cache(Core $core, $output) |
||
300 | { |
||
301 | $cacheDir = $core->getCacheDir(); |
||
302 | $cachedFile = $this->getCacheFilename($core); |
||
303 | |||
304 | // the code below is courtesy of Rasmus Schultz, |
||
305 | // thanks for his help on avoiding concurency issues |
||
306 | $temp = tempnam($cacheDir, 'temp'); |
||
307 | if (!($file = @fopen($temp, 'wb'))) { |
||
308 | $temp = $cacheDir . uniqid('temp'); |
||
309 | if (!($file = @fopen($temp, 'wb'))) { |
||
310 | trigger_error('Error writing temporary file \'' . $temp . '\'', E_USER_WARNING); |
||
311 | |||
312 | return false; |
||
313 | } |
||
314 | } |
||
315 | |||
316 | fwrite($file, $output); |
||
317 | fclose($file); |
||
318 | |||
319 | $this->makeDirectory(dirname($cachedFile), $cacheDir); |
||
320 | if (!@rename($temp, $cachedFile)) { |
||
321 | @unlink($cachedFile); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
|
|||
322 | @rename($temp, $cachedFile); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
|
|||
323 | } |
||
324 | |||
325 | if ($this->chmod !== null) { |
||
326 | chmod($cachedFile, $this->chmod); |
||
327 | } |
||
328 | |||
329 | self::$cache['cached'][$this->cacheId] = true; |
||
330 | |||
331 | return $cachedFile; |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * Clears the cached template if it's older than the given time. |
||
336 | * |
||
337 | * @param Core $core the dwoo instance that was used to cache that template |
||
338 | * @param int $olderThan minimum time (in seconds) required for the cache to be cleared |
||
339 | * |
||
340 | * @return bool true if the cache was not present or if it was deleted, false if it remains there |
||
341 | */ |
||
342 | public function clearCache(Core $core, $olderThan = - 1) |
||
343 | { |
||
344 | $cachedFile = $this->getCacheFilename($core); |
||
345 | |||
346 | return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile)); |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * Returns the compiled template file name. |
||
351 | * |
||
352 | * @param Core $core the dwoo instance that requests it |
||
353 | * @param ICompiler $compiler the compiler that must be used |
||
354 | * |
||
355 | * @return string |
||
356 | */ |
||
357 | public function getCompiledTemplate(Core $core, ICompiler $compiler = null) |
||
358 | { |
||
359 | $compiledFile = $this->getCompiledFilename($core); |
||
360 | |||
361 | if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) { |
||
0 ignored issues
–
show
This
if statement is empty and can be removed.
This check looks for the bodies of These if (rand(1, 6) > 3) {
//print "Check failed";
} else {
print "Check succeeded";
}
could be turned into if (rand(1, 6) <= 3) {
print "Check succeeded";
}
This is much more concise to read.
Loading history...
|
|||
362 | // already checked, return compiled file |
||
363 | } elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) { |
||
364 | // template is compiled |
||
365 | self::$cache['compiled'][$this->compileId] = true; |
||
366 | } else { |
||
367 | // compiles the template |
||
368 | $this->compilationEnforced = false; |
||
369 | |||
370 | if ($compiler === null) { |
||
371 | $compiler = $core->getDefaultCompilerFactory($this->getResourceName()); |
||
372 | |||
373 | if ($compiler === null || $compiler === array('Dwoo\Compiler', 'compilerFactory')) { |
||
374 | $compiler = Compiler::compilerFactory(); |
||
375 | } else { |
||
376 | $compiler = call_user_func($compiler); |
||
377 | } |
||
378 | } |
||
379 | |||
380 | $this->compiler = $compiler; |
||
381 | |||
382 | $compiler->setCustomPlugins($core->getCustomPlugins()); |
||
383 | $compiler->setSecurityPolicy($core->getSecurityPolicy()); |
||
384 | $this->makeDirectory(dirname($compiledFile), $core->getCompileDir()); |
||
385 | file_put_contents($compiledFile, $compiler->compile($core, $this)); |
||
386 | if ($this->chmod !== null) { |
||
387 | chmod($compiledFile, $this->chmod); |
||
388 | } |
||
389 | |||
390 | if (extension_loaded('Zend OPcache')) { |
||
391 | opcache_invalidate($compiledFile); |
||
392 | } elseif (extension_loaded('apc') && ini_get('apc.enabled')) { |
||
393 | apc_delete_file($compiledFile); |
||
394 | } |
||
395 | |||
396 | self::$cache['compiled'][$this->compileId] = true; |
||
397 | } |
||
398 | |||
399 | return $compiledFile; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Checks if compiled file is valid (it exists). |
||
404 | * |
||
405 | * @param string $file |
||
406 | * |
||
407 | * @return bool True cache file existence |
||
408 | */ |
||
409 | protected function isValidCompiledFile($file) |
||
410 | { |
||
411 | return file_exists($file); |
||
412 | } |
||
413 | |||
414 | /** |
||
415 | * Returns a new template string object with the resource id being the template source code. |
||
416 | * |
||
417 | * @param Core $core the dwoo instance requiring it |
||
418 | * @param mixed $resourceId the filename (relative to this template's dir) of the template to include |
||
419 | * @param int $cacheTime duration of the cache validity for this template, if null it defaults to the |
||
420 | * Dwoo instance that will render this template if null it defaults to the Dwoo |
||
421 | * instance that will render this template |
||
422 | * @param string $cacheId the unique cache identifier of this page or anything else that makes this |
||
423 | * template's content unique, if null it defaults to the current url makes this |
||
424 | * template's content unique, if null it defaults to the current url |
||
425 | * @param string $compileId the unique compiled identifier, which is used to distinguish this template from |
||
426 | * others, if null it defaults to the filename+bits of the path template from |
||
427 | * others, if null it defaults to the filename+bits of the path |
||
428 | * @param ITemplate $parentTemplate the template that is requesting a new template object (through an include, |
||
429 | * extends or any other plugin) an include, extends or any other plugin) |
||
430 | * |
||
431 | * @return $this |
||
432 | */ |
||
433 | public static function templateFactory(Core $core, $resourceId, $cacheTime = null, $cacheId = null, |
||
434 | $compileId = null, ITemplate $parentTemplate = null) |
||
435 | { |
||
436 | return new self($resourceId, $cacheTime, $cacheId, $compileId); |
||
437 | } |
||
438 | |||
439 | /** |
||
440 | * Returns the full compiled file name and assigns a default value to it if |
||
441 | * required. |
||
442 | * |
||
443 | * @param Core $core the Core instance that requests the file name |
||
444 | * |
||
445 | * @return string the full path to the compiled file |
||
446 | */ |
||
447 | protected function getCompiledFilename(Core $core) |
||
448 | { |
||
449 | return $core->getCompileDir() . hash('md4', $this->compileId) . '.d' . Core::RELEASE_TAG . '.php'; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Returns the full cached file name and assigns a default value to it if |
||
454 | * required. |
||
455 | * |
||
456 | * @param Core $core the dwoo instance that requests the file name |
||
457 | * |
||
458 | * @return string the full path to the cached file |
||
459 | */ |
||
460 | protected function getCacheFilename(Core $core) |
||
0 ignored issues
–
show
getCacheFilename uses the super-global variable $_SERVER which is generally not recommended.
Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable: // Bad
class Router
{
public function generate($path)
{
return $_SERVER['HOST'].$path;
}
}
// Better
class Router
{
private $host;
public function __construct($host)
{
$this->host = $host;
}
public function generate($path)
{
return $this->host.$path;
}
}
class Controller
{
public function myAction(Request $request)
{
// Instead of
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
// Better (assuming you use the Symfony2 request)
$page = $request->query->get('page', 1);
}
}
Loading history...
|
|||
461 | { |
||
462 | // no cache id provided, use request_uri as default |
||
463 | if ($this->cacheId === null) { |
||
464 | if (isset($_SERVER['REQUEST_URI']) === true) { |
||
465 | $cacheId = $_SERVER['REQUEST_URI']; |
||
466 | } elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) { |
||
467 | $cacheId = $_SERVER['SCRIPT_FILENAME'] . '-' . implode('-', $_SERVER['argv']); |
||
468 | } else { |
||
469 | $cacheId = ''; |
||
470 | } |
||
471 | // force compiled id generation |
||
472 | $this->getCompiledFilename($core); |
||
473 | |||
474 | $this->cacheId = str_replace('../', '__', |
||
475 | $this->compileId . strtr($cacheId, '\\%?=!:;' . PATH_SEPARATOR, '/-------')); |
||
476 | } |
||
477 | |||
478 | return $core->getCacheDir() . $this->cacheId . '.html'; |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Returns some php code that will check if this template has been modified or not. |
||
483 | * if the function returns null, the template will be instanciated and then the Uid checked |
||
484 | * |
||
485 | * @return string |
||
486 | */ |
||
487 | public function getIsModifiedCode() |
||
488 | { |
||
489 | return null; |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * Ensures the given path exists. |
||
494 | * |
||
495 | * @param string $path any path |
||
496 | * @param string $baseDir the base directory where the directory is created |
||
497 | * ($path must still contain the full path, $baseDir |
||
498 | * is only used for unix permissions) |
||
499 | * |
||
500 | * @throws Exception |
||
501 | */ |
||
502 | protected function makeDirectory($path, $baseDir = null) |
||
503 | { |
||
504 | if (is_dir($path) === true) { |
||
505 | return; |
||
506 | } |
||
507 | |||
508 | if ($this->chmod === null) { |
||
509 | $chmod = 0777; |
||
510 | } else { |
||
511 | $chmod = $this->chmod; |
||
512 | } |
||
513 | |||
514 | $retries = 3; |
||
515 | while ($retries --) { |
||
516 | @mkdir($path, $chmod, true); |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
|
|||
517 | if (is_dir($path)) { |
||
518 | break; |
||
519 | } |
||
520 | usleep(20); |
||
521 | } |
||
522 | |||
523 | // enforce the correct mode for all directories created |
||
524 | if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) { |
||
525 | $path = strtr(str_replace($baseDir, '', $path), '\\', '/'); |
||
526 | $folders = explode('/', trim($path, '/')); |
||
527 | foreach ($folders as $folder) { |
||
528 | $baseDir .= $folder . DIRECTORY_SEPARATOR; |
||
529 | if (!chmod($baseDir, $chmod)) { |
||
530 | throw new Exception('Unable to chmod ' . "$baseDir to $chmod: " . print_r(error_get_last(), true)); |
||
531 | } |
||
532 | } |
||
533 | } |
||
534 | } |
||
535 | } |
||
536 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.