Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 02/03/2022 in Posts

  1. @Kiwi Chris Migrations have their place and I definitely wouldn't do without them. I think it's best if config and migrations complement each other. I think there needs to be a distinctions between shared (community / open source) modules and site-specific modules. For Craft, this distinction is between plugins (external plugins installed via Composer and tracked in the project config) and modules (site-specific modules providing site-specific functionality). Both can provide config to be tracked in the project configuration. But they work slightly different and keeping those things separate makes it easier to talk about them. @horst I just meant that existing fields that already exist in the database and still exists in the config aren't wiped and recreated when applying the config (since that would wipe the data as well). The config always includes all fields (as well as entry types, settings, etc) that exist on the site. So if I remove a field in dev, its removed from the config. If I apply that config to other environments, the field is removed from those as well. You don't need the content in the version control. Local test environments only create data for testing purposes. So when I check out my colleagues PR, I will have all entry types for the news blog etc, but no actual data. For quick testing I can just create a couple of blog posts manually. For larger projects, you can use a data seeder plugin, or a content migration that imports some data. We've done this for a project where an existing database was imported to a new system, there we could just run the import migration to seed development/staging environments. Admittedly, it's a tiny bit of additional work. But far easier than making sure you don't put garbage data into version control, figuring out how to merge diverging content branches, dealing with assets. And I don't want content dumps muddying up my git commits anyway. Once you start working this way, it's super relaxing not having to worry about creating 'real' content in your dev environment, being able to wipe out content on a whim, try something out etc. The 'real' content editing happens in the staging/production environment anyway. How are you merging diverging branches from multiple people once the time comes to merge them? You can't really merge database dumps (and stay sane). Also, database dumps are terrible for version control, much to noisy to be readable in diffs. With a YAML config, I can just look at the diff view of the PR in Github and tell at a glance what changed in the config, I don't think you can do that with an SQL dump unless you're Cypher from The Matrix … The main branch is always the source of truth for the entire site state, the config includes all fields, entry types, general config settings, etc. Everyone working on a feature creates a new branch that modifies the config in some way – but those branches still include the entire site state, including the feature being worked on. So once you merge that feature in to the main branch and deploy in staging/production, the system can 'interpolate' between the current state and the config. That is, compare the config with the database state, then adjust the database to match the config by creating fields that are in the config but not in the database, removing fields from the database that aren't in the config anymore, applying any settings changes from the config to the database etc. Of course, there may be merge conflicts if some branches contain conflicting changes. In this case, after merging in the first branch, you'd get a merge conflict which prevents the PR from being merged. You resolve these like all regular merge conflicts in git, which is made easier since the config is just a bunch of YAML files. For simple conflicts, you can resolve those directly in the Github UI. For more complicated conflicts (which are very rare), you would do that locally by either rebasing or merging in the main branch.
    4 points
  2. Hi @kaz Hope, I got your message right if($page->matches('id=1233|3434')) {}
    3 points
  3. Just wanted to add my 2 cents and say that a database migration system (like Ruby on Rails... they've had it perfected since 2005 or so) takes ProcessWire from being a CMS/CMF to something more web application framework-like, at least from my point of view. That's a defining feature the way I see it and what I believe Bernard is shooting for (I haven't experimented with RM yet). Personally, I do everything by hand the way Ryan described (because I'm impatient and it's fast enough), combined with little 1-off scripts that modify big chunks of data as needed, but that approach will fall apart when there's multiple developers involved, syncing changes, or even re-implementing your own changes on a production site that were originally done on a development site. I do wonder if I would use a migrations feature if it were native to ProcessWire. Right now, I rarely even use Field/Template/Page Exports when making field/template/page changes from dev to production, but I definitely understand the use case (having worked with web applications frameworks extensively). While having a database migration system is the more 'proper' and 12-factor-y way to do complex development, I don't personally view ProcessWire as a web application framework like Laravel and Rails. There's something to be said about being able to throw ProcessWire around and experiment with things quickly. It has had a real impact on my productivity and solutions. Hand-writing every field or template added or changed would be tiring (although it would optional). Having it auto-recorded like CraftCMS would be interesting and there have been attempts to do that. Not sure where I'm going with this, but just some thoughts I felt like sharing.
    3 points
  4. Don't have much time to discuss, but I just came across this article that's talks about Craft's Project Config https://adigital.agency/blog/understanding-and-using-project-config-in-craft-cms that some may find relevant to this discussion?
    3 points
  5. Wire Mail SMTP An extension to the (new) WireMail base class that uses SMTP-transport This module integrates EmailMessage, SMTP and SASL php-libraries from Manuel Lemos into ProcessWire. I use this continously evolved libraries for about 10 years now and there was never a reason or occasion not to do so. I use it nearly every day in my office for automated composing and sending personalized messages with attachments, requests for Disposition Notifications, etc. Also I have used it for sending personalized Bulkmails many times. The WireMailSmtp module extends the new email-related WireMail base class introduced in ProcessWire 2.4.1 (while this writing, the dev-branch only). Here are Ryans announcement. Current Version 0.6.0 Changelog: https://github.com/horst-n/WireMailSmtp/blob/master/CHANGELOG.md get it from the Modules Directory Install and Configure Download the module into your site/modules/ directory and install it. In the config page you fill in settings for the SMTP server and optionaly the (default) sender, like email address, name and signature. You can test the smtp settings directly there. If it says "SUCCESS! SMTP settings appear to work correctly." you are ready to start using it in templates, modules or bootstrap scripts. Usage Examples The simplest way to use it: $numSent = wireMail($to, $from, $subject, $textBody); $numSent = wireMail($to, '', $subject, $textBody); // or with a default sender emailaddress on config page This will send a plain text message to each recipient. You may also use the object oriented style: $mail = wireMail(); // calling an empty wireMail() returns a wireMail object $mail->to($toEmail, $toName); $mail->from = $yourEmailaddress; // if you don't have set a default sender in config // or if you want to override that $mail->subject($subject); $mail->body($textBody); $numSent = $mail->send(); Or chained, like everywhere in ProcessWire: $mail = wireMail(); $numSent = $mail->to($toEmail)->subject($subject)->body($textBody)->send(); Additionaly to the basics there are more options available with WireMailSmtp. The main difference compared to the WireMail BaseClass is the sendSingle option. With it you can set only one To-Recipient but additional CC-Recipients. $mail = wireMail(); $mail->sendSingle(true)->to($toEmail, $toName)->cc(array('person1@example.com', 'person2@example.com', 'person3@example.com')); $numSent = $mail->subject($subject)->body($textBody)->send(); The same as function call with options array: $options = array( 'sendSingle' => true, 'cc' => array('person1@example.com', 'person2@example.com', 'person3@example.com') ); $numSent = wireMail($to, '', $subject, $textBody, $options); There are methods to your disposal to check if you have the right WireMail-Class and if the SMTP-settings are working: $mail = wireMail(); if($mail->className != 'WireMailSmtp') { // Uups, wrong WireMail-Class: do something to inform the user and quit echo "<p>Couldn't get the right WireMail-Module (WireMailSmtp). found: {$mail->className}</p>"; return; } if(!$mail->testConnection()) { // Connection not working: echo "<p>Couldn't connect to the SMTP server. Please check the {$mail->className} modules config settings!</p>"; return; } A MORE ADVANCED DEBUG METHOD! You can add some debug code into a template file and call a page with it: $to = array('me@example.com'); $subject = 'Wiremail-SMTP Test ' . date('H:i:s') . ' äöü ÄÖÜ ß'; $mail = wireMail(); if($mail->className != 'WireMailSmtp') { echo "<p>Couldn't get the right WireMail-Module (WireMailSmtp). found: {$mail->className}</p>"; } else { $mail->from = '--INSERT YOUR SENDER ADDRESS HERE --'; // <--- !!!! $mail->to($to); $mail->subject($subject); $mail->sendSingle(true); $mail->body("Titel\n\ntext text TEXT text text\n"); $mail->bodyHTML("<h1>Titel</h1><p>text text <strong>TEXT</strong> text text</p>"); $dump = $mail->debugSend(1); } So, in short, instead of using $mail->send(), use $mail->debugSend(1) to get output on a frontend testpage. The output is PRE formatted and contains the areas: SETTINGS, RESULT, ERRORS and a complete debuglog of the server connection, like this one: Following are a ... List of all options and features testConnection () - returns true on success, false on failures sendSingle ( true | false ) - default is false sendBulk ( true | false ) - default is false, Set this to true if you have lots of recipients (50+) to ($recipients) - one emailaddress or array with multiple emailaddresses cc ($recipients) - only available with mode sendSingle, one emailaddress or array with multiple emailaddresses bcc ($recipients) - one emailaddress or array with multiple emailaddresses from = 'person@example.com' - emailaddress, can be set in module config (called Sender Emailaddress) but it can be overwritten here fromName = 'Name Surname' - optional, can be set in module config (called Sender Name) but it can be overwritten here priority (3) - 1 = Highest | 2 = High | 3 = Normal | 4 = Low | 5 = Lowest dispositionNotification () or notification () - request a Disposition Notification subject ($subject) - subject of the message body ($textBody) - use this one alone to create and send plainText emailmessages bodyHTML ($htmlBody) - use this to create a Multipart Alternative Emailmessage (containing a HTML-Part and a Plaintext-Part as fallback) addSignature ( true | false ) - the default-behave is selectable in config screen, this can be overridden here (only available if a signature is defined in the config screen) attachment ($filename, $alternativeBasename = "") - add attachment file, optionally alternative basename send () - send the message(s) and return number of successful sent messages debugSend(1) - returns and / or outputs a (pre formatted) dump that contains the areas: SETTINGS, RESULT, ERRORS and a complete debuglog of the server connection. (See above the example code under ADVANCED DEBUG METHOD for further instructions!) getResult () - returns a dump (array) with all recipients (to, cc, bcc) and settings you have selected with the message, the message subject and body, and lists of successfull addresses and failed addresses, logActivity ($logmessage) - you may log success if you want logError ($logmessage) - you may log warnings, too. - Errors are logged automaticaly useSentLog (true | false) - intended for usage with e.g. third party newsletter modules - tells the send() method to make usage of the sentLog-methods - the following three sentLog methods are hookable, e.g. if you don't want log into files you may provide your own storage, or add additional functionality here sentLogReset () - starts a new LogSession - Best usage would be interactively once when setting up a new Newsletter sentLogGet () - is called automaticly within the send() method - returns an array containing all previously used emailaddresses sentLogAdd ($emailaddress) - is called automaticly within the send() method Changelog: https://github.com/horst-n/WireMailSmtp/blob/master/CHANGELOG.md
    1 point
  6. I think that since $stack is a Page the valid parameter for render() is a field name and not a path. You might be better of doing something like this: <?php namespace ProcessWire; if (wireCount($value)) { echo "<div class='stacks'>"; foreach ($value as $stack) { if (!$stack->isHidden()) { echo wireRenderFile("fields/stacks/{$stack->template}", ['stack' => $stack]); } } echo "</div>"; // stacks } Just adapt the array, passes as a second parameter to wireRenderFile to be named according to the variables in "stack-{$template}.php"
    1 point
  7. Thanks @Zeka! I didn’t know about ‘owner’. That sounds awesome. I’ve clocked off for the day (with a large Negroni) but look forward to trying this first thing in the morning. Reading that post was enlightening.
    1 point
  8. Hi @strandoo Probably, you can handle it by using owner selector. https://processwire.com/blog/posts/processwire-3.0.95-core-updates/ $speakers = $pages->find("template=speaker, speakers_field.owner.parent.parent.name=your-event-name"); Not tested, but should be someshing like this. It's always a bit difficult to grasp, but try to paly with it a little.
    1 point
  9. Yes, I think a hook would be needed. Something like this should give you a start, added to site/ready.php: $wire->addHookBefore("ProcessPageEdit::buildForm", function (HookEvent $event) { $page = $event->object->getPage(); $myTemplate = "logbook"; //or whatever your template is called if ($page->template != $myTemplate) return; if ($page->startMileage) return; //not if the start Mileage is present $prevPage = wire('pages')->get("template=$myTemplate,endMileage!=,sort=-created"); //get the most recent logbook with an endMileage value if ($prevPage->id) { $page->startMileage = $prevPage->endMileage; } });
    1 point
  10. @bernhard - what do you think about me including a modules refresh in the Clear Sessions & Cookies option? Just trying to avoid yet another option and a very long name, eg "Modules Refresh, Clear Session & Cookies". Can you think of any instance where it would be a problem to also refresh modules when you really just want to clear the session and cookies? I am actually kind of surprised that you need to also do a modules refresh, because the session/cookies option actually logs you out and back in again, so I would have thought that also refreshed modules, but I've probably just forgotten. Can you confirm that both are needed?
    1 point
  11. Thanks @Ivan Gretsky - I've implemented it locally (will commit soon) with a 100 default and 250 for the "big" version and of course you can change it as needed with: d($config->paths, [n, n, 500]); or: d($config->paths, ['maxItems' => 500]);
    1 point
  12. If I need content into one page I call the id if ($page->id == 1042 ): When I need these content on a second page, is it possible to add a second id? Or need the line to be duplicated, then it would be nested.
    1 point
  13. @kongondo Great explanation. I had started along the right lines, but this clarifies it a whole lot. The only one adjustment is that I substituted $page with $product, where I'm loading the product from a url $segment and referencing the product page as $product as per Padloper2 docs. Big thanks for your time
    1 point
  14. Thank you for the detailed answer, it's very educational. I was aware of some of the considerations but I didn't think about cache, for example. I understand that the DeepL service take time, especially with a 5000 word document. ? I did go through the readme multiple times before. Great module and thanks again for the brilliant support here.
    1 point
  15. There are various ways to approach this. First, the concepts (being verbose below to help others as well). Everything is a page. Attributes are pages. For instance, Colour, Size, Grade. They are not tied to any product. This makes them reusable and translatable. Attribute Options are pages. For instance, Red, Small, Premium. They are not tied to any product. This makes them reusable and translatable. They are children of the respective attribute pages. Products are also pages. After you enable variants on a product page, you are able to select attributes (Colour, Size, Grade, etc.) that will make up the variants you create. Attributes are added to a product page via a multi page reference field (padloper_product_attributes). Variants are pages. After enabling variants on a product page and saving it, then subsequently adding attributes to the product and saving again, you are then able to generate variants. Variants are generated in a modal. In this modal, you select the attribute options for each of the attributes on the product itself. After generating the variants based on your choices, the attribute options are saved to the variant pages' multi page reference field named padloper_product_attributes_options. This field is not directly editable. Variants are child pages of product pages. Based on the above relationships, as you can probably tell, there are various approaches to finding the available attribute options for a product. The most straightforward way is to go through the variants as they have the exact colours and sizes you want. Things to note here are that there could be lots of variants (like in your case ) and there will be duplicate options to deal with, e.g. Red x Small and Red x Large. <- Two reds. Since you are dealing with lots of variants, I'd go for findRaw(). Below, some example code: <?php namespace ProcessWire; // PREPARE ATTRIBUTES // fields in attributes that we want $fields = ['id', 'title']; $attributesValues = $page->padloper_product_attributes->explode($fields); // get attributes as $attributeID => $attributeTitle pairs $attributes = []; foreach ($attributesValues as $attributeValue) { $attributes[$attributeValue['id']] = $attributeValue['title']; } // ------- // PREPARE OPTIONS // fields in options that we want // @note: for some reason the syntax in the docs pagefield=>['id','title'] always throws an error for me // so, we use the longhand version $fields = ['id', 'title', 'padloper_product_attributes_options.title', 'padloper_product_attributes_options.id', 'padloper_product_attributes_options.parent.id']; $variants = $padloper->findRaw("template=variant,parent={$page}", $fields); // array for final attributes and their options // in our case we expect only colours and sizes $coloursAndSizes = []; foreach ($variants as $variant) { foreach ($variant['padloper_product_attributes_options'] as $options) { $attributeID = $options['parent_id']; $optionID = $options['id']; // ##### $coloursAndSizes[$attributeID]['attribute_name'] = $attributes[$attributeID]; $coloursAndSizes[$attributeID]['options'][$optionID] = $options['title']; } } // your attributes and their respective option are in $coloursAndSizes
    1 point
  16. I didn't think of pagination! You could make the last line a find() and, though I haven't tried this, you might be OK. $limitedResults = $allResults->find('limit=15'); echo $limitedResults->render();
    1 point
×
×
  • Create New...