diff --git a/wire/modules/Process/ProcessModule/ProcessModule.module b/wire/modules/Process/ProcessModule/ProcessModule.module index 868afdb1..d9fb8118 100644 --- a/wire/modules/Process/ProcessModule/ProcessModule.module +++ b/wire/modules/Process/ProcessModule/ProcessModule.module @@ -11,7 +11,7 @@ * This version also lifts several pieces of code from Soma's Modules Manager * specific to the parts involved with downloading modules from the directory. * - * ProcessWire 3.x, Copyright 2020 by Ryan Cramer + * ProcessWire 3.x, Copyright 2022 by Ryan Cramer * https://processwire.com * * @todo add support for module configuration inputfields with useLanguages option @@ -32,7 +32,7 @@ class ProcessModule extends Process { return array( 'title' => __('Modules', __FILE__), // getModuleInfo title 'summary' => __('List, edit or install/uninstall modules', __FILE__), // getModuleInfo summary - 'version' => 119, + 'version' => 120, 'permanent' => true, 'permission' => 'module-admin', 'useNavJSON' => true, @@ -133,11 +133,7 @@ class ProcessModule extends Process { */ public function __construct() { $this->labels['download'] = $this->_('Download'); - if($this->input->get('update')) { - $this->labels['download_install'] = $this->_('Download and Update'); - } else { - $this->labels['download_install'] = $this->_('Download and Install'); - } + $this->labels['download_install'] = $this->_('Download and Install'); $this->labels['get_module_info'] = $this->_('Get Module Info'); $this->labels['modules'] = $this->_('Modules'); $this->labels['module_information'] = $this->_x("Module Information", 'edit'); @@ -149,8 +145,8 @@ class ProcessModule extends Process { $this->labels['download_zip'] = $this->_('Add Module From URL'); $this->labels['check_new'] = $this->_('Check for New Modules'); $this->labels['installed_date'] = $this->_('Installed'); - $this->labels['requires'] = $this->_x("Requires", 'list'); // Label that precedes list of required prerequisite modules - $this->labels['installs'] = $this->_x("Also Installs", 'list'); // Label that precedes list of other modules a given one installs + $this->labels['requires'] = $this->_x('Requires', 'list'); // Label that precedes list of required prerequisite modules + $this->labels['installs'] = $this->_x('Also Installs', 'list'); // Label that precedes list of other modules a given one installs $this->labels['reset'] = $this->_('Refresh'); $this->labels['core'] = $this->_('Core'); $this->labels['site'] = $this->_('Site'); @@ -159,13 +155,23 @@ class ProcessModule extends Process { $this->labels['install'] = $this->_('Install'); // Label for Install tab $this->labels['cancel'] = $this->_('Cancel'); // Label for Cancel button - if($this->wire('languages') && !$this->wire('user')->language->isDefault()) { + require(dirname(__FILE__) . '/ProcessModuleInstall.php'); + } + + /** + * Wired to API + * + */ + public function wired() { + parent::wired(); + if($this->wire()->languages && !$this->wire()->user->language->isDefault()) { // Use previous translations when new labels aren't available (can be removed in PW 2.6+ when language packs assumed updated) if($this->labels['install'] == 'Install') $this->labels['install'] = $this->labels['install_btn']; if($this->labels['reset'] == 'Refresh') $this->labels['reset'] = $this->labels['check_new']; } - - require(dirname(__FILE__) . '/ProcessModuleInstall.php'); + if($this->wire()->input->get('update')) { + $this->labels['download_install'] = $this->_('Download and Update'); + } } /** @@ -185,7 +191,7 @@ class ProcessModule extends Process { * */ protected function formatVersion($version) { - return $this->wire('modules')->formatVersion($version); + return $this->wire()->modules->formatVersion($version); } /** @@ -473,13 +479,13 @@ class ProcessModule extends Process { $markup->icon = 'folder-open-o'; $markup->value .= $this->renderListTable($siteModulesArray, array('allowDelete' => true)) . - "
" . + "
" . wireIconMarkup('star', 'fw') . " " . sprintf($this->_('Browse the modules directory at %s'), "processwire.com/modules") . "
" . - "" . + "
" . wireIconMarkup('eraser', 'fw') . " " . $this->_("To remove a module, click the module to edit, check the Uninstall box, then save. Once uninstalled, the module's file(s) may be removed from /site/modules/. If it still appears in the list above, you may need to click the Refresh button for ProcessWire to see the change.") . // Instructions on how to remove a module "
" . - "" . + "
" . wireIconMarkup('info-circle') . " " . $this->_('The button below clears compiled site modules and template files, forcing them to be re-compiled the next time they are accessed. Note that this may cause a temporary delay for one or more requests while files are re-compiled.') . "
" . "" . $button->render() . "
"; @@ -842,7 +848,7 @@ class ProcessModule extends Process { $configurable = $info['configurable']; $title = !empty($info['title']) ? $sanitizer->entities1($info['title']) : substr($name, strlen($section)); if($options['allowClasses']) $title .= "" . $sanitizer->entities($moduleInfoJSON) . ""; + $f->icon = 'code'; + $f->themeOffset = 1; + $form->add($f); + + /** @var InputfieldMarkup $f */ + $f = $modules->get('InputfieldMarkup'); + $f->attr('name', 'module_info_verbose'); + $f->label = $moduleInfoLabel . ' ' . $this->_('(verbose)'); + $f->icon = 'code'; + $f->value = "
" . $sanitizer->entities($moduleInfoVerboseJSON) . ""; + $f->themeOffset = 1; + $form->add($f); + + $form->prependMarkup = + "
" . + $this->_('This data comes from the module or is determined at runtime, so it is not editable here.') . + "
"; + + return $form->render(); + } + + /** + * Edit module in raw/JSON mode + * + * @param string $moduleName + * @throws WireException + * @throws WirePermissionException + * @return string + * + */ + protected function renderEditRaw($moduleName) { + + $modules = $this->wire()->modules; + $session = $this->wire()->session; + $config = $this->wire()->config; + $input = $this->wire()->input; + $user = $this->wire()->user; + + if(!$user->isSuperuser()) throw new WirePermissionException('Superuser required'); + if(!$config->advanced) throw new WireException('This feature requires config.advanced=true;'); + + $moduleData = $modules->getModuleConfigData($moduleName); + $sinfo = self::getModuleInfo(); + + $this->headline(sprintf($this->_('%s raw config data'), $moduleName)); + $this->breadcrumb("./", $sinfo['title']); + $this->breadcrumb("./edit?name=$moduleName", $moduleName); + + /** @var InputfieldForm $form */ + $form = $modules->get('InputfieldForm'); + $form->attr('id', 'ModuleEditRawForm'); + $form->attr('action', "edit?name=$moduleName&edit_raw=1"); + $form->attr('method', 'post'); + + if(empty($moduleData) && !$input->is('post')) $this->warning($this->_('This module has no configuration data')); + $moduleData['_name'] = $moduleName . ' (' . $this->_('do not remove this') . ')'; + unset($moduleData['submit_save_module'], $moduleData['uninstall']); + $jsonFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + $moduleDataJSON = is_array($moduleData) ? json_encode($moduleData, $jsonFlags) : array(); + $rows = substr_count($moduleDataJSON, "\n") + 2; + + /** @var InputfieldMarkup $f */ + $f = $modules->get('InputfieldTextarea'); + $f->attr('name', 'module_config_json'); + $f->label = $this->_('Module config (raw/JSON)'); + $f->icon = 'code'; + $f->value = $moduleDataJSON; + $f->attr('style', 'font-family:monospace;white-space:nowrap'); + $f->attr('rows', $rows > 5 ? $rows : 5); + $form->add($f); + + /** @var InputfieldSubmit $submit */ + $submit = $modules->get('InputfieldSubmit'); + $submit->attr('name', 'submit_save_module_config_json'); + $submit->showInHeader(true); + $submit->val($this->_('Save')); + $form->add($submit); + + if(!$input->post('submit_save_module_config_json')) return $form->render(); + + $form->processInput($input->post); + $json = $f->val(); + $data = json_decode($json, true); + + if($data === null) { + $this->error($this->_('Cannot save because JSON could not be parsed (invalid JSON)')); + return $form->render(); + } + + if(empty($data['_name']) || strpos($data['_name'], "$moduleName ") !== 0) { + $this->error($this->_('Cannot save because JSON not recognized as valid for module')); + return $form->render(); + } + + $changes = array(); + unset($data['_name'], $moduleData['_name']); + + foreach($moduleData as $key => $value) { + if(!array_key_exists($key, $data) || $data[$key] !== $value) $changes[$key] = $key; + } + + foreach($data as $key => $value) { + if(!array_key_exists($key, $moduleData) || $moduleData[$key] !== $value) $changes[$key] = $key; + } + + if(count($changes)) { + $modules->saveModuleConfigData($moduleName, $data); + $this->message($this->_('Updated module config data') . ' (' . implode(', ', $changes) . ')'); + } else { + $this->message($this->_('No changes detected')); + } + + $session->location($form->action); + + return ''; + } /** * Build and render for the form for editing a module's settings * @@ -1405,7 +1597,7 @@ class ProcessModule extends Process { } if(!$moduleId) { - $this->error("Unknown module"); + $this->error($this->_('Unknown module')); $session->redirect('./'); return ''; } @@ -1579,6 +1771,7 @@ class ProcessModule extends Process { if(wireCount($fields)) foreach($fields->getAll() as $field) { // note field names beginning with '_' will not be stored if(($name = $field->attr('name')) && strpos($name, '_') !== 0) { + if($name === 'submit_save_module') continue; $value = $field->attr('value'); if(!isset($data[$name]) || $value != $data[$name]) $updatedNames[] = $name; $data[$name] = $value; @@ -1608,6 +1801,7 @@ class ProcessModule extends Process { $redirectURL = './?deleted=1'; } else { + unset($data['submit_save_module'], $data['uninstall']); $modules->saveModuleConfigData($moduleName, $data); $updatedNames = count($updatedNames) ? ' (' . implode(', ', $updatedNames) . ')' : ''; $this->message($this->_("Saved Module") . " - $moduleName $updatedNames"); // Message shown before the name of a module that was just saved @@ -1728,7 +1922,7 @@ class ProcessModule extends Process { if($allowDisabledFlag) { $checkboxClass = $adminTheme ? $adminTheme->getClass('input-checkbox') : ''; $checked = ($flags & Modules::flagsDisabled ? " checked='checked'" : ""); - $table->row(array($this->_x('Debug', 'edit'), + $table->row(array('* ' . $this->_x('Debug', 'edit'), "" )); } + + if($config->advanced && $this->wire()->user->isSuperuser()) { + $table->row(array( + '* ' . $this->_x('Advanced', 'edit'), + "" . wireIconMarkup('pencil') . ' ' . $this->_('Raw config') . " " . + "" . wireIconMarkup('info-circle') . ' ' . $this->_('Raw info') . "" + )); + } + /** @var InputfieldMarkup $field */ $field = $modules->get("InputfieldMarkup"); @@ -1745,6 +1948,9 @@ class ProcessModule extends Process { $field->attr('value', $table->render()); $field->label = $this->labels['module_information']; $field->icon = 'info-circle'; + if($config->advanced) { + $field->appendMarkup .= "* " . $this->_('Options available in advanced mode only.') . "
"; + } if($collapseInfo) $field->collapsed = Inputfield::collapsedYes; $form->prepend($field); @@ -1755,7 +1961,7 @@ class ProcessModule extends Process { protected function renderModuleHooks($moduleName) { $out = ''; - $hooks = array_merge($this->wire()->getHooks('*'), $this->wire('hooks')->getAllLocalHooks()); + $hooks = array_merge($this->wire()->getHooks('*'), $this->wire()->hooks->getAllLocalHooks()); foreach($hooks as $hook) { $toObject = !empty($hook['toObject']) ? $hook['toObject'] : ''; if(empty($toObject) || wireClassName($toObject, false) != $moduleName) continue; @@ -1773,7 +1979,7 @@ class ProcessModule extends Process { public function ___executeInstallConfirm() { - $name = $this->wire('input')->get->name('name'); + $name = $this->wire()->input->get->name('name'); if(!$name) throw new WireException("No module name specified"); if(!$this->wire()->modules->isInstallable($name, true)) throw new WireException("Module is not currently installable");