"MJML to HTML", "author" => "EPRC", "version" => "1.2.4", "summary" => "Allows you to write your Processwire template using MJML and get a converted HTML output using MJML API.", "href" => "https://github.com/eprcstudio/PageMjmlToHtml", "icon" => "code", "autoload" => true, "singular" => true, ]; } public function getDefaults() { return [ "apiId" => "", "apiKey" => "", "areCredentialsValid" => false, "noAppendFile" => 0, "noPrependFile" => 0, "allowedTemplates" => [], "superuserNoCache" => 0, "rolesNoCache" => [], "cachePerGet" => "", "disableMinify" => 0, "convertRelativeLinks" => 0, "relativeLinksHost" => "", "relativeLinksParams" => "", "relativeLinksIgnore" => "", ]; } public function init() { $this->addHookBefore("Modules::saveModuleConfigData", $this, "moduleConfigSaved"); if($this->apiId && $this->apiKey && $this->areCredentialsValid) { $this->addHookAfter("Pages::saved", $this, "clearPageCache"); $this->addHookBefore("PageRender::renderPage", $this, "readFromCache"); $this->addHookAfter("PageRender::renderPage", $this, "convertToHtml"); $this->addHookAfter("ProcessPageEdit::getViewActions", $this, "addViewRaw"); $this->addHookAfter("ProcessPageView::execute", $this, "addCopyRaw"); } } /** * Clear the cache after a Page::save() * * @param HookEvent $event * */ protected function clearPageCache(HookEvent $event) { $page = $event->arguments(0); $template = $page->template; if($template->name === "admin") return; if(!in_array($template->id, $this->allowedTemplates)) return; $this->clearCache($page); } /** * Check if a cache already exists in the user’s language and return it * * This will override the page’s `render` function if a cache is present * * If a `raw` GET parameter is present, display the content as text/plain * * If the template has a more recent modified timestamp than the cache’s * or if the role is set to bypass it, we regenerate the HTML from MJML * * If there is no cache, check first if we should remove prepended * / appended files * * @param HookEvent $event * */ protected function readFromCache(HookEvent $event) { $parentEvent = $event->arguments(0); $page = $parentEvent->object; $template = $page->template; if($template->name === "admin") return; if(!in_array($template->id, $this->allowedTemplates)) return; $bypassCache = false; if($this->user()->isSuperuser() && $this->superuserNoCache) { $bypassCache = true; } elseif(count($this->rolesNoCache)) { foreach($this->rolesNoCache as $role) { if($this->user()->hasRole($role)) { $bypassCache = true; break; } } } $cache = $this->cache()->getFor($this, $this->getCacheName($page)); if($cache && $cache["markup"] && $cache["timestamp"] > $template->modified && !$bypassCache) { if(!is_null($this->input("get", "raw"))) { header("Content-Type: text/plain"); } $parentEvent->return = $cache["markup"]; $event->cancelHooks = true; } else { $options = $parentEvent->arguments(0) ?: []; if($this->noAppendFile) { $options = array_merge([ "appendFile" => null, "appendFiles" => [], ], $options); } if($this->noPrependFile) { $options = array_merge([ "prependFile" => null, "prependFiles" => [], ], $options); } $parentEvent->arguments(0, $options); } } /** * Once the page has been rendered, convert the MJML to HTML and save in * the cache. Show as text/plain if the `raw` GET parameter is present * * A debug view will be rendered only for the superuser if there are errors. * However if the user can edit the page and there are errors the page will * display a message inviting to contact the superuser for more details * * @param HookEvent $event * */ protected function convertToHtml($event) { $parentEvent = $event->arguments(0); $page = $parentEvent->object; $template = $page->template; $rootPageUrl = $this->sanitizer("httpUrl", $this->relativeLinksHost) ?: $this->pages(1)->httpUrl; $rootPageUrl = rtrim($rootPageUrl, '/') . '/'; // force trailing slash if($template->name === "admin") return; if(!in_array($template->id, $this->allowedTemplates)) return; $mjml = $parentEvent->return; if($this->convertRelativeLinks) { $params = $this->getRelativeLinksParams($page); $ignore = explode("\n", $this->relativeLinksIgnore); $mjml = preg_replace_callback( '/href=(?:\"|\')(.+)(?:\"|\')/U', function($matches) use ($rootPageUrl, $params, $ignore) { if( strpos($matches[1], "http") === 0 || strpos($matches[1], "mailto") === 0 || strpos($matches[1], "tel") === 0 ) { return $matches[0]; } foreach($ignore as $i) { $i = trim($i); if(!$i) continue; if(strpos($i, "regex:") === 0) { $regex = "{" . trim(substr($i, 6)) . "}"; if(preg_match($regex, $matches[1])) { return $matches[0]; } } elseif(strpos($matches[1], $i) === 0) { return $matches[0]; } } if(strpos($matches[1], "/") === 0) { $matches[1] = substr($matches[1], 1); } $href = "href=\""; $href .= $rootPageUrl; $href .= $matches[1]; if($params) { if(strpos($matches[1], "?") === false) { $href .= "?"; } else { $href .= "&"; } $href .= $params; } $href .= "\""; return $href; }, $mjml ); } if(!$this->disableMinify) { $mjml = preg_replace("/(\w)\t(\w)/", '$1 $2', $mjml); $mjml = preg_replace("/[\r\t\f\v]+/", "", $mjml); $mjml = preg_replace("/ {2,}/", " ", $mjml); $mjml = preg_replace("/\n{2,}/", "\n", $mjml); } $mjml = $this->callToApi($mjml); $debugView = $this->renderDebugView($mjml); if($debugView === false) { if(!$this->disableMinify) { $mjml->html = preg_replace("/[\r\t\f\v]+/", "", $mjml->html); $mjml->html = preg_replace("/<(\n| )*/", "<", $mjml->html); $mjml->html = preg_replace("/(\n| )*>/", ">", $mjml->html); $mjml->html = preg_replace("/(\n| )*}(\n| )*/", "}", $mjml->html); $mjml->html = preg_replace("/(\n| )*{(\n| )*/", "{", $mjml->html); $mjml->html = preg_replace("/\n+/", " ", $mjml->html); $mjml->html = preg_replace("/ {2,}/", " ", $mjml->html); $mjml->html = preg_replace("/ style>/", ">", $mjml->html); $mjml->html = preg_replace("/> <", $mjml->html); } $mjml->html = trim($mjml->html); $this->cache()->saveFor($this, $this->getCacheName($page), [ "markup" => $mjml->html, "timestamp" => time(), ], WireCache::expireNever); if(!is_null($this->input("get", "raw"))) { header("Content-Type: text/plain"); } $out = $mjml->html; } else { $this->clearCache($page); if($page->get("mailType")) { $this->error(sprintf($this->_('There was an issue generating this email. Check the %1$spage%2$s to get more details'), "httpUrl\" target=\"_blank\">", ""), Notice::allowMarkup); // ProMailer: return an empty string so the sending process gets cancelled $out = ""; } elseif($this->user()->isSuperuser()) { $out = $debugView; } elseif($page->editable()) { $out = "

$page->title

"; $out .= "

"; $out .= $this->_("There was an issue generating this email. Please contact your administrator to get details"); $out .= "

"; } else { $out = "

$page->title

"; } } $parentEvent->return = $out; } /** * Get sanitized query parameters with page fields value populated * * @param Page $page * @return string * */ private function getRelativeLinksParams(Page $page) { if(!$this->relativeLinksParams) return ""; $params = $this->relativeLinksParams; if(strpos($this->relativeLinksParams, "{") !== false) { $params = $page->getText($params, true); } $params = $this->sanitizer("url", "https://eprc.studio/?$params"); return str_replace("https://eprc.studio/?", "", $params); } /** * Make a call to the MJML API using the credentials provided in the * module’s config. The HTTP code and errors (if any) are populated as well * * @param string|Array $mjml MJML markup to convert or acts as $api if it is * an Array * @param Array $api API Credentials to test the call with (expected keys: * `id` and `key`) * @return object * */ private function callToApi($mjml = "", $api = []) { if(is_array($mjml)) { $api = $mjml; $mjml = ""; } $id = $api["id"] ?? $this->apiId; $key = $api["key"] ?? $this->apiKey; $json = json_encode($mjml); $http = new WireHttp(); $user = base64_encode("$id:$key"); $http->setHeader("authorization", "basic $user"); $response = $http->post($this->apiUrl, "{\"mjml\":$json}", ["use" => "curl"]); $response = json_decode($response) ?? (object) ["message" => ""]; $response->code = $http->getHttpCode(); $response->error = $http->getError(); $response->mjml = $mjml; return $response; } /** * Get a formatted HTTP message from the HTTP code * * @param int $code * @return string * */ private function getHttpMessage(int $code) { $messages = [ 200 => "OK", 400 => $this->_("Please check the error message"), 401 => $this->_("Please check your credentials"), 403 => $this->_("Your credentials do not allow this"), 500 => $this->_("An unknown error has occured, please try again later") ]; if (isset($messages[$code])) { $message = $messages[$code]; } else { $message = $messages[500]; } return $message; } /** * Render a debug view. * * If there is no errors we just return `false`, * otherwise we provide a simple markup viewer with an highlight on * problematic lines. Note it might not be 100% accurate * * @param object $mjml * @return string|bool * */ private function renderDebugView($mjml) { $debug = false; // Start the debug view $view = ""; $view .= "
"; $view .= ""; $view .= ""; $view .= "
";

		// Split into an array with one line of code per row
		$code = explode("\n", $mjml->mjml);
		// Array containing our formatted lines
		$lines = [];
		// We track the depth for indentation
		$depth = 0;
		foreach($code as $key => $c) {
			$line = "";
			$num = $key + 1;

			// Check if there is any error for this line
			$errors = [];
			if(!empty($mjml->errors)) {
				$debug = true;
				$errors = array_filter($mjml->errors, function($item) use($num) {
					return $item->line === $num;
				});
			}

			// Wrap the line with an error message
			if(!empty($errors)) {
				$line .= "message . "\n";
					return $carry;
				}));
				$line .= "\">";
			}

			$line .= "$num";

			// Indentation
			preg_match_all("/<(?!\/|\!)(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)[^<]+?(?/", $c, $openingTags);
			preg_match_all("/<\/.+?>/", $c, $closingTags);
			if(count($openingTags[0]) < count($closingTags[0])) {
				$depth--;
			}
			for($i = 0; $i < $depth; $i++) {
				$line .= "\t";
			}
			if(count($openingTags[0]) > count($closingTags[0])) {
				$depth++;
			}

			$line .= htmlentities($c);

			if(!empty($errors)) {
				$line .= "";
			}

			$lines[] = $line;
		}

		// Add the formatted lines in the debug view
		$view .= implode("\n", $lines);
		$view .= "
"; $view .= "
"; if($mjml->code === 200) { $view = preg_replace('/()/mU', '$1'.$view, $mjml->html); } else { $debug = true; $view .= "
"; $view .= $this->getHttpMessage($mjml->code); if($mjml->code === 400) { $view .= ":
$mjml->message"; } $view .= "
"; } $view .= "
"; $view .= "
"; if($debug) { $debug = $view; } return $debug; } /** * Add a “Copy Raw Text” button only when viewing the page, not in its raw * format, if the user can edit it and if there are no errors * * We need to check for ProcessWire’s version as the `getPage()` function * has been removed from ProcessPageView * * @param HookEvent $event * */ protected function addCopyRaw(HookEvent $event) { if($this->config()->version("3.0.186")) { $request = $this->pages()->request(); $page = $request->getPage(); } else { $page = $event->object->getPage(); } if(!($page && $page->id)) return; $template = $page->template; if($template->name === "admin") return; if(!in_array($template->id, $this->allowedTemplates)) return; if(!is_null($this->input("get", "raw"))) return; if(!$this->cache()->getFor($this, $this->getCacheName($page))) return; if(!$page->editable()) return; $rawButton = $this->renderRawButton($page); $event->return = str_replace("", "$rawButton", $event->return); } /** * Render a “Copy Raw Text” button * * @param Page $page * @return string * */ private function renderRawButton(Page $page) { $out = ""; $out .= ""; $out .= "httpUrl}?raw"; if($this->cachePerGet && count($_GET)) { $get = array_key_first($_GET); if(strpos($this->cachePerGet, "*") !== false || in_array($get, explode(" ", $this->cachePerGet))) { $out .= "&$get"; } } $out .= "\" class=\"mjml2html_btn\""; $out .= " data-copying=\""; $out .= $this->_("Copying..."); $out .= "\" data-copied=\""; $out .= $this->_("Copied!"); $out .= "\" data-error=\""; $out .= $this->_("Failed to copy"); $out .= "\" target=\"_blank\">"; $out .= $this->_("Copy Raw Text"); $out .= ""; return $out; } /** * Add a “Raw Code” button under the “View” one in the admin edit page * * @param HookEvent $event * */ protected function addViewRaw(HookEvent $event) { $ProcessPageEdit = $event->object; $page = $ProcessPageEdit->getPage(); $template = $page->template; if($template->name === "admin") return; if(!in_array($template->id, $this->allowedTemplates)) return; // https://github.com/processwire/processwire/blob/master/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module#L747 $url = $ProcessPageEdit->getViewUrl() . "?raw"; $label = " " . $this->_("Raw Code"); $class = ""; $languages = $this->getLanguages(); $languageUrls = []; if($languages) { $class .= " pw-has-items"; foreach($languages as $language) { if(!$page->viewable($language)) continue; $localUrl = $page->localHttpUrl($language) . "?raw"; $languageUrls[$language->id] = $localUrl; } } $name = "raw"; $action = "$label"; if(count($languageUrls) > 1) { $ul = ""; $action = str_replace("", "  ", $action) . $ul; } else { $action = str_replace(" pw-has-items", "", $action); } $actions = $event->return; $actions = array_merge([$name => $action], $actions); $event->return = $actions; } /** * Get a formatted string for the cache name * * The cache name will contain the language, when applicable, and the first * GET variable (other than “raw”) if present * * @return string * */ private function getCacheName($page) { $cacheName = "mjml-{$page->id}"; $language = $this->user()->language; if($this->getLanguages() && $language) { $cacheName .= "-$language->name"; } if($this->cachePerGet && count($_GET)) { $get = array_key_first($_GET); if($get === "raw" && count($_GET) > 1) { $get = array_keys($_GET)[1]; } if($get !== "raw") { if(strpos($this->cachePerGet, "*") !== false || in_array($get, explode(" ", $this->cachePerGet))) { $cacheName .= "-$get"; } } } return $cacheName; } private function clearAllCache() { $this->cache()->deleteFor($this); } private function clearCache($page) { $this->cache()->deleteFor($this, "mjml-{$page->id}*"); } private function getLanguages() { if( $this->modules()->isInstalled('LanguageSupport') && $this->modules()->isInstalled('LanguageSupportPageNames') ) { return $this->languages(); } return false; } /** * Check and clear the cache and/or invalidate the credentials if applicable * * @param HookEvent $event * */ protected function moduleConfigSaved(HookEvent $event) { $class = $event->arguments(0); if($class != $this) return; $data = $event->arguments(1); if($data["clear"] === 1) { $this->clearAllCache(); $this->message($this->_("Cache has been cleared")); } if( $data["noAppendFile"] !== $this->noAppendFile || $data["noPrependFile"] !== $this->noPrependFile || $data["disableMinify"] !== $this->disableMinify ) { $this->clearAllCache(); } if(!$data["apiKey"] || !$data["apiId"]) { $this->clearAllCache(); $data["areCredentialsValid"] = false; } elseif( !$data["areCredentialsValid"] || $data["apiId"] !== $this->apiId || $data["apiKey"] !== $this->apiKey ) { $this->clearAllCache(); $response = $this->callToApi(["id" => $data["apiId"], "key" => $data["apiKey"]]); if($response->code !== 200) { $data["areCredentialsValid"] = false; $this->error("{$response->error} {$response->message}"); } else { $data["areCredentialsValid"] = true; $this->message($this->_("Your credentials are valid and the API is ready to use")); } } $event->arguments(1, $data); } public function getModuleConfigInputfields(InputfieldWrapper $inputfields) { $modules = $this->modules(); $valid = $this->areCredentialsValid; $validLabel = $valid ? " - " . $this->_("VALIDATED") . "!" : ""; $wrapper = $modules->get("InputfieldFieldset"); $wrapper->label = $this->_("MJML API Authentification") . $validLabel; $wrapper->description = sprintf($this->_('Get an application ID and a secret key ([link](%s)) and paste these here'), "https://mjml.io/api/"); $wrapper->icon = "key"; $wrapper->set('themeOffset', 1); if($valid) { $wrapper->collapsed = true; $wrapper->notes = $this->_("Your credentials are valid and the API is ready to use"); } elseif(!$valid) { $wrapper->notes = $this->_("Enter and save to continue. Your API application ID and secret key will be validated"); } $f = $modules->get("InputfieldText"); $f->attr("name", "apiId"); $f->columnWidth = 50; $f->label = $this->_("Application ID"); $f->required = true; $f->value = $this->apiId; $wrapper->add($f); $f = $modules->get("InputfieldText"); $f->attr("name", "apiKey"); $f->columnWidth = 50; $f->label = $this->_("Secret key"); $f->required = true; $f->value = $this->apiKey; $wrapper->add($f); $inputfields->add($wrapper); if($modules->isInstalled("TracyDebugger")) { $f = $modules->get("InputfieldMarkup"); $f->description = sprintf($this->_('By default the debug bar is added on all templates and thus present when generating the markup. While this is great when debugging, once you’re done you might want to exclude the templates with the setting “[No debug bar in selected frontend templates](%s)”'), $modules->getModuleEditUrl("TracyDebugger") . "#debugBarAndPanels"); $f->label = $this->_("It seems you are using TracyDebugger"); $f->icon = "exclamation-triangle"; $f->set('themeOffset', 1); if (!$valid) { $f->collapsed = Inputfield::collapsedHidden; } $inputfields->add($f); } $f = $modules->get("InputfieldAsmSelect"); $f->attr("name", "allowedTemplates"); $f->label = $this->_("Templates to convert from MJML to HTML"); $f->icon = "envelope-o"; $templates = $this->wire()->templates; foreach($templates as $t) { if($t->flags & Template::flagSystem) continue; $label = $t->name; $tLabel = $t->getLabel(); if($tLabel === $t->name) { $tLabel = ""; } $attrs = ["data-desc" => $tLabel]; $tIcon = $t->getIcon(); if($tIcon) { $attrs["data-handle"] = ""; } $f->addOption($t->id, $label, $attrs); } $f->set('themeOffset', 1); $f->value = $this->allowedTemplates; if (!$valid) { $f->collapsed = Inputfield::collapsedHidden; } $inputfields->add($f); $fieldset = $modules->get("InputfieldFieldset"); $fieldset->label = "Files"; $fieldset->set('themeOffset', 1); if (!$valid) { $fieldset->collapsed = Inputfield::collapsedHidden; } elseif (!$this->config->useMarkupRegions) { $fieldset->collapsed = Inputfield::collapsedYes; } $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "noAppendFile"); $f->checked = $this->noAppendFile === 1; $f->columnWidth = 50; $f->description = sprintf($this->_('If you are using the [markup regions](%s) output strategy, it might be best to not append files to preserve your MJML markup before calling the API'), "https://processwire.com/docs/front-end/output/markup-regions/"); $f->label = $this->_("Don’t append file(s) to selected templates?"); $f->label2 = $this->_("Yes"); $f->icon = "chain-broken"; $f->value = $this->noAppendFile; $fieldset->add($f); $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "noPrependFile"); $f->checked = $this->noPrependFile === 1; $f->columnWidth = 50; $f->label = $this->_("Don’t prepend file(s) to selected templates?"); $f->label2 = $this->_("Yes"); $f->icon = "chain-broken"; $f->showIf = "noAppendFile=1"; $f->value = $this->noPrependFile; $fieldset->add($f); $inputfields->add($fieldset); $fieldset = $modules->get("InputfieldFieldset"); $fieldset->label = "Cache"; $fieldset->set('themeOffset', 1); if (!$valid) { $fieldset->collapsed = Inputfield::collapsedHidden; } $f = $modules->get("InputfieldText"); $f->attr("name", "cachePerGet"); $f->description = $this->_("You can have a unique output per GET variables. “raw” is ignored as it’s already used by the module"); $f->label = $this->_("Cache output per GET variables"); $f->icon = "question"; $f->notes = $this->_("If entering more than one, separate each by a space. To generate a unique output when any variable is present, enter just an asterisk: *"); $f->placeholder = "param1 param2 ..."; $f->value = $this->cachePerGet; $f->collapsed = InputField::collapsedBlank; $fieldset->add($f); $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "superuserNoCache"); $f->checked = $this->superuserNoCache === 1; $f->columnWidth = 50; $f->description = $this->_("The MJML output is cached to avoid making too many API calls. However, by default, the cache is cleared if you save the page or if you modify the template file. With this option you can regenerate the cache on each render"); $f->label = $this->_("Bypass cache for superusers?"); $f->label2 = $this->_("Yes"); $f->icon = "user"; $f->collapsed = InputField::collapsedBlank; $fieldset->add($f); $f = $modules->get("InputfieldAsmSelect"); $f->attr("name", "rolesNoCache"); $f->columnWidth = 50; $f->label = $this->_("Bypass cache for specific roles"); $f->icon = "users"; $roles = $this->wire()->roles; foreach($roles->find("") as $t) { if($t->name === "superuser") continue; $f->addOption($t->id, $t->name); } $f->showIf = "superuserNoCache=1"; $f->value = $this->rolesNoCache; $fieldset->add($f); $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "clear"); $f->label = $this->_("Clear the cache for all templates?"); $f->label2 = $this->_("Yes"); $f->icon = "trash-o"; if (!$valid) { $f->collapsed = Inputfield::collapsedHidden; } $fieldset->add($f); $inputfields->add($fieldset); $fieldset = $modules->get("InputfieldFieldset"); $fieldset->label = "Links"; $fieldset->set('themeOffset', 1); if (!$valid) { $fieldset->collapsed = Inputfield::collapsedHidden; } $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "convertRelativeLinks"); $f->checked = $this->convertRelativeLinks === 1; $f->columnWidth = 50; $f->description = $this->_("Ideally you should handle this in your textarea’s setting, however in cases where you can’t you can toggle this option to convert relative links into absolute ones"); $f->label = $this->_("Convert relative links?"); $f->label2 = $this->_("Yes"); $f->icon = "link"; $fieldset->add($f); $f = $modules->get("InputfieldText"); $f->attr("name", "relativeLinksHost"); $f->columnWidth = 50; $f->description = sprintf($this->_("By default the prepended host is the root’s page `httpUrl`"), $this->pages(1)->httpUrl); $f->label = $this->_("Prepend a different host"); $f->icon = "link"; $https = $this->config("https") ? "https" : "http"; $hosts = "`$https://" . implode("/`, `$https://", $this->config->httpHosts) . "/`"; $f->notes = $this->_("Possible options from your config: ") . $hosts; $f->placeholder = $this->pages(1)->httpUrl; $f->showIf = "convertRelativeLinks=1"; $f->value = $this->relativeLinksHost; $fieldset->add($f); $f = $modules->get("InputfieldText"); $f->attr("name", "relativeLinksParams"); $f->columnWidth = 50; $f->description = $this->_("You can append query parameters to converted links, i.e: if you want to have control over you `utm_` parameters. You can also use page fields within `{brackets}`"); $f->label = $this->_("Append query parameters"); $f->icon = "question"; $f->notes = $this->_("If a query string is already present in the url, your parameters are simply appended"); $f->placeholder = "param1=value¶m2=value&..."; $f->showIf = "convertRelativeLinks=1"; $f->value = $this->relativeLinksParams; $fieldset->add($f); $f = $modules->get("InputfieldTextarea"); $f->attr("name", "relativeLinksIgnore"); $f->columnWidth = 50; $f->description = $this->_("You can decide which links (with a matching `href`) to ignore by inserting one string per line and prepend with `regex:` if you want to use a regular expression. This is useful to skip keywords from emailing tools, i.e: `\*|UNSUBSCRIBE|\*`"); $f->label = $this->_("Skip specific links"); $f->icon = "chain-broken"; $f->placeholder = "link1\nlink2\nregex:\*\|.*\|\*"; $f->showIf = "convertRelativeLinks=1"; $f->value = $this->relativeLinksIgnore; $fieldset->add($f); $inputfields->add($fieldset); $f = $modules->get("InputfieldCheckbox"); $f->attr("name", "disableMinify"); $f->checked = $this->disableMinify === 1; $f->description = $this->_("By default the output generated by MJML is minified by this module to reduce its size, however if your layout looks weird somehow you can disable it"); $f->label = $this->_("Disable minification?"); $f->label2 = $this->_("Yes"); $f->icon = "file-archive-o"; if (!$valid) { $f->collapsed = Inputfield::collapsedHidden; } else { $f->collapsed = InputField::collapsedBlank; } $f->set('themeOffset', 1); $inputfields->add($f); return $inputfields; } public function ___upgrade($fromVersion, $toVersion) { $this->clearAllCache(); $modules = $this->modules(); $moduleConfig = $modules->getModuleConfigData($this); $moduleConfig = array_merge($this->getDefaults(), $moduleConfig); if(version_compare($fromVersion, "1.0.2", "<=")) { $moduleConfig["allowedTemplates"] = $moduleConfig["templates"]; } $modules->saveModuleConfigData($this, $moduleConfig); } public function ___install() { $this->modules()->saveModuleConfigData($this, $this->getDefaults()); } public function ___uninstall() { $this->clearAllCache(); } }