From 79b36a320aa31b3aa51754eaf6bbd677ad68053e Mon Sep 17 00:00:00 2001
From: Namith Jawahar <48271037+namithj@users.noreply.github.com>
Date: Wed, 13 Nov 2024 10:21:39 +0530
Subject: [PATCH] Clear Logs and View Logs Functionality Added (#168)
---
assets/css/aspire-update.css | 214 ++++++++++++++++---------
assets/js/aspire-update.js | 128 ++++++++++++++-
includes/class-admin-settings.php | 3 +-
includes/class-controller.php | 63 ++++++++
includes/class-debug.php | 120 +++++++++++---
includes/views/page-admin-settings.php | 9 ++
6 files changed, 437 insertions(+), 100 deletions(-)
diff --git a/assets/css/aspire-update.css b/assets/css/aspire-update.css
index 06856c1..7eea8b2 100644
--- a/assets/css/aspire-update.css
+++ b/assets/css/aspire-update.css
@@ -1,42 +1,42 @@
@keyframes glow {
- 0% {
- background-color: rgba(255, 223, 0, 0.1);
- }
+ 0% {
+ background-color: rgba(255, 223, 0, 0.1);
+ }
- 100% {
- background-color: none;
- }
+ 100% {
+ background-color: none;
+ }
}
.glow-reveal {
- animation: glow 0.5s ease-in-out;
+ animation: glow 0.5s ease-in-out;
}
.aspireupdate-settings-field-hosts-wrapper .aspireupdate-settings-field-hosts-row {
- margin: 0 0 10px;
+ margin: 0 0 10px;
}
#aspireupdate-generate-api-key {
- display: inline-block;
- width: 30px;
- height: 30px;
- background: url(../images/icon-key.svg) no-repeat center center / 24px 24px;
- background-color: #cbcbcb;
- border: 1px solid #8c8f94;
- border-radius: 3px;
- clip-path: inset(0 0 0 0);
- color: transparent;
- cursor: pointer;
- transition: background-color 0.3s ease;
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ background: url(../images/icon-key.svg) no-repeat center center / 24px 24px;
+ background-color: #cbcbcb;
+ border: 1px solid #8c8f94;
+ border-radius: 3px;
+ clip-path: inset(0 0 0 0);
+ color: transparent;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
}
#aspireupdate-generate-api-key:hover {
- background-color: #e3e3e3;
+ background-color: #e3e3e3;
}
.aspireupdate-settings-field-wrapper p.error {
- color: #bc3b3b;
- display: none;
+ color: #bc3b3b;
+ display: none;
}
.aspireupdate-notice {
@@ -47,88 +47,138 @@
}
.aspireupdate-notice p::before {
- content: '';
+ content: "";
display: inline-block;
- margin-inline-end: .5em;
+ margin-inline-end: 0.5em;
vertical-align: middle;
- background: url(../images/aspirepress-logo-icon.svg) no-repeat center center / 20px 20px;
- height: 20px;
- width: 20px;
+ background: url(../images/aspirepress-logo-icon.svg) no-repeat center center / 20px 20px;
+ height: 20px;
+ width: 20px;
}
-#voltron {
- color: transparent;
- font-size: clamp(4px, 0.9vw, 8px);
- line-height: 6px;
- display: inline-block;
- cursor: default;
+.button.button-clearlog {
+ border-color: #b32d2e;
+ color: #b32d2e;
}
-#voltron:hover {
- animation: blink 1.8s ease-in-out infinite;
- animation-delay: 5s;
+#aspireupdate-log-viewer {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 999998;
+ display: none;
}
-@keyframes blink {
- 0%,
- 100% {
- color: inherit;
- }
- 50% {
- color: transparent;
- }
+#aspireupdate-log-viewer:before {
+ content: "";
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 999998;
+ background: rgba(255, 255, 255, 0.9);
+}
+
+#aspireupdate-log-viewer .outer {
+ position: fixed;
+ width: calc(100% - 60px);
+ height: calc(100% - 60px);
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: lightgray;
+ z-index: 999999;
+ box-shadow: 0 0 5px #000;
+ padding: 15px;
+}
+
+#aspireupdate-log-viewer .outer span.close {
+ position: fixed;
+ right: -1px;
+ z-index: 999999;
+ display: block;
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ font-size: 20px;
+ top: 1px;
+ text-align: center;
+ font-weight: 600;
+ cursor: pointer;
}
-@media only screen and (max-width: 576px) {
- #voltron {
- display: none;
- }
-=======
+#aspireupdate-log-viewer .outer span.close:before,
+#aspireupdate-log-viewer .outer span.close:after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 4px;
+ height: 20px;
+ background-color: #b32d2e;
+ transform: rotate(45deg) translate(-50%, -50%);
+ transform-origin: top left;
+ content: '';
+}
-@keyframes glow {
- 0% {
- background-color: rgba(255, 223, 0, 0.1);
- }
+#aspireupdate-log-viewer .outer span.close:after {
+ transform: rotate(-45deg) translate(-50%, -50%);
+}
- 100% {
- background-color: none;
- }
+#aspireupdate-log-viewer .inner {
+ position: fixed;
+ width: calc(100% - 40px);
+ height: calc(100% - 40px);
+ top: 20px;
+ right: 20px;
+ bottom: 20px;
+ left: 20px;
+ counter-reset: line;
+ overflow: auto;
}
-.glow-reveal {
- animation: glow 0.5s ease-in-out;
+#aspireupdate-log-viewer .inner div {
+ display: flex;
+ align-items: flex-start;
}
-.aspireupdate-settings-field-hosts-wrapper .aspireupdate-settings-field-hosts-row {
- margin: 0 0 10px;
+#aspireupdate-log-viewer .inner>div:nth-child(odd) {
+ background-color: #e9e9e9;
}
-#aspireupdate-generate-api-key {
- display: inline-block;
- width: 30px;
- height: 30px;
- background: url(../images/icon-key.svg) no-repeat center center / 24px 24px;
- background-color: #cbcbcb;
- border: 1px solid #8c8f94;
- border-radius: 3px;
- clip-path: inset(0 0 0 0);
- color: transparent;
- cursor: pointer;
- transition: background-color 0.3s ease;
+#aspireupdate-log-viewer .inner>div:nth-child(even) {
+ background-color: #e0e0e0;
}
-#aspireupdate-generate-api-key:hover {
- background-color: #e3e3e3;
+#aspireupdate-log-viewer .inner .number {
+ counter-increment: line;
+ width: 60px;
+ text-align: right;
+ padding-right: 10px;
+ font-weight: bold;
+ color: #555;
}
-.aspireupdate-settings-field-wrapper p.error {
- color: #bc3b3b;
- display: none;
+#aspireupdate-log-viewer .inner .number::before {
+ content: counter(line);
+}
+
+#aspireupdate-log-viewer .inner .content {
+ white-space: pre-wrap;
+ word-break: break-all;
+ margin: 0;
+ flex: 1;
}
#voltron {
color: transparent;
- font-size: 8px;
+ font-size: clamp(4px, 0.9vw, 8px);
line-height: 6px;
display: inline-block;
cursor: default;
@@ -140,11 +190,19 @@
}
@keyframes blink {
+
0%,
100% {
color: inherit;
}
+
50% {
color: transparent;
}
}
+
+@media only screen and (max-width: 576px) {
+ #voltron {
+ display: none;
+ }
+}
diff --git a/assets/js/aspire-update.js b/assets/js/aspire-update.js
index dd6e76e..eeaa95c 100644
--- a/assets/js/aspire-update.js
+++ b/assets/js/aspire-update.js
@@ -2,8 +2,128 @@
jQuery(document).ready(function () {
new ApiRewrites();
new ApiDebug();
+
+ new ClearLog();
+ new ViewLog();
});
+class ClearLog {
+ constructor() {
+ ClearLog.clearlog_button.init();
+ }
+
+ static clearlog_button = {
+ field: jQuery('#aspireupdate-button-clearlog'),
+ init() {
+ ClearLog.clearlog_button.field.click(function () {
+ ClearLog.clearlog_button.clear();
+ });
+ },
+ show() {
+ ClearLog.clearlog_button.field.show();
+ },
+ hide() {
+ ClearLog.clearlog_button.field.hide();
+ },
+ clear() {
+ let parameters = {
+ "url": aspireupdate.ajax_url,
+ "type": "POST",
+ "data": {
+ "nonce": aspireupdate.nonce,
+ "action": "aspireupdate_clear_log"
+ }
+ };
+ jQuery.ajax(parameters)
+ .done(function (response) {
+ if ('' != response.data.message) {
+ alert(response.data.message);
+ } else {
+ alert(aspireupdate.unexpected_error);
+ }
+ })
+ .fail(function (response) {
+ alert(aspireupdate.unexpected_error);
+ });
+ },
+ }
+}
+
+class ViewLog {
+ constructor() {
+ ViewLog.viewlog_button.init();
+ ViewLog.viewlog_popup.init();
+ }
+
+ static viewlog_button = {
+ field: jQuery('#aspireupdate-button-viewlog'),
+ init() {
+ ViewLog.viewlog_button.field.click(function () {
+ ViewLog.viewlog_popup.show();
+ });
+ },
+ show() {
+ ViewLog.viewlog_button.field.show();
+ },
+ hide() {
+ ViewLog.viewlog_button.field.hide();
+ }
+ }
+
+ static viewlog_popup = {
+ field: jQuery('#aspireupdate-log-viewer'),
+ popup_inner: jQuery('#aspireupdate-log-viewer .inner'),
+ close_button: jQuery('#aspireupdate-log-viewer span.close'),
+ init() {
+ ViewLog.viewlog_popup.close_button.click(function () {
+ ViewLog.viewlog_popup.close();
+ });
+
+ jQuery(document).keydown(function (event) {
+ if ((event.keyCode === 27) && ViewLog.viewlog_popup.field.is(':visible')) {
+ ViewLog.viewlog_popup.close();
+ }
+ });
+ },
+ show() {
+ let parameters = {
+ "url": aspireupdate.ajax_url,
+ "type": "POST",
+ "data": {
+ "nonce": aspireupdate.nonce,
+ "action": "aspireupdate_read_log"
+ }
+ };
+ jQuery.ajax(parameters)
+ .done(function (response) {
+ if ((true == response.success) && ('' != response.data.content)) {
+ let lines = response.data.content.split(aspireupdate.line_ending);
+ jQuery.each(lines, function (index, line) {
+ jQuery('
')
+ .append(
+ jQuery('
').addClass('number'),
+ jQuery('').addClass('content').text(line)
+ )
+ .appendTo(ViewLog.viewlog_popup.popup_inner);
+ });
+ ViewLog.viewlog_popup.field.show();
+ } else if ('' != response.data.message) {
+ alert(response.data.message);
+ } else {
+ alert(aspireupdate.unexpected_error);
+ }
+ })
+ .fail(function (response) {
+ alert(aspireupdate.unexpected_error);
+ });
+ },
+ close() {
+ ViewLog.viewlog_popup.field.hide();
+ ViewLog.viewlog_popup.popup_inner.html('');
+ }
+ }
+}
+
class ApiRewrites {
constructor() {
ApiRewrites.host_selector.init();
@@ -150,7 +270,7 @@ class ApiRewrites {
if ((response.status === 400) || (response.status === 401)) {
ApiRewrites.api_key.show_error(response.responseJSON?.error);
} else {
- ApiRewrites.api_key.show_error(aspireupdate.string_unexpected_error + ' ' + response.status);
+ ApiRewrites.api_key.show_error(aspireupdate.unexpected_error + ' : ' + response.status);
}
});
},
@@ -192,7 +312,7 @@ class ApiDebug {
init() {
ApiDebug.enabled_debug.sub_fields = [
ApiDebug.debug_type,
- ApiDebug.disable_ssl_verification
+ ApiDebug.disable_ssl_verification,
];
ApiDebug.enabled_debug.field.change(function () {
@@ -205,9 +325,13 @@ class ApiDebug {
},
show_options() {
Fields.show(ApiDebug.enabled_debug.sub_fields);
+ ViewLog.viewlog_button.show();
+ ClearLog.clearlog_button.show();
},
hide_options() {
Fields.hide(ApiDebug.enabled_debug.sub_fields);
+ ViewLog.viewlog_button.hide();
+ ClearLog.clearlog_button.hide();
}
}
diff --git a/includes/class-admin-settings.php b/includes/class-admin-settings.php
index e02f792..339aa99 100644
--- a/includes/class-admin-settings.php
+++ b/includes/class-admin-settings.php
@@ -295,7 +295,8 @@ public function admin_enqueue_scripts( $hook ) {
'ajax_url' => network_admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'aspireupdate-ajax' ),
'domain' => Utilities::get_top_level_domain(),
- 'string_unexpected_error' => esc_html__( 'Unexpected Error:', 'AspireUpdate' ),
+ 'line_ending' => PHP_EOL,
+ 'unexpected_error' => esc_html__( 'Unexpected Error', 'AspireUpdate' ),
]
);
}
diff --git a/includes/class-controller.php b/includes/class-controller.php
index d573ecd..43b41c7 100644
--- a/includes/class-controller.php
+++ b/includes/class-controller.php
@@ -21,6 +21,8 @@ public function __construct() {
Branding::get_instance();
$this->api_rewrite();
add_action( 'init', [ $this, 'load_textdomain' ] );
+ add_action( 'wp_ajax_aspireupdate_clear_log', [ $this, 'clear_log' ] );
+ add_action( 'wp_ajax_aspireupdate_read_log', [ $this, 'read_log' ] );
}
/**
@@ -48,8 +50,69 @@ private function api_rewrite() {
}
}
+ /**
+ * Ajax action to clear the Log file.
+ *
+ * @return void
+ */
+ public function clear_log() {
+ if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'aspireupdate-ajax' ) ) {
+ wp_send_json_error(
+ [
+ 'message' => __( 'Error: You are not authorized to access this resource.', 'AspireUpdate' ),
+ ]
+ );
+ }
+
+ $status = Debug::clear();
+ if ( is_wp_error( $status ) ) {
+ wp_send_json_error(
+ [
+ 'message' => $status->get_error_message(),
+ ]
+ );
+ }
+
+ wp_send_json_success(
+ [
+ 'message' => __( 'Log file cleared successfully.', 'AspireUpdate' ),
+ ]
+ );
+ }
+
+ /**
+ * Ajax action to read the Log file.
+ *
+ * @return void
+ */
+ public function read_log() {
+ if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'aspireupdate-ajax' ) ) {
+ wp_send_json_error(
+ [
+ 'message' => __( 'Error: You are not authorized to access this resource.', 'AspireUpdate' ),
+ ]
+ );
+ }
+
+ $content = Debug::read( 1000 );
+ if ( is_wp_error( $content ) ) {
+ wp_send_json_error(
+ [
+ 'message' => $content->get_error_message(),
+ ]
+ );
+ }
+
+ wp_send_json_success(
+ [
+ 'content' => $content,
+ ]
+ );
+ }
+
/**
* Load translations.
+ *
* @return void
*/
public function load_textdomain() {
diff --git a/includes/class-debug.php b/includes/class-debug.php
index 5a64b24..182cc02 100644
--- a/includes/class-debug.php
+++ b/includes/class-debug.php
@@ -19,6 +19,15 @@ class Debug {
*/
private static $log_file = 'debug-aspire-update.log';
+ /**
+ * Get the Log file path.
+ *
+ * @return string The Log file path.
+ */
+ private static function get_file_path() {
+ return WP_CONTENT_DIR . '/' . self::$log_file;
+ }
+
/**
* Initializes the WordPress Filesystem.
*
@@ -36,14 +45,13 @@ private static function init_filesystem() {
}
/**
- * Logs a message to the debug log file.
+ * Checks the filesystem status and logs error to debug log.
*
- * @param mixed $message The message to log.
- * @param string $type The log level ('string', 'request', 'response').
+ * @param WP_Filesystem_Base $wp_filesystem The filesystem object.
+ *
+ * @return boolean true on success and false on failure.
*/
- public static function log( $message, $type = 'string' ) {
- $wp_filesystem = self::init_filesystem();
-
+ private static function verify_filesystem( $wp_filesystem ) {
if ( ! $wp_filesystem ) {
if (
defined( 'WP_DEBUG' ) &&
@@ -58,29 +66,97 @@ public static function log( $message, $type = 'string' ) {
error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' );
// phpcs:enable
}
- return;
+ return false;
}
+ return true;
+ }
- $timestamp = gmdate( 'Y-m-d H:i:s' );
- $formatted_message = sprintf(
- "[%s] [%s]: %s\n" . PHP_EOL,
- $timestamp,
- strtoupper( $type ),
- self::format_message( $message )
- );
+ /**
+ * Get the content of the log file truncated upto N number of lines.
+ *
+ * @param integer $limit Max no of lines to return. Defaults to a 1000 lines.
+ *
+ * @return string|WP_Error The File content truncate upto the number of lines set in the limit parameter.
+ */
+ public static function read( $limit = 1000 ) {
+ $wp_filesystem = self::init_filesystem();
+ $file_path = self::get_file_path();
+ if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) {
+ return new \WP_Error( 'not_readable', __( 'Error: Unable to read the log file.', 'AspireUpdate' ) );
+ }
- $file_path = WP_CONTENT_DIR . '/' . self::$log_file;
+ $file_content = $wp_filesystem->get_contents_array( $file_path );
+ $content = '';
+ $index = 0;
+ foreach ( $file_content as $file_content_lines ) {
+ if ( ( $index < $limit ) ) {
+ $content .= $file_content_lines . PHP_EOL;
+ ++$index;
+ }
+ }
+ if ( '' === trim( $content ) ) {
+ $content = esc_html__( '*****Log file is empty.*****', 'AspireUpdate' );
+ } elseif ( $limit < count( $file_content ) ) {
+ $content .= PHP_EOL . sprintf(
+ /* translators: 1: The number of lines at which the content was truncated. */
+ esc_html__( '*****Log truncated at %s lines.*****', 'AspireUpdate' ),
+ $limit
+ );
+ }
+ return $content;
+ }
- $content = '';
- if ( $wp_filesystem->exists( $file_path ) ) {
- $content = $wp_filesystem->get_contents( $file_path );
+ /**
+ * Clear content of the log file.
+ *
+ * @return boolean|WP_Error true on success and false on failure.
+ */
+ public static function clear() {
+ $wp_filesystem = self::init_filesystem();
+ $file_path = self::get_file_path();
+ if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_writable( $file_path ) ) {
+ return new \WP_Error( 'not_accessible', __( 'Error: Unable to access the log file.', 'AspireUpdate' ) );
}
$wp_filesystem->put_contents(
$file_path,
- $content . $formatted_message,
+ '',
FS_CHMOD_FILE
);
+ return true;
+ }
+
+ /**
+ * Logs a message to the debug log file.
+ *
+ * @param mixed $message The message to log.
+ * @param string $type The log level ('string', 'request', 'response').
+ */
+ public static function log( $message, $type = 'string' ) {
+ $wp_filesystem = self::init_filesystem();
+ if ( self::verify_filesystem( $wp_filesystem ) ) {
+ $timestamp = gmdate( 'Y-m-d H:i:s' );
+ $formatted_message = sprintf(
+ '[%s] [%s]: %s',
+ $timestamp,
+ strtoupper( $type ),
+ self::format_message( $message )
+ ) . PHP_EOL;
+
+ $file_path = self::get_file_path();
+
+ $content = '';
+ if ( $wp_filesystem->exists( $file_path ) ) {
+ if ( $wp_filesystem->is_readable( $file_path ) ) {
+ $content = $wp_filesystem->get_contents( $file_path );
+ }
+ }
+ $wp_filesystem->put_contents(
+ $file_path,
+ $formatted_message . $content,
+ FS_CHMOD_FILE
+ );
+ }
}
/**
@@ -105,6 +181,8 @@ private static function format_message( $message ) {
* Log an info message.
*
* @param mixed $message The message to log.
+ *
+ * @return void
*/
public static function log_string( $message ) {
$admin_settings = Admin_Settings::get_instance();
@@ -119,6 +197,8 @@ public static function log_string( $message ) {
* Log a warning message.
*
* @param mixed $message The message to log.
+ *
+ * @return void
*/
public static function log_request( $message ) {
$admin_settings = Admin_Settings::get_instance();
@@ -133,6 +213,8 @@ public static function log_request( $message ) {
* Log an error message.
*
* @param mixed $message The message to log.
+ *
+ * @return void
*/
public static function log_response( $message ) {
$admin_settings = Admin_Settings::get_instance();
diff --git a/includes/views/page-admin-settings.php b/includes/views/page-admin-settings.php
index 5174d2e..4649029 100644
--- a/includes/views/page-admin-settings.php
+++ b/includes/views/page-admin-settings.php
@@ -15,7 +15,16 @@
+
+
+