diff --git a/_build/test/Tests/Processors/System/ConsoleProgressProtocolTest.php b/_build/test/Tests/Processors/System/ConsoleProgressProtocolTest.php new file mode 100644 index 0000000000..a97453009b --- /dev/null +++ b/_build/test/Tests/Processors/System/ConsoleProgressProtocolTest.php @@ -0,0 +1,48 @@ +assertStringContainsString("__MODX_PROGRESS__:indeterminate", $source); + $this->assertStringContainsString("'__MODX_PROGRESS__:' . \$current . ':' . \$total", $source); + + $console = file_get_contents(MODX_CORE_PATH . 'src/Revolution/Processors/System/Console.php'); + $this->assertStringContainsString("show_progress", $console); + $this->assertStringContainsString('__MODX_PROGRESS__:', $console); + $this->assertStringNotContainsString("strpos(\$message['msg'], 'PROGRESS:')", $console); + + $stub = new class ($this->modx) extends Processor { + public function process() + { + return $this->success(); + } + }; + $stub->logProgress(0, 0); + $stub->logProgress(9, 5); + $this->assertTrue(true); + } +} diff --git a/_build/test/phpunit.xml b/_build/test/phpunit.xml index 6eba756c6c..351dfbb449 100644 --- a/_build/test/phpunit.xml +++ b/_build/test/phpunit.xml @@ -46,6 +46,7 @@ Tests/Processors/Context Tests/Processors/Element Tests/Processors/Resource + Tests/Processors/System Tests/Transport diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php index 76c22f58ab..298b5147d6 100644 --- a/core/lexicon/en/default.inc.php +++ b/core/lexicon/en/default.inc.php @@ -80,6 +80,7 @@ $_lang['confirm_unpublish'] = 'Un-publishing this document now will delete any (un)publishing dates that may have been set. If you wish to set or keep publish or unpublish dates, please choose to edit the document instead.\n\nProceed?'; $_lang['console'] = 'Console'; $_lang['console_download_output'] = 'Download Output to File'; +$_lang['console_progress'] = 'Processing...'; $_lang['console_running'] = 'Console running...'; $_lang['content'] = 'Content'; $_lang['content_elements'] = 'Content Elements'; diff --git a/core/src/Revolution/Processors/Processor.php b/core/src/Revolution/Processors/Processor.php index 50e19f02d5..faa23457ae 100644 --- a/core/src/Revolution/Processors/Processor.php +++ b/core/src/Revolution/Processors/Processor.php @@ -117,6 +117,28 @@ public function getLanguageTopics() return []; } + /** + * Log progress for console progress bar. When registry logging is active (e.g. console), + * the message is read by System/Console and drives the optional progress bar. + * + * @param int $current Current step (1-based index or count). + * @param int $total Total steps. Use 0 for indeterminate progress. + * @return void + */ + public function logProgress(int $current, int $total = 0): void + { + $current = max(0, $current); + if ($total <= 0) { + $this->modx->log(modX::LOG_LEVEL_INFO, '__MODX_PROGRESS__:indeterminate'); + return; + } + $total = max(1, $total); + if ($current > $total) { + $current = $total; + } + $this->modx->log(modX::LOG_LEVEL_INFO, '__MODX_PROGRESS__:' . $current . ':' . $total); + } + /** * Return a success message from the processor. * @param string $msg diff --git a/core/src/Revolution/Processors/System/ClearCache.php b/core/src/Revolution/Processors/System/ClearCache.php index 707d0fed2c..cab138f205 100644 --- a/core/src/Revolution/Processors/System/ClearCache.php +++ b/core/src/Revolution/Processors/System/ClearCache.php @@ -36,6 +36,9 @@ public function process() { $this->runBeforeEvents(); + // Show activity during the actual refresh (results are logged after). + $this->logProgress(0, 0); + $results = []; $partitions = $this->getPartitions(); $this->modx->cacheManager->refresh($partitions, $results); @@ -50,9 +53,13 @@ public function process() $o = ''; sleep(1); + $total = count($results); + $index = 0; $result = reset($results); $partition = key($results); while ($partition && $result) { + $index++; + $this->logProgress($index, $total); switch ($partition) { case 'auto_publish': $this->modx->log(modX::LOG_LEVEL_INFO, $this->modx->lexicon('refresh_auto_publish')); diff --git a/core/src/Revolution/Processors/System/Console.php b/core/src/Revolution/Processors/System/Console.php index 43ec760c6c..6cd75a151d 100644 --- a/core/src/Revolution/Processors/System/Console.php +++ b/core/src/Revolution/Processors/System/Console.php @@ -1,4 +1,5 @@ '', 'complete' => false, ]; + $showProgress = (bool)$this->getProperty('show_progress', false); foreach ($messages as $messageKey => $message) { if ($message['msg'] === 'COMPLETED') { $response['complete'] = true; continue; } - if (!empty ($message['def'])) { + $progressPrefix = '__MODX_PROGRESS__:'; + if ($showProgress && str_starts_with((string)$message['msg'], $progressPrefix)) { + $progressMsg = substr($message['msg'], strlen($progressPrefix)); + if ($progressMsg === 'indeterminate') { + $response['progress'] = ['indeterminate' => true]; + continue; + } + $parts = explode(':', $progressMsg, 2); + if ( + count($parts) === 2 + && ctype_digit($parts[0]) + && ctype_digit($parts[1]) + && (int)$parts[1] > 0 + ) { + $current = (int)$parts[0]; + $total = (int)$parts[1]; + if ($current > $total) { + $current = $total; + } + $response['progress'] = [ + 'current' => $current, + 'total' => $total, + ]; + } + continue; + } + if (!empty($message['def'])) { $message['def'] .= ' '; } - if (!empty ($message['file'])) { + if (!empty($message['file'])) { $message['file'] = '@ ' . $message['file'] . ' '; } - if (!empty ($message['line'])) { + if (!empty($message['line'])) { $message['line'] = 'line ' . $message['line'] . ' '; } $response['data'] .= ''; diff --git a/manager/assets/modext/core/modx.js b/manager/assets/modext/core/modx.js index bbdf81902b..9ed2f79a67 100644 --- a/manager/assets/modext/core/modx.js +++ b/manager/assets/modext/core/modx.js @@ -236,6 +236,7 @@ Ext.extend(MODx,Ext.Component,{ ,topic: topic ,clear: true ,show_filename: 0 + ,showProgress: true ,listeners: { 'shutdown': {fn:function() { if (this.fireEvent('afterClearCache')) { diff --git a/manager/assets/modext/widgets/core/modx.console.js b/manager/assets/modext/widgets/core/modx.console.js index 8e34d4ec59..6e07fb4837 100644 --- a/manager/assets/modext/widgets/core/modx.console.js +++ b/manager/assets/modext/widgets/core/modx.console.js @@ -13,12 +13,18 @@ MODx.Console = function(config) { ,height: 400 ,width: 600 ,refreshRate: 2 + ,showProgress: false ,cls: 'modx-window modx-console' ,items: [{ itemId: 'header' ,cls: 'modx-console-text' ,html: _('console_running') ,border: false + },{ + xtype: 'progress' + ,itemId: 'progressBar' + ,hidden: true + ,style: 'margin: 8px 0;' },{ xtype: 'panel' ,itemId: 'body' @@ -85,6 +91,7 @@ Ext.extend(MODx.Console,Ext.Window,{ ,clear: false ,show_filename: this.config.show_filename || 0 ,format: this.config.format || 'html_log' + ,show_progress: this.config.showProgress ? 1 : 0 } }); Ext.Direct.addProvider(this.provider); @@ -92,6 +99,9 @@ Ext.extend(MODx.Console,Ext.Window,{ } ,onMessage: function(e,p) { + if (this.config.showProgress && e.progress) { + this.updateProgressBar(e.progress); + } var out = this.getComponent('body'); if (out) { out.el.insertHtml('beforeEnd',e.data); @@ -104,12 +114,33 @@ Ext.extend(MODx.Console,Ext.Window,{ delete e; } + ,updateProgressBar: function(progress) { + const bar = this.getComponent('progressBar'); + if (!bar) { return; } + bar.show(); + if (progress.indeterminate) { + bar.wait({ interval: 200, text: _('console_progress') }); + } else if (progress.current != null && progress.total != null && progress.total > 0) { + bar.reset(); + let pct = progress.current / progress.total; + if (pct < 0) { pct = 0; } + if (pct > 1) { pct = 1; } + const text = progress.current + ' / ' + progress.total; + bar.updateProgress(pct, text); + } + } + ,onComplete: function() { if (this.provider && this.provider.disconnect) { try { this.provider.disconnect(); } catch (e) {} } + const bar = this.getComponent('progressBar'); + if (bar) { + bar.reset(); + bar.hide(); + } this.fbar.setDisabled(false); this.keyMap.setDisabled(false); }