Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 04/08/2018 in all areas

  1. As threatened in the Pub sub forum in the "What are you currently building?" thread, I've toyed around with Collabora CODE and built file editing capabilities for office documents (Libre-/OpenOffice formats and MS Office as well as a few really old file types) into a PW module. If you are running OwnCloud or NextCloud, you'll perhaps be familiar with the Collabora app for this purpose. LoolEditor Edit office files directly in ProcessWire Edit your docx, odt, pptx, xlsx or whatever office files you have stored in your file fields directly from ProcessWire's page editor. Upload, click the edit icon, make your changes and save. Can be enabled per field, even in template context. Currently supports opening and saving of office documents. Locking functionality is in development. See the README on GitHub for installation instructions. You should be reasonably experienced with configuring HTTPS and running docker images to get things set up quickly. Pull requests are welcome! Here is a short demonstration:
    9 points
  2. Everybody's currently hyping about Collabora CODE and editing OpenOffice, MS Office and PDF documents inside their Own-/NextCloud. Recently, a colleague wanted to know if it was possible to do that in our corporate intranet too. And so I thought, why not? So I have started dabbling with NextCloud and the Collabora docker image a bit and, after banging my head against the wall for a while (figuratively) and doing a lot of reverse engineering to wrap my mind around the wopi and lool stuff, I managed to include the LOOL leaflet in a page of my own making and load and save documents in it through a small PHP script. I'm starting to create a PW module from this that allows inline (more precisely: modal) editing of supported document types. The next steps: A small file abstraction layer that converts back and forth between WOPI-style file ids necessary for LOOL and PW file urls, provides locking and checks access permissions A file access token generator / verifier A WOPI REST endpoint library that handles all the actions needed for opening, saving, locking etc. and provides auxiliary information like user info, avatar etc. Finally, the module itself, containing both the WOPI endpoint logic that the Collabora image uses and the UI extension for InputfieldFile that creates the editor I'm going to get the basics set up, then I'll whip up a getting started manual that also documents the "missing links" that I had so much trouble with and finally push this and the code to github so anybody interested can contribute too.
    6 points
  3. Here's a ProcessWire 3 example. ProcessWire does all the heavy lifting regarding headers, etc. Please see comments in the code and the assumptions made. In this example, we assume a download link is present on the page. Modify the code to suit your needs, if you find it useful . <?php namespace ProcessWire; // @todo: you can add an Array $options parameter to pass to $files->zip() function createZip($zipfile = '', $filesToZip = array()) { // @see: https://processwire.com/api/ref/files/ $files = wire('files'); // @todo: error checks here, e.g. if $zipfile (destination) is empty; // save zipped files to disk // @see: https://processwire.com/api/ref/files/zip/ $files->zip($zipfile, $filesToZip); // force download of zipped files // @see: https://processwire.com/api/ref/files/send/ $files->send($zipfile, array( 'forceDownload' => true, 'exit' => false )); // delete zip file on server after download unlink($zipfile); exit; } $result = array(); $imagesArray = array(); $zipImages = array(); // grab and sanitize image names string in $_GET parameter 'images' // in this example, names are comma-separated $images = $sanitizer->entities($input->get->images); // if we have a 'get' input if($images) { // create array of image names (array('image-1.jpg', 'image2.jpg')) etc $imagesArray = explode(',', $images); // sanitize each image name as per ProcessWire filename expectations $imagesArray = $sanitizer->array($imagesArray, 'filename'); // if we got an array of sanitized image names if(count($imagesArray)) { // get the image repository // in this example, we store all images in one page in... // ... an image field named 'images' $imagesPage = $pages->get("/zip-files/zip-file-images/"); // create selector to find images requested in $_GET // this example assumes $_GET parameter 'images' contains image basenames // @note: we make sure image names are lowercase // selector: "basename=image-1.jpg|image2.jpg" $imagesSelector = mb_strtolower(implode('|', $imagesArray)); $images = $imagesPage->images->find("basename={$imagesSelector}"); // if we got a match if($images->count) { // create array of full disk paths to the image files $zipImages = $images->explode('filename'); // just making sure we got an array back if(count($zipImages)) { // name of zip file to create (or update) $zipfile = $config->paths->assets . "my_zip_".time().".zip"; // create zip file + force download //$result = createZip($zipfile, $zipImages); createZip($zipfile, $zipImages); } } } } // assuming $_GET will contain image basenames $downloadImagesLink = "/zip-files/zip-file-test/?images=abstract.jpg,Tree-Wide.jpg,fashion.jpg,dessert.jpg,citrus_fun.jpg"; ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title><?php echo $page->title; ?></title> <link rel="stylesheet" type="text/css" href="<?php echo $config->urls->templates?>styles/main.css" /> </head> <body> <h1><?php echo $page->title; ?></h1> <?php $out = "<p>Download Zipped Images</p>"; $out .= "<p><a href='{$downloadImagesLink}'>Click to download</a></p>"; echo $out; ?> </body> </html>
    5 points
  4. This is one reason why ProCache buster supports file.version.ext URLs as an option. But I wouldn't use that option if the reason is to accommodate a proxy server issue, which by all indications no longer exists. Use it if you prefer it for some other reason (maybe GTmetrix is a valid one). From what I understand, there was a version of a proxy server (Squid) that didn't cache query string assets due to a configuration issue that was fixed like 10 years ago or more. But mention of it ended up in some O'Reilly web optimization book around the same time, and so the issue took on a life of its own, and carries on due to tools continuing to look for it, even if apparently no longer relevant. Technically, query string is simpler and just slightly more efficient because no Apache rewrite rules have to get involved.
    4 points
  5. I think I'm almost done Plugins are now very easy to create and extremely useful as they are reuseable across multiple projects: Sample setup of column statistics: // custom column stats grid.plugins.colStats.valueGetters = { id: function(column, selected) { return grid.pluck(0,{selected}).length + (selected ? ' selected' : ' rows'); }, percent: function(column, selected) { return grid.avg(column, {selected}); }, }; Everything works in the backend and in the frontend, just echo the field as you would do with any other field echo $page->mygridfield Data loads blazingly fast. The 10.001 rows here load in 500ms and are automatically compressed by the server to reduce transfer time (here it results in 90KB instead of 1MB of data!). Thx again @adrian for that idea. Cell renderers can be combined as you like! That's really powerful and great! We can build libraries of different cellRenderers that we need often and then just wrap them in a function and return the one's we like: In this column I combine the "actionItems" renderer and the "percentBar" renderer. The actionItems are only applied for regular rows. For rows of the "colStats" plugin we only append " (avg)" to the cell and don't show the icons because they are here to edit this lines related page (and of course the statistics row has no related page): colDef.cellRenderer = function(params) { var str = ''; if(!params.data.colStatsRowType) { str = RockGrid.renderers.actionItems(params, [ { icon: 'fa fa-bolt', href: '/admin/page/edit/?id=' + params.data.id, target: ' target="_blank"', str: 'Open external: ' + params.data.title, },{ icon: 'fa fa-search', href: '/admin/page/edit/?field=percent&id=' + params.data.id, str: 'Open panel: ' + params.data.title, class: ' class="pw-panel hover"', }, ]); } else { str = ' (avg)' } return RockGrid.renderers.percentBar(params, str, (!params.data.colStatsRowType ? 0 : 2)); }; Updating the grid after closing the panel is on my todo-list
    2 points
  6. I have a suggestion for an "explanation" for such a client in form of an offer you could send him: Form with honeypot spam-protection: X € Form with recaptcha spam-protection: X + Y € And with the Y € we could fund the implementation of recaptcha into RockForms "Y" has to be totally overpriced, of course
    2 points
  7. Something like this... // your functions function doSomething($) { // @note: non-self-executing function // 'config' is a global PW variable var text = config.demo; $("h2.tagline").addClass("color-red"); console.log(text); } // ready jQuery(document).ready(function ($) { doSomething($);// pass jQuery ($) if needed });
    2 points
  8. Just wanted to share what I recently used to create forms in modules and in frontend using the API and Inputfield modules PW provides and uses on its own. I think many newcomers or also advanced user aren't aware what is already possible in templates with some simple and flexible code. Learning this can greatly help in any aspect when you develop with PW. It's not as easy and powerful as FormBuilder but a great example of what can be archieved within PW. Really? Tell me more The output markup generated with something like echo $form->render(); will be a like the one you get with FormBuilder or admin forms in backend. It's what PW is made of. Now since 2.2.5~ somewhere, the "required" option is possible for all fields (previous not) and that makes it easier a lot for validation and also it renders inline errors already nicely (due to Ryan FormBuilder yah!). For example the Password inputfield already provides two field to confirm the password and will validate it. De- and encryption method also exists. Or you can also use columns width setting for a field, which was added not so long ago. Some fields like Asm MultiSelect would require to also include their css and js to work but haven't tried. Also file uploads isn't there, but maybe at some point there will be more options. It would be still possible to code your own uploader when the form is submitted. Validation? If you understand a little more how PW works with forms and inputfields you can simply add you own validation, do hooks and lots of magic with very easy code to read and maintain. You can also use the processInput($input->post) method of a form that PW uses itself to validate a form. So getting to see if there was any errors is simply checking for $form->getErrors();. Also the $form->processInput($input->post) will prevent CSRF attacks and the form will append a hidden field automaticly. It's also worth noting that processInput() will work also with an array (key=>value) of data it doesn't have to be the one from $input->post. Styling? It works well if you take your own CSS or just pick the inputfields.css from the templates-admin folder as a start. Also the CSS file from the wire/modules/InputfieldRadios module can be helpful to add. And that's it. It's not very hard to get it display nicely. Here an code example of a simple form. <?php $out = ''; // create a new form field (also field wrapper) $form = $modules->get("InputfieldForm"); $form->action = "./"; $form->method = "post"; $form->attr("id+name",'subscribe-form'); // create a text input $field = $modules->get("InputfieldText"); $field->label = "Name"; $field->attr('id+name','name'); $field->required = 1; $form->append($field); // append the field to the form // create email field $field = $modules->get("InputfieldEmail"); $field->label = "E-Mail"; $field->attr('id+name','email'); $field->required = 1; $form->append($field); // append the field // you get the idea $field = $modules->get("InputfieldPassword"); $field->label = "Passwort"; $field->attr("id+name","pass"); $field->required = 1; $form->append($field); // oh a submit button! $submit = $modules->get("InputfieldSubmit"); $submit->attr("value","Subscribe"); $submit->attr("id+name","submit"); $form->append($submit); // form was submitted so we process the form if($input->post->submit) { // user submitted the form, process it and check for errors $form->processInput($input->post); // here is a good point for extra/custom validation and manipulate fields $email = $form->get("email"); if($email && (strpos($email->value,'@hotmail') !== FALSE)){ // attach an error to the field // and it will get displayed along the field $email->error("Sorry we don't accept hotmail addresses for now."); } if($form->getErrors()) { // the form is processed and populated // but contains errors $out .= $form->render(); } else { // do with the form what you like, create and save it as page // or send emails. to get the values you can use // $email = $form->get("email")->value; // $name = $form->get("name")->value; // $pass = $form->get("pass")->value; // // to sanitize input // $name = $sanitizer->text($input->post->name); // $email = $sanitizer->email($form->get("email")->value); $out .= "<p>Thanks! Your submission was successful."; } } else { // render out form without processing $out .= $form->render(); } include("./head.inc"); echo $out; include("./foot.inc"); Here the code snippet as gist github: https://gist.github.com/4027908 Maybe there's something I'm not aware of yet, so if there something to still care about just let me know. Maybe some example of hooks could be appended here too. Thanks Edit March 2017: This code still works in PW2.8 and PW3.
    1 point
  9. Rebuilt my own site recently using all the knowledge, tips & tricks I've learnt from the PW docs, forums etc, including adding PWA features. Ran it through several publicly available audits and it passed OK - w3c validation, google page speed insights, google lighthouse, etc. Also engaged a digital marketing agency to audit it from a Google search POV. Still some tweaking to do on content & backlink fronts. Overall though happy with the result which could never have been achieved without the help and support of the ProcessWire community. Thank each and everyone of you. https://www.clipmagic.com.au
    1 point
  10. That kind of inline editing has been on my radar for ages. Until now, all solutions were either incredibly complicated or died before reaching maturity (e.g. WebODF). CODE is going to be the final piece in so many puzzles
    1 point
  11. Cool, I saw you swing by #libreoffice-dev to give thanks just now
    1 point
  12. In this post we look at a new core version on the master branch, and a new version of ProCache that includes a browser-cache busting feature called Buster: https://processwire.com/blog/posts/pw-3.0.98-procache-buster/
    1 point
  13. 'recipes' => [ // sample callback as recipe function() { $this->msg('Installing AOS...'); $aos = $this->installModule('AdminOnSteroids', 'https://github.com/rolandtoth/AdminOnSteroids/archive/master.zip'); $this->wire->modules->saveConfig($aos, [ 'enabled' => 1, 'enabledSubmodules' => ['FieldAndTemplateEditLinks'], ]); $tracy = $this->installModule('TracyDebugger', 'https://github.com/adrianbj/TracyDebugger/archive/master.zip'); $this->wire->modules->saveConfig($tracy, [ 'superuserForceDevelopment' => 1, 'editor' => 'vscode://file/%file:%line', ]); }, ], Current version of the kickstartfile: Installs AOS, enables it, enables editlinks. Installs TracyDebugger and sets editor protocoll handler for vscode. 1-click installation of ProcessWire with custom module setup and (in contrary to using site profiles) UP-TO-DATE modules and pw-version! Since january I have not run the default pw installer once...
    1 point
  14. Thanks @gebeer I took a quick look but have no time (and interest) to implement this at the moment. At least not as long as I don't have any spam issues with the existing honeypot solution. I'm happy to accept pull requests though. I've fixed a small bug and I've added automatic loading of assets while checking your request. V2 is on gitlab
    1 point
  15. @kongondo's example is exactly what I meant. It's unfortunately a bit hidden in the jQuery api docs, but the third example in the $(document).ready() docs illustrates that the ready handler is passed the jQuery object.
    1 point
  16. @bernhard this looks very promising, thank you for sharing! It would be great if you could add Recaptcha V2 support. I was once struggling on getting it to work with a Nette Form implementation for a project. Here is the most up to date and seemingly well maintained Nette extension I could find: https://packagist.org/packages/contributte/reCAPTCHA
    1 point
  17. Yes of course - not sure why that didn't occur to me I have replaced those arrows with FA icons for now. I am loading FA for the PW Info panel anyway. FYI - I am a huge SVG fan, but I thought for simple icons like this UTF8 icons would be simpler. Maybe I'll revamp this at some point, but this will do for now.
    1 point
  18. It's not so much an issue of which UTF glyph as which font is used to render the glyph. Each font designer is free to interpret each glyph (e.g. the letter "y", a "left arrow") any way they like, so changing to a different glyph wouldn't necessarily solve the issue. To get the same appearance on all devices you'll need to specify a font that will be available on all devices. Currently the font family is just "sans-serif", which in Windows 10 is Segoe UI, and no doubt something different in MacOS, Android, etc. So you could try and find a font that is included in the system fonts on all OSs. Or you could use the FontAwesome version that is bundled with PW. Or you could include a different webfont as part of Tracy Debugger, e.g. Material Icons. Or you could create your own custom symbol font that just contains the glyphs used in the module, using Fontastic or similar. Or you could use individual SVG icons for all buttons. Heaps of options.
    1 point
  19. My guess is that you would need to supply full absolute paths instead relative ones.
    1 point
  20. Thanks for the thoughtful replies! Playing around with the Minimimal Site Profile, I just managed to achieve exactly what I wanted - hide the page title only on the home page - by adding an IF to _main.php - not an elegant solution, but one that worked, and the feeling was awesome. I'm used to messing with parts of applications, themes, plugins, to get what I want, but this is the first time it's actually how the whole thing works, not some isolated hack, but real learning. Amazing! @DonPachi Thanks for the detailed rave review! Sounds like we're both in that huge class of folks who aren't by first choice focused on the tech side of the web, but are inevitably drawn in because it's all part of the same thing. Kinda like DJs when they started also producing and making music, some said they weren't real musicians...but that evolved! I've spent maybe 3-4 hours total with ProcessWire so far, reading and with an installation of the default Minimal site, and my main thought is, "Why isn't everything like this?" This forum thread was also more than ecouraging: Site Architecture: How far to take Processwire "is the database"? Hope to one day help someone with my own PW forum reply that's something like yours! @Robin S Thanks for the high level view! Platform vs developer is very much on my mind, and your comments keep PW in perspective. Totally agree, an excellent fit with a highly competent developer is critical. That's a big part of why I'm checking out ProcessWire. If we took our specs to an extremely competent dev, and said we'd heard WordPress was a popular platform, the dev would either agree to do it in WordPress (with a quality outcome, for WP, and also all of the WP overhead), or hopefully suggest another, better option. Then we'd be into a whole pile of great frameworks and CMS platforms to consider. I'm trying to get ahead of that by picking the platform first, if possible, a platform that I can use as well, then finding the developer. I guess it does sound odd. I'm using WordPress as a step before detailed specs (including a wireframe), because it's so easy to do. WP and two main plugins (Gravity Forms, Advanced Custom Fields) let me create forms with the required functionality, and adequately display the data in the front end, using only the WP admin GUI. I'm writing copy on a wiki (DokuWiki), and using WP like this is no more involved than entering text in the wiki, which is pretty cool - we can get a hands-on feel for the forms and supporting content on an actual web site, and let other people test it, at no significant development cost. @bernhard Thanks for the forms comments! I'm finding my way around and will soon get to forms and check out RockForms, which looks fantastic (being invited to suggest features is not a bad plus). Docs are everything, and helpful community forums are a huge part of documentation. This is great!
    1 point
  21. Yeah, we got that Since inputfields are used by the admin, hooking into InputfieldPage::getSelectablePages is what makes ASM selects (and other multi page inputfields) "customizable".
    1 point
  22. I thin what you are looking for is the "Custom PHP code" in the Input tab of the field cnfiguration screen for your programs_list field In my case the field name is "myplaces" Now Go and insert this hook into site/ready.php $wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) { if($event->object->hasField == 'programs_list') { $page = $event->arguments('page'); $event->return = $event->pages->find("template=yourTemplateForProgramPages, program_id={$page->legacy_id}); } }); This gets the legazy_id of the page that you are currently editing and then finds all program pages with that id in field program_id.
    1 point
  23. @flydev this is what I call a thorough explanation Thank you for taking the time and sharing your knowledge!
    1 point
  24. Here's an example with a single "sounds_like" field containing both metaphone and soundex data. This is better I think. In /site/ready.php: $pages->addHookAfter('saveReady', function(HookEvent $event) { $page = $event->arguments(0); if(!$page->id || !$page->template->hasField('sounds_like')) return; $sounds_like = ''; $words = explode(' ', $page->title); // Get the individual words of field(s) foreach($words as $word) { if(strlen($word) < 3) continue; // Ignore short words $sounds_like .= metaphone($word) . ' ' . soundex($word) . ' '; } $page->sounds_like = $sounds_like; } In search template file: // $q is the sanitized search string $words = explode(' ', $q); $selector = ''; foreach($words as $word) { if(strlen($word) < 3) continue; // Ignore short words $selector .= 'sounds_like~=' . metaphone($word) . '|' . soundex($word) . ', '; } $results = $pages->find($selector);
    1 point
  25. A different approach which occurred to me is saving sounds-like data to hidden fields on a page and then searching those fields. This allows for other sounds-like algorithms such as metaphone, and allows for "contains" searches rather than only "equals" searches. Notes on the code that follows: You would create hidden fields "metaphones" and "soundex" and then add those to templates as needed. In the example I just save sounds-like data for the title field, but you could include other fields also. Using Double Metaphone would give more accurate results, but I just used metaphone in the example for simplicity. In the search code I am only searching the sounds-like data, but in the real world you would include other fields in the selector also as the "normal" part of the search. In /site/ready.php: $pages->addHookAfter('saveReady', function(HookEvent $event) { $page = $event->arguments(0); if(!$page->id || !($page->template->hasField('metaphones') && $page->template->hasField('soundex'))) return; $metaphones = ''; $soundex = ''; $words = explode(' ', $page->title); // Get the individual words of field(s) foreach($words as $word) { if(strlen($word) < 3) continue; // Ignore short words $metaphones .= metaphone($word) . ' '; $soundex .= soundex($word) . ' '; } $page->metaphones = $metaphones; $page->soundex = $soundex; }); In search template file: // $q is the sanitized search string $words = explode(' ', $q); $metaphones = ''; $soundex = ''; foreach($words as $word) { if(strlen($word) < 3) continue; // Ignore short words $metaphones .= metaphone($word) . ' '; $soundex .= soundex($word) . ' '; } $selector = "(metaphones~=$metaphones), "; $selector .= "(soundex~=$soundex)"; $results = $pages->find($selector); This allows matching a page title of "The quick brown fox jumps over the lazy dog" by search strings "offer took" and "quiz fogs" thanks to the differences between metaphone and soundex. Don't expect too much from it though - I found plenty of soundalike words that didn't match. The general principle could be expanded with other algorithms, and it would be cool to enhance this by allowing for mixed matches - for example, where in a two word search one word matches metaphone and the second word matches soundex. Edit: on that last point, a simple way would be to use just a single hidden field for both the metaphone and soundex data. The data from those two algorithms is sufficiently different that unwanted matches wouldn't happen. But if other algorithms were added you'd have to check to make sure the data from one algorithm wouldn't be confused with that of another.
    1 point
  26. If you're looking to do an elaborate a page builder with ProcessWire and don't want to pull your hair out, I highly recommended viewing this video: A couple notes: with the css grid specification, you can assign multiple blocks to the same grid-area but they will overlap each other. I've "overcome" this by combining multiple blocks into a parent div and assigning that instead. pretty easy to do. i didn't demonstrate it, if your blocks have a grid structure within them (like built with flexbox), you can still assign that block to a grid-area. so if your blocks themselves have a grid structure, that's ok. for example, if your css grid layout is 6 columns, but you have a block that has a grid inside of it (built with like uikit's grid that's 5 columns), you can assign that block to the grid-area. with the css grid specification, the flow of the blocks does not have to match the flow of the grid-areas. this is insanely powerful. Enjoy.
    1 point
  27. just added the renderTable() method: $form->onSuccess = function($form) { $form->linkedFields = [ 'pdf' => 'files' ]; $log = $form->createPage(123, 'rockforms_anfrage', date('d.m.Y H:i:s') . ', {forename} {surname} {email}'); $m = new WireMail(); $m->to('your@email.com') ->from('your@email.com') ->subject('Your great subject') ->bodyHTML($form->renderTable()); foreach($log->files as $file) $m->attachment($file->filename); $m->send(); // return success message return '<div class="uk-text-center uk-padding-large">'. '<h3>ENERGIEGURU bedankt sich für ihre Anfrage.</h3>'. '<div>Wir melden uns in Kürze bei Ihnen. Wenn Sie Ihre letzte Jahresrechnung hochgeladen '. 'haben erhalten Sie in den nächsten 24 Stunden Ihr individuelles Angebot!</div>'. $form->renderTable([ 'wrapper' => '<div class="uk-card uk-card-secondary uk-card-body uk-margin">{table}</div>', ]). '</div>'; }; I agree this could be automated, but at least until that is finished it is already REALLY simple to accomplish.
    1 point
  28. Hi all, I have created a fronted form to allow user to update their profiles (fields in the user template). It works, but I am sort of wondering what security checks I should put in place to ensure that a user can only update his/her own fields? // if user isn't logged in, forward to login page if(!$user->isLoggedin()) { $session->redirect("/login/"); } //***UPDATE PROFILE***// if($input->post->profile_submit) { //instantiate variables taking in the form data $email = $sanitizer->email($input->post->email); $full_name = $sanitizer->text($input->post->full_name); //Update user details $user->of(false); $user->email = $email; $user->user_full_name = $full_name; $user->save(); $user->of(true); } //***UPDATE PROFILE***// //** Update details form *// <form class="form-horizontal" action="./" accept-charset="UTF-8" autocomplete="off" method="post"> <div> <input type="text" class="form-control" id="inputFullname3" name="full_name" value="<?php echo $user->user_full_name; ?>" > </div> <div> <input type="text" class="form-control" id="inputEmail3" name="email" value="<?php echo $user->email; ?>"> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" name="profile_submit" value="profile_submit">Update Details</button> </form> //** Update details form *//
    1 point
×
×
  • Create New...