Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 05/18/2020 in all areas

  1. Hi huseyin, 1. Use the appropriate Sanitizer methods to test user input from post and get. Depending on the circumstance, you'll either want to validate (reject it completely if there's something wrong with it), or filter/sanitize it (accept it but strip out unwanted characters). If doing both filter and validate, do your validation AFTER your filtering. 2. Even more important than step #1 is to use escaping on your output. This means using htmlspecialchars() or htmlentities() or $sanitizer->entities() when you output any field from the database or user input to the page (if you have htmlentities setup on your field's output formatting, then you can skip this step for those fields). Even if you mess up on the filter/validation from #1, as long as you've escaped all of the html, you should be ok. 3. When using user input (get or post variables) inside ProcessWire selector strings, use the Sanitizer::selectorValue() method on the value first. Even better, just use Selector Arrays since selectorValue can sometimes strip out characters (quotes and commas) that you actually want to search for. 4. If you're using any SQL directly, you must use prepared statements to bind any user input, which automatically escapes the input for SQL. 5. For protection against Cross Site Request Forgery (CSRF), use ProcessWire's SessionCSRF class when building custom forms. See https://processwire.com/api/ref/session-c-s-r-f/ for details on how to use this. 6. Don't use GET for secret data (passwords, security codes, etc). That data can get picked up by browser extensions or appear in server logs that might get compromised. 7. Use SSL/https on your whole site.
    6 points
  2. This week I was back to focusing on the core and got quite a lot done. A lot of GitHub issue reports were resolved, plus several minor tweaks and additions were made in 3.0.156 as well. But the biggest update was the addition of the $pages->parents() API, which is something that I think you’ll likely have zero use for (and why I’m not putting it into a blog post) but something that the core itself will use quite a bit, and is a really nice improvement for the system and its scalability. So if you don’t mind some technical reading, read on. Whenever you call a $page->find() method ($page, not $pages) or use a “has_parent=“ in a selector, ProcessWire joins in a special table for the purpose called pages_parents. It uses this table to keep track of family relationships that aren’t otherwise apparent. For instance, let’s say we have page “g” that lives at path /a/b/c/d/e/f/g/. Page “g” only knows that it has page “f” as its parent. It doesn’t know that page “e” is its grandparent unless or until page “f” is loaded. Once “f” is loaded, then “f” can reveal its parent “e”. It works the same for every relationship down to the root parent “a”. So the pages are like a linked list or blockchain of sorts, where only 1 relationship forward or backward is known per page. The “pages_parents” table fills in this gap, enabling PW to quickly identify these relationships without having to load all the pages in the family. This is particularly useful in performing find() operations that you want to limit to a branch started by a particular parent. It’s the reason why we have both $pages->find() that searches the entire site, and $page->find() that limits the search within the branch started by $page. I haven’t paid much attention to the code behind this pages_parents table because it generally just worked, needing little attention. But I came across a couple of cases where the data in the table wasn’t fully accurate with the page tree, without a clear reason why. Then I became aware of one large scale case from a PW user where it was a huge bottleneck. It involved a large site (250k+ pages) and a recursive clone operation that appeared to involve hundreds of pages. But that operation was taking an unreasonable 10 minutes, so something wasn’t right. It came down to something going on with the pages_parents table. Once I dove into trying to figure out what was going on, I realized that if I was to have any chance of keeping track of it, we needed a dedicated API for managing these relationships and the table that keeps track of them. So that’s what got a lot of attention this week. While still testing here, it does appear initially that the 10 minute clone time has gone down to a few seconds, and everything about this relationship management is now rewritten, optimized and significantly improved. It was a lot of work, but absolutely worth it for PW. Rebuilding the entire table from scratch now takes between 2-3 seconds on a site with 250k pages and 150k relationships. The new API can be accessed from $pages->parents(). This API is really useful to the work that I do here (maintaining the core) but I’ll be honest, it’s probably not useful to most others, so I won’t go into the details here, other than to say I’m happy with it. But if you are interested, there are methods finding all the parents in a site, or a particular branch, and methods for rebuilding the pages_parents table, among others. Maybe more will be added to it later that actually would be useful in the public API, but for now I’ll likely leave it out of our public API docs. The $sanitizer->selectorValue() method also got a full rewrite this week (actually, one the last few weeks). It’s now quite a bit more comprehensive and configurable (see the new method options). The previous version was just fine, and actually still remains — you can use it by specifying [ ‘version’ => 1 ] in the $options argument to the selectorValue() method. But the new version is coded better and covers more edge cases, plus provides a lot more configurability for the times when you need it.
    3 points
  3. Here's an example of a basic module to store each download as a row on a separate table. I hope it can be useful somehow ? And here's how to call it from your template, form hook etc.: $download_counter = wire('modules')->get("ExternalDownloadCounter"); $download_counter->countDownload($page_name, $resource_file, $selected_language);
    2 points
  4. There are multiple ways to do this. The "standard" approach I'd say would be event tracking using a JavaScript tracker. If you're already using some tracking system like Google Analytics or Matomo, you can do that by attaching an event listener to the download link / button and triggering an event (guides for Google Analytics and Matomo). Of course, in this case the events will end up in your analytics software, not in the user page on your site (though if you are using some tracking software already this may be preferable). You can also roll your own version of this, by sending your JavaScript-triggered events to a custom API endpoint on your site that records the download for the current user (you can use the active session for this, or send the user ID alongside the event). Another approach which completely avoids JavaScript is to not link to your downloads directly, but instead link to yet another custom endpoint which will (1) log the request and (2) serve the download manually. I have written a bit about manually serving downloads here and here.
    2 points
  5. Many, many thanks @flydev ?? and @kixe !!! That makes sense. I can confirm that both solutions work.
    2 points
  6. This is great news for our dev team. I can image it took some effort to dig into this but i am glad you found the bottle neck. I can imagine, when the parents table will contain less page parent references, this might give an overall performance boost with large sites that have a lot of repeater (matrix) pages.
    2 points
  7. Assuming you have a field called 'checkbox' of type Checkbox // these two will return identical results $found = $pages->find("checkbox=1");// find those whose values equal 1 // OR $found2 = $pages->find("checkbox!=''");// find those that are not empty // highly recommended; use Tracy :-) bd($found,'found 1'); bd($found2,'found 2'); Boolean fields will be stored as true = 1.
    1 point
  8. To anyone else who runs into this issue: I solved it with a hook like this: $wire->addHookAfter('SeoMaestro::renderMetatags', function (HookEvent $event) { $tags = $event->arguments(0); $group = $event->arguments(1); if ($group === null) { foreach(wire("languages") as $lang) { $code = $lang->name;//$lang->isDefault() ? $defaultLang : $lang->name; $tags["link_rel_{$code}"] = preg_replace("/domain.tld/", $lang->domain, $tags["link_rel_{$code}"]); } $event->return = $tags; } }); $lang->domain stores the domain used for a language.
    1 point
  9. v0.0.5 adds column alias support for @David Karich s options field shortcut and adds another one to get the title instead of the options value: Showing the title/value of selected options instead of ID values Options fields store 3 things in the database: The ID of the option, the value of the option and the title (for each language) of the option: There are two custom column types to get the value or the title of a selected options field:
    1 point
  10. Thanks! I'm not sure this needs to be posted anywhere else – there's already a lot of information on those topics out there. I primarily wrote this to (1) present all that information in a ProcessWire context, using ProcessWire modules and the class loader as examples and (2) to have it as a resource I can link to when questions regarding those topics come up. But maybe I should cross-post this to Medium for some ProcessWire exposure, there's a lot of useless stuff there anyway ?
    1 point
  11. Thanks for the module, it is installed on all PW sites I've ever made. For the code snippet I still couldn't figure out how to run a database backup from an ordinary cron job. Will I have to create a php file with the hook and call that from the cron r can I call a module file directly from cron? Or is it possible to integrate it with the PWCron module? Update: I got it working in a PHP file this way based on @kixe's answer: <?php namespace ProcessWire; include_once("./index.php"); $event = new HookEvent(); $cron_backup = $modules->CronjobDatabaseBackup; $cron_backup->cronBackup($event);
    1 point
  12. Why not? Once you have the user-ID and the document he/she wants to download, you can write this into fields of your (user) page(s). If you want to show a user his/her history, simply prepare and render the stored data from the user page.
    1 point
  13. @kongondo, thanks. 2. It isn't but it could be. @horst, that module looks like a gem. Thank you. @gmclelland, I'm sure it will help me. Thanks guys. I do have a better ideia about what can be done and I'll try it in the coming days.
    1 point
  14. Great tutorial. I had implemented something like this in 2018 using Hanna Code. I my case the website has a seperate gallery section and the relevant galleries are imported wherever needed at appropriate positions in articles. Example page: https://ashajsr.org/updates/udaan-girl-child-day-2019/
    1 point
  15. Notice. The script doesn't work with $config->pagefileSecure = true;
    1 point
  16. Ok fair, but that could work for a module version ?
    1 point
  17. That's quite a timely update. Just this week I've been working on a site where there are a lot of selectors that depend on various levels of parent/child relationships, and I was wondering about performance, so if this area of Processwire has had a big performance improvement it's very well timed.
    1 point
  18. Great read! Perfectly laid out! A ready Medium article in a post! And maybe it should be published elsewhere, as it provides a lot of useful info not only for PW devs (and would bring some attention to PW).
    1 point
  19. As far I can see you handle only the array version of the get parameter 'level', but not the pipe separated version. I didn't tried it out, please check: if($input->get->level) { if (is_array($input->get->level)) { // Level is an array. Code adapted from Ryan's snippet here: // https://processwire.com/talk/topic/3472-enable-pagination-for-search-results/?tab=comments#comment-38042 $level = array(); foreach($input->get->level as $id) $level[] = (int) $id; // sanitize to INTs $level = implode('|', $level); // convert to 123|456|789 string, ready for selector } else $level = "$input->get->level"; } else { $level = ''; } @flydev ?? ... one second quicker ?
    1 point
  20. @jacmaes you can simply add a new condition if the the GET var level is a string ? let's try : if($input->get->level && is_array($input->get->level)) { // level as array given $level = array(); foreach($input->get->level as $id) $level[] = (int) $id; // sanitize to INTs $level = implode('|', $level); // convert to 123|456|789 string, ready for selector } elseif($input->get->level && is_string($input->get->level) && $input->get->level !== '') { // level as string given // can be optimized (sanitizer) $level = array(); foreach(explode('|', $input->get->level) as $id) $level[] = (int) $id; // sanitize to INTs $level = implode('|', $level); // convert to 123|456|789 string, ready for selector } else { $level = ''; }
    1 point
  21. If I remember correctly I think they do something similar on the https://processwire-recipes.com/ site using the code from https://github.com/processwire-recipes/ProcessRecipeInstaller Markdown files are stored in this repo https://github.com/processwire-recipes/Recipes Maybe that can give you some ideas?
    1 point
  22. Hi @Jorge, welcome to the forums. As Kongondo already said, you can or need to use a hook to inject your desired functionality. You can use one in the site/ready.php like this: $wire->addHookAfter('Pages::saved', null, function(HookEvent $event) { $page = $event->arguments(0); // now, after you have the page related to the hook event, check if it belongs to the pages / templates you want to process if('my-desired-templatename' != $page->template->name) return; if(!$page->mycheckbox_use_github) return; // after you definetly know that you want to process this page, execute what you want to ... }); Maybe a little OT, but also maybe useful for inspiration:
    1 point
  23. @Jorge, Welcome to the forums ? There's 2 - 3 issues here: Every time a page is saved: Easily achieved using ProcessWire Hooks Commit and Push to Github: How are you currently doing this? Are you pushing from the server where you have installed ProcessWire? This is related to #1, basically, what you will do when you call your Hook Automation: Depend on your case this and #2 could be one and the same. Or this could be an independent process, e.g. a cron job triggered by #1 which then calls #2
    1 point
  24. When requesting a page with selector you do not request other fields exept title as it is set to autojoin.
    1 point
  25. We recently relaunched the website of IBIS Backwaren, a brand for international pastries and bakery products. Concept, design and implementation by schwarzdesign, built with ProcessWire. As always, we aimed to deliver a clean website design with a focus on content and fast loading times. Here's a list of modules used on this site, followed by a couple of interesting features included. The site is bilingual, though currently only the German version is available. The English version will be released at a later date. ProFields FormBuilder WireMailSMTP Cache Control MarkupSitemap Hanna Code Automatically link page titles Unique image variations Product database and product collections The website features a product database with all the products IBIS distributes. The product template is rather extensive, including fields for product categories and attributes, nutrition facts, certifications etc. We make heavy use of page reference fields, e.g. for product categories and attributes such as vegan or gluten-free products. Nutrition facts are stored in a Textareas field. There are multiple ways for visitors to explore products. One is a classic product finder with filters for product category and attributes. But there are also individual product collections (Produktwelten) which can be regularly updated by the client. Product collections include a list of related products. This allows the marketing department to quickly set up targeted landing pages. Recipes and custom forms Another approach to marketing and activating fans is the recipe section. Each recipe uses at least one IBIS product to give visitors some ideas. Of course, recipes and products are cross-linked to allow for exploration and discovery. There's a recipe submission form which allows you visitors to send in their own recipes, as well as a couple of other forms. All forms on the site are built with the Form Builder module. We made use of the form submissions to pages feature, which automatically creates a new (unpublished) page for new recipe submissions. Those can be reviewed by the staff and published directly. We also use a hook to automatically transform the plaintext fields for ingredients and instructions into HTML lists. Multi-brand IBIS uses different brands for different sets of products. Some landing pages as well as the gluten-free selection are branded differently. To accomodate this, we create an additional "brand" template with an override for the default logo. Each page has a brand selection field to allow switching the logo / branding for that page. Since brands are represented by normal pages, then client can create additional brands on their own.
    1 point
  26. For a gallery of images you wouldn't want to insert the images separately into the CKEditor field. You'd want to loop over all the images in the field and output markup for each of them. A typical approach would be to show thumbnail images linked to larger images that display in a JS lightbox (I like to use Fresco), so your template code might look like this (assumes your images field is named "images"): <?php if($page->images->count): ?> <div class="gallery"> <?php foreach($page->images as $image): ?> <a href="<?= $image->maxSize(1000,1000)->url ?>" class="fresco" data-fresco-caption="<?= $image->description ?>"> <img src="<?= $image->size(300,300)->url ?>" alt="<?= $image->description ?>"> </a> <?php endforeach; ?> </div> <?php endif; ?> So the simplest scenario is if your blog text and gallery are separate from each other - e.g. the gallery appears before or after the text. This way you just output the gallery and text separately in your template file. Chances are that's what you want to do, but here are a couple of other scenarios and possible solutions... 1. You want to optionally insert one gallery somewhere within the blog text. In other words, you want some text, followed by the gallery, followed by some more text. One way to do this would be to have two CKEditor fields in your template, labelled "Before gallery" and "After gallery". You divide your text between these two fields. The two CKEditor fields and the images field are all output directly by code in your template file. Another way is to have a single CKEditor field use the Hanna Code module to insert the gallery somewhere in the text. You would create a PHP Hanna tag named "gallery" and use the gallery code shown above for the tag. Then you'd insert [[gallery]] in your CKEditor field wherever you want the gallery to appear. 2. You want to insert multiple galleries somewhere within the blog text. If you stump up some $$ to buy the excellent ProFields module you could use the included Repeater Matrix module to create separate matrix types for Text and Gallery. Then you just add alternating Text and Gallery items to the page as needed to build up the blog post content. Another approach for this that is a bit more advanced but that doesn't cost anything is to use a standard Repeater field for the galleries. You would add an Images field and the Title field to the Repeater field and create a Repeater item for each gallery on the page. Then you'd use Hanna Code to insert the galleries within a single CKEditor field. And to make it easier to select a gallery in the Hanna tag you can use the Hanna Code Dialog module. The "gallery" Hanna tag would have a "title" attribute and the code would look like this: <?php $repeater_item = $page->galleries->findOne("title=$title"); ?> <?php if($repeater_item->id): ?> <div class="gallery"> <?php foreach($repeater_item->images as $image): ?> <a href="<?= $image->maxSize(1000,1000)->url ?>" class="fresco" data-fresco-caption="<?= $image->description ?>"> <img src="<?= $image->size(300,300)->url ?>" alt="<?= $image->description ?>"> </a> <?php endforeach; ?> </div> <?php endif; ?> And the Hanna Code Dialog hook in /site/ready.php to build the dialog form would look like this: $wire->addHookAfter('HannaCodeDialog::buildForm', function(HookEvent $event) { // The Hanna tag that is being opened in the dialog $tag_name = $event->arguments(0); // The page open in Page Edit /* @var Page $edited_page */ $edited_page = $event->arguments(1); // The form rendered in the dialog /* @var InputfieldForm $form */ $form = $event->return; if($tag_name === 'gallery') { $modules = $event->wire('modules'); $gallery_titles = $edited_page->galleries->explode('title'); /* @var InputfieldSelect $f */ $f = $modules->InputfieldSelect; $f->name = 'title'; $f->id = 'title'; $f->label = 'Gallery title'; $f->addOptions($gallery_titles, false); $form->add($f); } }); And this would give you an interface in Page Edit that looks like this (when the Hanna Code dialog is open):
    1 point
  27. I use this 5 lines of code, placed in an access controlled folder under root, to run a cronjob at fixed time. Just set 'cycle' in module settings to 'none' to prevent database backups triggered by LazyCron as well. <?php namespace ProcessWire; $root = dirname(__DIR__); include_once("$root/index.php"); $cdb = $modules->CronjobDatabaseBackup; $cdb->cronBackup(); Thanks for the input. I thought about it. In the end, I'll leave it as it is, and the inclusion of manually created dumps in the cleaning routine remains the standard. When I manually create a dump, I usually move or download it immediately. I rarely (never?) leave it on the live server and I want the files removed automatically. The default name created by ProcessDatabaseBackups is the database name. Just use this as the 'stopword' and use a different name format in the setup of CronjobDatabaseBackup and voila it works exactly as you want it. Furthermore there is also the option to save the files in a user-defined directory. Manually created dumps are then never affected by the CDB's cleaning routine.
    1 point
  28. @szabesz had already mentioned it in this thread. But I think it is worth picking up again. I recently switched to https://vscodium.com/ and it is working absolutely great. It removes Microsofts branding and, more importantly usage telemetry. It is available for all platforms. Only drawback I could see is not having automated one-click updates. I'm all for open source and privacy. Microsoft's VS Code is based on the same codebase as VSCodium only MS adds their branding and collects lots of data because, well, that is just what they love to do. Being a Linux user for more than 15 years now, I really appreciate MS releasing this phantastic editor to the community. At least they are giving back a fraction of what they earned over the last decades with their half baked software which they make their beta testers pay a lot of money for. Ingenious concept, though...
    1 point
  29. Maybe try markdown syntax [Title](http//:…/). This at least works for the description/notes.
    1 point
  30. what.i use this is good it does.work top {not buttock}, of htaccess u will.put it . enjoy <IfModule mod_expires.c> ExpiresActive On ExpiresDefault "access plus 1 seconds" ExpiresByType image/x-icon "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType text/javascript "access plus 1 month" ExpiresByType application/octet-stream "access plus 1 month" ExpiresByType application/x-javascript "access plus 1 month" </IfModule> <IfModule mod_headers.c> <FilesMatch "\\.(ico|jpe?g|png|gif|swf|woff)$"> Header set Cache-Control "max-age=31536000, public" </FilesMatch> <FilesMatch "\\.(css)$"> Header set Cache-Control "max-age=2692000, public" </FilesMatch> <FilesMatch "\\.(js)$"> Header set Cache-Control "max-age=2692000, private" </FilesMatch> <FilesMatch "\.(js|css|xml|gz)$"> Header append Vary: Accept-Encoding </FilesMatch> Header unset ETag Header append Cache-Control "public" </IfModule> <IfModule mod_deflate.c> AddOutputFilter DEFLATE js css AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html </IfModule>
    1 point
×
×
  • Create New...