PluginProbe
WPIDE – File Manager & Code Editor / trunk
WPIDE – File Manager & Code Editor vtrunk
3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 3.4 All 54 releases
wpide / wp-content / fatal-error-handler.php

fatal-error-handler.php in WPIDE – File Manager & Code Editor trunk, at wp-content/fatal-error-handler.php

888 lines 32.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * WPide - Fatal Error Handler - Drop-In
4 * Takes care of catching errors and exception after editing php files using WPide file editor and offers an easy way to recover erroneous files by restoring them from saved backups
5 */
6
7 if (!class_exists('WPIDE_Error_Handler')) {
8
9 class WPIDE_Error_Handler
10 {
11
12 protected static $slug = 'wpide';
13 protected static $backupDir = '/wpide/backups/';
14
15 public static function boot()
16 {
17
18 if (version_compare(PHP_VERSION, '7.4.0', '>=')) {
19
20 register_shutdown_function([__CLASS__, 'shutdownHandler']);
21 set_exception_handler([__CLASS__, 'exceptionHandler']);
22 set_error_handler([__CLASS__, 'errorHandler']);
23 }
24 }
25
26 public static function isPluginScreen(): bool
27 {
28
29 $screen = function_exists('get_current_screen') ? get_current_screen() : null;
30
31 if(!empty($screen)) {
32 return strpos($screen->base, 'page_' . self::$slug) !== false;
33 }
34
35 if(!empty($_GET['page'])) {
36 $page = sanitize_text_field($_GET['page']);
37 return strpos($page, self::$slug) !== false;
38 }
39
40 return false;
41 }
42
43 public static function shutdownHandler()
44 {
45
46 $error = error_get_last();
47
48 if ($error) {
49 self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']);
50 }
51 }
52
53 public static function exceptionHandler(Throwable $exception)
54 {
55 self::errorHandler(-1, $exception->getMessage(), $exception->getFile(), $exception->getLine());
56 }
57
58 public static function errorHandler($error_type, $error_message, $error_file, $error_line)
59 {
60
61 if (!$error_type) {
62 return;
63 }
64
65 // Respect the active error_reporting mask so @-suppressed warnings
66 // and globally filtered notices do not get re-logged by this handler.
67 if (-1 !== $error_type && 0 === (error_reporting() & $error_type)) {
68 return false;
69 }
70
71 $fatal = false;
72
73 switch ($error_type) {
74
75 case -1:
76 case E_ERROR:
77 case E_USER_ERROR:
78 case E_PARSE:
79 case E_CORE_ERROR:
80 case E_COMPILE_ERROR:
81 case E_RECOVERABLE_ERROR:
82 $fatal = true;
83 break;
84 }
85
86 switch ($error_type) {
87 case -1: // -1 //
88 $typestr = 'EXCEPTION';
89 break;
90 case E_ERROR: // 1 //
91 $typestr = 'E_ERROR';
92 break;
93 case E_WARNING: // 2 //
94 $typestr = 'E_WARNING';
95 break;
96 case E_PARSE: // 4 //
97 $typestr = 'E_PARSE';
98 break;
99 case E_NOTICE: // 8 //
100 $typestr = 'E_NOTICE';
101 break;
102 case E_CORE_ERROR: // 16 //
103 $typestr = 'E_CORE_ERROR';
104 break;
105 case E_CORE_WARNING: // 32 //
106 $typestr = 'E_CORE_WARNING';
107 break;
108 case E_COMPILE_ERROR: // 64 //
109 $typestr = 'E_COMPILE_ERROR';
110 break;
111 case E_COMPILE_WARNING: // 128 //
112 $typestr = 'E_COMPILE_WARNING';
113 break;
114 case E_USER_ERROR: // 256 //
115 $typestr = 'E_USER_ERROR';
116 break;
117 case E_USER_WARNING: // 512 //
118 $typestr = 'E_USER_WARNING';
119 break;
120 case E_USER_NOTICE: // 1024 //
121 $typestr = 'E_USER_NOTICE';
122 break;
123 case E_STRICT: // 2048 //
124 $typestr = 'E_STRICT';
125 break;
126 case E_RECOVERABLE_ERROR: // 4096 //
127 $typestr = 'E_RECOVERABLE_ERROR';
128 break;
129 case E_DEPRECATED: // 8192 //
130 $typestr = 'E_DEPRECATED';
131 break;
132 case E_USER_DEPRECATED: // 16384 //
133 $typestr = 'E_USER_DEPRECATED';
134 break;
135
136 }
137
138 $message = '<strong>' . $typestr . ': </strong>' . $error_message . ' in <strong>' . $error_file . '</strong> on line <strong>' . $error_line . '</strong><br/><br/>';
139
140 //Logging error on php file error log...
141 if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
142 error_log(strip_tags($message), 0);
143 }
144
145 if ($fatal) {
146
147 $lockfile = self::backupDir().'.lock';
148 $backup_file = self::findBackupFile($error_file);
149
150 // If backup exists
151 if (!empty($backup_file) && file_exists($backup_file)) {
152
153 if (ob_get_length()) ob_end_clean();
154
155 // If on admin side, show file recovery wizard
156 if (is_admin()) {
157 $recover = !empty($_GET['recover']);
158 if ($recover) {
159
160 $success = self::recoverFile($error_file, $backup_file);
161
162 if(file_exists($lockfile)) {
163 @unlink($lockfile);
164 }
165
166 wp_send_json([
167 'success' => $success
168 ]);
169
170 } else {
171
172 self::recoverTemplate($typestr, $error_message, $error_file, $error_line, $backup_file);
173
174 file_put_contents($lockfile, '1');
175 }
176
177 // If on the frontend, try to recover the file silently
178 } else if(!file_exists($lockfile)){
179
180 if(file_exists($lockfile)) {
181 @unlink($lockfile);
182 }
183
184 $success = self::recoverFile($error_file, $backup_file);
185
186 // redirect home on success
187 if ($success) {
188
189 die('<meta http-equiv="refresh" content="0">');
190
191 } else {
192
193 // if failed, show error message
194 self::displayError($message);
195 }
196 }
197 die();
198 }
199 }
200
201 }
202
203 public static function displayError($message) {
204
205 // Display error only if WP_DEBUG & WP_DEBUG_DISPLAY are enabled and not DOING_AJAX
206 if(!self::isPluginScreen() && defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY && !defined('DOING_AJAX')) {
207 printf('%s', $message);
208 }
209 }
210
211 public static function backupDir(): string
212 {
213
214 $uploadsBaseDir = function_exists('wp_upload_dir') ? wp_upload_dir()['basedir'] : __DIR__.'/uploads/';
215
216 return $uploadsBaseDir.self::$backupDir;
217 }
218
219 public static function findBackupFile($error_file): ?string
220 {
221
222 $backupFolders = scandir(self::backupDir());
223
224 if(empty($backupFolders)) {
225 return null;
226 }
227
228 // Get backup folders
229 $backupFolders = array_filter($backupFolders, function ($folder) {
230
231 if ($folder === '.' || $folder === '..') {
232 return false;
233 }
234
235 $date = date_create_from_format('Y-m-d', $folder);
236
237 return $date !== false;
238 });
239
240 // Sort from latest to oldest
241 sort($backupFolders);
242 $backupFolders = array_reverse($backupFolders);
243
244 // Loop and find the latest backup file
245 foreach ($backupFolders as $folder) {
246
247 $backup_file = self::backupDir() . $folder . '/' . str_replace(ABSPATH, "", $error_file);
248
249 if (file_exists($backup_file)) {
250 return $backup_file;
251 }
252 }
253
254 return null;
255 }
256
257 public static function recoverFile($error_file, $backup_file): bool
258 {
259
260 ob_start();
261 $backup_file_content = file_get_contents($backup_file);
262 $result = file_put_contents($error_file, $backup_file_content);
263 ob_end_clean();
264
265 return $result !== false;
266 }
267
268 public static function recoverTemplate($error_type, $error_message, $error_file, $error_line, $backup_file)
269 {
270
271 ?>
272 <div class="container">
273 <div class="error">
274 <div class="error-inner">
275 <h2><?php echo $error_type; ?></h2>
276 <h3>Ooh-oh, something went wrong after modifying this file:</h3>
277 <p class="error-file">
278 <code><strong>File</strong>
279 <span><?php echo str_replace(__DIR__, "", $error_file); ?></span></code>
280 <code class="error-message"><strong>Error</strong>
281 <span><?php echo $error_message; ?></span></code>
282 <code><strong>Line</strong> <span><?php echo $error_line; ?></span></code>
283 <code><strong>Backup</strong>
284 <span><?php echo str_replace(__DIR__, "", $backup_file); ?></span></code>
285 </p>
286 <p class="recovering-message">
287 <strong>WPIDE</strong> will try to recover the file for you using the latest saved backup!
288 </p>
289 <p>
290 <button class="button recover-btn"><span>Recover</span></button>
291 <button class="button refresh-btn"><span>Refresh</span></button>
292 </p>
293 </div>
294 </div>
295 <div class="stack-container">
296 <div class="card-container">
297 <div class="perspec" style="--spreaddist: 125px; --scaledist: .75; --vertdist: -25px;">
298 <div class="card">
299 <div class="writing">
300 <div class="topbar">
301 <div class="red"></div>
302 <div class="yellow"></div>
303 <div class="green"></div>
304 </div>
305 <div class="code">
306 <ul></ul>
307 </div>
308 </div>
309 </div>
310 </div>
311 </div>
312 <div class="card-container">
313 <div class="perspec" style="--spreaddist: 100px; --scaledist: .8; --vertdist: -20px;">
314 <div class="card">
315 <div class="writing">
316 <div class="topbar">
317 <div class="red"></div>
318 <div class="yellow"></div>
319 <div class="green"></div>
320 </div>
321 <div class="code">
322 <ul></ul>
323 </div>
324 </div>
325 </div>
326 </div>
327 </div>
328 <div class="card-container">
329 <div class="perspec" style="--spreaddist:75px; --scaledist: .85; --vertdist: -15px;">
330 <div class="card">
331 <div class="writing">
332 <div class="topbar">
333 <div class="red"></div>
334 <div class="yellow"></div>
335 <div class="green"></div>
336 </div>
337 <div class="code">
338 <ul></ul>
339 </div>
340 </div>
341 </div>
342 </div>
343 </div>
344 <div class="card-container">
345 <div class="perspec" style="--spreaddist: 50px; --scaledist: .9; --vertdist: -10px;">
346 <div class="card">
347 <div class="writing">
348 <div class="topbar">
349 <div class="red"></div>
350 <div class="yellow"></div>
351 <div class="green"></div>
352 </div>
353 <div class="code">
354 <ul></ul>
355 </div>
356 </div>
357 </div>
358 </div>
359 </div>
360 <div class="card-container">
361 <div class="perspec" style="--spreaddist: 25px; --scaledist: .95; --vertdist: -5px;">
362 <div class="card">
363 <div class="writing">
364 <div class="topbar">
365 <div class="red"></div>
366 <div class="yellow"></div>
367 <div class="green"></div>
368 </div>
369 <div class="code">
370 <ul></ul>
371 </div>
372 </div>
373 </div>
374 </div>
375 </div>
376 <div class="card-container">
377 <div class="perspec" style="--spreaddist: 0px; --scaledist: 1; --vertdist: 0px;">
378 <div class="card">
379 <div class="writing">
380 <div class="topbar">
381 <div class="red"></div>
382 <div class="yellow"></div>
383 <div class="green"></div>
384 </div>
385 <div class="code">
386 <ul></ul>
387 </div>
388 </div>
389 </div>
390 </div>
391 </div>
392 </div>
393 </div>
394 <style>
395 body {
396 background: #f5f6fa;
397 -webkit-font-smoothing: antialiased;
398 -moz-osx-font-smoothing: grayscale;
399 }
400
401 body,
402 html {
403 padding: 0;
404 margin: 0;
405 font-family: 'Quicksand', sans-serif;
406 font-weight: 400;
407 overflow: hidden;
408 }
409
410 .writing {
411 width: 320px;
412 height: 200px;
413 background-color: #3f3f3f;
414 border: 1px solid #bbbbbb;
415 border-radius: 6px 6px 4px 4px;
416 position: relative;
417 }
418
419 .writing .topbar {
420 position: absolute;
421 width: 100%;
422 height: 12px;
423 background-color: #f1f1f1;
424 border-top-left-radius: 4px;
425 border-top-right-radius: 4px;
426 }
427
428 .writing .topbar div {
429 height: 6px;
430 width: 6px;
431 border-radius: 50%;
432 margin: 3px;
433 float: left;
434 }
435
436 .writing .topbar div.green {
437 background-color: #60d060;
438 }
439
440 .writing .topbar div.red {
441 background-color: red;
442 }
443
444 .writing .topbar div.yellow {
445 background-color: #e6c015;
446 }
447
448 .writing .code {
449 padding: 15px;
450 }
451
452 .writing .code ul {
453 list-style: none;
454 margin: 0;
455 padding: 0;
456 }
457
458 .writing .code ul li {
459 background-color: #9e9e9e;
460 width: 0;
461 height: 7px;
462 border-radius: 6px;
463 margin: 10px 0;
464 }
465
466 .container {
467 display: -webkit-box;
468 display: -ms-flexbox;
469 display: flex;
470 -webkit-box-align: center;
471 -ms-flex-align: center;
472 align-items: center;
473 -webkit-box-pack: center;
474 -ms-flex-pack: center;
475 justify-content: center;
476 height: 100vh;
477 width: 100%;
478 -webkit-transition: -webkit-transform .5s;
479 transition: -webkit-transform .5s;
480 transition: transform .5s;
481 transition: transform .5s, -webkit-transform .5s;
482 }
483
484 .stack-container {
485 position: relative;
486 -webkit-transition: width 1s, height 1s;
487 transition: width 1s, height 1s;
488 width: 420px;
489 height: 210px;
490 }
491
492 @media screen and (max-width: 930px) {
493 .stack-container {
494 height: 50vh;
495 }
496 }
497
498 .pokeup {
499 -webkit-transition: all .3s ease;
500 transition: all .3s ease;
501 }
502
503 .pokeup:hover {
504 -webkit-transform: translateY(-10px);
505 transform: translateY(-10px);
506 -webkit-transition: .3s ease;
507 transition: .3s ease;
508 }
509
510 .error {
511 width: 50vw;
512 height: 100vh;
513 text-align: left;
514 display: flex;
515 flex-direction: column;
516 justify-content: center;
517 }
518
519 @media screen and (max-width: 930px) {
520 .error {
521 width: 100%;
522 height: 50vh;
523 }
524 }
525
526 .error h2 {
527 margin: 0 0 15px 0;
528 padding: 0px;
529 font-size: 30px;
530 letter-spacing: 0px;
531 }
532
533 .error-inner {
534 padding: 5vw;
535 display: flex;
536 flex-direction: column;
537 justify-content: center;
538 }
539
540 .error-file {
541 border: 1px solid #ddd;
542 padding: 15px;
543 border-radius: 4px;
544 display: block;
545 position: relative;
546 }
547
548 .error-file:before {
549 content: '';
550 position: absolute;
551 top: 0;
552 left: 80px;
553 width: 1px;
554 height: 100%;
555 border-right: 1px dashed #ddd;
556 }
557
558 .error-file code {
559 display: flex;
560 }
561
562 .error-file code strong {
563 flex: 0 0 80px;
564 }
565
566 .error-file code:not(:last-child) {
567 margin-bottom: 10px;
568 padding-bottom: 10px;
569 border-bottom: 1px dashed #ddd;
570 }
571
572 .error-message span {
573 color: #b11212;
574 }
575
576 .button {
577 background: #7789fd;
578 color: #fff;
579 border: 0;
580 padding: 12px 20px;
581 font-size: 16px;
582 font-weight: 600;
583 border-radius: 2px;
584 transition: background 0.3s;
585 cursor: pointer;
586 position: relative;
587 display: inline-block;
588 margin-right: 5px;
589 }
590
591 .button:hover {
592 background-color: #5269fe;
593 }
594
595 .button.success {
596 background: #50a350;
597 cursor: initial;
598 }
599
600 .button.failed {
601 background: #a04646;
602 cursor: initial;
603 }
604
605 .button.loading span {
606 visibility: hidden;
607 opacity: 0;
608 }
609
610 .button.loading:after {
611 content: "";
612 position: absolute;
613 width: 16px;
614 height: 16px;
615 top: 0;
616 left: 0;
617 right: 0;
618 bottom: 0;
619 margin: auto;
620 border: 4px solid transparent;
621 border-top-color: #ffffff;
622 border-radius: 50%;
623 animation: button-loading-spinner 1s ease infinite;
624 }
625
626 .refresh-btn {
627 display: none;
628 }
629
630 @keyframes button-loading-spinner {
631 from {
632 transform: rotate(0turn);
633 }
634
635 to {
636 transform: rotate(1turn);
637 }
638 }
639
640 .perspec {
641 -webkit-perspective: 1000px;
642 perspective: 1000px;
643 }
644
645 .writeLine {
646 -webkit-animation: writeLine .4s linear forwards;
647 animation: writeLine .4s linear forwards;
648 }
649
650 .explode {
651 -webkit-animation: explode .5s ease-in-out forwards;
652 animation: explode .5s ease-in-out forwards;
653 }
654
655 .card {
656 -webkit-animation: tiltcard .5s ease-in-out 1s forwards;
657 animation: tiltcard .5s ease-in-out 1s forwards;
658 position: absolute;
659 }
660
661 @-webkit-keyframes tiltcard {
662 0% {
663 -webkit-transform: rotateY(0deg);
664 transform: rotateY(0deg);
665 }
666
667 100% {
668 -webkit-transform: rotateY(-30deg);
669 transform: rotateY(-30deg);
670 }
671 }
672
673 @keyframes tiltcard {
674 0% {
675 -webkit-transform: rotateY(0deg);
676 transform: rotateY(0deg);
677 }
678
679 100% {
680 -webkit-transform: rotateY(-30deg);
681 transform: rotateY(-30deg);
682 }
683 }
684
685 @-webkit-keyframes explode {
686 0% {
687 -webkit-transform: translate(0, 0) scale(1);
688 transform: translate(0, 0) scale(1);
689 }
690
691 100% {
692 -webkit-transform: translate(var(--spreaddist), var(--vertdist)) scale(var(--scaledist));
693 transform: translate(var(--spreaddist), var(--vertdist)) scale(var(--scaledist));
694 }
695 }
696
697 @keyframes explode {
698 0% {
699 -webkit-transform: translate(0, 0) scale(1);
700 transform: translate(0, 0) scale(1);
701 }
702
703 100% {
704 -webkit-transform: translate(var(--spreaddist), var(--vertdist)) scale(var(--scaledist));
705 transform: translate(var(--spreaddist), var(--vertdist)) scale(var(--scaledist));
706 }
707 }
708
709 @-webkit-keyframes writeLine {
710 0% {
711 width: 0;
712 }
713
714 100% {
715 width: var(--linelength);
716 }
717 }
718
719 @keyframes writeLine {
720 0% {
721 width: 0;
722 }
723
724 100% {
725 width: var(--linelength);
726 }
727 }
728
729 @media screen and (max-width: 1000px) {
730 .container {
731 -webkit-transform: scale(.85);
732 transform: scale(.85);
733 }
734 }
735
736 @media screen and (max-width: 850px) {
737 .container {
738 -webkit-transform: scale(.75);
739 transform: scale(.75);
740 }
741 }
742
743 @media screen and (max-width: 930px) {
744 .container {
745 -ms-flex-wrap: wrap-reverse;
746 flex-wrap: wrap-reverse;
747 }
748 }
749
750 @media screen and (max-width: 370px) {
751 .container {
752 -webkit-transform: scale(.6);
753 transform: scale(.6);
754 }
755 }
756 </style>
757 <script lang="js">
758 const stackContainer = document.querySelector('.stack-container');
759 const cardNodes = document.querySelectorAll('.card-container');
760 const perspecNodes = document.querySelectorAll('.perspec');
761 const perspec = document.querySelector('.perspec');
762 const card = document.querySelector('.card');
763 const recoverBtn = document.querySelector('.recover-btn');
764 const refreshBtn = document.querySelector('.refresh-btn');
765
766 let counter = stackContainer.children.length;
767
768 const onSuccess = () => {
769 recoverBtn.querySelector('span').innerText = 'File Recovered Successfully!';
770 recoverBtn.classList.add("success");
771 recoverBtn.classList.remove("loading");
772 refreshBtn.style.display = 'inline-block';
773 };
774
775 const onFailed = () => {
776 recoverBtn.querySelector('span').innerText = 'Failed Recovering File!';
777 recoverBtn.classList.add("failed");
778 recoverBtn.classList.remove("loading");
779 };
780
781 recoverBtn.addEventListener('click', (evt) => {
782 evt.preventDefault();
783 if (recoverBtn.classList.contains("success") || recoverBtn.classList.contains("loading")) {
784 return;
785 }
786 recoverBtn.classList.add("loading");
787 const recoverUrl = '<?php echo add_query_arg('recover', 1, admin_url('admin.php?page=' . self::$slug . '-filemanager'));?>';
788 fetch(recoverUrl)
789 .then(response => response.json())
790 .then(data => {
791 if (data.success) {
792
793 cardNodes.forEach(function (elem, index) {
794 setTimeout(() => {
795 animateCard(elem);
796
797 if (cardNodes.length === index + 1) {
798
799 onSuccess();
800 }
801
802 }, 500 * (index * 0.5) + index * 50);
803 });
804
805 } else {
806
807 onFailed();
808 }
809 })
810 .catch(() => onFailed());
811 });
812
813 refreshBtn.addEventListener('click', (evt) => {
814 evt.preventDefault();
815
816 if (refreshBtn.classList.contains("loading")) {
817 return;
818 }
819 refreshBtn.classList.add("loading");
820
821 location.reload();
822 });
823
824 function animateCard(elem) {
825 let updown = [800, -800];
826 let randomY = updown[Math.floor(Math.random() * updown.length)];
827 let randomX = Math.floor(Math.random() * 1000) - 1000;
828 elem.style.transform = `translate(${randomX}px, ${randomY}px) rotate(-540deg)`;
829 elem.style.transition = "transform 1s ease, opacity 2s";
830 elem.style.opacity = "0";
831 counter--;
832 if (counter === 0) {
833 stackContainer.style.width = "0";
834 stackContainer.style.height = "0";
835 }
836 }
837
838 //function to generate random number
839 function randomIntFromInterval(min, max) {
840 return Math.floor(Math.random() * (max - min + 1) + min);
841 }
842
843 //after tilt animation, fire the explode animation
844 card.addEventListener('animationend', function () {
845 perspecNodes.forEach(function (elem, index) {
846 elem.classList.add('explode');
847 });
848 });
849
850 //after explode animation do a bunch of stuff
851 perspec.addEventListener('animationend', function (e) {
852 if (e.animationName === 'explode') {
853 cardNodes.forEach(function (elem, index) {
854
855 //add hover animation class
856 elem.classList.add('pokeup');
857
858
859 //generate random number of lines of code between 4 and 10 and add to each card
860 let numLines = randomIntFromInterval(5, 10);
861
862 //loop through the lines and add them to the DOM
863 for (let index = 0; index < numLines; index++) {
864 let lineLength = randomIntFromInterval(25, 97);
865 var node = document.createElement("li");
866 node.classList.add('node-' + index);
867 elem.querySelector('.code ul').appendChild(node).setAttribute('style', '--linelength: ' + lineLength + '%;');
868
869 //draw lines of code 1 by 1
870 if (index == 0) {
871 elem.querySelector('.code ul .node-' + index).classList.add('writeLine');
872 } else {
873 elem.querySelector('.code ul .node-' + (index - 1)).addEventListener('animationend', function (e) {
874 elem.querySelector('.code ul .node-' + index).classList.add('writeLine');
875 });
876 }
877 }
878 });
879 }
880 });
881 </script>
882 <?php
883 }
884 }
885
886 WPIDE_Error_Handler::boot();
887 }
888