Leaderboard
Popular Content
Showing content with the highest reputation on 04/12/2018 in all areas
-
Each year we cycle 1000km with our company to fight against cancer. To participate with 4 teams we have to collect 20.000 euros. Our graphic designers made some goodies en we also organise a spaghetti fundraiser. To help the sales we decided to make a webshop with our webteam. And of course we used our favourite CMS for the job! ? https://shop.typografics.be/ At this moment we raised more than 20.000 euros, so all our teams can participate. This is a very special project for us, unfortunately one of our colleagues has been diagnosed with cancer and currently is fighting against this terrible disease. Premium PW modules used in this project: PadLoper FormBuilder ProFields Variations With a few hooks here and there we managed to make it work. To handle the payments we made use of Mollie, we created our own payment module and will opensource this soon. You can read more about this project in our blog (in dutch) Unfortunately we're currently only shipping to Belgium We’re already brainstorming to make a 2.0 version of this webshop, so all your feedback is more than welcome.4 points
-
Have you tried to do $page->of(false) before you add the image? $page->of(false); $page->filepreviews->add($img); $success = $page->save(); EDIT: this would explain why the first did not get added but the follwing did. Because of() was only set to false after adding the first image4 points
-
I know you know about this now @bernhard but others may not and since I just stumbled across this post, I thought I post this which shows running a $pages->find in the console panel and then viewing the resulting SQL Query in the "Selector Queries" section of the Debug Mode panel (from the ajax bar). Hope that helps others who come across this thread.4 points
-
Hey @tpr - thanks for your work on this - I think it's a great addition. Not sure why, but I had to change max-height for the rolled up version from 39px to 47px, otherwise I got a scrollbar in the header. Does it look OK for you with this change (just committed). I moved the js into main.js (which is new) as a place to load all general JS (that doesn't need injected PHP vars). The last version also includes some z-index fixes (introduced recently in the Tracy core), your sticky table header, and I added the fullscreen/halfscreen button to a couple of other panels that I thought could benefit from it. Also added @gmclelland's idea of linking to fullsize version of image. In the PW admin this opens in a modal, but just same page in frontend. Thanks again!4 points
-
I've been searching for a CSS-only solution for a while to reveal a scroll-to-top link only after scrolling down the page, but no luck. Today I figured out that it's possible with position: sticky. The beauty of this is that you don't need to check the scroll position in a scroll event (+ in document ready, window resize), which makes it really lightweight. Update: added "html { scroll-behavior: smooth; }" and now there's animated scroll (Firefox 58+, Chrome 63+). It seems that Js is not required anymore to this feature. CodePen demo3 points
-
@PWaddict Here is very useful site http://youmightnotneedjquery.com/ that you want to visit when you try to convert jQuery to vanilla.3 points
-
The cloning is in the scrolling event, because PW also clones the buttons at the page load (for the head area). If I clone the original savebuttons during pageload, they will also be duplicated in the head. However, it is ensured that this event is only executed once during scrolling as soon as the top threshold is reached. If a page is not long enough, no button needs to be cloned. The position storage is also intercepted with a debounce. The position is not saved until 250 milliseconds after the end of the scrolling event. It may look like this in the code, but any manipulation of the DOM is done only once.3 points
-
@wbmnfktr thanks lots, if I could get that to work, that would make me happy! This works beautifully [[jumplinks for=h2]] with your code did it all in one go! I tried the following code, but it would not work, can you see where I went wrong? <?php // $for defines the headlines you want to take care of. // i.e.: h2, h3, h4 $for = str_replace('h2', 'h3', $for); $for = explode(' ', $for); foreach($for as $k => $v) $for[$k] = trim($v); $for = implode('|', $for); $anchors = array(); $value = $hanna->value; if(preg_match_all('{<(' . $for . ')[^>]*>(.+?)</\1>}i', $value, $matches)) { foreach($matches[1] as $key => $tag) { $text = $matches[2][$key]; $anchor = $sanitizer->pageName($text, true); $anchors[$anchor] = $text; $full = $matches[0][$key]; $value = str_replace($full, "<a name='$anchor' href='#'></a>$full", $value); } $hanna->value = $value; } if(count($anchors)) { echo "<ul class='jumplinks'>"; foreach($anchors as $anchor => $text) { echo "<li><a href='$page->url#$anchor'>$text</a></li>"; } echo "</ul>"; } else { echo ''; } @bernhard thanks, I prefer not to go the route of repeaters here, as I just want to write the article and use h2 and h3 headers and then let Hanna deal with it. But thanks for chiming in, much appreciated.3 points
-
@ryan provides exactly this kind of TOC/jumplinks as an example for the Hanna Code module. Take a look at the end of the page: http://modules.processwire.com/modules/process-hanna-code/ So you could use Hanna Code inside your pages or create something new from that example. Here is the code: <?php // $for defines the headlines you want to take care of. // i.e.: h2, h3, h4 $for = str_replace(',', ' ', $for); $for = explode(' ', $for); foreach($for as $k => $v) $for[$k] = trim($v); $for = implode('|', $for); $anchors = array(); $value = $hanna->value; if(preg_match_all('{<(' . $for . ')[^>]*>(.+?)</\1>}i', $value, $matches)) { foreach($matches[1] as $key => $tag) { $text = $matches[2][$key]; $anchor = $sanitizer->pageName($text, true); $anchors[$anchor] = $text; $full = $matches[0][$key]; $value = str_replace($full, "<a name='$anchor' href='#'></a>$full", $value); } $hanna->value = $value; } if(count($anchors)) { echo "<ul class='jumplinks'>"; foreach($anchors as $anchor => $text) { echo "<li><a href='$page->url#$anchor'>$text</a></li>"; } echo "</ul>"; } else { echo ''; } For better understanding you might want to install Hanna Code and or want to customize this for your needs.3 points
-
Oh yes. Very much so. Try it yourself. Edit a value in chrome dev and inspect what's been sent. I forgot to mention. If you are not doing it already, use CSRF protection and a honey pot.3 points
-
I have opened a ticket on GitHub, in which I present a modification respectively a proof of concept, which significantly improves the editing of pages, especially with long contents. The save buttons are always in view with this modification, and the scroll position when editing and saving is saved and restored after saving. You can also find a screencast and code extensions on GitHub. What do you think of that? Link: https://github.com/processwire/processwire-requests/issues/1772 points
-
2 points
-
Not sure about adding to AOS, there is already ctrl+s there which is available every part of the screen As for the JS part of the code, I see that too much is happening on a scroll event (cloning, adding events, appending to dom). This may cause unnecssary cpu hickups so I would recommend solving it other ways. I would either clone the buttons on page load (outside the scroll event), or toggle a class on the body and position the buttons with CSS. As for the scroll event I would sure add debounce. And perhaps I would use localStorage instead cookies, and save it only on window.unload (or beforeunload) event only.2 points
-
yes, you can $session->redirect() after processing your form, then it will clear your post data. next thing to take care of is when the form is submitted and there is a validation error on the server, the form for the user needs to be re-populated with the values already submitted2 points
-
Hi @SamC, great to see you are making progress 2 things: 1) I'm always sending forms to the same page an in that template I do something like this: if($config->ajax AND $input->post->submit) { // process form } // regular template code Usually this makes the setup easier. 2) As you see there is a LOT to consider with forms. That's why I built the RockForms module using Nette Forms, because that takes care of all this stuff. I'm not saying "stop learning and use my module" here But the nette docs might be an interesting read for you. You'll come across the things mentioned above (CSRF, validation etc.). And besides that, it is the only library I found that does validation on the client side and on the server side with one single setup of the form2 points
-
@mel47 Thanks. I just pushed another release so the module will only load in the admin, as it was before. But it is still no longer limited to specific admin processes.2 points
-
It's always good to sanitise and validate. You don't lose anything. The only things you don't sanitise are passwords (just validate) and usually, a submit button (just check if post sent). Regarding the radio buttons, you are using $sanitizer->option which is validating the options sent. Implicitly, you are sanitising here as well since you provide the array of sanitised values. Yes. Sanitise it too as necessary. But, these depend on what you are going to do with the values. General practice though is to sanitise at the earliest opportunity. There is also encoding of html entities if you are going to be echoing back input values. So, this... echo $sanitizer->entities($str);2 points
-
Worth watching entirely. Don't miss the fallback solution (5 steps). Firefox/Firefox Developer Edition has a grid inspector. https://www.mozilla.org/en-US/developer/css-grid/ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout https://gridbyexample.com/ https://rachelandrew.co.uk/css/cheatsheets/grid-fallbacks https://cssgrid.io/ (there is also https://flexbox.io/ by the same author) https://cssgridgarden.com/ (already mentioned in the topic) And so on.2 points
-
So I decided to wade into module development and created a wrapper module around ImageOptim, a service that compresses and optimizes images in the cloud. ImageOptim currently handles JPG, PNG and GIF files and, depending on the settings you use, shaves off between 15% and 60% in filesize. Great for bandwidth and great for users, especially on mobile. This module handles the part of uploading images to ImageOptim via their official API, downloading the optimized version and storing it alongside the original image. Download & Info GitHub / Module directory / Readme / Usage Why ImageOptim? There are other image optimization services out there, some of them free, that have outstanding ProcessWire modules. A few things make ImageOptim the best tool for most of my customers: It's not free, i.e. it will probably be around for a while and offers support. However, it's cheaper than some of the bigger competitors like Cloudinary. And it does PNG compression better than any of the free services out there, especially those with alpha channels. Installation Install the module like any other ProcessWire module, by either copying the folder into your modules folder or installing it via the admin. See above for downloads links on GitHub and in the module directory. Requirements To be able to upload images to the service, allow_url_fopen must be set on the server. The module will abort installation if that's not the case. I have only tested the module on ProcessWire 3.x installations. I don't see why it shouldn't work in 2.x, if anyone wants to try it out and report back. ImageOptim account To compress images, you first need to sign up for an ImageOptim account. They offer free trials to try the service. Usage (manual optimization) Images can be optimized by calling the optimize() method on any image. You can pass an options array to set ImageOptim API parameters. $image->size(800,600)->optimize()->url $image->optimize(['quality' => 'low', 'dpr' => 2]) // Set quality to low and enable hi-dpi mode Automatic optimization The module also has an automatic mode that optimizes all image variations after resizing. This is the recommended way to use this module since it leaves the original image uncompressed, but optimizes all derivative images. $image->size(800,600)->url // nothing to do here; image is optimized automatically To change compression setting for single images, you can pass an options array along with the standard ImageResizer options. Passing false disables optimization. $image->size(800, 600, ['optimize' => 'medium']) $image->size(800, 600, ['optimize' => ['quality' => 'low', 'dpr' => 2]]) $image->size(800, 600, ['optimize' => false]) For detailed usage instructions and all API parameters, see the usage instructions on GitHub. Filenames Optimized images will be suffixed, e.g. image.jpg becomes image.optim.jpg. You can configure the suffix in the module settings. Roadmap Asynchronous processing. Not really high on the list. Image variations need to be created anyway, so waiting a few seconds longer on first load is preferable to adding complexity to achieve async optimization. Optimize image variations created by other modules. CroppableImage comes to mind. I don't use any of these, so if somebody wants to help out and submit a pull request — all for it! Add a dedicated page in the setup menu with a dashboard and detailed statistics. ImageOptim's API is very barebones for now, so not sure if that's feasible or even necessary. Stability I've been using this module on production sites for some time now, without hiccups. If you do notice oddities, feel free to comment here or investigate and submit PRs.1 point
-
MultiValue Textformatter for ProcessWire Set flexible data structures in "key = value1, value2, ..." format to use as variable groups in templates. Great for general site settings, social links, menus, etc. Converts contents of textarea field "social_links" from this: @ url ::: title ::: target Facebook = https://facebook.com/mycompany ::: Follow us on Facebook ::: _blank Linkedin = https://www.linkedin.com/mycompany ::: NULL ::: _blank Email = #contact ::: Contact to an object to be used like this: echo $page->social_links->facebook->title // result: "Follow us on Facebook" echo $page->social_links->email->title // result: "Contact" Putting it together: <ul> <?php foreach($page->social_links as $item) { ?> <li> <a href="<?php echo $item->url; ?>" target="<?php echo $item->target; ?>" title="<?php echo $item->title; ?>"> <?php // name is the original key echo $item->name; ?> </a> </li> <?php } ?> </ul> Syntax: @ header1 ::: header2 ::: header3 Key = value ::: value ::: value [ ::: ...] Another key = value ::: value [...] More info: Modules directory GitHub Feedbacks and suggestions are welcome.1 point
-
http://caniuse.com/#feat=css-grid The CSS standard Grid Layout will soon be usable in the stable versions of the leading browsers. It should hit Chrome and Firefox in March. It is in Safari tech preview, but no clear date. MS Edge is working on updating support. I guess support in mobile browsers will follow. 2017 is the year to use it on sites, where it is OK to experiment with bleeding edge stuff. A complete guide to the system on CSS Tricks Learn by examples (includes video tutorials) Rachel Andrews summarizing use cases for Grid, Flexbox and Box Alignment in a single article on Smashing Mag (note: heavy with Codepens) News on all things CSS Layout curated by Rachel Polyfill support is unfortunately dragging behind with no contributors stepping up to help Fremy1 point
-
I don't know . But this is what Google says . I trust Google 100% (), so this must be correct. OK, so, it's StackOverflow. I trust SO .... <script type="text/javascript"> function loadXMLDoc() { var params = "lorem=ipsum&name=binny";// your data to the server var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4 if (xmlhttp.status == 200) { document.getElementById("output").innerHTML = xmlhttp.responseText; } else if (xmlhttp.status == 400) { alert('There was an error 400'); } else { alert('something else other than 200 was returned'); } } }; xmlhttp.open("POST", '../my-url/', true); xmlhttp.send(params); } </script> source1 point
-
Can confirm it works on PW3 too, I had some issues with the included AWS SDK: So just for trying something out because I couldn't figure out anything from the error, I switched to installing the Amazon SDK through composer to get the latest version of the SDK and I basically just had to change how the S3 client is initialized. $this->client = new S3Client([ 'version' => 'latest', //Hardcoded value, think this has to do with the SDK version? 'region' => 'us-east-2', //Hard coded value for now, could be a config field 'credentials' => [ 'key' => $this->s3_key, 'secret' => $this->s3_secret, ], ]); Also removed the require_once for the included SDK.1 point
-
I guess you are talking about the block and the corresponding detail page. You seem to put the text in the pte template. Why not put a page select field to the pte template and put the content of that textblock where it belongs, to the detail page (let's call it "summary"). Assuming your detail template is "detail" and the pte template is "pte_textblock". Add a page select to pte_textblock named "linkedPage". So in your pte_textblock template you have access to the fields of the detail page via $page->linkedPage->summary and also to the read more destination via $page->linkedPage->url. Ring a bell?1 point
-
Yepp, the border is a bit thick now. I'm fine with v4 if you think panels doesn't merge too much with the rest of the site. One thing though: the icon positions need to be adjusted (before the title and the window/close icons too) + the newly added rollup state needs adjustments too.1 point
-
1 point
-
yes, if you need to hold those sorts of things, especially images, you should stick with real fields. The main advantages of using a settings module could be summed up as: (1) fast an easy to add a settings page (since you can reuse the same json file) and don't need to setup fields (2) dedicated edit screen with no tabs as on the edit screen (no children, settings etc), no delete (3) settings stored as JSON in 1 table/field (module config) (4) human readable editing url, no url param (5) since it is a process module, easy to link to the settings page directly in the admin menus more discussions on the Settings Factory topic..1 point
-
Awesome news, really! From now on, whenever I need a form, your module will be the one to go...1 point
-
just merged @gebeer's changes to support multiple renderers in separate files and he also added a bootstrap renderer thank you!1 point
-
ProcessWire supports that out of the box. See here for a getting started documentation.1 point
-
Hi everyone, just writing a new tutorial as a 'break' from learning PHP. It involves making a contact form. In brief, the part of the form with the radios and a checkbox: contact.php <fieldset class="form-group"> <div class="row"> <legend class="col-form-label col-sm-2 pt-0">Choose a colour</legend> <div class="col-sm-10"> <div class="form-check"> <input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" value="red" checked> <label class="form-check-label" for="gridRadios1">Red</label> </div> <div class="form-check"> <input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" value="blue"> <label class="form-check-label" for="gridRadios2">Blue</label> </div> <div class="form-check"> <input class="form-check-input" type="radio" name="gridRadios" id="gridRadios3" value="green"> <label class="form-check-label" for="gridRadios3">Green</label> </div> </div> </div> </fieldset> <div class="form-group row"> <div class="col-sm-2"><a href="#">Terms & Conditions</a> (required)</div> <div class="col-sm-10"> <div class="form-check"> <input class="form-check-input" type="checkbox" name="checkboxAgree" id="checkboxAgree"> <label class="form-check-label" for="checkboxAgree">Tick to agree</label> </div> </div> </div> ...and the template where the form posts to (very early days work in progress): contact-process.php <?php namespace ProcessWire; // Sanitize user inputs $name = $sanitizer->text($input->post->name); $email = $sanitizer->email($input->post->email); $message = $sanitizer->textarea($input->post->message); // Radio button values $allowed_values = ['red', 'green', 'blue']; /** * Only allow allowed values above, returns NULL if anything other * than the values in the above array */ $radio_value = $sanitizer->option($input->post->gridRadios, $allowed_values); // DOES THIS NEED TO BE SANITIZED?? $checkbox_agree = $input->post->checkboxAgree; $submitted = $input->post->submitted; // Concantenate all the values $str = 'Name: ' . $name; $str .= '<br>'; $str .= 'Email: ' . $email; $str .= '<br>'; $str .= 'Message: ' . $message; $str .= '<br>'; $str .= 'Radio value: ' . $radio_value; $str .= '<br>'; $str .= 'Checkbox agree: ' . $checkbox_agree; $str .= '<br>'; $str .= 'Form is submitted: ' . $submitted; echo $str; Form outputs: Name: Sam Email: sam@test.com Message: Hi, this is a test message... Radio value: blue Checkbox agree: on Form is submitted: 1 So would it be necessary to sanitize the radio buttons so only red, blue or green are submitted, and also, what about the checkbox? Could a user manipulate the POST array to send values other than the ones I want? Maybe just being paranoid but don't want to give out information that I haven't checked first. Thanks1 point
-
Another option could be to use repeaters in your template. This might or might not be better and depends a lot on your situation... The TOC would be really simple then. Find operations might be harder...1 point
-
1 point
-
Thanks, works fine here. The 47px vs 39px issue may come from a box-sizing border-box/content-box issue, perhaps my frontend CSS was interfering somehow. If so, .tracy-panels should get box-sizing: border-box in Tracy. Sticky table headers are working fine too, thanks. What do you think about making the panel headers a bit smaller? I feel they are a bit too large, a slightly slower font and less padding wouldn't hurt imho.1 point
-
1 point
-
No issues with me either, I just started a new profile with 3.0.98 and latest AOS and all works fine, I can switch languages from admin etc.1 point
-
Like you said, $page->inscr_nr is a text field, not a repeater, so output its value directly, removing "->title": $content .= $page->inscr_nr . '</p></td></tr>';1 point
-
@zimali. Consider this your final warning. You have been posting Drupal stuff in the ProcessWire forums. You have, presumably, ignored my request for clarification. This has trolling written all over it.1 point
-
Of course several unforseen issues came up but most of them are fixed: closing a rolled-up panel and re-activating it from the debug bar opened it in the rolled-up state. To fix, on closing a rolled-up panel "tracy-mode-rollup" class is removed so it opens up in normal state. setting width-height were tricky here and there but seems to be OK now with all the panels I've tried (Chrome only though). "ProcessWire Logs" panel has a link in h1, I've added pointer-events: none in rolled-up state to prevent accidental click on move resized panels were loaded cropped after page reload (if they were rolled up before reload). I've fixed it with a "beforeunload" event to allow Tracy save positions correctly keeping state after page reload: would be nice to have but I think this would be better handled in the core (Nette Tracy) Changes: style.css Lines at the bottom + added "user-select: none" on line 111 for h1 TracyDebugger.module Lines 823-867 - I haven't found a better place to add the JS part, I thought there's a js file that the module always loads but apparently there isn't. tracy-rollup.zip1 point
-
@SamC Best practice is to sanitize everything a user sends to server, never trust users. It is easy to hack forms using just your browser "developer tools" it lets you change values of everything including all form values. Using your example colors values, the values could be changed to do multiple things from "using unsuported other values" to injecting SQL or PHP code for whatever nefarious purpose. The only time you can relax is if you have predefined values and match those values to what is sent to the server. For example in your case, on form submit you could compare the gridRadios array to a PHP array of values to see if there is any match, if so then data is valid and proceed otherwise reject submission as invalid. but 99.9% of the time it's best to just sanizite user input1 point
-
@MarcoPLY Should work now imo. Repository is updated. This version also creates and deletes the permission, but you still have to add it to your editor role. https://github.com/theo222/ImagePickerField1 point
-
@MarcoPLY Can you test this? In ImagePickerList.module (should be in site/modules/ImagePicker/) Add 'permission' => 'imagepicker' to the getModuleInfo() block public static function getModuleInfo() { return array( 'title' => 'ImagePicker Lister', 'version' => 90, 'summary' => 'ImagePicker Lister', 'singular' => true, 'autoload' => false, 'permission' => 'imagepicker' ); } Then as admin in the backend do: Access -> Permissions -> Add New: Name: imagepicker After saving, give it a title like "Image Picker". Then add this permission to your editor role (Acesss -> Roles). Try if it works. Maybe you have to log-out and in and refresh modules (Modules->Refresh).1 point
-
If you add the error line to https://github.com/processwire/processwire-issues/issues/408 Ryan will no doubt make short work of it.1 point
-
1. This part... // 4. // Split search phrase // If nothing above matches, try each word separatly if( !count($matches) ) { $q_separate = preg_replace('/\PL/u', ' ', $q); // "Remove" everything but letters $q_separate = preg_split('/\s+/', $q_separate); foreach ($q_separate as $q_word) { if ( $q_word != '' && strlen($q_word) > 4 ) { $append_products_separate = $pages->find("title|headline|summary~=$q_word, template=product, limit=50"); $matches->append($append_products_separate); } } } ...is needlessly inefficient. You don't need to do separate database queries per word here - you can use the pipe as an OR condition between words. So basically replace spaces with pipes in your search phrase and match against title|headline|summary. 2. Consider using the %= operator so you can match part words. So a search for "toast" will match "toaster". 3. If you don't have a huge number of products then maybe a fuzzy search using Levenshtein distance could be a possibility, for product title at least. I did a quick test against 196 country names (239 words) and it was reasonably fast. $q = 'jermany'; $countries = $pages->find("template=country"); $matches = new PageArray(); foreach($countries as $country) { $words = explode(' ', strtolower($country->title)); foreach($words as $word) { // Adjust max Levenshtein distance depending on how fuzzy you want the search if(levenshtein($q, strtolower($word)) < 2) { $matches->add($country); break; } } }1 point
-
Few random thoughts... Log all searches (you probably already do). This works $log->save('search',"Query = '$q' Results = ($results_count) $pages_found"); gives something like 2018-03-12 16:19:08 guest https://example.com/search/?q=taxi Query = 'taxi' Results = (3) 1170|1073|1693 2018-03-13 11:22:27 guest https://example.com/search/?q=9001 Query = '9001' Results = (1) 1021 This is valuable because it shows you what you are up against! Consider incorporating stemming (if you are working in English, at least). The best-known algo is the Porter stemming algorithm. There are PHP implementations on GitHub etc. What you could look at is for each product, hook on product save and have a back-end field programmatically populated with stemmed versions of Product Title etc., then stem search words and build that into your selectors. Also consider a back-end 'search keywords' type field, and if there's a glaring regular user mis-spelling just add that manually. This doesn't scale all that well, but can help with a quick fix. (I built a search for a fishing tackle shop site many years ago - pre-PW - and nearly as many searchers spelt 'Daiwa' as 'Diawa' as those who got it right. Easy quick fix.)1 point
-
To send emails from the template you would use WireMail, accessible in the API through $mail To get values for your image and other fields, just use regular $page->image, $page->title etc. The hardest part will be the cross-client responsive Email template. This is not a trivial thing to do. Some resources: http://leemunroe.github.io/responsive-html-email-template/email.html http://tedgoas.github.io/Cerberus/ If you send emails from ProcessWire and want to send them in plain text and in HTML, you typically construct the HTML email first and then convert that to a plain text version with something like $emailMessageAdminHtml = "<h1>Some HTML</h1><p>with paragraph</p>"; $emailMessage = str_replace( "<br>", "\n", strip_tags($emailMessageAdminHtml, '<br>') ); // construct email $mail->to($recipient); $mail->from($sender); $mail->subject($subject); $mail->body($emailMessage); $mail->bodyHTML('<html><body>' . $emailMessageAdminHtml . '</body></html>'); // send $mail->send();1 point
-
A recent similar topic...Maybe the suggested approaches there will help?1 point
-
» A more exhaustive version of this article is also available on Medium in English and German « First, we'd like to thank the very helpful community here for the excellent support. In many cases we found guidance or even finished solutions for our problems. So a big THANK YOU!!! We are pleased to introduce you to the new Ladies Lounge 18 website. The next ICF Women’s Conference will take place in Zurich and several satellite locations across Europe. We embarked on bold new directions for the development of the website — in line with the BRAVE theme. Ladies Lounge 18 — ICF Woman’s Conference website on Processwire ICF Church is a European Church Movement that started 20 years ago in Zurich and since experienced tremendous growth. There are already well over 60 ICF churches across Europe and Asia. ICF is a non-denominational church with a biblical foundation that was born out of the vision to build a dynamic, tangible church that is right at the heartbeat of time. With the growth of the Ladies Lounge from a single-site event to a multi-site event, the demands and challenges to the website have also increased. A simple HTML website no longer cuts it. Simplified frontend Our goal with the development of the new site was it to present the different locations — with different languages and partly different content — under once uniform umbrella — while at the same time minimising the administrative effort. In addition to the new bold look and feel, this year’s website is now simpler and easier and the information is accessible with fewer clicks. Some highlights of the new website Thanks to processwire, all contents are maintained in one place only, even if they are displayed several times on the website 100% customised data model for content creators Content can be edited directly inline with a double-click: Multi-language in the frontend and backend Dynamic Rights: Editors can edit their locations in all available languages and the other content only in their own language Easy login with Google account via OAuth2 Plugin Uikit Frontend with SCSS built using PW internal features (find of files…) Custom Frontend Setup with Layout, Components, Partials and Snippets Only about 3 weeks development time from 0 to 100 (never having published a PW before) Despite multi-location multi-language requirement, the site is easy to use for both visitors and editors: The search for a good CMS is over It’s hard to find a system that combines flexibility and scope with simplicity, both in maintainance and development. The search for such a system is difficult. By and large, the open source world offers you the following options: In most cases, the more powerful a CMS, the more complex the maintenance and development It is usually like that; The functionality of a system also increases the training and operating effort — or the system is easy to use, but is powerless, and must be reporposed for high demands beyond its limits. Quite different Processwire : You do not have to learn a new native language, you don’t have to fight plugin hell and mess around with the loop, you don’t have to torment yourself with system-generated front-end code or even learn an entierly new, old PHP framework . All our basic requirements are met: Custom Content Types Flexible and extensible rights-management Multilanguage Intuitive backend Well curated Plugin Directory Actually working front-end editing Simple templating with 100% frontend freedom In addition, Processwire has an exceptionally friendly and helpful community. As a rule of thumb, questions are answered constructively in a few hours . The development progresses in brisk steps , the code is extremely easy to understand and simple. Processwire has a supremely powerful yet simple API , and for important things there are (not 1000) but certainly one module which at least serves as a good starting point for further development. Last but not least, the documentation is easy to understand, extensive and clever . Our experience shows, that you can find a quick and simple solution with Processwire, even for things like extending the rights-management — most of the time a highly complex task with other systems. This is also reflected positively in the user interface. The otherwise so “simple” Wordpress crumbles when coping with more complex tasks. It sumbles over its apparent simplicity and suddenly becomes complex: Old vs. New — Simple and yet complicated vs. easy and hmmm … easy Our experience with Processwire as first-timers Before we found out about Processwire, we found CraftCMS on our hunt for a better CMS. We were frustrated by the likes of Typo3, WP or Drupal like many here. CraftCMS looked very promising but as we were digging deeper into it, we found it did not met our requirements for some big projects in our pipeline that require many different locations, languages and features. Initially we were sceptical about Processwire because; A. CraftCMS Website (and before UiKit also the admin interface) simply locked much nicer and B. because it is built on top of a Framework It was only later, that we found out, that NOT depending on a Framework is actually a very good thing in Processwire's case. Things tend to get big and cumbersome rather then lean and clean. But now we are convinced, that Processwire is far superior to any of the other CMS right now available in most cases. The good Processwire is the first CMS since time immemorial that is really fun to use (almost) from start to finish— whether API, documentation, community, modules or backend interface. Every few hours you will be pleasantly surprised and a sense of achievement is never far away. The learning curve is very flat and you’ll find your way quickly arround the system. Even modules can be created quickly without much experience. Processwire is not over-engineered and uses no-frills PHP code — and that’s where the power steams from: simplicity = easy to understand = less code = save = easy maintanance = faster development … Even complex modules in Processwire usually only consist of a few hundred lines of code — often much less. And if “hooks” cause wordpress-damaged developers a cold shiver, Hooks in Processwire are a powerful tool to expand the core. The main developer Ryan is a child prodigy — active, eloquent and helpful. Processwire modules are stable even across major releases as the code base is so clean, simple and small. There is a GraphQL Plugin — anyone said Headless-CMS?! Image and file handling is a pleasure: echo "<img src='{$speaker->image->size(400, 600)->url}' alt='{$speaker->fullname}' />"; I could go on all day … The not soooo good Separation of Stucture and Data The definition of the fields and templates is stored in the database, so the separation between content and system is not guaranteed. This complicates clean development with separate live- and development-environments. However, there is a migration module that looks promising — another module, which is expected to write these configurations into the file system, unfortunately nuked our system. I'm sure there will be (and maybe we will invest) some clever solutions for this in the future. Some inspiration could also be drawn here, one of the greatest Plugins for WP: https://deliciousbrains.com/wp-migrate-db-pro/ Access rights The Access-Rights where missing some critical features: Editors needed to be able to edit pages in all languages on their own location, and content on the rest of the page only in their respective language. We solved it by a custom field adding a relation between a page the user and a role that we dynamically add to the user to escalate access rights; /** * Initialize the module. * * ProcessWire calls this when the module is loaded. For 'autoload' modules, this will be called * when ProcessWire's API is ready. As a result, this is a good place to attach hooks. */ public function init() { $this->addHookBefore('ProcessPageEdit::execute', $this, 'addDynPermission'); $this->addHookBefore('ProcessPageAdd::execute', $this, 'addDynPermission'); } public function addDynPermission(HookEvent $event) { $message = false; $page = $event->object->getPage(); $root = $page->rootParent; $user = $this->user; if ($user->template->hasField('dynroles')) { if ($message) { $this->message('User has Dynroles: '.$user->dynroles->each('{name} ')); } // for page add hook… if ($page instanceof NullPage) { // click new and it's get, save it's post… $rootid = wire('input')->get->int('parent_id') ? wire('input')->get->int('parent_id') : wire('input')->post->parent_id; if ($message) { $this->message('Searching Root '.$rootid); } $root = wire('pages')->get($rootid)->rootParent; } elseif ($page->template->hasField('dynroles')) { if ($message) { $this->message('Page "'.$page->name.'" has Dynroles: '.$page->dynroles->each('{name} ')); } foreach ($page->get('dynroles') as $role) { if ($role->id && $user->dynroles->has($role)) { if ($message) { $this->message('Add dynamic role "'.$role->name.'" because of page "'.$page->name.'"'); } $user->addRole($role); } } } if (!($root instanceof NullPage) && $root->template->hasField('dynroles')) { if ($message) { $this->message('Root "'.$root->name.'" has dynamic roles: '.$root->dynroles->each('{name} ')); } foreach ($root->get('dynroles') as $role) { if ($role->id && $user->dynroles->has($role)) { if ($message) { $this->message('Add dynamic role "'.$role->name.'" because of root page "'.$root->name.'"'); } $user->addRole($role); } } } } } With the Droles and Access Groups Modules we were not able to find a solution. I thought it was hard to get absolute URLs out of the system — Ha! What a fool I was. So much for the topic of positive surprise. (Maybe you noticed, the point actually belongs to the top.) But while we’re at it — that I thought it would not work, was due to a somewhat incomplete documentation in a few instances. Although it is far better than many others, it still lacks useful hints at one point or another. As in the example above, however, the friendly community quickly helps here. processwire.com looks a bit old-fashioned and could use some marketing love. You notice the high level to moan with Processwire. There is no free Tesla here. Conclusion Processwire is for anyone who is upset about any Typo3, Wordpress and Drupal lunacy — a fresh breeze of air, clear water, a pure joy. It’s great as a CMF and Headless CMS, and we keep asking ourselves — why is it not more widely known? If you value simple but clean code, flexibility, stability, speed, fast development times and maximum freedom, you should definitely take a look at it. You have to like — or at least not hate PHP — and come to terms with the fact that the system is not over-engineerd to excess. If that’s okay with you, everything is possible — with GraphQL you can even build a completely decoupled frontend. We are convinced of the simplicity of Processwire and will implement future sites from now on using it as a foundation. Links & resources we found helpful API documentation and selectors API cheatsheet pretty handy, not quite complete for version 3.0 Captain Hook Overview of Hooks Weekly.PW newsletter a week, exciting Wireshell command line interface for Processwire Nice article about Processwire Plugins & Techniques that we used Custom Frontend Setup with Uikit 3 and SCSS, and Markup Regions Uikit Backend Theme ( github ) Oauth2 login modules In-house development Login with E-Mail Pro Fields for repeater matrix fields (infos, price tables, daily routines) Wire upgrade to update plugins and the core Wire Mail Mandrill to send mails FunctionalFields for translatable front-end texts that are not part of a content type (headings, button labels, etc.) Runtime markup for dynamic backend fields (combination of first and last name) Tracy debugger for fast debugging Textformatter OEmbed to convert Vimeo and Youtube links into embed codes HideUneditablePages thanks to @adrian1 point