From 04f4f85048dc6072f6937ed385fcfb5caaa069e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D1=96=D0=B9=20=D0=A7=D0=BE=D1=80?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9?= Date: Wed, 8 Jul 2026 17:07:13 +0200 Subject: [PATCH 1/2] 15453-CLI-Improvements --- Block/Adminhtml/Form.php | 69 +++++++++ Block/Adminhtml/History.php | 31 ++++ Block/Adminhtml/System/Config/Form/Info.php | 25 ++- Controller/Adminhtml/History/Index.php | 28 ++++ Controller/Adminhtml/Index/Cli.php | 26 ++++ Controller/Adminhtml/MfcliHistory/Index.php | 32 ++++ Controller/Adminhtml/MfcliIndex/Cli.php | 146 ++++++++++++++++++ Controller/Adminhtml/MfcliIndex/Index.php | 38 +++++ Cron/Cleanup.php | 50 ++++++ etc/acl.xml | 0 etc/adminhtml/menu.xml | 3 +- etc/adminhtml/routes.xml | 18 +-- etc/adminhtml/system.xml | 9 +- etc/config.xml | 3 +- etc/crontab.xml | 8 + etc/db_schema.xml | 20 +++ etc/module.xml | 8 +- .../layout/admin_mfcli_history_index.xml | 8 + .../layout/admin_mfcli_index_index.xml | 8 + view/adminhtml/layout/mfcli_history_index.xml | 8 + view/adminhtml/templates/form.phtml | 128 ++++++++------- view/adminhtml/templates/history.phtml | 29 ++++ 22 files changed, 602 insertions(+), 93 deletions(-) create mode 100644 Block/Adminhtml/History.php create mode 100644 Controller/Adminhtml/History/Index.php create mode 100644 Controller/Adminhtml/MfcliHistory/Index.php create mode 100644 Controller/Adminhtml/MfcliIndex/Cli.php create mode 100644 Controller/Adminhtml/MfcliIndex/Index.php create mode 100644 Cron/Cleanup.php mode change 100755 => 100644 etc/acl.xml create mode 100644 etc/crontab.xml create mode 100644 etc/db_schema.xml create mode 100644 view/adminhtml/layout/admin_mfcli_history_index.xml create mode 100644 view/adminhtml/layout/admin_mfcli_index_index.xml create mode 100644 view/adminhtml/layout/mfcli_history_index.xml create mode 100644 view/adminhtml/templates/history.phtml diff --git a/Block/Adminhtml/Form.php b/Block/Adminhtml/Form.php index c56b2a9..63be982 100644 --- a/Block/Adminhtml/Form.php +++ b/Block/Adminhtml/Form.php @@ -8,6 +8,8 @@ use Magento\Framework\View\Element\Template; use Magento\Framework\Console\CommandListInterface; +use Magento\Framework\View\Helper\SecureHtmlRenderer; +use Magento\Framework\Data\Form\FormKey; use Magefan\Cli\Model\Config; use Magento\Framework\AuthorizationInterface; @@ -28,12 +30,30 @@ class Form extends \Magento\Framework\View\Element\Template */ private $authorization; + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var SecureHtmlRenderer + */ + private $secureHtmlRenderer; + + /** + * @var FormKey + */ + private $formKey; + /** * Form constructor. * @param Template\Context $context * @param CommandListInterface $commandList * @param Config $config * @param AuthorizationInterface $authorization + * @param \Magento\Framework\App\ResourceConnection $resource + * @param SecureHtmlRenderer|null $secureHtmlRenderer + * @param FormKey|null $formKey * @param array $data */ public function __construct( @@ -41,14 +61,63 @@ public function __construct( CommandListInterface $commandList, Config $config, AuthorizationInterface $authorization, + \Magento\Framework\App\ResourceConnection $resource, + ?SecureHtmlRenderer $secureHtmlRenderer = null, + ?FormKey $formKey = null, array $data = [] ) { $this->commandList = $commandList; $this->config = $config; $this->authorization = $authorization; + $this->resource = $resource; + $this->secureHtmlRenderer = $secureHtmlRenderer ?? \Magento\Framework\App\ObjectManager::getInstance()->get(SecureHtmlRenderer::class); + $this->formKey = $formKey ?? \Magento\Framework\App\ObjectManager::getInstance()->get(FormKey::class); parent::__construct($context, $data); } + /** + * Get form key for AJAX POST (required by Magento admin). + * @return string + */ + public function getFormKeyValue() + { + return $this->formKey->getFormKey(); + } + + /** + * Render script tag securely (works without Magefan_Community). + * @param string $script + * @return string + */ + public function renderScript($script) + { + return $this->secureHtmlRenderer->renderTag('script', [], $script, false); + } + + /** + * Return recent commands history (most recent first) + * @param int $limit + * @return array + */ + public function getCommandHistory($limit = 100) + { + try { + $connection = $this->resource->getConnection(); + $table = $this->resource->getTableName('magefan_cli_log'); + $select = $connection->select()->from($table, ['command'])->order('executed_at DESC')->limit((int)$limit); + $rows = $connection->fetchAll($select); + $commands = []; + foreach ($rows as $r) { + if (!empty($r['command'])) { + $commands[] = $r['command']; + } + } + return $commands; + } catch (\Exception $e) { + return []; + } + } + /** * Preparing global layout * diff --git a/Block/Adminhtml/History.php b/Block/Adminhtml/History.php new file mode 100644 index 0000000..7bbc004 --- /dev/null +++ b/Block/Adminhtml/History.php @@ -0,0 +1,31 @@ +resource = $resource; + parent::__construct($context, $data); + } + + /** + * Get recent logs + * @param int $limit + * @return array + */ + public function getLogs($limit = 100) + { + $connection = $this->resource->getConnection(); + $table = $this->resource->getTableName('magefan_cli_log'); + $select = $connection->select()->from($table)->order('executed_at DESC')->limit((int)$limit); + return $connection->fetchAll($select); + } +} diff --git a/Block/Adminhtml/System/Config/Form/Info.php b/Block/Adminhtml/System/Config/Form/Info.php index ac4b091..2615913 100644 --- a/Block/Adminhtml/System/Config/Form/Info.php +++ b/Block/Adminhtml/System/Config/Form/Info.php @@ -8,24 +8,21 @@ namespace Magefan\Cli\Block\Adminhtml\System\Config\Form; -class Info extends \Magefan\Community\Block\Adminhtml\System\Config\Form\Info -{ - /** - * Return extension url - * @return string - */ - protected function getModuleUrl():string - { - return 'https://mage' . - 'fan.com/magento2-extensions?utm_source=m2admin_cli_config&utm_medium=link&utm_campaign=regular'; - } +use Magento\Config\Block\System\Config\Form\Field; +use Magento\Framework\Data\Form\Element\AbstractElement; +class Info extends Field +{ /** - * Return extension title + * @param AbstractElement $element * @return string */ - protected function getModuleTitle():string + protected function _getElementHtml(AbstractElement $element) { - return 'Command Line Interface'; + return '
' . + 'Magefan Command Line Interface
' . + 'Version: 1.0.0
' . + 'Visit Magefan.com →' . + '
'; } } diff --git a/Controller/Adminhtml/History/Index.php b/Controller/Adminhtml/History/Index.php new file mode 100644 index 0000000..fa976d1 --- /dev/null +++ b/Controller/Adminhtml/History/Index.php @@ -0,0 +1,28 @@ +resultPageFactory = $resultPageFactory; + parent::__construct($context); + } + + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('Magefan_Cli::history'); + $resultPage->getConfig()->getTitle()->prepend(__('Command Line History')); + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Index/Cli.php b/Controller/Adminhtml/Index/Cli.php index da02089..82b75da 100644 --- a/Controller/Adminhtml/Index/Cli.php +++ b/Controller/Adminhtml/Index/Cli.php @@ -8,6 +8,7 @@ use Magefan\Cli\Model\Config; use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\App\ResourceConnection; class Cli extends \Magento\Backend\App\Action { @@ -45,6 +46,11 @@ class Cli extends \Magento\Backend\App\Action */ private $formKey; + /** + * @var ResourceConnection + */ + private $resource; + /** * Constructor * @@ -70,6 +76,7 @@ public function __construct( $this->authSession = $authSession; $this->config = $config; $this->formKey = $formKey; + $this->resource = $context->getObjectManager()->get(ResourceConnection::class); parent::__construct($context); } @@ -132,6 +139,25 @@ public function execute() if (!$message) { $message = __('Command not found or error occurred.') . PHP_EOL; } + // persist log to DB + try { + $connection = $this->resource->getConnection(); + $tableName = $this->resource->getTableName('magefan_cli_log'); + $userId = null; + try { + $user = $this->authSession->getUser(); + $userId = $user ? (int)$user->getId() : null; + } catch (\Exception $e) { + $userId = null; + } + + $connection->insertMultiple($tableName, [ + ['command' => $command, 'result' => $message, 'user_id' => $userId, 'executed_at' => (new \DateTime())->format('Y-m-d H:i:s')] + ]); + } catch (\Exception $e) { + // swallow DB errors to not break execution + } + unlink($logFile); } catch (\Exception $e) { $message = $e->getMessage() . PHP_EOL; diff --git a/Controller/Adminhtml/MfcliHistory/Index.php b/Controller/Adminhtml/MfcliHistory/Index.php new file mode 100644 index 0000000..63b3886 --- /dev/null +++ b/Controller/Adminhtml/MfcliHistory/Index.php @@ -0,0 +1,32 @@ +resultPageFactory = $resultPageFactory; + parent::__construct($context); + } + + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('Magefan_Cli::history'); + $resultPage->getConfig()->getTitle()->prepend(__('Command Line History')); + return $resultPage; + } +} diff --git a/Controller/Adminhtml/MfcliIndex/Cli.php b/Controller/Adminhtml/MfcliIndex/Cli.php new file mode 100644 index 0000000..a1e3c4b --- /dev/null +++ b/Controller/Adminhtml/MfcliIndex/Cli.php @@ -0,0 +1,146 @@ +resultPageFactory = $resultPageFactory; + $this->jsonHelper = $jsonHelper; + $this->dir = $dir; + $this->authSession = $authSession; + $this->config = $config; + $this->formKey = $formKey; + $this->resource = $context->getObjectManager()->get(ResourceConnection::class); + parent::__construct($context); + } + + public function execute() + { + try { + if (!$this->config->isEnabled()) { + throw new \Exception( + __(strrev('.ecafretnI eniL dnammoC > snoisnetxE nafegaM > noitarugifnoC > serotS ot etagivan esaelp noisnetxe eht elbane ot ,delbasid si ecafretnI eniL dnammoC nafegaM')), + 1 + ); + } + + $this->validateUser(); + + $command = $this->getRequest()->getParam('command'); + $phpCommand = $this->config->getPhpCommand(); + + if (!$this->_authorization->isAllowed('Magefan_Cli::admin')) { + $needle = 'bin/magento '; + $position = stripos($command, $needle); + $needleLen = strlen($needle); + $commandStart = $position + $needleLen; + $magentoCommand = substr($command, $commandStart); + + if ($position === false || !in_array($magentoCommand, $this->config->getNonAdminCommands())) { + throw new \Exception(__('You don\'t have permission to execute this command.'), 1); + } + } + + if ($phpCommand) { + if (stripos($command, 'php ') === 0) { + $command = str_replace('php ', $phpCommand . ' ', $command); + } elseif (stripos($command, 'bin/magento') === 0) { + $command = $phpCommand . ' ' . $command; + } + } + + $blackCommands = ['admin:user']; + foreach ($blackCommands as $bc) { + if (strpos($command, $bc) !== false) { + throw new \Exception(__('Error: Cannot run this command due to security reasons.'), 1); + } + } + + if (strpos($command, 'cd') === 0) { + throw new \Exception(__('cd command is not supported.'), 1); + } + + $logFile = $this->dir->getPath('var') . '/mfcli.txt'; + exec($c = 'cd ' . $this->dir->getRoot() . ' && ' . $command . ' > ' . $logFile . ' 2>&1', $a, $b); + + $message = file_get_contents($logFile); + if (!$message) { + $message = __('Command not found or error occurred.') . PHP_EOL; + } + try { + $connection = $this->resource->getConnection(); + $tableName = $this->resource->getTableName('magefan_cli_log'); + $userId = null; + try { + $user = $this->authSession->getUser(); + $userId = $user ? (int)$user->getId() : null; + } catch (\Exception $e) { + $userId = null; + } + + $connection->insertMultiple($tableName, [ + ['command' => $command, 'result' => $message, 'user_id' => $userId, 'executed_at' => (new \DateTime())->format('Y-m-d H:i:s')] + ]); + } catch (\Exception $e) { + // ignore + } + + unlink($logFile); + } catch (\Exception $e) { + $message = $e->getMessage() . PHP_EOL; + } + + $response = [ + 'message' => nl2br($message), + 'newFormKey' => $this->formKey->getFormKey() + ]; + + return $this->getResponse()->representJson( + $this->jsonHelper->jsonEncode($response) + ); + } + + protected function validateUser() + { + $password = $this->getRequest()->getParam( + \Magento\User\Block\Role\Tab\Info::IDENTITY_VERIFICATION_PASSWORD_FIELD + ); + if (!$password) { + throw new \Exception(__('Please enter your password.')); + } + $user = $this->authSession->getUser(); + $user->performIdentityCheck($password); + + return $this; + } +} diff --git a/Controller/Adminhtml/MfcliIndex/Index.php b/Controller/Adminhtml/MfcliIndex/Index.php new file mode 100644 index 0000000..4c12220 --- /dev/null +++ b/Controller/Adminhtml/MfcliIndex/Index.php @@ -0,0 +1,38 @@ +resultPageFactory = $resultPageFactory; + $this->config = $config; + parent::__construct($context); + } + + public function execute() + { + $phpCommand = $this->config->getPhpCommand(); + if ($phpCommand && $this->config->isEnabled()) { + $this->messageManager->addNoticeMessage(__('Commands will be executed by custom PHP binary: ') . $phpCommand); + } + return $this->resultPageFactory->create(); + } +} diff --git a/Cron/Cleanup.php b/Cron/Cleanup.php new file mode 100644 index 0000000..5295ba1 --- /dev/null +++ b/Cron/Cleanup.php @@ -0,0 +1,50 @@ +resource = $resource; + $this->dateTime = $dateTime; + $this->scopeConfig = $scopeConfig; + $this->logger = $logger; + } + + public function execute() + { + try { + $days = (int)$this->scopeConfig->getValue(self::XML_PATH_LOG_LIFETIME) ?: 30; + $connection = $this->resource->getConnection(); + $table = $this->resource->getTableName('magefan_cli_log'); + $threshold = date('Y-m-d H:i:s', strtotime('-' . $days . ' days', $this->dateTime->gmtTimestamp())); + $where = ['executed_at < ?' => $threshold]; + $connection->delete($table, $where); + } catch (\Exception $e) { + $this->logger->error('Magefan_Cli cron cleanup error: ' . $e->getMessage()); + } + } +} diff --git a/etc/acl.xml b/etc/acl.xml old mode 100755 new mode 100644 diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml index 4a8e5d6..0dd7034 100644 --- a/etc/adminhtml/menu.xml +++ b/etc/adminhtml/menu.xml @@ -7,6 +7,7 @@ --> - + + diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml index 7e72709..216ef48 100644 --- a/etc/adminhtml/routes.xml +++ b/etc/adminhtml/routes.xml @@ -1,14 +1,8 @@ - - + - - - - - + + + + + diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml index 8281d4b..bef433d 100644 --- a/etc/adminhtml/system.xml +++ b/etc/adminhtml/system.xml @@ -7,6 +7,9 @@ --> + + +
separator-top @@ -23,7 +26,7 @@ - Magefan\Community\Block\Adminhtml\System\Config\Form\ProductKeyField + Optional. Leave empty for free usage. @@ -38,6 +41,10 @@ Magefan\Cli\Model\Config\Source\Commands Admin users with no "Command Line Admin" access will be able to run only these commands. + + + Number of days to keep command execution logs. Default is 30. +
diff --git a/etc/config.xml b/etc/config.xml index 257a1bb..c2783d3 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -12,9 +12,10 @@ 0 Cli 1 - + php8.2 cache:flush,indexer:reindex cache:flush,indexer:reindex + 30 diff --git a/etc/crontab.xml b/etc/crontab.xml new file mode 100644 index 0000000..ab939de --- /dev/null +++ b/etc/crontab.xml @@ -0,0 +1,8 @@ + + + + + 0 3 * * * + + + diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..17dfd77 --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + +
+
diff --git a/etc/module.xml b/etc/module.xml index c60d291..d7734a3 100644 --- a/etc/module.xml +++ b/etc/module.xml @@ -7,8 +7,8 @@ --> - - - - + + + + diff --git a/view/adminhtml/layout/admin_mfcli_history_index.xml b/view/adminhtml/layout/admin_mfcli_history_index.xml new file mode 100644 index 0000000..c7aed1d --- /dev/null +++ b/view/adminhtml/layout/admin_mfcli_history_index.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/view/adminhtml/layout/admin_mfcli_index_index.xml b/view/adminhtml/layout/admin_mfcli_index_index.xml new file mode 100644 index 0000000..a0bea30 --- /dev/null +++ b/view/adminhtml/layout/admin_mfcli_index_index.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/view/adminhtml/layout/mfcli_history_index.xml b/view/adminhtml/layout/mfcli_history_index.xml new file mode 100644 index 0000000..c7aed1d --- /dev/null +++ b/view/adminhtml/layout/mfcli_history_index.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/view/adminhtml/templates/form.phtml b/view/adminhtml/templates/form.phtml index ac970f3..3e36c40 100644 --- a/view/adminhtml/templates/form.phtml +++ b/view/adminhtml/templates/form.phtml @@ -10,7 +10,6 @@ * CLI template * * @var $block \Magefan\Cli\Block\Adminhtml\Form - * @var $mfSecureRenderer \Magefan\Community\Api\SecureHtmlRendererInterface * * @codingStandardsIgnoreStart */ @@ -112,75 +111,71 @@ $script = ''; - - - getCommandHistory(200)); + $cliUrlJs = json_encode($block->getUrl('*/*/cli')); + $errorMsgJs = json_encode((string)$block->escapeHtml(__('Unexpected error. Please refresh the page or try later.'))); + $formKeyJs = json_encode($block->getFormKeyValue()); + $script .= " + var mfcliFormKey = " . $formKeyJs . "; + var mfcliUrl = " . $cliUrlJs . "; + var mfcliErrorMsg = " . $errorMsgJs . "; + require(['jquery', 'domReady!', 'Magento_Ui/js/modal/confirm'], function($, _, confirmation) { + var commandLog = " . $commandLogJson . "; + var commandLogIndex = 0; + function showCommand() { var i = commandLog.length - 1 - commandLogIndex; - // console.log(i); if (typeof commandLog[i] !== 'undefined') { - $('#command').val(commandLog[i]) + $('#command').val(commandLog[i]); } else { $('#command').val(''); } } - - function addCommand() - { + function addCommand() { var el = $('#command'); - v = $.trim(el.val()); + var v = $.trim(el.val()); if (v) { commandLog.push(v); } - var commandLogIndex = 0; + commandLogIndex = 0; el.val(''); } - - function addContent(c) - { + function addContent(c) { addCommand(); $('.history-hld').append(c); $('#shell-area').scrollTop($('#shell-area')[0].scrollHeight); } - - var loading = false; - $('#command').keydown(function(event) { + $(function(){ + $('#shell-area').on('click', function(){ $('#command').focus(); }); + var loading = false; + $('#command').on('keydown', function(event) { var el = $(this); - if(event.which == 40) { //up + if (event.which === 40) { + event.preventDefault(); commandLogIndex--; - if (commandLogIndex < 0) { - commandLogIndex = 0; - } + if (commandLogIndex < 0) commandLogIndex = 0; showCommand(); - } else if(event.which == 38) { //down + return; + } + if (event.which === 38) { + event.preventDefault(); showCommand(); commandLogIndex++; - if (commandLogIndex >= commandLog.length) { - commandLogIndex = commandLog.length-1; - } - } else if(event.which == 13) { //enter + if (commandLogIndex >= commandLog.length) commandLogIndex = commandLog.length - 1; + return; + } + if (event.which === 13) { + event.preventDefault(); if (loading) return; loading = true; - if (!$.trim(el.val())) { addContent('
'); loading = false; return; } - $.ajax({ - url: '" . $block->escapeUrl($block->getUrl('*/*/cli')) . "', - data : {command:el.val(),current_password:$('#user_current_password').val()}, + url: mfcliUrl, + data: {command:el.val(),current_password:$('#user_current_password').val(),form_key:mfcliFormKey}, type: 'POST', dataType: 'json', showLoader: true @@ -190,42 +185,53 @@ $script = ''; addContent('$ ' + el.val() + '
'); addContent(data.message); } else { - alert('" . $block->escapeHtml(__('Unexpected error. Please refresh the page or try later.')) . "'); + alert(mfcliErrorMsg); } + if (data && data.newFormKey) { + mfcliFormKey = data.newFormKey; + var formKeyInput = document.querySelector('input[name=\"form_key\"]'); + if (formKeyInput) formKeyInput.value = data.newFormKey; + } + }).fail(function() { + loading = false; + alert(mfcliErrorMsg); }); - } - }); + } + }); - $('.execute').click(function() { - var command = 'bin/magento ' + $(this).attr('data-name'); - var loading = false; - confirmation({ + $('.execute').on('click', function() { + var command = 'bin/magento ' + $(this).attr('data-name'); + var loadingExecute = false; + confirmation({ title: $.mage.__('Execute Command:') + ' ' + command, content: $.mage.__('Are you sure you want to do this?'), actions: { confirm: function(){ - if (loading) return; - loading = true; + if (loadingExecute) return; + loadingExecute = true; $.ajax({ - url: '" . $block->escapeUrl($block->getUrl('*/*/cli')) . "', - data : {command:'php ' + command,current_password:$('#user_current_password').val()}, + url: mfcliUrl, + data: {command:'php ' + command,current_password:$('#user_current_password').val(),form_key:mfcliFormKey}, type: 'POST', dataType: 'json', showLoader: true }).done(function (data) { - loading = false; + loadingExecute = false; if (data && data.message) { addContent('$ ' + command + '
'); addContent(data.message); } else { - alert('" . $block->escapeHtml(__('Unexpected error. Please refresh the page or try later.')) . "'); + alert(mfcliErrorMsg); } - if (data && data.newFormKey) { - FORM_KEY = data.newFormKey; - document.querySelector('input[name=\"form_key\"]').value = FORM_KEY; + mfcliFormKey = data.newFormKey; + var formKeyInput = document.querySelector('input[name=\"form_key\"]'); + if (formKeyInput) formKeyInput.value = data.newFormKey; } + }).fail(function() { + loadingExecute = false; + alert(mfcliErrorMsg); }); } }, @@ -243,10 +249,12 @@ $script = ''; } }] }); - }) + }); + }); }); - "; ?> + "; + ?> - renderTag('script', [], $script, false) ?> + renderScript($script) ?> diff --git a/view/adminhtml/templates/history.phtml b/view/adminhtml/templates/history.phtml new file mode 100644 index 0000000..0862087 --- /dev/null +++ b/view/adminhtml/templates/history.phtml @@ -0,0 +1,29 @@ +getLogs(200); +?> +
+

escapeHtml(__('Command Line History')) ?>

+ + + + + + + + + + + + + + + + + + + + + +
escapeHtml(__('ID')) ?>escapeHtml(__('Command')) ?>escapeHtml(__('Result')) ?>escapeHtml(__('User ID')) ?>escapeHtml(__('Executed At')) ?>
escapeHtml($row['command']) ?>
escapeHtml($row['result']) ?>
escapeHtml($row['executed_at']) ?>
+
From 417c7636612a9b48f769e4fdc208af113ae8f20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D1=96=D0=B9=20=D0=A7=D0=BE=D1=80?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9?= Date: Thu, 9 Jul 2026 11:15:52 +0200 Subject: [PATCH 2/2] 15453-CLI-Improvements --- Block/Adminhtml/Form.php | 2 + Block/Adminhtml/History.php | 7 ++ Controller/Adminhtml/History/Index.php | 7 ++ Controller/Adminhtml/Index/Cli.php | 2 + Controller/Adminhtml/Index/Index.php | 2 + Controller/Adminhtml/MfcliHistory/Index.php | 4 ++ Controller/Adminhtml/MfcliIndex/Cli.php | 67 +++++++++++++++++-- Controller/Adminhtml/MfcliIndex/Index.php | 3 + Cron/Cleanup.php | 23 +++++-- etc/adminhtml/routes.xml | 6 ++ etc/crontab.xml | 6 ++ etc/db_schema.xml | 7 +- .../layout/admin_mfcli_history_index.xml | 6 ++ .../layout/admin_mfcli_index_index.xml | 6 ++ view/adminhtml/layout/mfcli_history_index.xml | 6 ++ view/adminhtml/templates/history.phtml | 8 ++- 16 files changed, 149 insertions(+), 13 deletions(-) diff --git a/Block/Adminhtml/Form.php b/Block/Adminhtml/Form.php index 63be982..fe7d545 100644 --- a/Block/Adminhtml/Form.php +++ b/Block/Adminhtml/Form.php @@ -4,6 +4,8 @@ * Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement). */ +declare(strict_types=1); + namespace Magefan\Cli\Block\Adminhtml; use Magento\Framework\View\Element\Template; diff --git a/Block/Adminhtml/History.php b/Block/Adminhtml/History.php index 7bbc004..37493c8 100644 --- a/Block/Adminhtml/History.php +++ b/Block/Adminhtml/History.php @@ -1,4 +1,11 @@ config->isEnabled()) { throw new \Exception( - __(strrev('.ecafretnI eniL dnammoC > snoisnetxE nafegaM > noitarugifnoC > serotS ot etagivan esaelp noisnetxe eht elbane ot ,delbasid si ecafretnI eniL dnammoC nafegaM')), - 1 + __(strrev('.ecafretnI eniL dnammoC > snoisnetxE nafegaM > noitarugifnoC > + serotS ot etagivan esaelp noisnetxe eht elbane ot ,delbasid si ecafretnI eniL dnammoC nafegaM')), + 1 ); } @@ -97,6 +141,7 @@ public function execute() if (!$message) { $message = __('Command not found or error occurred.') . PHP_EOL; } + // persist log to DB try { $connection = $this->resource->getConnection(); $tableName = $this->resource->getTableName('magefan_cli_log'); @@ -112,7 +157,7 @@ public function execute() ['command' => $command, 'result' => $message, 'user_id' => $userId, 'executed_at' => (new \DateTime())->format('Y-m-d H:i:s')] ]); } catch (\Exception $e) { - // ignore + // swallow DB errors to not break execution } unlink($logFile); @@ -130,6 +175,14 @@ public function execute() ); } + /** + * Validate current user password + * + * @return $this + * @throws UserLockedException + * @throws \Magento\Framework\Exception\AuthenticationException + * @throws \Exception + */ protected function validateUser() { $password = $this->getRequest()->getParam( diff --git a/Controller/Adminhtml/MfcliIndex/Index.php b/Controller/Adminhtml/MfcliIndex/Index.php index 4c12220..88d9613 100644 --- a/Controller/Adminhtml/MfcliIndex/Index.php +++ b/Controller/Adminhtml/MfcliIndex/Index.php @@ -1,9 +1,12 @@ + diff --git a/etc/crontab.xml b/etc/crontab.xml index ab939de..2255bce 100644 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -1,4 +1,10 @@ + diff --git a/etc/db_schema.xml b/etc/db_schema.xml index 17dfd77..d4dc8aa 100644 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -1,5 +1,10 @@ - + diff --git a/view/adminhtml/layout/admin_mfcli_history_index.xml b/view/adminhtml/layout/admin_mfcli_history_index.xml index c7aed1d..706ff51 100644 --- a/view/adminhtml/layout/admin_mfcli_history_index.xml +++ b/view/adminhtml/layout/admin_mfcli_history_index.xml @@ -1,4 +1,10 @@ + diff --git a/view/adminhtml/layout/admin_mfcli_index_index.xml b/view/adminhtml/layout/admin_mfcli_index_index.xml index a0bea30..a970a21 100644 --- a/view/adminhtml/layout/admin_mfcli_index_index.xml +++ b/view/adminhtml/layout/admin_mfcli_index_index.xml @@ -1,4 +1,10 @@ + diff --git a/view/adminhtml/layout/mfcli_history_index.xml b/view/adminhtml/layout/mfcli_history_index.xml index c7aed1d..706ff51 100644 --- a/view/adminhtml/layout/mfcli_history_index.xml +++ b/view/adminhtml/layout/mfcli_history_index.xml @@ -1,4 +1,10 @@ + diff --git a/view/adminhtml/templates/history.phtml b/view/adminhtml/templates/history.phtml index 0862087..04cfa27 100644 --- a/view/adminhtml/templates/history.phtml +++ b/view/adminhtml/templates/history.phtml @@ -1,9 +1,15 @@ + + getLogs(200); ?>
-

escapeHtml(__('Command Line History')) ?>