├── .codeclimate.yml ├── .github └── FUNDING.yml ├── .gitignore ├── Api └── ServiceInterface.php ├── Block ├── Adminhtml │ └── System │ │ └── Config │ │ └── Form │ │ └── Fieldset │ │ └── IsEnabled.php ├── Tab │ ├── Content │ │ ├── Config.php │ │ ├── Help.php │ │ ├── Layout.php │ │ ├── Log.php │ │ ├── PhpInfo.php │ │ ├── Request.php │ │ ├── Sql.php │ │ └── Translation.php │ ├── Panel.php │ └── Wrapper.php └── Toolbar.php ├── Console └── Command │ ├── AbstractStatusToolbar.php │ ├── Database.php │ ├── DisableToolBar.php │ └── EnableToolBar.php ├── Controller ├── Action │ ├── Cache.php │ ├── CacheCss.php │ ├── ConfigSearch.php │ ├── ConfigUpdate.php │ └── Cookie.php ├── Adminhtml │ ├── Index.php │ └── Tab │ │ └── PhpInfo.php ├── Index.php ├── Index │ └── Ajax.php ├── Log │ ├── Reset.php │ └── View.php └── Tab │ ├── Ajax.php │ ├── PhpInfo.php │ └── Translation.php ├── Helper ├── Cookie.php ├── Data.php ├── Debug.php ├── Register.php └── Translate.php ├── Model └── Config │ └── Source │ ├── Activate.php │ ├── Area.php │ ├── DumperHandler.php │ └── Ide.php ├── Observer ├── ControllerFrontSendResponseBeforeObserver.php ├── LayoutGenerateBlocksAfterObserver.php └── UpdateLayoutObserver.php ├── Plugin ├── Framework │ ├── App │ │ ├── Cache.php │ │ ├── FrontController.php │ │ └── UpdateCookies.php │ ├── Event │ │ ├── Invoker.php │ │ └── Manager.php │ ├── Form │ │ └── Element.php │ └── Http │ │ └── Response.php ├── PageCache │ └── FrontController │ │ └── BuiltinPlugin.php ├── Search │ ├── ResponseFactory.php │ └── SearchClient.php └── Zend │ └── DbAdapter.php ├── Profiler ├── App.php └── Db.php ├── README.md ├── Service ├── App │ └── Cache.php ├── Config.php ├── Dumper.php ├── Elasticsearch.php ├── Event │ ├── Instance.php │ └── Manager.php ├── Layout │ ├── Handle.php │ └── Hierarchy.php ├── Module.php ├── Observer.php ├── Plugin.php ├── Request.php └── Sql.php ├── composer.json ├── doc ├── Changelog.md └── images │ ├── phpstorm_debugger.png │ ├── qdb_screen_config_ko.png │ ├── qdb_screen_dark.png │ ├── qdb_screen_dispatch.png │ ├── qdb_screen_dispatch.youtube.png │ ├── qdb_screen_queries.png │ └── qdb_screen_request.png ├── etc ├── adminhtml │ ├── di.xml │ ├── routes.xml │ └── system.xml ├── config.xml ├── csp_whitelist.xml ├── di.xml ├── events.xml ├── frontend │ ├── di.xml │ └── routes.xml └── module.xml ├── modman ├── registration.php └── view ├── base ├── layout │ ├── default.xml │ └── quickdevbar.xml ├── templates │ ├── tab │ │ ├── action.phtml │ │ ├── design │ │ │ ├── block.phtml │ │ │ ├── handles.phtml │ │ │ └── layout.phtml │ │ ├── dumper.phtml │ │ ├── help.phtml │ │ ├── info │ │ │ ├── config.phtml │ │ │ ├── module.phtml │ │ │ ├── request.phtml │ │ │ └── store.phtml │ │ ├── log.phtml │ │ ├── profile │ │ │ ├── cache.phtml │ │ │ ├── collection.phtml │ │ │ ├── event.phtml │ │ │ ├── model.phtml │ │ │ ├── observer.phtml │ │ │ ├── plugin.phtml │ │ │ ├── preference.phtml │ │ │ └── profiler.phtml │ │ ├── sql.phtml │ │ └── translation │ │ │ ├── file.phtml │ │ │ └── plain.phtml │ ├── tabs.phtml │ └── toolbar.phtml └── web │ ├── css │ └── quickdevbar.css │ ├── images │ ├── asc.gif │ ├── bg.gif │ ├── config.png │ ├── database.png │ ├── desc.gif │ ├── dump.png │ ├── help.png │ ├── info.png │ ├── lastnode.png │ ├── layout.png │ ├── loader-128.gif │ ├── loader-32.gif │ ├── loader-64.gif │ ├── log.png │ ├── node.png │ ├── profile.png │ ├── qdb-icon.png │ ├── setting.png │ ├── tools.png │ ├── translate.png │ └── vline.png │ └── js │ ├── filter-table.js │ ├── sortable-table.js │ └── tabbis.js └── frontend └── layout └── quickdevbar.xml /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | PHP: true 4 | engines: 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - javascript 10 | - php 11 | eslint: 12 | enabled: true 13 | fixme: 14 | enabled: true 15 | phpmd: 16 | enabled: true 17 | checks: 18 | CleanCode/ElseExpression: 19 | enabled: false 20 | ratings: 21 | paths: 22 | - "**.js" 23 | - "**.php" 24 | exclude_paths: 25 | - "view/base/web/js/tablesorter/" 26 | - "view/base/web/js/sunnywalker/" 27 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: vpietri 2 | custom: ["https://paypal.me/vpietri"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings/ 4 | .idea/ 5 | -------------------------------------------------------------------------------- /Api/ServiceInterface.php: -------------------------------------------------------------------------------- 1 | _qdbHelper = $qdbHelper; 20 | } 21 | 22 | /** 23 | * @param AbstractElement $element 24 | * @return string 25 | */ 26 | protected function _getElementHtml(AbstractElement $element) 27 | { 28 | $html = []; 29 | if ($this->_qdbHelper->isToolbarAccessAllowed(true)) { 30 | $html[] = __('Yep'); 31 | } else { 32 | $html[] = '' . __('Nope') .''; 33 | if (!$this->_qdbHelper->isIpAuthorized()) { 34 | $html[] = __('Your Ip "%1" is not allowed, you should register it in the field below.', $this->_qdbHelper->getClientIp()); 35 | } 36 | if (!$this->_qdbHelper->isUserAgentAuthorized()) { 37 | $html[] = __('Your User Agent "%1" is not allowed, you should add a user-agent pattern', $this->_qdbHelper->getUserAgent()); 38 | } 39 | } 40 | 41 | 42 | return implode('
', $html); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Block/Tab/Content/Config.php: -------------------------------------------------------------------------------- 1 | _appConfig = $appConfig; 21 | 22 | parent::__construct($context, $qdbHelper, $qdbHelperRegister, $data); 23 | } 24 | 25 | public function getTitleBadge() 26 | { 27 | return $this->count($this->getConfigValues()); 28 | } 29 | 30 | public function getConfigValues() 31 | { 32 | if (is_null($this->_config_values)) { 33 | $this->_config_values = $this->_buildFlatConfig($this->_appConfig->getValue( null, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 34 | null)); 35 | } 36 | 37 | return $this->_config_values; 38 | } 39 | 40 | protected function _buildFlatConfig($scope, $path = '') 41 | { 42 | $flatConfig = []; 43 | if (is_array($scope)) { 44 | foreach ($scope as $scopeKey => $scopeValue) { 45 | $buildedPath = !empty($path) ? $path.'/'.$scopeKey : $scopeKey; 46 | $flatConfig = array_merge($flatConfig, $this->_buildFlatConfig($scopeValue, $buildedPath)); 47 | } 48 | } else { 49 | $flatConfig[$path] = ['path' => $path, 'value' => $scope]; 50 | } 51 | 52 | return $flatConfig; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Block/Tab/Content/Help.php: -------------------------------------------------------------------------------- 1 | componentRegistrar = $componentRegistrar; 28 | $this->readFactory = $readFactory; 29 | } 30 | 31 | 32 | public function getModuleVersion() 33 | { 34 | //return $this->helper->getModuleVersion($this->getModuleName()); 35 | return $this->getMagentoModuleVersion($this->getModuleName()); 36 | } 37 | 38 | 39 | /** 40 | * @see https://www.rakeshjesadiya.com/get-module-composer-version-programmatically-by-magento/ 41 | * 42 | */ 43 | public function getMagentoModuleVersion(string $moduleName): string 44 | { 45 | $path = $this->componentRegistrar->getPath( 46 | ComponentRegistrar::MODULE, 47 | $moduleName 48 | ); 49 | $directoryRead = $this->readFactory->create($path); 50 | $composerJsonData = ''; 51 | if ($directoryRead->isFile('composer.json')) { 52 | $composerJsonData = $directoryRead->readFile('composer.json'); 53 | } 54 | $data = json_decode($composerJsonData); 55 | 56 | return !empty($data->version) ? $data->version : ''; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Block/Tab/Content/Layout.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister->getLayoutHandles(); 14 | } 15 | 16 | public function getHtmlBlocksHierarchy($treeBlocks = [], $level = 0) 17 | { 18 | if (empty($treeBlocks)) { 19 | $treeBlocks = [$this->qdbHelperRegister->getLayoutHierarchy()]; 20 | } 21 | 22 | $html = ''; 23 | foreach ($treeBlocks as $treeNode) { 24 | if(empty($treeNode)) { 25 | continue; 26 | } 27 | 28 | $openAttr = !empty($treeNode['children']) && $level < 2 ? " open " : ""; 29 | $classes = ['type-'.$treeNode['type']]; 30 | $classes[] = (!$treeNode['cacheable']) ? ' qdb-warning ' : ''; 31 | $classes[] = !empty($treeNode['children']) ? ' haschild ' : ''; 32 | 33 | 34 | $blockInfo = []; 35 | if (!empty($treeNode['class_name'])) { 36 | $blockInfo[]= 'Class: ' . $this->helper->getIDELinkForFile($treeNode['class_file'],1, $treeNode['class_name']); 37 | } 38 | if (!empty($treeNode['file'])) { 39 | $blockInfo[]= 'Template: ' . $this->helper->getIDELinkForFile($treeNode['file']); 40 | } 41 | if (empty($treeNode['cacheable'])) { 42 | $blockInfo[]= 'Not cacheable'; 43 | } 44 | 45 | $html .= '
' . 50 | '' . $treeNode['name'] . ''; 51 | 52 | if (!empty($blockInfo)) { 53 | $html .= '
' . implode('
', $blockInfo) . '
'; 54 | } 55 | 56 | if (!empty($treeNode['children'])) { 57 | $html .= $this->getHtmlBlocksHierarchy($treeNode['children'], $level+1); 58 | } 59 | $html .= '
'; 60 | } 61 | $html .= ''; 62 | 63 | return $html; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Block/Tab/Content/Log.php: -------------------------------------------------------------------------------- 1 | _jsonHelper = $jsonHelper; 23 | 24 | parent::__construct($context, $qdbHelper, $qdbHelperRegister, $data); 25 | } 26 | 27 | public function getTailLines() 28 | { 29 | return 20; 30 | } 31 | 32 | public function getLogFiles() 33 | { 34 | return $this->helper->getLogFiles(); 35 | } 36 | 37 | public function getJsonLogFiles() 38 | { 39 | return $this->_jsonHelper->jsonEncode($this->helper->getLogFiles()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Block/Tab/Content/PhpInfo.php: -------------------------------------------------------------------------------- 1 | showPhpInfo(); 15 | } 16 | 17 | /** 18 | * Return PHP Info CSS 19 | */ 20 | protected function phpInfoCssLambda($value) 21 | { 22 | return ".phpinfodisplay " . preg_replace("/,/", ",.phpinfodisplay ", $value); 23 | } 24 | 25 | public function showPhpInfo() 26 | { 27 | $what = $this->hasShortWhat() ? INFO_VARIABLES|INFO_ENVIRONMENT : INFO_ALL; 28 | 29 | ob_start(); 30 | phpinfo($what); 31 | if (preg_match('%.*?(.*)%s', ob_get_clean(), $matches)) { 32 | return "" . PHP_EOL . 42 | "
" . PHP_EOL . 43 | $matches[2]. PHP_EOL . 44 | "
" . PHP_EOL; 45 | } else { 46 | return ''; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Block/Tab/Content/Request.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister->getContextData(); 17 | return $requestData; 18 | } 19 | 20 | public function formatValue($data) 21 | { 22 | if (is_array($data['value'])) { 23 | return '
' . print_r($data['value'], true) . '
'; 24 | } elseif (!empty($data['is_url'])) { 25 | return '' . $data['value'] . ''; 26 | } else { 27 | return $data['value']; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Block/Tab/Content/Sql.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister = $qdbHelperRegister; 25 | $this->objectFactory = $objectFactory; 26 | $this->cookieHelper = $cookieHelper; 27 | } 28 | 29 | 30 | public function getTitleBadge() 31 | { 32 | if ($this->getSqlProfiler()) { 33 | return $this->getSqlProfiler()->getTotalNumQueries(); 34 | } 35 | return false; 36 | } 37 | 38 | 39 | 40 | /** 41 | * @return Zend_Db_Profiler 42 | */ 43 | public function getSqlProfiler() 44 | { 45 | return $this->objectFactory->create()->setData($this->qdbHelperRegister->getRegisteredData('sql_profiler')); 46 | } 47 | 48 | 49 | public function getAllQueries() 50 | { 51 | return $this->getSqlProfiler()->getAllQueries(); 52 | } 53 | 54 | public function getTotalNumQueries($queryType = null) 55 | { 56 | return $this->getSqlProfiler()->getTotalNumQueries($queryType); 57 | } 58 | 59 | public function getTotalNumQueriesByType($queryType = null) 60 | { 61 | $numQueriesByType = $this->getSqlProfiler()->getTotalNumQueriesByType(); 62 | return isset($numQueriesByType[$queryType]) ? $numQueriesByType[$queryType] : 0; 63 | } 64 | 65 | public function getTotalElapsedSecs() 66 | { 67 | return $this->getSqlProfiler()->getTotalElapsedSecs(); 68 | } 69 | 70 | public function getAverage() 71 | { 72 | return $this->getSqlProfiler()->getAverage(); 73 | } 74 | 75 | public function getLongestQuery() 76 | { 77 | return $this->getSqlProfiler()->getLongestQuery(); 78 | } 79 | 80 | public function getLongestQueryTime() 81 | { 82 | return $this->getSqlProfiler()->getLongestQueryTime(); 83 | } 84 | 85 | public function getNumQueriesPerSecond() 86 | { 87 | 88 | return $this->getSqlProfiler()->getNumQueriesPerSecond(); 89 | } 90 | 91 | public function formatSql($sql) 92 | { 93 | $htmlSql = preg_replace('/\b(SET|AS|ASC|COUNT|DESC|IN|LIKE|DISTINCT|INTO|VALUES|LIMIT)\b/', '\\1', $sql); 94 | $htmlSql = preg_replace('/\b(UNION ALL|DESCRIBE|SHOW|connect|begin|commit)\b/', '
\\1', $htmlSql); 95 | $htmlSql = preg_replace('/\b(UPDATE|SELECT|FROM|WHERE|LEFT JOIN|INNER JOIN|RIGHT JOIN|ORDER BY|GROUP BY|DELETE|INSERT)\b/', '
\\1', $htmlSql); 96 | 97 | return preg_replace('/^/', '', $htmlSql); 98 | } 99 | 100 | public function formatParams($params) { 101 | if (is_array($params)) { 102 | ksort($params); 103 | 104 | return \json_encode($params); 105 | } 106 | 107 | return ''; 108 | } 109 | 110 | 111 | public function formatSqlTime($time, $decimals = 2) 112 | { 113 | return number_format(round(1000 * $time, $decimals), $decimals) . 'ms'; 114 | } 115 | 116 | public function formatSqlTrace($bt) 117 | { 118 | $traceFormated = []; 119 | foreach ($bt as $i=>$traceLine) { 120 | $traceFormated[] = sprintf('#%d %s %s->%s()', $i, $this->helper->getIDELinkForFile($traceLine['file'],$traceLine['line']) , $traceLine['class'], $traceLine['function']); 121 | } 122 | return '
'.implode('
', $traceFormated).'
'; 123 | } 124 | 125 | public function getProfilerEnabled() 126 | { 127 | return $this->cookieHelper->isProfilerEnabled(); 128 | } 129 | 130 | public function getProfilerBacktraceEnabled() 131 | { 132 | return $this->cookieHelper->isProfilerBacktraceEnabled(); 133 | } 134 | 135 | 136 | public function getButtonProfilerLabel() 137 | { 138 | return $this->getProfilerEnabled() ? 'Disable profiler session' : 'Enable profiler session'; 139 | } 140 | 141 | public function getButtonProfilerBactraceLabel() 142 | { 143 | return $this->getProfilerBacktraceEnabled() ? 'Disable backtrace' : 'Enable backtrace'; 144 | 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Block/Tab/Content/Translation.php: -------------------------------------------------------------------------------- 1 | translate = $translate; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getType() 36 | { 37 | $type = $this->getTitle(); 38 | if (!empty($this->_data['type'])) { 39 | $type = $this->_data['type']; 40 | } 41 | 42 | return strtolower($type); 43 | } 44 | 45 | /** 46 | * Get relevant path to template 47 | * 48 | * @return string 49 | */ 50 | public function getTemplate() 51 | { 52 | if (empty($this->_template)) { 53 | if (in_array($this->getType(), ['module','theme'])) { 54 | $this->_template = "tab/translation/file.phtml"; 55 | } else { 56 | $this->_template = "tab/translation/plain.phtml"; 57 | } 58 | } 59 | 60 | return $this->_template; 61 | } 62 | 63 | /** 64 | * @return array 65 | * @throws LocalizedException 66 | */ 67 | public function getTranslations() 68 | { 69 | return $this->translate->getTranslationsByType($this->getType()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Block/Tab/Panel.php: -------------------------------------------------------------------------------- 1 | helper = $helper; 29 | $this->qdbHelperRegister = $qdbHelperRegister; 30 | } 31 | 32 | /** 33 | * @param $key 34 | * @param $index 35 | * @return array|\Magento\Framework\DataObject|string|null 36 | * @throws \Exception 37 | */ 38 | public function getData($key = '', $index = null) 39 | { 40 | if(!isset($this->_data[$key]) && $key==$this->getDataKey()) { 41 | return $this->getQdbData(); 42 | } 43 | return parent::getData($key, $index); 44 | } 45 | 46 | public function getDataKey() 47 | { 48 | return $this->_data['data_key'] ?? null; 49 | } 50 | 51 | 52 | 53 | public function getTitleBadge() 54 | { 55 | $qdbData = $this->getQdbData(); 56 | return $this->count($qdbData); 57 | } 58 | 59 | protected function count($registeredData) 60 | { 61 | return is_countable($registeredData) ? count($registeredData) : 0; 62 | } 63 | 64 | 65 | protected function getQdbData() 66 | { 67 | if(!$this->getDataKey()) { 68 | return ''; 69 | } 70 | 71 | if(!$this->getDataKey()) { 72 | throw new \Exception('property qdbDataKey is not defined.'); 73 | } 74 | 75 | return $this->qdbHelperRegister->getRegisteredData($this->getDataKey()); 76 | } 77 | 78 | 79 | public function getTitle() 80 | { 81 | $title = $this->getData('title'); 82 | if(!$title && $title = $this->getDataKey()) { 83 | return ucfirst($title); 84 | } 85 | return $title ?? $this->getNameInLayout(); 86 | } 87 | 88 | 89 | public function getId($prefix = '') 90 | { 91 | $id = ($this->getData('id')) ? $this->getData('id') : $this->getNameInLayout(); 92 | $id = str_replace('.', '-', $id); 93 | if ($prefix) { 94 | $id = $prefix . $id; 95 | } 96 | return $id; 97 | } 98 | 99 | public function getClass() 100 | { 101 | $class = $this->getId(); 102 | if ($this->isAjax(false)) { 103 | $class .= ' use-ajax'; 104 | } 105 | 106 | return $class; 107 | } 108 | 109 | public function isAjax($asString = true) 110 | { 111 | $return = (($this->hasData('ajax_url') || $this->hasData('is_ajax'))? true : false); 112 | if ($asString) { 113 | $return = ($return) ? "true" : "false"; 114 | } 115 | 116 | return $return; 117 | } 118 | 119 | public function getTabUrl() 120 | { 121 | $tabUrl = ''; 122 | if ($this->getData('tab_url')) { 123 | $tabUrl = $this->getData('tab_url'); 124 | } else { 125 | if ($this->getData('ajax_url')) { 126 | $tabUrl = $this->getFrontUrl($this->getData('ajax_url')); 127 | } elseif ($this->getData('is_ajax')) { 128 | $tabUrl = $this->getFrontUrl('quickdevbar/tab/ajax', ['block'=>$this->getNameInLayout(), '_query'=>['isAjax'=>1]]); 129 | } 130 | } 131 | 132 | return $tabUrl; 133 | } 134 | 135 | 136 | /** 137 | * Generate url by route and parameters 138 | * 139 | * @param string $route 140 | * @param array $params 141 | * @return string 142 | */ 143 | public function getFrontUrl($route = '', $params = []) 144 | { 145 | if ($this->_frontUrl === null) { 146 | $this->_frontUrl = ObjectManager::getInstance()->get('Magento\Framework\Url'); 147 | } 148 | 149 | return $this->_frontUrl->getUrl($route, $params); 150 | } 151 | 152 | public function getHtmlLoader($class='') 153 | { 154 | $html = '
'; 155 | 156 | return $html; 157 | } 158 | 159 | 160 | // public function getTabBlocks() 161 | // { 162 | // if ($this->_mainTabs === null) { 163 | // $this->_mainTabs = $this->getLayout()->getChildBlocks($this->getNameInLayout()); 164 | // } 165 | // 166 | // return $this->_mainTabs; 167 | // } 168 | 169 | public function getStore() 170 | { 171 | return $this->_storeManager->getStore(); 172 | } 173 | 174 | public function getWebsite() 175 | { 176 | return $this->_storeManager->getWebsite(); 177 | } 178 | 179 | public function getGroup() 180 | { 181 | return $this->_storeManager->getGroup(); 182 | } 183 | 184 | /** 185 | * Render block HTML 186 | * 187 | * @return string 188 | */ 189 | protected function _toHtml() 190 | { 191 | try { 192 | $buffer = parent::_toHtml(); 193 | return $this->sanitizeOutput($buffer); 194 | } catch (\Exception $e) { 195 | return $e->getMessage(); 196 | } 197 | } 198 | 199 | 200 | /** 201 | * @see http://stackoverflow.com/a/6225706 202 | * @param $buffer 203 | * @return array|string|string[]|null 204 | */ 205 | protected function sanitizeOutput($buffer) 206 | { 207 | if($this->getDoNotMinify()) { 208 | return $buffer; 209 | } 210 | 211 | $search = [ 212 | '/\>[^\S ]+/s', // strip whitespaces after tags, except space 213 | '/[^\S ]+\', 219 | '<', 220 | '\\1' 221 | ]; 222 | 223 | $buffer = preg_replace($search, $replace, $buffer); 224 | 225 | return $buffer; 226 | } 227 | 228 | public function htmlFormatClass($class) 229 | { 230 | return $this->helper->getIDELinkForClass($class); 231 | } 232 | 233 | /** 234 | * @param array $bt 235 | * @return string 236 | */ 237 | public function formatTrace(array $bt) 238 | { 239 | return $this->helper->getIDELinkForFile($bt['file'], $bt['line']); 240 | } 241 | 242 | public function getQdbConfig($key, $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) 243 | 244 | { 245 | return $this->helper->getQdbConfig($key, $scopeType, $scopeCode); 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /Block/Tab/Wrapper.php: -------------------------------------------------------------------------------- 1 | _jsonHelper = $jsonHelper; 27 | 28 | parent::__construct($context, $qdbHelper, $qdbHelperRegister, $data); 29 | } 30 | 31 | public function getTabBlocks() 32 | { 33 | if ($this->_mainTabs === null) { 34 | $this->_mainTabs=[]; 35 | foreach ($this->getLayout()->getChildBlocks($this->getNameInLayout()) as $alias => $block) { 36 | $this->_mainTabs[$alias]=$block; 37 | } 38 | } 39 | 40 | return $this->_mainTabs; 41 | } 42 | 43 | public function getSubTabSuffix() 44 | { 45 | return SimpleDataObjectConverter::snakeCaseToCamelCase(str_replace('.', '_', $this->getNameInLayout())); 46 | } 47 | 48 | public function getUiTabClass() 49 | { 50 | return ($this->getIsMainTab()) ? 'qdb-ui-tabs' : 'qdb-ui-subtabs'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Block/Toolbar.php: -------------------------------------------------------------------------------- 1 | _qdnHelper = $qdnHelper; 25 | 26 | parent::__construct($context, $data); 27 | } 28 | 29 | /** 30 | * Determine if action is allowed 31 | * 32 | * @return bool 33 | */ 34 | protected function canDisplay() 35 | { 36 | return $this->_qdnHelper->isToolbarAccessAllowed() && $this->_qdnHelper->isToolbarAreaAllowed($this->getArea()); 37 | } 38 | 39 | public function getAppearance() 40 | { 41 | return $this->_qdnHelper->defaultAppearance(); 42 | } 43 | 44 | public function getBaseUrl() 45 | { 46 | if ($this->_frontUrl === null) { 47 | $this->_frontUrl = ObjectManager::getInstance()->get('Magento\Framework\Url'); 48 | } 49 | 50 | return $this->_frontUrl->getUrl(); 51 | } 52 | 53 | public function isAjaxLoading() 54 | { 55 | return $this->_qdnHelper->isAjaxLoading() ? "true" : "false"; 56 | } 57 | 58 | public function toHtml() 59 | { 60 | return (!$this->canDisplay()) ? '' : parent::toHtml(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Console/Command/AbstractStatusToolbar.php: -------------------------------------------------------------------------------- 1 | resourceConfig = $resourceConfig; 65 | $this->cacheManager = $cacheManager; 66 | $this->eventManager = $eventManager; 67 | $this->writer = $writer; 68 | $this->arrayManager = $arrayManager; 69 | } 70 | 71 | /** 72 | * {@inheritdoc} 73 | */ 74 | protected function configure() 75 | { 76 | $this->setName($this->name); 77 | $this->setDescription($this->description); 78 | $this->addOption( 79 | self::CLEAN_HTML, 80 | null, 81 | InputOption::VALUE_NONE, 82 | 'Clear front cache block_html & full_page' 83 | ); 84 | $this->addOption( 85 | self::ACTIVATE_SQL_PROFILER, 86 | null, 87 | InputOption::VALUE_NONE, 88 | 'Activate/deactivate SQL profiler' 89 | ); 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | protected function execute(InputInterface $input, OutputInterface $output) 96 | { 97 | 98 | $this->resourceConfig->saveConfig('dev/quickdevbar/enable', $this->status); 99 | $output->writeln("" . $this->message . ""); 100 | 101 | $this->eventManager->dispatch('adminhtml_cache_flush_system'); 102 | 103 | $cachesToClear=['config']; 104 | if ($input->getOption(self::CLEAN_HTML)) { 105 | $cachesToClear = array_merge($cachesToClear, ['block_html', 'full_page']); 106 | } 107 | 108 | $lockTargetPath = ConfigFilePool::APP_ENV; 109 | if(!$this->status) { 110 | $this->writer->saveConfig( 111 | [$lockTargetPath => $this->arrayManager->set('db/connection/default/profiler', [], 0)], 112 | false 113 | ); 114 | $output->writeln("SQL profiler is disabled in env.php"); 115 | } elseif ($input->getOption(self::ACTIVATE_SQL_PROFILER) ) { 116 | 117 | $profilerValue = [ 'enabled'=>1]; 118 | $this->writer->saveConfig( 119 | [$lockTargetPath => $this->arrayManager->set('db/connection/default/profiler', [], $profilerValue)], 120 | false 121 | ); 122 | $output->writeln("SQL profiler is enabled in env.php"); 123 | } 124 | 125 | $this->cacheManager->clean($cachesToClear); 126 | $output->writeln("Cache cleared: ".implode(",", $cachesToClear).""); 127 | 128 | return \Magento\Framework\Console\Cli::RETURN_SUCCESS; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Console/Command/Database.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | protected function configure() 49 | { 50 | $this->setName($this->name); 51 | $this->setDescription($this->description); 52 | $this->addArgument( 53 | self::DB_TABLENAME, 54 | \Symfony\Component\Console\Input\InputArgument::REQUIRED, 55 | 'Table name to describe' 56 | ); 57 | $this->addOption( 58 | self::DB_TABLE_COMMENT, 59 | 'c', 60 | null, 61 | 'Table name comment' 62 | ); 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | protected function execute(InputInterface $input, OutputInterface $output) 69 | { 70 | 71 | $connection = $this->resource->getConnection(); 72 | $tableName = $input->getArgument(self::DB_TABLENAME); 73 | 74 | $ddl = $connection->describeTable($input->getArgument(self::DB_TABLENAME)); 75 | 76 | $createTable = $connection->getCreateTable($input->getArgument(self::DB_TABLENAME)); 77 | $createTableComment = [self::DDL_COMMENT_TABLE_KEY => $tableName . ' Table']; 78 | 79 | if(preg_match_all('/.*COMMENT[\s=]\'(.*)\'/', $createTable, $matchComment)) { 80 | 81 | } 82 | 83 | $tableEngine = 'innodb'; 84 | $tableComment = $tableName . ' Table'; 85 | 86 | $dbSchemaArr = ['']; 87 | $dbSchemaArr[] = ' '; 88 | 89 | foreach ($ddl as $ddlColumn) { 90 | $xmlColumn=[]; 91 | $xmlColumn['name'] = $ddlColumn['COLUMN_NAME']; 92 | $xmlColumn['xsi:type'] = $ddlColumn['DATA_TYPE']; 93 | $xmlColumn['nullable'] = $ddlColumn['NULLABLE']; 94 | if($ddlColumn['DEFAULT']) { 95 | $xmlColumn['default'] = $ddlColumn['DEFAULT']; 96 | } 97 | $xmlColumn['scale'] = $ddlColumn['SCALE']; 98 | $xmlColumn['precision'] = $ddlColumn['PRECISION']; 99 | $xmlColumn['unsigned'] = $ddlColumn['UNSIGNED']; 100 | if($ddlColumn['PRIMARY']) { 101 | $xmlColumn['primary'] = $ddlColumn['PRIMARY']; 102 | } 103 | if($ddlColumn['IDENTITY']) { 104 | $xmlColumn['identity'] = $ddlColumn['IDENTITY']; 105 | } 106 | $xmlColumn['length'] = $ddlColumn['LENGTH']; 107 | $xmlColumn['comment'] = $ddlColumn['COLUMN_NAME']; 108 | array_filter($xmlColumn, fn($var) => $var !== null); 109 | array_walk($xmlColumn, function(&$item, $key) { 110 | if(!is_string($item)) { 111 | $item = empty($item) ? 'false' : 'true'; 112 | } 113 | $item = $key . '="'.$item.'"'; 114 | }); 115 | 116 | $dbSchemaArr[] = ' '; 117 | } 118 | $dbSchemaArr[] = '
'; 119 | $dbSchemaArr[] = '
'; 120 | 121 | 122 | $output->writeln("" . implode(PHP_EOL, $dbSchemaArr) . ""); 123 | 124 | return Cli::RETURN_SUCCESS; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /Console/Command/DisableToolBar.php: -------------------------------------------------------------------------------- 1 | _qdbHelper->getControllerMessage(); 10 | $output = ''; 11 | try { 12 | 13 | $cacheFrontEndPool = $this->_qdbHelper->getCacheFrontendPool(); 14 | $this->_eventManager->dispatch('adminhtml_cache_flush_all'); 15 | foreach ($cacheFrontEndPool as $cacheFrontend) { 16 | $cacheFrontend->clean(); 17 | $cacheFrontend->getBackend()->clean(); 18 | } 19 | 20 | $output = 'Cache cleaned'; 21 | 22 | } catch (\Exception $e) { 23 | $output = $e->getMessage(); 24 | $error = true; 25 | } 26 | 27 | if ($ctrlMsg) { 28 | $output = $ctrlMsg . ' (' . $output .')'; 29 | } 30 | 31 | $resultRaw = $this->_resultRawFactory->create(); 32 | return $resultRaw->setContents($output); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Controller/Action/CacheCss.php: -------------------------------------------------------------------------------- 1 | _mergeService = $mergeService; 28 | } 29 | 30 | 31 | 32 | 33 | public function execute() 34 | { 35 | 36 | try { 37 | $this->_mergeService->cleanMergedJsCss(); 38 | $output = 'Cache merged Js and Css cleaned'; 39 | } catch (\Exception $e) { 40 | $output = $e->getMessage(); 41 | } 42 | 43 | $this->_view->loadLayout(); 44 | $resultRaw = $this->_resultRawFactory->create(); 45 | return $resultRaw->setContents($output); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Controller/Action/ConfigSearch.php: -------------------------------------------------------------------------------- 1 | _tabs = $configStructure->getTabs(); 39 | } 40 | 41 | 42 | 43 | public function execute() 44 | { 45 | $this->_tabs->rewind(); 46 | foreach ($this->_tabs as $tab) { 47 | echo $tab->getLabel(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Controller/Action/ConfigUpdate.php: -------------------------------------------------------------------------------- 1 | _resourceConfig = $resourceConfig; 45 | $this->_storeManager = $storeManager; 46 | $this->_resultForwardFactory = $resultForwardFactory; 47 | } 48 | 49 | 50 | 51 | public function execute() 52 | { 53 | 54 | $error = false; 55 | $output = ''; 56 | $config = $this->getRequest()->getParam('config'); 57 | try { 58 | if (empty($config['key'])) { 59 | throw new \Exception('Key is missing'); 60 | } else { 61 | $configKey = $config['key']; 62 | } 63 | 64 | 65 | switch ($configKey) { 66 | case 'template_hints_admin': 67 | case 'template_hints_storefront': 68 | case 'template_hints_blocks': 69 | case 'translate': 70 | $configScope = 'stores'; 71 | break; 72 | case 'devadmin': 73 | $configScope = 'default'; 74 | break; 75 | default: 76 | throw new \Exception('Scope auto is unrecognized'); 77 | break; 78 | } 79 | 80 | 81 | $configValue = 'toggle'; 82 | switch ($configScope) { 83 | case 'stores': 84 | $configScopeId = $this->_storeManager->getStore()->getId(); 85 | break; 86 | case 'websites': 87 | $configScopeId = $this->_storeManager->getWebsite()->getId(); 88 | break; 89 | default: 90 | $configScopeId = 0; 91 | break; 92 | } 93 | 94 | 95 | switch ($configKey) { 96 | case 'template_hints_admin': 97 | case 'template_hints_storefront': 98 | case 'template_hints_blocks': 99 | $configValue = ($this->_qdbHelper->getConfig('dev/debug/' . $configKey, $configScope, $configScopeId)) ? 0 : 1; 100 | $this->_resourceConfig->saveConfig('dev/debug/' . $configKey, $configValue, $configScope, $configScopeId); 101 | $output = ucwords(str_replace('_', ' ', $configKey)) . " set " . ($configValue ? 'On' : 'Off'); 102 | break; 103 | case 'translate': 104 | $configValue = ($this->_qdbHelper->getConfig('dev/translate_inline/active', $configScope, $configScopeId)) ? 0 : 1; 105 | 106 | $this->_resourceConfig->saveConfig('dev/translate_inline/active', $configValue, $configScope, $configScopeId); 107 | $output = "Translate set " . ($configValue ? 'On' : 'Off'); 108 | break; 109 | case 'devadmin': 110 | $this->_resourceConfig->saveConfig('admin/security/password_lifetime', 0, $configScope, $configScopeId); 111 | $this->_resourceConfig->saveConfig('admin/security/password_is_forced', 0, $configScope, $configScopeId); 112 | $output = "Done"; 113 | break; 114 | default: 115 | break; 116 | } 117 | 118 | if ($output) { 119 | $this->_qdbHelper->setControllerMessage($output); 120 | } 121 | 122 | 123 | } catch (\Exception $e) { 124 | $output = $e->getMessage(); 125 | $error = true; 126 | } 127 | 128 | if (!$error) { 129 | /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ 130 | $resultForward = $this->_resultForwardFactory->create(); 131 | $resultForward->forward('cache'); 132 | return $resultForward; 133 | } else { 134 | $resultRaw = $this->_resultRawFactory->create(); 135 | return $resultRaw->setContents($output); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Controller/Action/Cookie.php: -------------------------------------------------------------------------------- 1 | cookieManager = $cookieManager; 25 | $this->cookieMetadataFactory = $cookieMetadataFactory; 26 | $this->sessionConfig = $sessionConfig; 27 | } 28 | 29 | public function execute() 30 | { 31 | $cookieName = $this->getRequest()->getParam('qdbName'); 32 | $output = 'No cookie name'; 33 | try { 34 | if ($cookieName) { 35 | $cookieValue = $this->getRequest()->getParam('qdbValue'); 36 | if(is_null($cookieValue)) { 37 | if($this->getRequest()->getParam('qdbToggle')) { 38 | $cookieValue = $this->cookieManager->getCookie($cookieName) ? null : true; 39 | } else { 40 | throw new \Exception('No value to set'); 41 | } 42 | } 43 | 44 | 45 | $metadata = $this->cookieMetadataFactory->createPublicCookieMetadata(); 46 | $metadata->setPath($this->sessionConfig->getCookiePath()); 47 | $metadata->setDomain($this->sessionConfig->getCookieDomain()); 48 | $metadata->setDuration($this->sessionConfig->getCookieLifetime()); 49 | $metadata->setSecure($this->sessionConfig->getCookieSecure()); 50 | $metadata->setHttpOnly($this->sessionConfig->getCookieHttpOnly()); 51 | $metadata->setSameSite($this->sessionConfig->getCookieSameSite()); 52 | 53 | $this->cookieManager->setPublicCookie( 54 | $cookieName, 55 | $cookieValue, 56 | $metadata 57 | ); 58 | 59 | $output = $cookieName.':'.$cookieValue; 60 | } 61 | } catch (\Exception $e) { 62 | $output = $e->getMessage(); 63 | } 64 | 65 | 66 | $resultRaw = $this->_resultRawFactory->create(); 67 | return $resultRaw->setContents($output); 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Index.php: -------------------------------------------------------------------------------- 1 | _layoutFactory = $layoutFactory; 31 | $this->_resultRawFactory = $resultRawFactory; 32 | parent::__construct($context); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Tab/PhpInfo.php: -------------------------------------------------------------------------------- 1 | _layoutFactory->create() 16 | ->createBlock('ADM\QuickDevBar\Block\Tab\Content\PhpInfo') 17 | ->toHtml(); 18 | } catch (Exception $e) { 19 | $output = $e->getMessage(); 20 | } 21 | 22 | $resultRaw = $this->_resultRawFactory->create(); 23 | return $resultRaw->setContents($output); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Controller/Index.php: -------------------------------------------------------------------------------- 1 | _qdbHelper = $qdbHelper; 40 | $this->_resultRawFactory = $resultRawFactory; 41 | $this->_layoutFactory = $layoutFactory; 42 | } 43 | 44 | /** 45 | * @param \Magento\Framework\App\RequestInterface $request 46 | * @return \Magento\Framework\App\ResponseInterface 47 | */ 48 | public function dispatch(\Magento\Framework\App\RequestInterface $request) 49 | { 50 | if (!$this->_isAllowed()) { 51 | throw new NotFoundException(__('Page not found.')); 52 | } 53 | 54 | return parent::dispatch($request); 55 | } 56 | 57 | /** 58 | * Determine if action is allowed 59 | * 60 | * @return bool 61 | */ 62 | protected function _isAllowed() 63 | { 64 | return $this->_qdbHelper->isToolbarAccessAllowed(); 65 | } 66 | 67 | public function execute() 68 | { 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Controller/Index/Ajax.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister = $qdbHelperRegister; 22 | } 23 | 24 | 25 | /** 26 | * @return \Magento\Framework\Controller\Result\Raw|void 27 | */ 28 | public function execute() 29 | { 30 | /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ 31 | $resultRaw = $this->_resultRawFactory->create(); 32 | 33 | try { 34 | $this->qdbHelperRegister->loadDataFromFile(); 35 | 36 | $this->_view->loadLayout('quickdevbar'); 37 | $output = $this->_view->getLayout()->getBlock('quick.dev.maintabs') 38 | //->setNeedHtmlContent(true) 39 | ->toHtml(); 40 | } catch (\Exception $e) { 41 | $output = $e->getMessage(); 42 | $resultRaw->setStatusHeader( 43 | \Laminas\Http\Response::STATUS_CODE_202, 44 | \Laminas\Http\AbstractMessage::VERSION_11, 45 | 'QDB Error' 46 | ); 47 | } 48 | 49 | //We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache) 50 | $resultRaw->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); 51 | 52 | return $resultRaw->setContents($output); 53 | 54 | } 55 | 56 | /** 57 | * @param \Magento\Framework\App\RequestInterface $request 58 | * @return \Magento\Framework\App\ResponseInterface 59 | */ 60 | public function dispatch(\Magento\Framework\App\RequestInterface $request) 61 | { 62 | if (!$this->getRequest()->isXmlHttpRequest()) { 63 | throw new NotFoundException(__('Page not found.')); 64 | } 65 | 66 | return parent::dispatch($request); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Controller/Log/Reset.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('log_key', ''); 13 | $output = ''; 14 | 15 | $file = $this->_qdbHelper->getLogFiles($fileKey); 16 | if ($file) { 17 | if (!empty($file['size'])) { 18 | if (!unlink($file['path'])) { 19 | $output = 'Cannot reset file.'; 20 | } else { 21 | $output = 'File empty.'; 22 | } 23 | } else { 24 | $output = 'Cannot find file to reset.'; 25 | } 26 | } else { 27 | $output = $file['path']; 28 | } 29 | 30 | $this->_view->loadLayout(); 31 | $resultRaw = $this->_resultRawFactory->create(); 32 | //We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache) 33 | $resultRaw->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); 34 | 35 | return $resultRaw->setContents($output); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Controller/Log/View.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('log_key', ''); 13 | $lines = $this->getRequest()->getParam('tail', 20); 14 | $file = $this->_qdbHelper->getLogFiles($fileKey); 15 | if ($file) { 16 | $output = $this->_qdbHelper->tailFile($file['path'], $lines); 17 | } else { 18 | $output = __('No log file.'); 19 | } 20 | 21 | $this->_view->loadLayout(); 22 | 23 | $resultRaw = $this->_resultRawFactory->create(); 24 | //We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache) 25 | $resultRaw->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); 26 | 27 | return $resultRaw->setContents($output); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Controller/Tab/Ajax.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister = $qdbHelperRegister; 18 | } 19 | 20 | 21 | /** 22 | * 23 | * @return \Magento\Backend\Model\View\Result\Page 24 | */ 25 | public function execute() 26 | { 27 | $blockName = $this->getRequest()->getParam('block', ''); 28 | 29 | try { 30 | $this->_view->loadLayout('quickdevbar'); 31 | 32 | $block = $this->_view->getLayout()->getBlock($blockName); 33 | if ($block) { 34 | if($block->getNeedLoadData()) { 35 | $this->qdbHelperRegister->loadDataFromFile(true); 36 | } 37 | $block->setIsUpdateCall(true); 38 | $output = $block->toHtml(); 39 | } else { 40 | $output = 'Cannot found block: '. $blockName; 41 | } 42 | } catch (Exception $e) { 43 | $output = $e->getMessage(); 44 | } 45 | 46 | $resultRaw = $this->_resultRawFactory->create(); 47 | //We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache) 48 | $resultRaw->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); 49 | 50 | return $resultRaw->setContents($output); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Controller/Tab/PhpInfo.php: -------------------------------------------------------------------------------- 1 | _layoutFactory->create() 15 | ->createBlock('ADM\QuickDevBar\Block\Tab\Content\PhpInfo') 16 | ->toHtml(); 17 | } catch (Exception $e) { 18 | $output = $e->getMessage(); 19 | } 20 | 21 | $resultRaw = $this->_resultRawFactory->create(); 22 | return $resultRaw->setContents($output); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Controller/Tab/Translation.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('type'); 15 | 16 | $output = $this->_layoutFactory->create() 17 | ->createBlock('ADM\QuickDevBar\Block\Tab\Content\Translation') 18 | ->setType($type) 19 | ->toHtml(); 20 | } catch (Exception $e) { 21 | $output = $e->getMessage(); 22 | } 23 | 24 | $resultRaw = $this->_resultRawFactory->create(); 25 | //We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache) 26 | $resultRaw->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); 27 | 28 | return $resultRaw->setContents($output); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Helper/Cookie.php: -------------------------------------------------------------------------------- 1 | cookieManager = $cookieManager; 19 | } 20 | 21 | public function isProfilerEnabled() 22 | { 23 | return (bool)$this->cookieManager->getCookie(self::COOKIE_NAME_PROFILER_ENABLED); 24 | } 25 | 26 | public function isProfilerBacktraceEnabled() 27 | { 28 | return (bool)$this->cookieManager->getCookie(self::COOKIE_NAME_PROFILER_BACKTRACE_ENABLED); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Helper/Debug.php: -------------------------------------------------------------------------------- 1 | $traceLine) { 17 | $traceFormated[] = sprintf('#%d %s(%d) %s->%s()', $i, $traceLine['file'], $traceLine['line'], $traceLine['class'], $traceLine['function']); 18 | 19 | } 20 | return implode($separator, $traceFormated); 21 | 22 | } 23 | 24 | public static function traceHtml() 25 | { 26 | return self::traceString('
'); 27 | } 28 | 29 | /** 30 | * @param array $trace 31 | * @return array 32 | */ 33 | public static function trace(array $trace=[], $skipLine = null) 34 | { 35 | if(empty($trace)) { 36 | $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); 37 | is_null($skipLine) && $skipLine=1; 38 | } 39 | 40 | $returnTrace = []; 41 | foreach ($trace as $i => $data) { 42 | if($i<$skipLine) { 43 | continue; 44 | } 45 | 46 | $className = '[class]'; 47 | if (isset($data['class']) && isset($data['function'])) { 48 | $className = $data['class']; 49 | 50 | if (isset($data['object']) && get_class($data['object']) != $data['class']) { 51 | $className = get_class($data['object']); 52 | } 53 | } 54 | if(preg_match('/Interceptor$/', $className)) { 55 | $className = '[interceptor]'; 56 | } 57 | 58 | $methodName = $data['function'] ?? '[function]'; 59 | $fileName = $data['file'] ?? '[file]'; 60 | $line = $data['line'] ?? '[line]'; 61 | 62 | $returnTrace[]= ['file'=>$fileName, 'line'=> $line, 'class'=>$className, 'function'=>$methodName]; 63 | } 64 | 65 | return $returnTrace; 66 | } 67 | 68 | /** 69 | * @see \Magento\Framework\Debug::getRootPath 70 | * 71 | * @return false|string 72 | */ 73 | public static function getRootPath() 74 | { 75 | if (self::$_filePath === null) { 76 | if (defined('BP')) { 77 | self::$_filePath = BP; 78 | } else { 79 | self::$_filePath = dirname(__DIR__); 80 | } 81 | } 82 | return self::$_filePath; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Helper/Register.php: -------------------------------------------------------------------------------- 1 | objectFactory = $objectFactory; 37 | $this->qdbHelper = $qdbHelper; 38 | $this->services = $services; 39 | } 40 | 41 | 42 | /** 43 | * @return bool 44 | */ 45 | public function dumpToFile() 46 | { 47 | $moduleName = $this->_getRequest()->getModuleName(); 48 | if($this->_getRequest() && $moduleName=='quickdevbar') { 49 | return false; 50 | } 51 | 52 | //Test magewire for Hyva calls 53 | $isAjax = ( $this->_getRequest()->isAjax() || $moduleName=='magewire'); 54 | foreach ($this->services as $serviceKey => $serviceObj) { 55 | //TODO: Filter keys on $isAjax 56 | if($isAjax && $serviceKey!='dumps') { 57 | continue; 58 | } 59 | 60 | $this->setRegisteredData($serviceKey, $serviceObj->pullData()); 61 | } 62 | $content = $this->registeredData->convertToJson(); 63 | $this->qdbHelper->setWrapperContent($content, $isAjax); 64 | } 65 | 66 | /** 67 | * 68 | */ 69 | public function loadDataFromFile($ajax = false) 70 | { 71 | $wrapperContent = $this->qdbHelper->getWrapperContent($ajax); 72 | $this->setRegisteredData($wrapperContent); 73 | $this->pullDataFromService = false; 74 | } 75 | 76 | 77 | 78 | /** 79 | * @param null $key 80 | * @return \Magento\Framework\DataObject|null 81 | */ 82 | public function getRegisteredData($key = '') 83 | { 84 | if($this->pullDataFromService && !empty($this->services[$key])) { 85 | return $this->services[$key]->pullData(); 86 | } elseif (empty($this->registeredData)) { 87 | $this->registeredData = $this->objectFactory->create(); 88 | } 89 | return $this->registeredData->getData($key); 90 | } 91 | 92 | public function setRegisteredData($key, $value = null) 93 | { 94 | if(is_null($this->registeredData)) { 95 | $this->registeredData = $this->objectFactory->create(); 96 | } 97 | 98 | $this->registeredData->setData($key, $value); 99 | } 100 | 101 | public function getContextData() 102 | { 103 | return $this->getRegisteredData('request_data'); 104 | } 105 | 106 | /** 107 | * @return \Magento\Framework\DataObject|null 108 | */ 109 | public function getObservers() 110 | { 111 | return $this->getRegisteredData('observers'); 112 | } 113 | 114 | /** 115 | * @return \Magento\Framework\DataObject|null 116 | */ 117 | public function getEvents() 118 | { 119 | return $this->getRegisteredData('events'); 120 | } 121 | 122 | /** 123 | * @return \Magento\Framework\DataObject|null 124 | */ 125 | public function getCollections() 126 | { 127 | return $this->getRegisteredData('collections'); 128 | } 129 | 130 | /** 131 | * @return \Magento\Framework\DataObject|null 132 | */ 133 | public function getModels() 134 | { 135 | return $this->getRegisteredData('models'); 136 | } 137 | 138 | /** 139 | * @return \Magento\Framework\DataObject|null 140 | */ 141 | public function getBlocks() 142 | { 143 | return $this->getRegisteredData('blocks'); 144 | } 145 | 146 | /** 147 | * @return \Magento\Framework\DataObject|null 148 | */ 149 | public function getLayoutHandles() 150 | { 151 | return $this->getRegisteredData('layout_handles'); 152 | } 153 | 154 | /** 155 | * @return \Magento\Framework\DataObject|null 156 | */ 157 | public function getLayoutHierarchy() 158 | { 159 | return $this->getRegisteredData('layout_tree_blocks_hierarchy'); 160 | } 161 | 162 | } 163 | 164 | -------------------------------------------------------------------------------- /Helper/Translate.php: -------------------------------------------------------------------------------- 1 | directoryList = $directoryList; 83 | } 84 | 85 | /** 86 | * Gets relative file path for absolute path. 87 | * 88 | * @param string $absolutePath 89 | * @return string 90 | */ 91 | protected function _getRelativeFilePath($absolutePath) 92 | { 93 | return str_replace($this->directoryList->getRoot() . DIRECTORY_SEPARATOR, '', $absolutePath); 94 | } 95 | 96 | /** 97 | * Load current theme translation 98 | * 99 | * @return $this 100 | */ 101 | protected function _loadThemeTranslation() 102 | { 103 | $file = $this->_getThemeTranslationFile($this->getLocale()); 104 | if ($file) { 105 | $relativePath = $this->_getRelativeFilePath($file); 106 | foreach ($this->_getFileData($file) as $key => $value) { 107 | if ($key === $value) { 108 | continue; 109 | } 110 | $this->_data['theme'][htmlspecialchars($key)] = [ 111 | 'file' => $relativePath, 112 | 'translation' => htmlspecialchars((string)$value) 113 | ]; 114 | } 115 | } 116 | return $this; 117 | } 118 | 119 | /** 120 | * Load translation dictionary from language packages. 121 | * 122 | * @todo It's also possible to get the filename of the language pack here, but generally only a single 123 | * language pack will be installed for a given locale. 124 | * @return $this 125 | * @throws LocalizedException 126 | */ 127 | protected function _loadPackTranslation() 128 | { 129 | $this->_data['pack'] = $this->packDictionary->getDictionary($this->getLocale()); 130 | return $this; 131 | } 132 | 133 | /** 134 | * Loading current translation from DB 135 | * 136 | * @return $this 137 | */ 138 | protected function _loadDbTranslation() 139 | { 140 | $this->_data['db'] = $this->_translateResource->getTranslationArray(null, $this->getLocale()); 141 | return $this; 142 | } 143 | 144 | 145 | /** 146 | * Load data from module translation files by list of modules 147 | * 148 | * @param array $modules 149 | * @return $this 150 | */ 151 | protected function loadModuleTranslationByModulesList(array $modules) 152 | { 153 | foreach ($modules as $module) { 154 | $moduleFilePath = $this->_getModuleTranslationFile($module, $this->getLocale()); 155 | $relativePath = $this->_getRelativeFilePath($moduleFilePath); 156 | foreach ($this->_getFileData($moduleFilePath) as $key => $value) { 157 | if ($key === $value) { 158 | continue; 159 | } 160 | $this->_data['module'][htmlspecialchars($key)] = [ 161 | 'file' => $relativePath, 162 | 'translation' => htmlspecialchars((string)$value) 163 | ]; 164 | } 165 | } 166 | return $this; 167 | } 168 | 169 | /** 170 | * Gets translation data by type. 171 | * 172 | * @param string $type 173 | * @return array 174 | * @throws LocalizedException 175 | */ 176 | public function getTranslationsByType($type) 177 | { 178 | if ($this->_hasLoaded === false) { 179 | $this->loadData(null, true); 180 | $this->_hasLoaded = true; 181 | } 182 | return isset($this->_data[$type]) ? $this->_data[$type] : []; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Model/Config/Source/Activate.php: -------------------------------------------------------------------------------- 1 | 0, 'label' => __('No')], 16 | ['value' => 1, 'label' => __('Yes')], 17 | ['value' => 2, 'label' => __('Yes with restriction')] 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Model/Config/Source/Area.php: -------------------------------------------------------------------------------- 1 | \Magento\Framework\App\Area::AREA_GLOBAL, 'label' => __('All')], 15 | ['value' => \Magento\Framework\App\Area::AREA_FRONTEND, 'label' => __('Frontend only')], 16 | ['value' => \Magento\Framework\App\Area::AREA_ADMINHTML, 'label' => __('Adminhtml only')] 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Model/Config/Source/DumperHandler.php: -------------------------------------------------------------------------------- 1 | 0, 'label' => __('No')], 16 | ['value' => 1, 'label' => __('Current page')], 17 | ['value' => 2, 'label' => __('Current page and ajax calls')] 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Model/Config/Source/Ide.php: -------------------------------------------------------------------------------- 1 | helper = $helper; 14 | } 15 | 16 | /** 17 | * Options getter 18 | * 19 | * @return array 20 | */ 21 | public function toOptionArray() 22 | { 23 | $ides = [['value' => '', 'label' => __('None')]]; 24 | foreach ($this->helper->getIdeList() as $ide=>$ideREgex) { 25 | $ides[] = ['value' => $ide, 'label' => __($ide)]; 26 | } 27 | $ides[] = ['value' => 'Custom', 'label' => __('Custom ...')]; 28 | 29 | return $ides; 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Observer/ControllerFrontSendResponseBeforeObserver.php: -------------------------------------------------------------------------------- 1 | qdbHelperRegister = $qdbHelperRegister; 27 | $this->qdbHelper = $qdbHelper; 28 | } 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public function execute(Observer $observer) 34 | { 35 | /** @var RequestHttp $request */ 36 | //TODO: Show $request = $observer->getRequest(); 37 | 38 | /** @var ResponseHttp $response */ 39 | //TODO: Show $response = $observer->getResponse(); 40 | $response = $observer->getResponse(); 41 | 42 | /** @var Headers $header */ 43 | //TODO: Show $response->getHeaders() 44 | 45 | //Remove QDB trace 46 | if(!$this->qdbHelper->isToolbarAccessAllowed()) { 47 | $newContent = preg_replace('//', '', $response->getContent()); 48 | $response->setContent($newContent); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Observer/LayoutGenerateBlocksAfterObserver.php: -------------------------------------------------------------------------------- 1 | serviceHandle = $serviceHandle; 32 | $this->serviceHierarchy = $serviceHierarchy; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function execute(Observer $observer) 39 | { 40 | /** @var \Magento\Framework\View\LayoutInterface $layout */ 41 | $layout = $observer->getLayout(); 42 | 43 | $this->serviceHandle->addLayoutHandles($this->getHandles($layout)); 44 | $this->serviceHierarchy->addLayoutHierarchy($this->getTreeBlocksHierarchy($layout)); 45 | } 46 | 47 | 48 | /** 49 | * @param \Magento\Framework\View\LayoutInterface $layout 50 | * @return array 51 | */ 52 | protected function getHandles($layout) 53 | { 54 | return $layout->getUpdate()->getHandles(); 55 | } 56 | 57 | 58 | 59 | /** 60 | * TODO: Find a better way to access the layout structure 61 | * @see: https://github.com/balloz/magento2-developer-toolbar/blob/master/Block/Panel/Layout.php 62 | * 63 | * But by now seems no other way 64 | * @see: https://github.com/magento/magento2/issues/748 65 | * 66 | * @param \Magento\Framework\View\LayoutInterface $layout 67 | * @return array|null 68 | * @throws \ReflectionException 69 | */ 70 | public function getTreeBlocksHierarchy($layout) 71 | { 72 | //$layout = $this->getLayout(); 73 | 74 | $reflection = new \ReflectionClass($layout); 75 | 76 | /** @var \Magento\Framework\View\Layout\Data\Structure $structure */ 77 | $structure = $reflection->getProperty('structure'); 78 | $structure->setAccessible(true); 79 | $structure = $structure->getValue($layout); 80 | 81 | if($elements = $layout->getXpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]')) { 82 | foreach ($elements as $element) { 83 | $blockName = $element->getBlockName(); 84 | if ($blockName !== false && $structure->hasElement($blockName)) { 85 | $this->nonCacheableBlocks[$blockName] = $blockName; 86 | } 87 | } 88 | } 89 | 90 | $this->_elements = $structure->exportElements(); 91 | if ($this->_elements) { 92 | $treeBlocks = $this->buildTreeBlocks($layout); 93 | } else { 94 | $treeBlocks = []; 95 | } 96 | 97 | return $treeBlocks; 98 | } 99 | 100 | /** 101 | * @param \Magento\Framework\View\LayoutInterface $layout 102 | * @param $name 103 | * @param $alias 104 | * @return array|void 105 | * @throws \ReflectionException 106 | */ 107 | protected function buildTreeBlocks($layout, $name = 'root', $alias = '') 108 | { 109 | $element = $this->getElementByName($name); 110 | if ($element) { 111 | $treeBlocks = [ 112 | 'name' =>$name, 113 | 'alias' =>$alias, 114 | 'type' => $element['type'], 115 | 'label' => isset($element['label']) ? $element['label'] : '', 116 | 'file' => '', 117 | 'class_name' => '', 118 | 'class_file' => '', 119 | 'cacheable' => empty($this->nonCacheableBlocks[$name]) 120 | ]; 121 | 122 | /** @var \Magento\Framework\View\Element\AbstractBlock|bool $block */ 123 | $block = $layout->getBlock($name); 124 | if (false !== $block) { 125 | 126 | $templateFile = ''; 127 | if($block->getTemplate()) { 128 | $templateFile = $block->getTemplateFile(); 129 | } 130 | 131 | 132 | $treeBlocks['file'] = $templateFile; 133 | $treeBlocks['class_name'] = get_class($block); 134 | if (!empty($treeBlocks['class_name'])) { 135 | $reflectionClass = new \ReflectionClass($block); 136 | $treeBlocks['class_file'] = $reflectionClass->getFileName(); 137 | } 138 | } 139 | 140 | if (isset($element['children'])) { 141 | foreach ($element['children'] as $childName => $childAlias) { 142 | $treeBlocks['children'][] = $this->buildTreeBlocks($layout, $childName, $childAlias); 143 | } 144 | } 145 | } else { 146 | $treeBlocks = []; 147 | } 148 | 149 | return $treeBlocks; 150 | } 151 | 152 | /** 153 | * 154 | * @param unknown_type $name 155 | * 156 | * @return Ambigous 157 | */ 158 | protected function getElementByName($name) 159 | { 160 | return (!empty($this->_elements[$name])) ? $this->_elements[$name] : false; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Observer/UpdateLayoutObserver.php: -------------------------------------------------------------------------------- 1 | qdbHelper = $qdbHelper; 21 | } 22 | 23 | /** 24 | * @inheritDoc 25 | */ 26 | public function execute(Observer $observer) 27 | { 28 | if(!$this->qdbHelper->isAjaxLoading()) { 29 | /** @var \Magento\Framework\View\Layout $layout */ 30 | $layout = $observer->getData('layout'); 31 | $layout->getUpdate()->addHandle('quickdevbar'); 32 | } 33 | return $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Plugin/Framework/App/Cache.php: -------------------------------------------------------------------------------- 1 | cacheService = $cacheService; 16 | } 17 | 18 | 19 | /** 20 | * @param CacheInterface $subject 21 | * @param string $identifier 22 | */ 23 | public function beforeLoad(CacheInterface $subject, string $identifier) 24 | { 25 | $this->cacheService->addCache('load', $identifier); 26 | } 27 | 28 | /** 29 | * @param CacheInterface $subject 30 | * @param string $data 31 | * @param string $identifier 32 | * @param array $tags 33 | * @param $lifeTime 34 | * @return void 35 | */ 36 | public function beforeSave( 37 | CacheInterface $subject, 38 | string $data, 39 | string $identifier, 40 | array $tags = [], 41 | $lifeTime = null 42 | ) { 43 | 44 | $this->cacheService->addCache('save', $identifier); 45 | } 46 | 47 | 48 | /** 49 | * @param CacheInterface $subject 50 | * @param string $identifier 51 | * @return void 52 | */ 53 | public function beforeRemove(CacheInterface $subject, string $identifier) 54 | { 55 | $this->cacheService->addCache('remove', $identifier); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Plugin/Framework/App/FrontController.php: -------------------------------------------------------------------------------- 1 | request = $request; 33 | $this->qdbHelper = $qdbHelper; 34 | $this->register = $register; 35 | $this->dumper = $dumper; 36 | } 37 | 38 | /** 39 | * Be careful, two usage: 40 | * - dumpToFile 41 | * - VarDumper::setHandler 42 | * 43 | * @param \Magento\Framework\AppInterface $subject 44 | * @return void 45 | */ 46 | public function beforeDispatch(\Magento\Framework\App\FrontControllerInterface $subject) 47 | { 48 | 49 | 50 | if(!$this->qdbHelper->isToolbarAccessAllowed()) { 51 | return; 52 | } 53 | 54 | if($this->qdbHelper->isAjaxLoading()) { 55 | register_shutdown_function([$this->register, 'dumpToFile']); 56 | } 57 | 58 | if($enabledHandler = $this->qdbHelper->getQdbConfig('handle_vardumper')) { 59 | if($this->request->isAjax() && $enabledHandler<2) { 60 | return; 61 | } 62 | $prevHandler = \Symfony\Component\VarDumper\VarDumper::setHandler($this->dumperHandler(...)); 63 | } 64 | } 65 | 66 | /** 67 | * @param $var 68 | * @return void 69 | */ 70 | protected function dumperHandler($var) 71 | { 72 | $cloner = new \Symfony\Component\VarDumper\Cloner\VarCloner(); 73 | $dumper = new \Symfony\Component\VarDumper\Dumper\HtmlDumper(); 74 | 75 | $dumper->setTheme('dark'); 76 | $dumpBt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)[2]; 77 | 78 | $ajaxReq = $this->request->isAjax() ? $this->request->getActionName() : null; 79 | 80 | $output = $dumpBt['function'] != 'dd'; 81 | $dumpOutput = $dumper->dump($cloner->cloneVar($var), $output); 82 | if($output) { 83 | $this->dumper->addDump($dumpOutput, $dumpBt, $ajaxReq); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Plugin/Framework/App/UpdateCookies.php: -------------------------------------------------------------------------------- 1 | cookieManager = $cookieManager; 32 | $this->cookieMetadataFactory = $cookieMetadataFactory; 33 | } 34 | 35 | /** 36 | * Set form key from the cookie. 37 | * 38 | * @return void 39 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 40 | */ 41 | public function beforeDispatch(): void 42 | { 43 | 44 | $cookieValue = $this->cookieManager->getCookie(Cookie::COOKIE_NAME_PROFILER_ENABLED); 45 | if ($cookieValue) { 46 | //TODO: Update cookie lifetime 47 | 48 | // $metadata = $this->cookieMetadataFactory 49 | // ->createPublicCookieMetadata() 50 | // ->setDuration(Cookie::COOKIE_DURATION); 51 | // 52 | // $this->cookieManager->setPublicCookie( 53 | // DbAdapter::COOKIE_NAME_PROFILER_ENABLED, 54 | // $cookieValue, 55 | // $metadata 56 | // ); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Plugin/Framework/Event/Invoker.php: -------------------------------------------------------------------------------- 1 | serviceObserver = $serviceObserver; 20 | } 21 | 22 | public function beforeDispatch($class, $observerConfig, $wrapper) 23 | { 24 | $this->serviceObserver->addObserver($observerConfig, $wrapper); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Plugin/Framework/Event/Manager.php: -------------------------------------------------------------------------------- 1 | serviceManager = $serviceManager; 19 | } 20 | 21 | /** 22 | * Before dispatch event 23 | * 24 | * Calls all observer callbacks registered for this event 25 | * and multiple observers matching event name pattern 26 | * 27 | * @param \Magento\Framework\Event\Manager $interceptor 28 | * @param string $eventName 29 | * @param array $data 30 | */ 31 | public function beforeDispatch($interceptor, $eventName, $data = []) 32 | { 33 | $this->serviceManager->addEvent($eventName, $data); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Plugin/Framework/Form/Element.php: -------------------------------------------------------------------------------- 1 | getOriginalData('path') 16 | && $subject->getOriginalData('id')) { 17 | $html .= '

' . $subject->getOriginalData('path') . '/' .$subject->getOriginalData('id') . '

'; 18 | } 19 | return $html; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Plugin/Framework/Http/Response.php: -------------------------------------------------------------------------------- 1 | _qdbHelperRegister = $qdbHelperRegister; 22 | } 23 | 24 | 25 | public function afterSendResponse(\Magento\Framework\HTTP\PhpEnvironment\Response $subject) { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Plugin/PageCache/FrontController/BuiltinPlugin.php: -------------------------------------------------------------------------------- 1 | cacheService = $cacheService; 25 | } 26 | 27 | /** 28 | * @param PageCache $subject 29 | * @param string $identifier 30 | */ 31 | public function beforeLoad(PageCache $subject, string $identifier) 32 | { 33 | $this->cacheService->addCache('load', $identifier); 34 | } 35 | 36 | /** 37 | * @param PageCache $subject 38 | * @param string $data 39 | * @param string $identifier 40 | * @param array $tags 41 | * @param $lifeTime 42 | * @return void 43 | */ 44 | public function beforeSave( 45 | PageCache $subject, 46 | string $data, 47 | string $identifier, 48 | array $tags = [], 49 | $lifeTime = null 50 | ) { 51 | 52 | $this->cacheService->addCache('save', $identifier); 53 | } 54 | 55 | 56 | /** 57 | * @param PageCache $subject 58 | * @param string $identifier 59 | * @return void 60 | */ 61 | public function beforeRemove(PageCache $subject, string $identifier) 62 | { 63 | $this->cacheService->addCache('remove', $identifier); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Plugin/Search/ResponseFactory.php: -------------------------------------------------------------------------------- 1 | elasticsearchService = $elasticsearchService; 15 | } 16 | 17 | /** 18 | * @param \Magento\Elasticsearch\SearchAdapter\ResponseFactory $subject 19 | * @param $result 20 | * @param $response 21 | * @return mixed 22 | */ 23 | public function afterCreate(\Magento\Elasticsearch\SearchAdapter\ResponseFactory $subject, $result, $response) 24 | { 25 | 26 | //dd($result); 27 | 28 | return $result; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Plugin/Search/SearchClient.php: -------------------------------------------------------------------------------- 1 | cookieHelper = $cookieHelper; 16 | } 17 | 18 | 19 | /** 20 | * @param Zend_Db_Adapter_Abstract $subject 21 | * @param array|bool|Zend_Config|Zend_Db_Profiler $profiler 22 | * @return array 23 | */ 24 | public function beforeSetProfiler(Zend_Db_Adapter_Abstract $subject, $profiler): array 25 | { 26 | if($this->cookieHelper->isProfilerEnabled()) { 27 | $profiler = [ 28 | 'enabled'=>1, 29 | 'class' => \ADM\QuickDevBar\Profiler\Db::class 30 | ]; 31 | } 32 | 33 | return [$profiler]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Profiler/App.php: -------------------------------------------------------------------------------- 1 | cookieHelper)) { 22 | //Mea culpa, mea maxima culpa 23 | $objectManager = ObjectManager::getInstance(); 24 | $this->cookieHelper = $objectManager->create(\ADM\QuickDevBar\Helper\Cookie::class); 25 | } 26 | 27 | return $this->cookieHelper->isProfilerBacktraceEnabled(); 28 | } 29 | 30 | 31 | /** 32 | * {@inheritdoc } 33 | */ 34 | public function queryStart($queryText, $queryType = null) 35 | { 36 | $keyQuery = parent::queryStart($queryText, $queryType); 37 | if($keyQuery && $this->getBacktraceQuery()) { 38 | $this->queryBacktrace[$keyQuery] = Debug::trace([], 5); 39 | } 40 | return $keyQuery; 41 | } 42 | 43 | public function getQueryBt($keyQuery) 44 | { 45 | return $this->queryBacktrace[$keyQuery] ?? []; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developer Toolbar for Magento2 🚀 2 | 3 | ---- 4 | 5 | [![Code Climate](https://codeclimate.com/github/vpietri/magento2-developer-quickdevbar/badges/gpa.svg)](https://codeclimate.com/github/vpietri/magento2-developer-quickdevbar) 6 | [![Total Downloads](https://poser.pugx.org/vpietri/adm-quickdevbar/downloads)](https://packagist.org/packages/vpietri/adm-quickdevbar) 7 | 8 | ## Table of Content 9 | 10 | * [Overview](#Overview) 11 | * [Requirement](#Requirement) 12 | * [About](#About) 13 | * [Panels](#Panels) 14 | * [Screenshots](#Screenshots) 15 | * [Installation](#Installation) 16 | * [Manual](#Manual) 17 | * [Composer](#Composer) 18 | * [Modman](#Modman) 19 | * [Setup](#Setup) 20 | * [URI File to IDE](#URI-File-to-IDE) 21 | * [Sponsors](#Sponsors) 22 | * [Documentation](#Documentation) 23 | * [Credits](#Credits) 24 | 25 | ## Overview 26 | 27 | ✨ With the Magento 2.4.7 compatibility, and the vanilla javascript refactoring comes the compatibility with Hyvä and Breeze themes. 28 | 29 | 🎁 Till compatible with Full page cache and fit coding standard :sparkles: 30 | Functionalities like VarDumper are unforced and SQL profiler backtrace is only on demand. See more [Changelog](doc/Changelog.md) . 31 | 32 | ## Requirement 33 | 34 | Supported versions: Magento 2.4.x till 2.4.7 but should work with lower version. 35 | See composer.json for other requirements. 36 | 37 | ## About 38 | 39 | Hope this debug toolbar can speed up Magento2 development module. Any feedback and idea to improve this toolbar will be appreciated :beers: so get in touch via the [issue tracker on GitHub](https://github.com/vpietri/magento2-developer-quickdevbar/issues). Feel free to fork and pull request. 40 | The structure of this toolbar is extremely simple you just need to add a new block in the layout to get your tab running. 41 | 42 | ### Panels 43 | 44 | - Info : Main informations about controller, route, action and store. Search on core config data. Dedicated tab output for local and global phpinfo. 45 | - Design : List handles called and display layout structure of nested blocks and containers 46 | - Profile : View current observers, all events dispatched, collections and models loaded, plugins instanciated, preferences, cache hits 47 | - Queries : Statistics about executed queries and detailed query listing with syntax highlighting of main SQL keywords 48 | - Logs : Display log files with ability to reset these files 49 | - Dump : Catch all dump() in code 50 | - Actions : Easily toggle template hints and inline translation and flush cache 51 | - Translation : Quickly see module, pack,theme and DB translations 52 | - Help : Show module version and link to github 53 | 54 | ### Screenshots 55 | 56 | - Info tab 57 | ![](doc/images/qdb_screen_request.png) 58 | 59 | - Queries Tab 60 | ![](doc/images/qdb_screen_queries.png) 61 | 62 | - Profile Tab 63 | ![](doc/images/qdb_screen_dispatch.png) 64 | 65 | - Theme chooser 66 | ![](doc/images/qdb_screen_dark.png) 67 | 68 | ## Installation 69 | 70 | ### Manual 71 | 72 | - Download zip file of the last version of this extension under release tab 73 | - Extract files in the Magento root directory in the folder app/code/ADM/QuickDevBar 74 | - Enable the extension 75 | ``` 76 | php bin/magento --clear-static-content module:enable ADM_QuickDevBar 77 | ``` 78 | - Upgrade Magento setup 79 | ``` 80 | php bin/magento setup:upgrade 81 | ``` 82 | 83 | ### Composer 84 | 85 | In the Magento root directory 86 | 87 | - Install the module 88 | ``` 89 | composer require vpietri/adm-quickdevbar --dev 90 | php bin/magento module:enable ADM_QuickDevBar 91 | php bin/magento setup:upgrade 92 | ``` 93 | 94 | ### Modman 95 | 96 | In the Magento root directory 97 | 98 | - Install the module 99 | ``` 100 | modman clone git@github.com:vpietri/magento2-developer-quickdevbar.git 101 | php bin/magento module:enable ADM_QuickDevBar 102 | php bin/magento setup:upgrade 103 | ``` 104 | 105 | ### Setup 106 | 107 | The toolbar is displayed by default if your web server is on your local development environment. 108 | 109 | You can force activation via command line 110 | ``` 111 | php bin/magento dev:quickdevbar:enable 112 | ``` 113 | and activate full sql backtrace 114 | ``` 115 | php bin/magento dev:quickdevbar:enable --sql-qdb-profiler 116 | ``` 117 | 118 | Or via the standard configuration in the Advanced/Developer/Quick dev bar section. 119 | 120 | If you do not see the toolbar you should either force activation by filling your IP in the field "Allowed IPs" and fill a matching pattern of you user-agent in the field "Allowed user-agent pattern" if it's needed. 121 | ![](doc/images/qdb_screen_config_ko.png) 122 | 123 | 124 | #### URI File to IDE 125 | 126 | (Beta) In PhpStorm you can use **IDE Remote Control** to open file 127 | 128 | https://plugins.jetbrains.com/plugin/19991-ide-remote-control 129 | 130 | ![](doc/images/phpstorm_debugger.png) 131 | 132 | ## Sponsors 133 | 134 | [![Sansec.io](https://warden.dev/img/sponsors/sansec.svg)](https://www.sansec.io/) 135 | 136 | Add your logo on Github Sponsors 137 | 138 | ## Documentation 139 | 140 | - [Changelog](doc/Changelog.md) 141 | - ~~You can extend this toolbar with your own tabs, a [sample module](https://github.com/vpietri/magento2-brandnew_quikdevsample) is available.~~ (refactoring coming soon) 142 | 143 | ## Credits 144 | 145 | - [Jens Törnell](https://github.com/jenstornell) 146 | -------------------------------------------------------------------------------- /Service/App/Cache.php: -------------------------------------------------------------------------------- 1 | cacheEvents[$identifier] = ['load'=>0, 'save'=>0, 'remove'=>0]; 17 | } 18 | $this->cacheEvents[$identifier][$event]++; 19 | } 20 | 21 | public function pullData() 22 | { 23 | return $this->cacheEvents; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Service/Config.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | } 20 | 21 | /** 22 | * @inheritDoc 23 | */ 24 | public function pullData() 25 | { 26 | $preferences = array(); 27 | foreach ($this->config->getPreferences() as $type => $preference) { 28 | if(preg_match('/^(\w+\\\\\w+)/', $type, $matches)) { 29 | if(strpos($preference, $matches[1]) === false) { 30 | $preferences[$type] = $preference; 31 | } 32 | } 33 | } 34 | 35 | return $preferences; 36 | } 37 | } -------------------------------------------------------------------------------- /Service/Dumper.php: -------------------------------------------------------------------------------- 1 | dumps; 17 | } 18 | 19 | public function addDump(string $output, array $bt, $ajaxReq = null) 20 | { 21 | $this->dumps[] = ['dump'=>$output, 'bt'=> $bt, 'ajaxReq'=> $ajaxReq]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Service/Elasticsearch.php: -------------------------------------------------------------------------------- 1 | classType = $classType; 21 | } 22 | 23 | public function addClassToRegisterData($data) 24 | { 25 | $class = !empty($data[$this->classType]) ? get_class($data[$this->classType]) : false; 26 | 27 | if($class) { 28 | if (empty($this->listByClass[$class])) { 29 | $this->listByClass[$class] = ['class'=>$class, 'nbr'=>0]; 30 | } 31 | $this->listByClass[$class]['nbr']++; 32 | } 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function pullData() 39 | { 40 | return $this->listByClass; 41 | } 42 | } -------------------------------------------------------------------------------- /Service/Event/Manager.php: -------------------------------------------------------------------------------- 1 | services = $services; 22 | } 23 | 24 | /** 25 | * @param $eventName 26 | * @param $data 27 | */ 28 | public function addEvent($eventName, $data) 29 | { 30 | if (!isset($this->events[$eventName])) { 31 | $this->events[$eventName] = ['event'=>$eventName, 32 | 'nbr'=>0, 33 | 'args'=>array_keys($data) 34 | ]; 35 | } 36 | $this->events[$eventName]['nbr']++; 37 | if(!empty($this->services[$eventName])) { 38 | $this->services[$eventName]->addClassToRegisterData($data); 39 | } 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function pullData() 46 | { 47 | return $this->events; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Service/Layout/Handle.php: -------------------------------------------------------------------------------- 1 | handles; 19 | } 20 | 21 | public function addLayoutHandles(array $getHandles) 22 | { 23 | $this->handles = $getHandles; 24 | } 25 | } -------------------------------------------------------------------------------- /Service/Layout/Hierarchy.php: -------------------------------------------------------------------------------- 1 | treeBlocksHierarchy; 18 | } 19 | 20 | public function addLayoutHierarchy(array $getTreeBlocksHierarchy) 21 | { 22 | $this->treeBlocksHierarchy = $getTreeBlocksHierarchy; 23 | } 24 | } -------------------------------------------------------------------------------- /Service/Module.php: -------------------------------------------------------------------------------- 1 | moduleList = $moduleList; 16 | } 17 | 18 | 19 | public function pullData() 20 | { 21 | return $this->moduleList->getAll(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Service/Observer.php: -------------------------------------------------------------------------------- 1 | observers; 20 | } 21 | 22 | public function addObserver($observerConfig, $wrapper) 23 | { 24 | $data = $observerConfig; 25 | 26 | if (isset($data['disabled']) && true === $data['disabled']) { 27 | return; 28 | } 29 | 30 | 31 | $data['event'] = $wrapper->getEvent()->getName(); 32 | 33 | $key = crc32(json_encode($data)); 34 | if (isset($this->observers[$key])) { 35 | $this->observers[$key]['call_number']++; 36 | } else { 37 | $data['call_number']=1; 38 | $this->observers[$key] = $data; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Service/Plugin.php: -------------------------------------------------------------------------------- 1 | pluginList = $pluginList; 22 | } 23 | 24 | public function pullData() 25 | { 26 | if ($this->pluginsByTypes === null) { 27 | $this->pluginsByTypes = []; 28 | 29 | $reflection = new \ReflectionClass($this->pluginList); 30 | 31 | $processed = $reflection->getProperty('_processed'); 32 | $processed->setAccessible(true); 33 | $processed = $processed->getValue($this->pluginList); 34 | 35 | $inherited = $reflection->getProperty('_inherited'); 36 | $inherited->setAccessible(true); 37 | $inherited = $inherited->getValue($this->pluginList); 38 | 39 | 40 | $types = [DefinitionInterface::LISTENER_BEFORE=>'before', 41 | DefinitionInterface::LISTENER_AROUND=>'around', 42 | DefinitionInterface::LISTENER_AFTER=>'after']; 43 | 44 | /** 45 | * @see: Magento/Framework/Interception/PluginList/PluginList::_inheritPlugins($type) 46 | */ 47 | foreach ($processed as $currentKey => $processDef) { 48 | if (preg_match('/^(.*)_(.*)___self$/', $currentKey, $matches) or preg_match('/^(.*?)_(.*?)_(.*)$/', $currentKey, $matches)) { 49 | $type= $matches[1]; 50 | $method= $matches[2]; 51 | if (!empty($inherited[$type])) { 52 | foreach ($processDef as $keyType => $pluginsNames) { 53 | if (!is_array($pluginsNames)) { 54 | $pluginsNames = [$pluginsNames]; 55 | } 56 | 57 | foreach ($pluginsNames as $pluginName) { 58 | if (!empty($inherited[$type][$pluginName])) { 59 | $this->pluginsByTypes[] = ['type'=>$type, 'plugin'=>$inherited[$type][$pluginName]['instance'], 'plugin_name'=>$pluginName, 'sort_order'=> $inherited[$type][$pluginName]['sortOrder'], 'method'=>$types[$keyType].ucfirst($method)]; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | return $this->pluginsByTypes; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Service/Request.php: -------------------------------------------------------------------------------- 1 | requestHttp = $requestHttp; 47 | $this->appState = $appState; 48 | $this->productMetadata = $productMetadata; 49 | $this->frontNameResolver = $frontNameResolver; 50 | $this->session = $session; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function pullData() 57 | { 58 | $request = $this->requestHttp; 59 | $requestData = []; 60 | $requestData[] = ['name' => 'Date sys', 'value' => date("Y-m-d H:i:s")]; 61 | $requestData[] = ['name' => 'Base Url', 'value' => $request->getDistroBaseUrl(), 'is_url' => true]; 62 | $requestData[] = ['name' => 'Path Info', 'value' => $request->getPathInfo()]; 63 | $requestData[] = ['name' => 'Module Name', 'value' => $request->getControllerModule() ?: self::LABEL_CACHE_HIDDEN]; 64 | $requestData[] = ['name' => 'Controller', 'value' => $request->getControllerName() ?: self::LABEL_CACHE_HIDDEN]; 65 | $requestData[] = ['name' => 'Action', 'value' => $request->getActionName() ?: self::LABEL_CACHE_HIDDEN]; 66 | $requestData[] = ['name' => 'Full Action', 'value' => $request->getFullActionName() ?: self::LABEL_CACHE_HIDDEN]; 67 | $requestData[] = ['name' => 'Route', 'value' => $request->getRouteName() ?: self::LABEL_CACHE_HIDDEN]; 68 | $requestData[] = ['name' => 'Area', 'value' => $this->appState->getAreaCode()]; 69 | 70 | if ($this->session->isLoggedIn()) { 71 | $requestData[] = ['name' => 'Logged user', 'value' => $this->session->getCustomer()->getEmail()]; 72 | $requestData[] = ['name' => 'Group id', 'value' => $this->session->getCustomer()->getGroupId()]; 73 | } 74 | $requestData[] = ['name' => 'Session Id', 'value' => $this->session->getSessionId()]; 75 | 76 | if ($request->getBeforeForwardInfo()) { 77 | $requestData[] = ['name' => 'Before Forward', 'value' => $request->getBeforeForwardInfo()]; 78 | } 79 | 80 | if ($request->getParams()) { 81 | $requestData[] = ['name' => 'Params', 'value' => $request->getParams()]; 82 | } 83 | $requestData[] = ['name' => 'Client IP', 'value' => $request->getClientIp()]; 84 | $requestData[] = ['name' => 'Magento', 'value' => $this->productMetadata->getVersion()]; 85 | $requestData[] = ['name' => 'Mage Mode', 'value' => $this->appState->getMode()]; 86 | 87 | $requestData[] = ['name' => 'Backend', 'value' => $request->getDistroBaseUrl() . $this->frontNameResolver->getFrontName(), 'is_url' => true]; 88 | 89 | return $requestData; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Service/Sql.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 32 | } 33 | 34 | public function pullData() 35 | { 36 | if(is_null($this->sqlProfilerData)) { 37 | $this->sqlProfilerData = $this->initSqlProfilerData(); 38 | } 39 | return $this->sqlProfilerData; 40 | } 41 | 42 | protected function initSqlProfilerData() 43 | { 44 | $longestQueryTime = 0; 45 | $shortestQueryTime = 100000; 46 | $longestQuery = ''; 47 | $allQueries = []; 48 | if ($this->sqlProfiler === null) { 49 | $this->sqlProfiler = new \Zend_Db_Profiler(); 50 | if ($this->resource !== null) { 51 | $this->sqlProfiler = $this->resource->getConnection('read')->getProfiler(); 52 | 53 | $this->useQdbProfiler = method_exists($this->sqlProfiler, 'getQueryBt'); 54 | 55 | 56 | if ($this->sqlProfiler->getQueryProfiles() && is_array($this->sqlProfiler->getQueryProfiles())) { 57 | foreach ($this->sqlProfiler->getQueryProfiles() as $key => $query) { 58 | if ($query->getElapsedSecs() > $longestQueryTime) { 59 | $longestQueryTime = $query->getElapsedSecs(); 60 | $longestQuery = $query->getQuery(); 61 | } 62 | if ($query->getElapsedSecs() < $shortestQueryTime) { 63 | $shortestQueryTime = $query->getElapsedSecs(); 64 | } 65 | 66 | $allQueries[] = ['sql' => $query->getQuery(), 67 | 'params' => $query->getQueryParams(), 68 | 'time' => $query->getElapsedSecs(), 69 | 'grade' => 'medium', 70 | 'bt' => $this->useQdbProfiler ? $this->sqlProfiler->getQueryBt($key) : null 71 | ]; 72 | } 73 | } 74 | } 75 | } 76 | 77 | if(!$this->sqlProfiler->getTotalNumQueries()) { 78 | return []; 79 | } 80 | 81 | $numQueriesByType = []; 82 | foreach( [$this->sqlProfiler::INSERT, 83 | $this->sqlProfiler::UPDATE, 84 | $this->sqlProfiler::DELETE, 85 | $this->sqlProfiler::SELECT, 86 | $this->sqlProfiler::QUERY] as $type) { 87 | $numQueriesByType[$type] = $this->sqlProfiler->getTotalNumQueries($type); 88 | 89 | } 90 | 91 | $totalNumQueries = $this->sqlProfiler->getTotalNumQueries(); 92 | $totalElapsedSecs = $this->sqlProfiler->getTotalElapsedSecs(); 93 | $average = $totalElapsedSecs/$totalNumQueries; 94 | 95 | return [ 96 | 'all_queries' => $this->computeQueryGrade($allQueries, $shortestQueryTime, $longestQueryTime, $totalNumQueries, $average), 97 | 'longest_query_time' => $longestQueryTime, 98 | 'shortest_query_time' => $shortestQueryTime, 99 | 'longest_query' => $longestQuery, 100 | 'total_elapsed_secs' => $totalElapsedSecs, 101 | 'total_num_queries' => $totalNumQueries, 102 | 'num_queries_per_second' => floor($totalNumQueries/$totalElapsedSecs), 103 | 'average' => $average, 104 | 'total_num_queries_by_type' => $numQueriesByType, 105 | // 'show_backtrace' => $this->useQdbProfiler 106 | ]; 107 | } 108 | 109 | protected function computeQueryGrade($allQueries, $shortestQueryTime, $longestQueryTime, $totalNumQueries, $average) 110 | { 111 | $squareSum = 0; 112 | foreach ($allQueries as $index => $query) { 113 | $squareSum = pow($query['time'] - $average, 2); 114 | } 115 | 116 | $standardDeviation = 0; 117 | if ($squareSum and $totalNumQueries) { 118 | $standardDeviation = sqrt($squareSum/$totalNumQueries); 119 | } 120 | 121 | foreach ($allQueries as $index => $query) { 122 | if ($query['time']<($shortestQueryTime+2*$standardDeviation)) { 123 | $allQueries[$index]['grade'] = 'good'; 124 | } elseif ($query['time']>($longestQueryTime-2*$standardDeviation)) { 125 | $allQueries[$index]['grade'] = 'bad'; 126 | } 127 | } 128 | 129 | return $allQueries; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vpietri/adm-quickdevbar", 3 | "description": "QuickDevBar is a developer toolbar for magento 2", 4 | "type": "magento2-module", 5 | "license": [ 6 | "OSL-3.0", 7 | "AFL-3.0" 8 | ], 9 | "version": "0.3.2", 10 | "require": { 11 | "magento/magento-composer-installer": "*" 12 | }, 13 | "authors": [ 14 | { 15 | "name": "Vincent Pietri", 16 | "homepage": "http://www.vincent-pietri.fr/", 17 | "role": "Developer" 18 | } 19 | ], 20 | "autoload": { 21 | "files": [ 22 | "registration.php" 23 | ], 24 | "psr-4": { 25 | "ADM\\QuickDevBar\\": "" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /doc/Changelog.md: -------------------------------------------------------------------------------- 1 | Changelog: Quick Developer Toolbar for Magento2 2 | ==================================== 3 | 0.3.2 4 | * Fix adminhtml renderer 5 | * Add doc screenshots 6 | 7 | 8 | 0.3.1 9 | * Fix the typo in the XML closing tag within the comment, thanks to [hgati](https://github.com/vpietri/magento2-developer-quickdevbar/pull/86) 10 | 11 | 0.3.0 12 | * Compatibility Magento 2.4.7 with strict CSP policy 13 | * Hyva and Breeze compatibility (vanilla JS) 14 | * Dynamic SQL profiling with backtrace 15 | * Code refactoring 16 | 17 | 0.2.3 18 | * Catch VarDumper in ajax calls 19 | * Code refactoring 20 | 21 | 0.2.2 22 | * Use config to handle VarDumper 23 | * Move handler for VarDumper 24 | * Do not catch dd() 25 | 26 | 0.2.1 27 | * Remove hard log /tmp/debug.log 28 | 29 | 0.2.0 30 | * Full compatibility with FPC 31 | * Full code refactoring 32 | * Handle Symfony VarDumper 33 | * Command to activate SQL backtrace 34 | * New tabs added: Preferences, Cache (PageCache) hits, Debug 35 | * Skin chooser 36 | * Css cleaning 37 | * Remove module fingerprint from page is not allowed 38 | * Open file from url to IDE 39 | 40 | 41 | 0.1.20 42 | * Add command to enable/disable toolbar and activate sql profiler 43 | * Remove Deprecated Warning in Translate Helper, thanks [lefte](https://github.com/vpietri/magento2-developer-quickdevbar/pull/69) and [josue-rmoya](https://github.com/vpietri/magento2-developer-quickdevbar/pull/66) 44 | * Fix must be of type Countable, thanks @bbakalov, closes #70 45 | 46 | 0.1.19 47 | * Fix M244 compatibility, removed legacy text from readme, thanks [asannikov](https://github.com/vpietri/magento2-developer-quickdevbar/pull/63) 48 | * Compatibility 2.4.4, Closes #60, closes #62 49 | 50 | 0.1.18.1 51 | * Remove version from composer 52 | 53 | 0.1.18 54 | * Fix js error 'base is not a constructor', thanks to @asalgado0391, closes #53 55 | * Fix call appConfig->getValue, closes #26 56 | 57 | 0.1.17 58 | * Fixed report from Content Security Policies module. Thanks to [pikulsky and r-martins](https://github.com/vpietri/magento2-developer-quickdevbar/pull/47) 59 | 60 | 0.1.16 61 | * Replace Deprecated Function. Thanks to [lefte](https://github.com/vpietri/magento2-developer-quickdevbar/pull/42) 62 | 63 | 0.1.15 64 | * Replace Deprecated Function. Thanks to [lefte](https://github.com/vpietri/magento2-developer-quickdevbar/pull/40) 65 | * Support for modman. Thanks to [MagePsycho](https://github.com/vpietri/magento2-developer-quickdevbar/pull/37) 66 | * PHP < 7.0 backward compatible. Thanks to [tuyennn](https://github.com/vpietri/magento2-developer-quickdevbar/pull/33) 67 | 68 | 0.1.14 69 | * Specify area where display toolbar. Thanks to [zzarazza] and [tuyennn] 70 | * Small bug fix for users using a proxy with Magento 2. Thanks to [NateSwanson7](https://github.com/vpietri/magento2-developer-quickdevbar/pull/31) 71 | * Fix jquery error dependency in Admin. Thanks to [hoangnm89](https://github.com/vpietri/magento2-developer-quickdevbar/pull/29) 72 | * Update jquery.tablesorter.min.js. Thanks to [sebfie](https://github.com/vpietri/magento2-developer-quickdevbar/pull/27) 73 | 74 | 0.1.13 75 | * Memorize toolbar state 76 | * Fix on require_js 77 | * Add translation tab. Thanks to [danslo](https://github.com/vpietri/magento2-developer-quickdevbar/pull/23) 78 | * Check whether event observer is disabled. Thanks to [tufahu](https://github.com/vpietri/magento2-developer-quickdevbar/pull/24) 79 | 80 | 81 | 0.1.12 82 | * Fix Wbug when profiler is disabled. Thanks to [rakibabu](https://github.com/vpietri/magento2-developer-quickdevbar/pull/16) 83 | 84 | 0.1.11 85 | * Improve layout view with a js tree 86 | * Add css lazy load to keep toolbar completly hidden when disabled 87 | * Fix compatibility with Magento 2.1.3. Thanks to [Koc](https://github.com/vpietri/magento2-developer-quickdevbar/pull/15) 88 | * Fix ajax tab load bug 89 | 90 | 0.1.10 91 | * Fix conflicts in backend with magento tabs widget 92 | * Improve profiler detection 93 | 94 | 0.1.9 95 | * Add config list tab 96 | * Use common ajax controller 97 | 98 | 0.1.8 - 22 Jul 2016 99 | * Add plugin list tab 100 | * Fix bug on action hints. Thanks to [adpeate](https://github.com/vpietri/magento2-developer-quickdevbar/pull/7) 101 | 102 | 0.1.7 - 7 Jul 2016 103 | * Configuration section improvement 104 | * Code refactoring 105 | * Authorize IPv6 localhost. Thanks to [Dayssam](https://github.com/vpietri/magento2-developer-quickdevbar/pull/5) 106 | 107 | 0.1.6.1 - 30 Jun 2016 108 | * Fix compatibility bugs with Magento 2.1 109 | 110 | 0.1.6 - 17 Jun 2016 111 | * UI improvement 112 | * Add Block subtab 113 | * Add icon from [iconsdb.com](http://www.iconsdb.com/) 114 | 115 | 0.1.5.2 - 22 Apr 2016 116 | * Fit to PHP coding standards 117 | 118 | 0.1.5.1 - 25 Feb 2016 119 | * Fix tab bug in backoffice 120 | 121 | 0.1.5 - 12 Dec 2015 122 | * Back office toolbar 123 | * Reorganize tabs 124 | * Add list of collection and model instanciated 125 | * Add [Christian Bach's tablesorter plugin](https://github.com/christianbach/tablesorter) 126 | 127 | 0.1.4 - 6 Dec 2015 128 | * Fix bug on composer.json with registration.php 129 | * Clean layout display 130 | 131 | 0.1.3 - 4 Dec 2015 132 | * Compatibility with Magento 2.0.0 Publication 133 | * Add action tab (Template hints, Translate inline, Flush Cache Storage) 134 | * Controller structure cleaning 135 | 136 | 0.1.2 - 29 Jun 2015 137 | * Add sub-tab and reorganize existing tabs 138 | 139 | 0.1.1 - 19 Jun 2015 140 | * Javascript cleaning to meet coding standards 141 | * Add [sunnywalker/filterTable](https://github.com/sunnywalker/jQuery.FilterTable) 142 | * Fix bugs on the log screen 143 | * Css improvements 144 | 145 | 0.0.1 - 18 Jun 2015 146 | * module initialization 147 | -------------------------------------------------------------------------------- /doc/images/phpstorm_debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/phpstorm_debugger.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_config_ko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_config_ko.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_dark.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_dispatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_dispatch.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_dispatch.youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_dispatch.youtube.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_queries.png -------------------------------------------------------------------------------- /doc/images/qdb_screen_request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/doc/images/qdb_screen_request.png -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | ADM\QuickDevBar\Model\Config\Source\Activate 10 | "Yes with restriction" means that your web server is your localhost or your ip is in "Allowed ips" or your user-agent is matching "Allowed user-agent pattern" 11 | 12 | 13 | 14 | ADM\QuickDevBar\Block\Adminhtml\System\Config\Form\Fieldset\IsEnabled 15 | 16 | 2 17 | 18 | 19 | 20 | 21 | Comma separated list. If empty check only localhost IPs. 22 | Magento\Developer\Model\Config\Backend\AllowedIps 23 | 24 | 2 25 | 26 | 27 | 28 | 29 | Pattern that will be check (with preg_match) in the user agent sent by your browser. You should use a user agent switcher extension to add a specific pattern. 30 | 31 | 2 32 | 33 | 34 | 35 | 36 | ADM\QuickDevBar\Model\Config\Source\Area 37 | 38 | 1,2 39 | 40 | 41 | 42 | 43 | Some files can be open via hyperlink by your IDE 44 | ADM\QuickDevBar\Model\Config\Source\Ide 45 | 46 | 1,2 47 | 48 | 49 | 50 | 51 | 52 |
  • %1$s absolute path of magento root
  • 53 |
  • %2$s relative file path from magento root (magento may run in a docker and have a mapped path on host)
  • 54 |
  • %3$d line number
  • 55 | Sample for phpstorm, with standard path: http://127.0.0.1:63342/api/file/%2$s:%3$d]]>
    56 | 57 | 1,2 58 | Custom 59 | 60 |
    61 | 62 | 63 | 64 | ADM\QuickDevBar\Model\Config\Source\DumperHandler 65 | 66 | 1,2 67 | 68 | 69 |
    70 |
    71 |
    72 |
    73 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 2 7 | global 8 | memorize 9 | 1 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/csp_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | github.blog 7 | 8 | 9 | 10 | 11 | 12 | http://127.0.0.1:63342 13 | http://127.0.0.1:34567 14 | 15 | 16 | 17 | 18 | 19 | SgvvFr+EuIbNctHUihu7npbOfmUqDK4M7sA5gSEUN48= 20 | hnovV8f+WhZ9ebCoXRqdN5AB0kK8XLdG0m1A1iJwEnY= 21 | qNCjKiE6ytQPnSBywIMEXvzw+Ar3jDurzNb87EqtM5E= 22 | cGE9z/R3oV10mtE4AQPzTjSc5JWIKk4tMjDOs+W5Y+Q= 23 | cGE9z/R3oV10mtE4AQPzTjSc5JWIKk4tMjDOs+W5Y+Q= 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ADM\QuickDevBar\Service\Plugin 45 | ADM\QuickDevBar\Service\Sql 46 | ADM\QuickDevBar\Service\Event\Manager 47 | ADM\QuickDevBar\Service\Collection 48 | ADM\QuickDevBar\Service\Model 49 | ADM\QuickDevBar\Service\Block 50 | ADM\QuickDevBar\Service\Request 51 | ADM\QuickDevBar\Service\Observer 52 | ADM\QuickDevBar\Service\Layout\Handle 53 | ADM\QuickDevBar\Service\Layout\Hierarchy 54 | ADM\QuickDevBar\Service\Config 55 | ADM\QuickDevBar\Service\Module 56 | ADM\QuickDevBar\Service\Dumper 57 | ADM\QuickDevBar\Service\App\Cache 58 | ADM\QuickDevBar\Service\Elasticsearch 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ADM\QuickDevBar\Service\Collection 67 | ADM\QuickDevBar\Service\Model 68 | ADM\QuickDevBar\Service\Block 69 | 70 | 71 | 72 | 73 | 74 | 75 | collection 76 | 77 | 78 | 79 | 80 | object 81 | 82 | 83 | 84 | 85 | block 86 | 87 | 88 | 89 | 90 | 91 | 92 | ADM\QuickDevBar\Console\Command\EnableToolBar 93 | ADM\QuickDevBar\Console\Command\DisableToolBar 94 | ADM\QuickDevBar\Console\Command\Database 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /etc/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 21 | 22 | -------------------------------------------------------------------------------- /etc/frontend/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /modman: -------------------------------------------------------------------------------- 1 | ./ app/code/ADM/QuickDevBar -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /view/base/templates/tab/action.phtml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
    Change QDB theme 11 | 16 |
    Template Path Hints for Storefront
    Template Path Hints for Admin
    Add Block Names to Hints
    Translate inline
    Flush Cache Storage
    48 | 49 | 60 | -------------------------------------------------------------------------------- /view/base/templates/tab/design/block.phtml: -------------------------------------------------------------------------------- 1 | 4 | getBlocks()):?> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | getBlocks() as $blockInfo): ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    BlockCall Number
    htmlFormatClass($blockInfo['class']); ?>
    24 | 25 | -------------------------------------------------------------------------------- /view/base/templates/tab/design/handles.phtml: -------------------------------------------------------------------------------- 1 | 4 | getHandles()):?> 5 | 6 | 7 | 8 | 14 | 15 |
    9 | getHandles() as $handle): ?> 10 |
    11 | 12 |
    13 |
    16 | 17 | -------------------------------------------------------------------------------- /view/base/templates/tab/design/layout.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 10 | 11 |
    8 | getHtmlBlocksHierarchy(); ?>
    9 |
    12 | -------------------------------------------------------------------------------- /view/base/templates/tab/dumper.phtml: -------------------------------------------------------------------------------- 1 | 4 | getQdbConfig('handle_vardumper')):?> 5 |

    You need to install VarDumper Component, see github.com/symfony/var-dumper

    6 | 7 | 8 | 9 | 10 | getDumps()): ?> 11 |
    12 | 13 | getDumps() as $dump):?> 14 |
    15 | formatTrace($dump['bt']) ?> 16 | 17 |
    18 | 19 | 20 | 21 | 22 | 23 | getIsUpdateCall() && $block->getQdbConfig('handle_vardumper')>1): ?> 24 | 74 | 75 | -------------------------------------------------------------------------------- /view/base/templates/tab/help.phtml: -------------------------------------------------------------------------------- 1 | 4 | Fork me on GitHub 5 | 6 | 7 | getModuleName()?> (version getModuleVersion()?>) 8 | 9 |
    10 |

    In order to use this toolbar on a remote server you should either specify allowed IP or set a part of your http header in the config.

    11 |

    Any feedback and idea to improve this toolbar will be appreciate so get in touch via the issue tracker on GitHub.

    12 | -------------------------------------------------------------------------------- /view/base/templates/tab/info/config.phtml: -------------------------------------------------------------------------------- 1 | 4 |

    5 | 6 |

    7 | getConfigValues()):?> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | getConfigValues() as $config): ?> 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    PathValue
    escapeHtml($config['value']); ?>
    27 | 28 | -------------------------------------------------------------------------------- /view/base/templates/tab/info/module.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | getModuleList() as $index => $module): ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    NameVersionSequence
    24 | -------------------------------------------------------------------------------- /view/base/templates/tab/info/request.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | getRequestData()) { 6 | 7 | echo 'An error occurred generating toolbar data'; 8 | return; 9 | } 10 | ?> 11 | 12 | 13 | 14 | getRequestData() as $data):?> 15 | 16 | 17 | 19 | 20 |
    formatValue($data);?> 18 |
    21 | -------------------------------------------------------------------------------- /view/base/templates/tab/info/store.phtml: -------------------------------------------------------------------------------- 1 | getWebsite(); 5 | $_store = $block->getStore(); 6 | ?> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    getId(); ?>
    getName(); ?>
    getId(); ?>
    getName(); ?>
    getCode(); ?>
    30 | -------------------------------------------------------------------------------- /view/base/templates/tab/log.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    6 |
    7 | getLogFiles() as $logKey => $logFile):?> 8 |

     

    9 | Tail the getTailLines() ?> last lines 10 |
    11 |     
    12 |
    13 | 14 | 15 | 16 | 59 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/cache.phtml: -------------------------------------------------------------------------------- 1 | 7 | getCacheEvents()):?> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | getCacheEvents() as $identifier=>$events): ?> 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    IdentifierSaveLoadRemove
    28 | 29 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/collection.phtml: -------------------------------------------------------------------------------- 1 | 4 | getCollections()):?> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | getCollections() as $collection): ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    CollectionCall Number
    htmlFormatClass($collection['class']); ?>
    24 | 25 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/event.phtml: -------------------------------------------------------------------------------- 1 | 4 |

    5 | 6 |

    7 | getEvents()):?> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | getEvents() as $event): ?> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    EventDataCall Number
  • '.implode('
  • ', $event['args']).'
  • ' : ''); ?>
    26 | 27 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/model.phtml: -------------------------------------------------------------------------------- 1 | 4 | getModels()):?> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | getModels() as $model): ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    EventCall Number
    htmlFormatClass($model['class']); ?>
    24 | 25 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/observer.phtml: -------------------------------------------------------------------------------- 1 | 4 |

    5 | 6 |

    7 | getObservers()):?> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | getObservers() as $observer): ?> 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
    EventNameCall NumberInstance
    htmlFormatClass($observer['instance']); ?>
    31 | 32 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/plugin.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | getPluginList() as $type): ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    TypesPluginSort OrderMethod
    htmlFormatClass($type['type']); ?>htmlFormatClass($type['plugin']); ?>
    25 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/preference.phtml: -------------------------------------------------------------------------------- 1 | 4 | Display only preferences cross modules 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | getObjectManagerConfig() as $type =>$preference): ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    TypesPreferences
    htmlFormatClass($type) ?>htmlFormatClass($preference) ?>
    22 | -------------------------------------------------------------------------------- /view/base/templates/tab/profile/profiler.phtml: -------------------------------------------------------------------------------- 1 | 4 |
    5 | 6 |
    7 | Since 2.3 you can enable the profiler with the following command 8 |
    bin/magento dev:profiler:enable html
    9 | You have to add a SetEnv MAGE_PROFILER "html" to your .htaccess (do not use "csvfile" nor "firebug" to have profiler displayed in tab).
    10 | Be careful with .htacces directives an redirection you can have
    11 |
    $_SERVER['REDIRECT_MAGE_PROFILER']
    12 | setted instead of
    13 |
    $_SERVER['MAGE_PROFILER']
    14 | prefer apache2.conf. 15 |

    16 | You can read the official documentation Enable profiling (MAGE_PROFILER) 17 |
    18 | 19 | 36 | -------------------------------------------------------------------------------- /view/base/templates/tab/sql.phtml: -------------------------------------------------------------------------------- 1 | 8 | getAllQueries()): ?> 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 54 | 59 | 60 | 61 |
    15 | getTotalNumQueries(), 18 | $block->formatSqlTime($block->getTotalElapsedSecs()), 19 | $block->formatSqlTime($block->getAverage()), 20 | $block->getNumQueriesPerSecond() 21 | ); ?> 22 |
    28 | getTotalNumQueriesByType(Zend_Db_Profiler::SELECT), 31 | $block->getTotalNumQueriesByType(Zend_Db_Profiler::INSERT), 32 | $block->getTotalNumQueriesByType(Zend_Db_Profiler::UPDATE), 33 | $block->getTotalNumQueriesByType(Zend_Db_Profiler::DELETE), 34 | $block->getTotalNumQueriesByType(Zend_Db_Profiler::TRANSACTION) 35 | ); ?> 36 |
    41 | formatSql($block->getLongestQuery()); ?> 42 |
    47 | (formatSqlTime($block->getLongestQueryTime()); ?>) 48 |
    52 | 53 | 55 | getProfilerEnabled()):?> 56 | 57 | 58 |
    62 | 63 |
    64 | 65 | 66 | 67 | 68 | 69 | 70 | getProfilerBacktraceEnabled()): ?> 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | getAllQueries() as $i => $query): ?> 81 | 82 | 83 | 84 | 85 | 86 | getProfilerBacktraceEnabled()): ?> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
    #SQLArgsBtTime
    formatSql($query['sql']); ?>formatParams($query['params']); ?>formatSqlTrace($query['bt']) ?>formatSqlTime($query['time']); ?>
    95 |
    96 | 97 | 98 | SQL profiler is not active
    99 | You can use command line 100 |
    bin/magento dev:quickdevbar:enable
    101 | and use a specific profiler with backtrace 102 |
    bin/magento dev:quickdevbar:enable --sql-profiler
    103 | OR
    104 | set a new key for $config array in file app/etc/env.php
    105 |
    $config[db][connection][default][profiler] = 1
    106 | OR
    107 | Dynamically enable profiler for your session (cookie qdb_db_profiler_ebnabled)
    108 |
    109 | Page need to be refresh 110 | 111 | -------------------------------------------------------------------------------- /view/base/templates/tab/translation/file.phtml: -------------------------------------------------------------------------------- 1 | 4 | getTranslations()):?> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | getTranslations() as $original => $translation): ?> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    FileOriginalTranslation
    26 | 27 | -------------------------------------------------------------------------------- /view/base/templates/tab/translation/plain.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 | getTranslations()):?> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | getTranslations() as $original => $translation): ?> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    OriginalTranslation
    25 | 26 | -------------------------------------------------------------------------------- /view/base/templates/tabs.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    9 | 24 |
    25 | 26 | getTabBlocks() as $tabBlock): ?> 27 |
    28 | isAjax(false)) ? $tabBlock->getHtmlLoader('big') : $tabBlock->toHtml(); ?> 29 |
    30 | 31 | 32 |
    33 |
    34 | 35 | -------------------------------------------------------------------------------- /view/base/web/images/asc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/asc.gif -------------------------------------------------------------------------------- /view/base/web/images/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/bg.gif -------------------------------------------------------------------------------- /view/base/web/images/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/config.png -------------------------------------------------------------------------------- /view/base/web/images/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/database.png -------------------------------------------------------------------------------- /view/base/web/images/desc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/desc.gif -------------------------------------------------------------------------------- /view/base/web/images/dump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/dump.png -------------------------------------------------------------------------------- /view/base/web/images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/help.png -------------------------------------------------------------------------------- /view/base/web/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/info.png -------------------------------------------------------------------------------- /view/base/web/images/lastnode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/lastnode.png -------------------------------------------------------------------------------- /view/base/web/images/layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/layout.png -------------------------------------------------------------------------------- /view/base/web/images/loader-128.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/loader-128.gif -------------------------------------------------------------------------------- /view/base/web/images/loader-32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/loader-32.gif -------------------------------------------------------------------------------- /view/base/web/images/loader-64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/loader-64.gif -------------------------------------------------------------------------------- /view/base/web/images/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/log.png -------------------------------------------------------------------------------- /view/base/web/images/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/node.png -------------------------------------------------------------------------------- /view/base/web/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/profile.png -------------------------------------------------------------------------------- /view/base/web/images/qdb-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/qdb-icon.png -------------------------------------------------------------------------------- /view/base/web/images/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/setting.png -------------------------------------------------------------------------------- /view/base/web/images/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/tools.png -------------------------------------------------------------------------------- /view/base/web/images/translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/translate.png -------------------------------------------------------------------------------- /view/base/web/images/vline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpietri/magento2-developer-quickdevbar/960d7df6e72a316533c4ab08ce4aca482eea445b/view/base/web/images/vline.png -------------------------------------------------------------------------------- /view/base/web/js/filter-table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla JS table filter 3 | * https://blog.pagesd.info/2019/10/01/search-filter-table-javascript/ 4 | * 5 | */ 6 | 7 | 8 | 'use strict'; 9 | 10 | class FilterTable { 11 | 12 | constructor(tableNode) { 13 | this.tableNode = tableNode; 14 | 15 | this.input= document.createElement("input") 16 | this.input.setAttribute("type","search");//, placeholder:"search this table", name:""}); 17 | this.input.addEventListener( 18 | "input", 19 | () => { 20 | this.onInputEvent(this.tableNode); 21 | }, 22 | false, 23 | ); 24 | 25 | let container = document.createElement("p"); 26 | container.classList.add("filter-table"); 27 | container.appendChild(document.createTextNode('Search filter: ')); 28 | container.appendChild(this.input); 29 | tableNode.insertAdjacentElement('beforeBegin', container); 30 | } 31 | 32 | 33 | onInputEvent(table) { 34 | for (let row of table.rows) 35 | { 36 | this.filter(row) 37 | } 38 | } 39 | 40 | filter(row) { 41 | var text = row.textContent.toLowerCase(); 42 | var val = this.input.value.toLowerCase(); 43 | row.style.display = text.indexOf(val) === -1 ? 'none' : 'table-row'; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /view/base/web/js/sortable-table.js: -------------------------------------------------------------------------------- 1 | /* 2 | * https://www.w3.org/WAI/ARIA/apg/patterns/table/examples/sortable-table/ 3 | * 4 | * This content is licensed according to the W3C Software License at 5 | * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 6 | * 7 | * 8 | * File: sortable-table.js 9 | * 10 | * Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices 11 | */ 12 | 13 | 'use strict'; 14 | 15 | class SortableTable { 16 | constructor(tableNode) { 17 | this.tableNode = tableNode; 18 | 19 | this.columnHeaders = tableNode.querySelectorAll('thead th'); 20 | 21 | this.sortColumns = []; 22 | 23 | for (var i = 0; i < this.columnHeaders.length; i++) { 24 | var ch = this.columnHeaders[i]; 25 | this.sortColumns.push(i); 26 | ch.setAttribute('data-column-index', i); 27 | ch.addEventListener('click', this.handleClick.bind(this)); 28 | } 29 | 30 | this.optionCheckbox = document.querySelector( 31 | 'input[type="checkbox"][value="show-unsorted-icon"]' 32 | ); 33 | 34 | if (this.optionCheckbox) { 35 | this.optionCheckbox.addEventListener( 36 | 'change', 37 | this.handleOptionChange.bind(this) 38 | ); 39 | if (this.optionCheckbox.checked) { 40 | this.tableNode.classList.add('show-unsorted-icon'); 41 | } 42 | } 43 | } 44 | 45 | setColumnHeaderSort(columnIndex) { 46 | if (typeof columnIndex === 'string') { 47 | columnIndex = parseInt(columnIndex); 48 | } 49 | 50 | for (var i = 0; i < this.columnHeaders.length; i++) { 51 | var ch = this.columnHeaders[i]; 52 | if (i === columnIndex) { 53 | var value = ch.getAttribute('aria-sort'); 54 | if (value === 'descending') { 55 | ch.setAttribute('aria-sort', 'ascending'); 56 | this.sortColumn( 57 | columnIndex, 58 | 'ascending', 59 | ch.classList.contains('num') 60 | ); 61 | } else { 62 | ch.setAttribute('aria-sort', 'descending'); 63 | this.sortColumn( 64 | columnIndex, 65 | 'descending', 66 | ch.classList.contains('num') 67 | ); 68 | } 69 | } 70 | } 71 | } 72 | 73 | sortColumn(columnIndex, sortValue, isNumber) { 74 | function compareValues(a, b) { 75 | if (sortValue === 'ascending') { 76 | if (a.value === b.value) { 77 | return 0; 78 | } else { 79 | if (isNumber) { 80 | return a.value - b.value; 81 | } else { 82 | return a.value < b.value ? -1 : 1; 83 | } 84 | } 85 | } else { 86 | if (a.value === b.value) { 87 | return 0; 88 | } else { 89 | if (isNumber) { 90 | return b.value - a.value; 91 | } else { 92 | return a.value > b.value ? -1 : 1; 93 | } 94 | } 95 | } 96 | } 97 | 98 | if (typeof isNumber !== 'boolean') { 99 | isNumber = false; 100 | } 101 | 102 | var tbodyNode = this.tableNode.querySelector('tbody'); 103 | var rowNodes = []; 104 | var dataCells = []; 105 | 106 | var rowNode = tbodyNode.firstElementChild; 107 | 108 | var index = 0; 109 | while (rowNode) { 110 | rowNodes.push(rowNode); 111 | var rowCells = rowNode.querySelectorAll('th, td'); 112 | var dataCell = rowCells[columnIndex]; 113 | 114 | var data = {}; 115 | data.index = index; 116 | data.value = dataCell.textContent.toLowerCase().trim(); 117 | if (isNumber) { 118 | data.value = parseFloat(data.value); 119 | } 120 | dataCells.push(data); 121 | rowNode = rowNode.nextElementSibling; 122 | index += 1; 123 | } 124 | 125 | dataCells.sort(compareValues); 126 | 127 | // remove rows 128 | while (tbodyNode.firstChild) { 129 | tbodyNode.removeChild(tbodyNode.lastChild); 130 | } 131 | 132 | // add sorted rows 133 | for (var i = 0; i < dataCells.length; i += 1) { 134 | tbodyNode.appendChild(rowNodes[dataCells[i].index]); 135 | } 136 | } 137 | 138 | /* EVENT HANDLERS */ 139 | 140 | handleClick(event) { 141 | var tgt = event.currentTarget; 142 | this.setColumnHeaderSort(tgt.getAttribute('data-column-index')); 143 | } 144 | 145 | handleOptionChange(event) { 146 | var tgt = event.currentTarget; 147 | 148 | if (tgt.checked) { 149 | this.tableNode.classList.add('show-unsorted-icon'); 150 | } else { 151 | this.tableNode.classList.remove('show-unsorted-icon'); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /view/base/web/js/tabbis.js: -------------------------------------------------------------------------------- 1 | class tabbisClass { 2 | tabOptions ={} 3 | 4 | constructor(options) { 5 | this.thisOptions(options); 6 | this.thisMemory(); 7 | this.setup(); 8 | } 9 | 10 | getOption(key, groupIndex) { 11 | if(typeof groupIndex !== "undefined" && this.tabOptions.hasOwnProperty(groupIndex) && this.tabOptions[groupIndex].hasOwnProperty(key)) { 12 | return this.tabOptions[groupIndex][key]; 13 | } 14 | return this.options[key]; 15 | } 16 | 17 | // Setup 18 | setup() { 19 | const panes = document.querySelectorAll(this.getOption('paneGroup')); 20 | const tabs = document.querySelectorAll(this.getOption('tabGroup')); 21 | 22 | tabs.forEach((tabGroups, groupIndex) => { 23 | const paneGroups = panes[groupIndex]; 24 | const activeIndex = this.getActiveIndex(tabGroups, groupIndex); 25 | 26 | tabGroups.setAttribute('role', 'tablist'); 27 | this.tabOptions[groupIndex] = JSON.parse(tabGroups.getAttribute('tabbis-options')); 28 | 29 | // Reset items 30 | this.resetTabs([ ...tabGroups.children ]); 31 | this.resetPanes([ ...paneGroups.children ]); 32 | 33 | [ ...tabGroups.children ].forEach((tabItem, tabIndex) => { 34 | const paneItem = paneGroups.children[tabIndex]; 35 | 36 | // Add attributes 37 | this.addTabAttributes(tabItem, groupIndex); 38 | this.addPaneAttributes(tabItem, paneItem); 39 | 40 | tabItem.groupIndex = groupIndex; 41 | 42 | // Trigger event 43 | tabItem.addEventListener(this.getOption('trigger'), (e) => { 44 | this.toggle(e.currentTarget, tabItem.groupIndex); 45 | }); 46 | 47 | // Key event 48 | if (this.getOption('keyboardNavigation')) { 49 | tabItem.addEventListener('keydown', (e) => { 50 | this.eventKey(e); 51 | }); 52 | } 53 | }); 54 | 55 | if (activeIndex !== null) { 56 | this.toggle([ ...tabGroups.children ][activeIndex]); 57 | } 58 | }); 59 | } 60 | 61 | // Event key 62 | eventKey(e) { 63 | if ([ 13, 37, 38, 39, 40 ].includes(e.keyCode)) { 64 | e.preventDefault(); 65 | } 66 | 67 | if (e.keyCode == 13) { 68 | e.currentTarget.click(); 69 | } else if ([ 39, 40 ].includes(e.keyCode)) { 70 | this.step(e, 1); 71 | } else if ([ 37, 38 ].includes(e.keyCode)) { 72 | this.step(e, -1); 73 | } 74 | } 75 | 76 | // Index 77 | index(el) { 78 | return [ ...el.parentElement.children ].indexOf(el); 79 | } 80 | 81 | // Step 82 | step(e, direction) { 83 | const children = e.currentTarget.parentElement.children; 84 | this.resetTabindex(children); 85 | 86 | let el = children[this.pos(e.currentTarget, children, direction)]; 87 | el.focus(); 88 | el.setAttribute('tabindex', 0); 89 | } 90 | 91 | resetTabindex(children) { 92 | [ ...children ].forEach((child) => { 93 | child.setAttribute('tabindex', '-1'); 94 | }); 95 | } 96 | 97 | // Pos 98 | pos(tab, children, direction) { 99 | let pos = this.index(tab); 100 | pos += direction; 101 | 102 | if (children.length <= pos) { 103 | pos = 0; 104 | } else if (pos == -1) { 105 | pos = children.length - 1; 106 | } 107 | 108 | return pos; 109 | } 110 | 111 | // Emit event 112 | emitEvent(eventName, tab, pane) { 113 | let event = new CustomEvent(eventName, { 114 | bubbles: true, 115 | detail: { 116 | tab: tab, 117 | pane: pane 118 | } 119 | }); 120 | 121 | tab.dispatchEvent(event); 122 | } 123 | 124 | // Set active 125 | getActiveIndex(groupTabs, groupIndex) { 126 | const memory = this.loadMemory(groupIndex); 127 | 128 | if (typeof memory !== 'undefined') { 129 | return memory; 130 | } else { 131 | let element = groupTabs.querySelector(this.getOption('tabActive')); 132 | 133 | if (!element) { 134 | element = groupTabs.querySelector('[aria-selected="true"]'); 135 | } 136 | 137 | if (element) { 138 | return this.index(element); 139 | } else if (this.getOption('tabActiveFallback') !== false) { 140 | return this.getOption('tabActiveFallback'); 141 | } else { 142 | return null; 143 | } 144 | } 145 | } 146 | 147 | // ATTRIBUTES 148 | 149 | // Add tab attributes 150 | addTabAttributes(tab, groupIndex) { 151 | const tabIndex = this.index(tab); 152 | const prefix = this.getOption('prefix'); 153 | 154 | tab.setAttribute('role', 'tab'); 155 | tab.setAttribute('aria-controls', `${prefix}tabpanel-${groupIndex}-${tabIndex}`); 156 | } 157 | 158 | // Add tabpanel attributes 159 | addPaneAttributes(tab, pane) { 160 | pane.setAttribute('role', 'tabpanel'); 161 | pane.setAttribute('aria-labelledby', tab.getAttribute('id')); 162 | pane.setAttribute('aria-controled-by', tab.getAttribute('aria-controls')); 163 | pane.setAttribute('tabindex', '0'); 164 | } 165 | 166 | toggle(tab, groupIndex) { 167 | if(this.isActiveTab(tab, groupIndex) && this.getOption('collapsible', groupIndex)) { 168 | this.resetForTab(tab); 169 | this.resetMemoryGroup(groupIndex); 170 | } else { 171 | this.activate(tab,groupIndex); 172 | } 173 | } 174 | 175 | resetForTab(tab) { 176 | const pane = this.getPaneForTab(tab); 177 | this.resetTabs([ ...tab.parentNode.children ]); 178 | this.resetPanes([ ...pane.parentElement.children ]); 179 | } 180 | 181 | getPaneForTab(tab) { 182 | return document.querySelector('[aria-controled-by="'+tab.getAttribute('aria-controls')+'"]'); 183 | } 184 | 185 | // Activate 186 | activate(tab, i) { 187 | this.resetForTab(tab) 188 | 189 | const pane = this.getPaneForTab(tab); 190 | let memorize = true; 191 | if(tab.getAttribute('data-ajax')) { 192 | this.loadPaneContent(tab, pane); 193 | tab.removeAttribute('data-ajax'); 194 | memorize = false; 195 | } 196 | 197 | this.activateTab(tab); 198 | this.activatePane(pane); 199 | 200 | if(memorize) { 201 | this.saveMemory(tab, i); 202 | } 203 | 204 | this.emitEvent('tabbis', tab, pane); 205 | this.emitEvent('tabbis_pane_activate', tab, pane); 206 | 207 | } 208 | 209 | isActiveTab(tab) { 210 | return tab.getAttribute('aria-selected') === "true"; 211 | } 212 | 213 | // Activate tab 214 | activateTab(tab) { 215 | 216 | tab.setAttribute('aria-selected', 'true'); 217 | tab.setAttribute('tabindex', '0'); 218 | tab.classList.add(this.getOption('tabActiveClass')); 219 | } 220 | 221 | // Activate pane 222 | activatePane(pane) { 223 | pane.removeAttribute('hidden'); 224 | } 225 | 226 | loadPaneContent(tab, pane) { 227 | let paneXhrUri = tab.getAttribute('data-ajax'); 228 | if(!paneXhrUri) { 229 | throw new Error("No data-ajax attribute"); 230 | } 231 | 232 | fetch(paneXhrUri, { 233 | //To be compliant with \Laminas\Http\Request::isXmlHttpRequest 234 | headers: { 235 | "X-Requested-With": "XMLHttpRequest", 236 | } 237 | } 238 | ) 239 | .then(response => { 240 | if (!response.ok) { 241 | throw new Error(response.status + " Failed Fetch "); 242 | } 243 | return response.text() 244 | }) 245 | .then(function (html) { 246 | // console.log(html); 247 | pane.innerHTML = html; 248 | this.emitEvent('tabbis_pane_ajax_loaded', tab, pane); 249 | }.bind(this)) 250 | .catch((err) => 251 | console.log("Can’t access " + url + " response. Blocked by browser?" + err) 252 | ); 253 | 254 | } 255 | 256 | // Remove tab attributes 257 | resetTabs(tabs) { 258 | tabs.forEach((el) => { 259 | el.setAttribute('aria-selected', 'false') 260 | el.classList.remove(this.getOption('tabActiveClass')); 261 | }); 262 | this.resetTabindex(tabs); 263 | } 264 | 265 | // Reset pane attributes 266 | resetPanes(panes) { 267 | panes.forEach((el) => el.setAttribute('hidden', '')); 268 | } 269 | 270 | // MEMORY 271 | 272 | // Load memory 273 | loadMemory(groupIndex) { 274 | if (!this.options.memory) return; 275 | if (typeof this.memory[groupIndex] === 'undefined') return; 276 | if (this.memory[groupIndex] === null) return; 277 | 278 | return parseInt(this.memory[groupIndex]); 279 | } 280 | 281 | // Save memory 282 | saveMemory(tab, groupIndex) { 283 | if (!this.getOption('memory')) return; 284 | this.memory[groupIndex] = this.index(tab); 285 | localStorage.setItem(this.options.memory, JSON.stringify(this.memory)); 286 | } 287 | 288 | resetMemoryGroup(groupIndex) { 289 | this.memory[groupIndex] = null; 290 | localStorage.setItem(this.options.memory, JSON.stringify(this.memory)); 291 | } 292 | 293 | 294 | // This memory 295 | thisMemory() { 296 | if (!this.getOption('memory')) return; 297 | const store = localStorage.getItem(this.options.memory); 298 | this.memory = store !== null ? JSON.parse(store) : []; 299 | } 300 | 301 | // OPTIONS 302 | 303 | // Defaults 304 | defaults() { 305 | return { 306 | keyboardNavigation: true, 307 | memory: false, 308 | paneGroup: '[data-panes]', 309 | prefix: '', 310 | tabActive: '[data-active]', 311 | tabActiveClass: 'ui-tabs-active', 312 | tabActiveFallback: 0, 313 | tabGroup: '[data-tabs]', 314 | trigger: 'click', 315 | collapsible: false 316 | }; 317 | } 318 | 319 | // This options 320 | thisOptions(options) { 321 | this.options = Object.assign(this.defaults(), options); 322 | if (this.options.memory !== true) return; 323 | this.options.memory = 'tabbis'; 324 | } 325 | } 326 | 327 | // Function call 328 | function tabbis(options = {}) { 329 | const tabs = new tabbisClass(options); 330 | } 331 | -------------------------------------------------------------------------------- /view/frontend/layout/quickdevbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Store 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------