Jump to content


Popular Content

Showing content with the highest reputation since 03/16/2021 in all areas

  1. 27 points
    It's spring break here and my kids are going back to school next week after being out for more than a year. Since it's a break week, the weather is great, and it's also the last week of the year-long covid break from school, I've spent a little less time at the computer this week. I've focused on some smaller module projects rather than the core. More specifically: posted a major update and refactor of the TextformatterHannaCode module, and a completely rewritten TextformatterVideoEmbed module. While making these updates, I've also made note of and attempted to resolve any reported issues in the GitHub repositories. Next week, it's back to the core, with both issue resolutions and pull requests scheduled for upcoming versions. Next week I also get my 2nd shot of covid vaccine, and I'm told it may slow me down a bit for a day, but will be well worth it. I had a day of tiredness from the 1st shot, but it was greatly outweighed by feelings of gratitude and reduction of worry. I highly recommend it as soon as you can get it, if you haven't already.
  2. 27 points
    I hope everyone is having a good week! Commits to the core this week include 10 issue report resolutions (so far). This will likely continue next week as well, and then I'll bump the version up then. Also included this week is an CKEditor upgrade from 4.14.0 to version 4.16.0. While that may sound like a minor CKEditor version bump, there's actually quite a list of updates in the CKEditor 4.x changelog, including a few security-related fixes, though none that I think are likely to affect PW users. I do still have a couple of core feature requests in progress as well, but there's more work still to do on those. Nothing too exciting this week, but I like to check in and say hello either way. I hope you all have a great weekend!
  3. 23 points
    This week I've been working on something a little different for the core. Specifically, ProcessWire's database class (WireDatabasePDO) has been rewritten to support separate read-only and read-write database connections. Jan, who administers the processwire.com server, asked if I could implement it. I looked into it and thought it had significant benefits for ProcessWire users, so it worked out that now was a good time to implement it. The rewritten database class is actually complete and now running on a live test installation, but I have not yet committed it to the core dev branch because I want to fully document it first. This will happen next week in a blog post. By then I'll have processwire.com using it too. But I can already say that it's a cool thing watching the graphs in AWS show the difference it makes when we start hitting the site with crawlers. You might be wondering what the benefits are in having separate read-only and read-write database connections. I'll get into some of the details next week. But essentially, read-only database connections can scale in a way (and at much lower cost) than read-write connections can. And when using a service like Amazon Aurora, they can scale on-the-fly automatically according to traffic and demand for resources. Not only does it open up the ability for a ProcessWire-powered site to scale much further than before, but it has potential to reduce the costs of doing so by perhaps 50% or more. If you are interested in reading more, we are currently testing the features using Amazon Aurora and RDS Read Replicas (see also Replication with Aurora). However, the ProcessWire core support for this feature is not bound to anything AWS specific and will work with any platform supporting a similar ability. Thanks for reading, I'll have more on this next week, and also have it ready to use should you want to try it out yourself.
  4. 21 points
    This week ProcessWire gained the ability to maintain separate read-only and read-write database connections to optimize scalability, cost and performance. The post covers why this can be so valuable and how to configure it in ProcessWire— https://processwire.com/blog/posts/pw-3.0.175/
  5. 19 points
    A Fieldtype for dynamic options that are generated at runtime via a hook. Configuration You can choose from a range of inputfield types on the Details tab of a Dynamic Options field. Your field will return a string or an array depending on if the selected input field type is for "single item selection" or "multiple item selection". Selectable options for a Dynamic Options field should be set in a FieldtypeDynamicOptions::getSelectableOptions hook in /site/ready.php. The hook should return an array of options as 'value' => 'label'. An example hook is shown on the Details tab of a Dynamic Options field: $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); if($field->name === 'your_field_name') { $event->return = [ 'red' => 'Red', 'green' => 'Green', 'blue' => 'Blue', ]; } }); Formatted value If a Dynamic Options field uses a "single" input type then its formatted value is a string, and if it uses a "multiple" input type then its formatted value is an array. The unformatted value of a Dynamic Options field is always an array. Examples of possible uses $wire->addHookAfter('FieldtypeDynamicOptions::getSelectableOptions', function(HookEvent $event) { // The page being edited $page = $event->arguments(0); // The Dynamic Options field $field = $event->arguments(1); // Select from the "files" field on the page if($field->name === 'select_files') { $options = []; foreach($page->files as $file) { // Value is basename, label is description if one exists $options[$file->basename] = $file->get('description|basename'); } $event->return = $options; } // Select from files in a folder if($field->name === 'select_folder_files') { $options = []; $path = $event->wire()->config->paths->root . 'my-folder/'; $files = $event->wire()->files->find($path); foreach($files as $file) { // Value is full path, label is basename $options[$file] = str_replace($path, '', $file); } $event->return = $options; } // Select from non-system templates if($field->name === 'select_template') { $options = []; foreach($event->wire()->templates as $template) { if($template->flags & Template::flagSystem) continue; $options[$template->id] = $template->name; } $event->return = $options; } // Select from non-system fields if($field->name === 'select_field') { $options = []; foreach($event->wire()->fields as $field) { if($field->flags & Field::flagSystem) continue; $options[$field->id] = $field->name; } $event->return = $options; } // Select from FormBuilder forms if($field->name === 'select_formbuilder_form') { $form_names = $event->wire()->forms->getFormNames(); // Use form names as both keys and values $event->return = array_combine($form_names, $form_names); } }); https://github.com/Toutouwai/FieldtypeDynamicOptions https://processwire.com/modules/fieldtype-dynamic-options/
  6. 15 points
    Well, yes and no. Two migration modules already exist in ProcessWire, but neither suited my needs: “Migrations” by @LostKobrakai seems effective but quite onerous to use and has been deprecated in favour of “RockMigrations” RockMigrations by @bernhard is simpler and has a nice declarative method: migrate(). However, it is ideally suited to “headless” development, where the API is used in preference to the Admin UI. This is great for professional PW developers, but for occasional developers like me, it is much easier to use the UI rather than just the API. In addition there @adrian's ProcessMigrator which is designed for migrating page trees. Concept I wanted something to achieve the following: To allow development to take place (in a separate environment on a copy of the live database, or on a test database with the same structure) using the Admin UI. When finished, to permit a declarative approach to defining the migration and implementing it (again in the UI). To allow testing of the migration in a test environment on a copy of the live database. To allow roll-back of a migration if installation causes problems (ideally while testing rather than after implementation!). To provide a record of changes applied. Although not originally intended, the module I developed also allows the selective reversion of parts of the database by exporting migration data from a backup copy. Also, if changes are made directly on the live system (presumably simple, low-risk mods – although not best practice), it allows reverse migration to the development system in a similar fashion. I should emphasise that what I have built is more of a 'proof of concept' than a fully-fledged module. The code is pretty hacky and uses some stuff outside of the module itself. Lots of validation is missing. However, I have used it successfully in a number of small tests and a medium-sized live migration. If there is sufficient interest, I will tidy the code and make it available, but it would still need input from better coders and PW-savants than me to make it into something more widely usable. Design The module has the following principal components: A PW module “ProcessMigrateData”, bundled with a bootstrap migration in the same ProcessMigrateData folder, to be placed in the site/modules folder; A Page Class “MigrationPage” to be placed in the site/classes folder; Php files migrationActions.php and migrationControl.php to be placed in the site/templates/RuntimeMarkup folder (and migrationActions.js to be placed in site/templates/RuntimeMarkup/scripts). There are also a methods which need to be put in class DefaultPage and a functions in the init.php file. The module requires the FieldtypeRuntimeMarkup module. Migration definitions are held in .json files in the ProcessMigrateData/migrations/{migration name} folder (I might move this). This folder contains up to 2 sub-folders - “new” and “old” which each contain a file called a migration.json file, which defines the scope of the migration in terms of fields, templates and pages, and also one or more of fields.json, templates.json, pages.json and remove.json. The first 3 of these files contain the field, template and file definitions within the migration scope and the remove.json file simply lists fields, templates and pages to be removed. These migration files are mirrored by pages of template “Migration” under a parent /migrations/ of template “Migrations”. The mirroring happens in two ways: If a new migration is created in the module (from the Setup -> DB Migration menu), then initially no json files exist. The json files are created, after the scope of the migration is defined on the page, by running “Export Data” from the eponymous button. If json files exist, but there is no matching migration page, then the latter is created by the module on accessing the DB Migration admin page. In this case, we are in the “target” database so there is no “Export Data” button, but instead “Install” and/or “Uninstall” buttons. Migrations therefore either view the current environment as a “source” (type 1) or a “target” (type 2). Installation The module creates templates called Migration and Migrations and a page below the root named ‘migrations’. Open the admin page “Setup -> DB Migration” to create a migration. One (“bootstrap” is already installed) and cannot be modified. The pic below illustrates the DB Migrations page in the source environment. The status of a migration (as a source page) can be ‘pending’ or ‘exported’. ‘Pending’ means either that the migration data have not yet been exported or that the current export files differ from the source database. On opening this page in the target environment, the individual Migration pages (type 2) are created from the definitions in their respective /new/migration.json file. The pic below illustrates the DB Migrations page in the target environment. In a target environment, a migration status can usually be ‘indeterminate’, ‘installed’ or ‘uninstalled’. ‘Indeterminate’ means either that the migration has not yet been installed (so no ‘old’ files containing the uninstall definition exist yet) or that the current state matches neither the ‘new’ or the ‘old’ state. ‘Installed’ means that the current state matches the ‘new’ definition and ‘uninstalled’ means that it matches the ‘old’ definition (i.e. it has been actively uninstalled rather than not yet installed). When carrying out development work, you keep a note of what fields, templates and pages you have added, changed or removed. The module does not track this – it is a declarative approach, not a macro recorder. Also, it does not handle other components such as Hanna codes and Formbuilder forms. These come equipped with their own export/import functions. You can update a migration page as you go along, rather than keep a separate note of changed components. The migration page also allows you to document the migration and add any number of “snippets”. These snippets do not do anything, but can be a convenient place to store (for example) Hanna code exports for pasting into the target environment and help to make the page a comprehensive record of the migration. See example below: Note that migration pages just define the scope of the migration. It is entirely feasible for other parts of the dev database to be changed which are outside this scope and which will therefore not be migrated. After sync'ing code files to the target environment, the new migration will be listed on the setup page. On the migration page, in the target environment, there are “preview” buttons to see what changes will be implemented. The migration can then be 'installed'. See example of the migration page in ‘installation’ mode below: That's the gist of it, but inevitably there are complications. Happy to discuss and share further if there is interest in this.
  7. 13 points
    I've realized that I've been jumping back and forth between the PW API docs and the source code for site modules far too much. The idea to hold all necessary documentation locally in one place has occurred to me before, but getting PHPDocumentor et al set up and running reliably (and producing readable output) as always been too much of a hassle. Today I was asked how I find the right hooks and their arguments, and that inspired me to finally get my backside down on the chair and whip something up, namely the Module Api Doc Viewer ProcessModuleApiDoc It lets you browse the inline documentation and public (optionally also protected) class/method/property information for all modules, core classes and template files in the ProcessWire instance. The documentation is generated on the fly, so you don't have to remember to update your docs whenever you update a module. The module is quite fresh, so expect some bugs there. Behind the scenes it uses PHP-Parser together with a custom class that extracts the information I needed, and the core TextformatterMarkdownExtra module for rendering the description part in the phpdoc style comments. This is not a replacement / competitor to the API Viewer included in the commercial ProDevTools package. There is quite some information included in the inline documentation that my module can't (and won't) parse, but which makes up parts of the official ProcessWire API docs. This, instead, is a kind of Swiss army knife to view PHPDoc style information and get a quick class or function reference. If you feel daring and want to give it a spin, or if you just want to read a bit more, visit the module's GitHub repository. This is the overview page under "Setup" -> "Module API Docs": And this is what the documentation for an individual class looks like: The core module documentation can of course be found online, but it didn't make sense not to include them. Let me know what you think!
  8. 12 points
    My wife loves to cook, and I always like to further my knowledge around Processwire. So I thought I'll build a small page with the some small function to learn something. Used modules: ProMailer RepeaterMatrix Pages2PDF AIOM and some other litte modules Current functions: JSON-LD for recipes and page search Automatic ingredient calculation when changing the number of servings Creation of a PDF of the recipe Basic PWA (here is something to do, actually) Planned functions: a lot 😉 Site: https://www.dothiscookingthing.de
  9. 11 points
    Hi, Thanks a lot for all the feedback. I did some additional tests based on all of the suggestions you gave me and results are already amazing!! Figure 1 shows @ryan suggestions tested independently: 1. I created the $template variable outside the loop. 2. I created the $parent variable outside the loop. The boost in performance is surprising! Defining the $parent outside the loop made a huge difference (before I didn't assigned the parent explicitly, it was already defined in the template thus the assignment was automatic) 4. I also tried this suggestion ($page->name = "protein" . $i;) and although it seems to boost a bit performance I didn't include the plot because results were not conclusive. Still I will include this in my code. Figure 2 is based on @horst suggestion. I tested the impact of calling gc_collect_cycles() and $pages->uncacheAll() after every $database->commit(). I didn't do a test for $pages->uncache($page) because I thought $pages->uncacheAll() was basically the same. Maybe this is not true (?). Results don't show any well defined boost in performance (I guess ryan's recent reply predicted this). I still need to try @BitPoet suggestion because I am sure this is something that will boost performance. I am now doing this tests on my personal computer. I will do this test when running on the dedicated server. I will also would like to try generators (first time a hear about them ) ______________________________________________________________________________________________________________________________________________________________________________________________ One last thing regarding the fields in the protein template and the data structure in general (the pseudo code I posted initially was just as an example). Proteins are classified into groups. Each protein can belong to more than one groups (max. 5). My original idea was to use repeaters because for each protein I have the following information repeated: GroupID [integer], start [integer], end [integer], sequence [text] The idea is that from GroupID you can go to the particular group page (I have around 50k groups) but I don't necessarily need a page reference for this. The csv is structured as follow. Note that some protein entries are repeated which means that I shouldn't create a new page but add an entry to the repeater field. Protein-name groupID start end sequence A0A151DJ30 41 3 94 CPFES[...]]VRQVEK A0A151DJ30 55 119 140 PWSGD[...]NWPTYKD A0A0L0D2B9 872 74 326 MPPRV[...]TTKWSKK V8NIV9 919 547 648 SFKYL[...]LEAKEC A0A1D2MNM4 927 13 109 GTRVW[...]IYTYCG A0A1D2MNM4 999 119 437 PWSGDN[...]]RQDTVT A0A167EE16 1085 167 236 KTYLS[...]YELLTT A0A0A0M635 1104 189 269 KADQE[...]INLVIV Since I know repeaters also creates additional overhead I am doing all my benchmarks without them. I can always build the websites without them. In the next days I will do some benchmarks including repeaters just to see how it goes. Once again, thanks for all the replies!
  10. 11 points
    I've been meaning to revise PageimageSrcset for a while now, to remove some features that I felt were unnecessary and to implement a better rendering strategy. The result is PageimageSource. What does it do? It provides a configurable srcset method/property for Pageimage It allows WebP to be enabled for images it generates. It allows Pageimage:render() to return a <picture> element It provides a Textformatter that replaces <img> elements with the output of Pageimage:render() Although it is based on a current module, this should still be considered beta and not used in production without a prior development stage. Here's the README: PageimageSource Extends Pageimage with a srcset property/method plus additional rendering options. Overview The main purpose of this module is to make srcset implementation as simple as possible in your template code. For an introduction to srcset, please read this Mozilla article about responsive images. Installation Download the zip file at Github or clone the repo into your site/modules directory. If you downloaded the zip file, extract it in your sites/modules directory. In your admin, go to Modules > Refresh, then Modules > New, then click on the Install button for this module. ProcessWire >= 3.0.165 and PHP >= 7.3 are required to use this module. Configuration To configure this module, go to Modules > Configure > PageimageSource. Default Set Rules These are the default set rules that will be used when none are specified, e.g. when calling the property: $image->srcset. Each set rule should be entered on a new line, in the format {width}x{height} {inherentwidth}w|{resolution}x. Not all arguments are required - you will probably find that specifying the width is sufficient for most cases. Here's a few examples of valid set rules and the sets they generate: Set Rule Set Generated Arguments Used 320 image.320x0-srcset.jpg 320w {width} 480x540 image.480x540-srcset.jpg 480w {width}x{height} 640x480 768w image.640x480-srcset.jpg 768w {width}x{height} {inherentwidth}w 2048 2x image.2048x0-srcset.jpg 2x {width} {resolution}x How you configure your rules is dependent on the needs of the site you are developing; there are no prescriptive rules that will meet the needs of most situations. This article gives a good overview of some of the things to consider. When you save your rules, a preview of the sets generated and an equivalent method call will be displayed to the right. Invalid rules will not be used, and you will be notified of this. WebP If enabled, WebP versions of the image and srcset variations will be generated and these will be returned by Pageimage::srcset(). As with the default implementation, the image with the smaller file size is returned. In most cases this is the WebP version, but sometimes can be the source. Make sure to experiment with the quality setting to find a value you find suitable. The default value of 90 is fine, but it is possible that lower values will give you excellent kB savings with little change in overall quality. For more information on WebP implementation please read the blog posts on the ProcessWire website. Rendering These settings control how the output of Pageimage::render() is modified. Use Lazy Loading? When enabled this adds loading="lazy" to the <img> attributes. It is useful to have this on by default, and you can always override it in the options for a specific image. Use the <picture> element? When enabled, the <img> element is wrapped in a <picture> element and <source> elements for original and WebP variations are provided. This requires WebP to be enabled. For more information on what this does, have a look at the examples in Pageimage::render() below. Remove Variations If checked, the image variations generated by this module are cleared on Submit. On large sites, this may take a while. It makes sense to run this after you have made changes to the set rules. Please note that although the module will generate WebP versions of all images if enabled, it will only remove the variations with the 'srcset' suffix. Usage Pageimage::srcset() // The property, which uses the set rules in the module configuration $srcset = $image->srcset; // A method call, using a set rules string // Delimiting with a newline (\n) would also work, but not as readable $srcset = $image->srcset('320, 480, 640x480 768w, 1240, 2048 2x'); // The same as above but using an indexed/sequential array $srcset = $image->srcset([ '320', '480', '640x480 768w', '1240', '2048 2x', ]); // The same as above but using an associative array // No rule checking is performed $srcset = $image->srcset([ '320w' => [320], '480w' => [480], '768w' => [640, 480], '1240w' => [1240], '2x' => [2048], ]); // The set rules above are a demonstration, not a recommendation! Image variations are only created for set rules which require a smaller image than the Pageimage itself. This may still result in a lot of images being generated. If you have limited storage, please use this module wisely. Pageimage::render() This module extends the options available to this method with: srcset: When the module is installed, this will always be added, unless set to false. Any values in the formats described above can be passed. sizes: If no sizes are specified, a default of 100vw is assumed. lazy: Pass true to add loading=lazy, otherwise false to disable if enabled in the module configuration. picture: Pass true to use the <picture> element, otherwise false to disable if enabled in the module configuration. Please refer to the API Reference for more information about this method. // Render an image using the default set rules // WebP and lazy loading are enabled, and example output is given for <picture> disabled and enabled echo $image->render(); // <img src='image.webp' alt='' srcset='image.jpg...' sizes='100vw' loading='lazy'> /* <picture> <source srcset="image.webp..." sizes="100vw" type="image/webp"> <source srcset="image.jpg..." sizes="100vw" type="image/jpeg"> <img src="image.jpg" alt="" loading="lazy"> </picture> */ // Render an image using custom set rules echo $image->render(['srcset' => '480, 1240x640']); // <img src='image.webp' alt='' srcset='image.480x0-srcset.webp 480w, image.1240x640-srcset.webp 1240w' sizes='100vw' loading='lazy'> /* <picture> <source srcset="image.480x0-srcset.webp 480w, image.1240x640-srcset.webp 1240w" sizes="100vw" type="image/webp"> <source srcset="image.480x0-srcset.jpg 480w, image.1240x640-srcset.jpg 1240w" sizes="100vw" type="image/jpeg"> <img src="image.jpg" alt="" loading="lazy"> </picture> */ // Render an image using custom set rules and sizes // Also use the `markup` argument // Also disable lazy loading // In this example the original jpg is smaller than the webp version echo $image->render('<img class="image" src="{url}" alt="Image">', [ 'srcset' => '480, 1240', 'sizes' => '(min-width: 1240px) 50vw', 'lazy' => false, ]); // <img class='image' src='image.jpg' alt='Image' srcset='image.480x0-srcset.webp 480w, image.1240x0-srcset.webp 1240w' sizes='(min-width: 1240px) 50vw'> /* <picture> <source srcset="image.480x0-srcset.webp 480w, image.1240x0-srcset.webp 1240w" sizes="(min-width: 1240px) 50vw" type="image/webp"> <source srcset="image.480x0-srcset.jpg 480w, image.1240x0-srcset.jpg 1240w" sizes="(min-width: 1240px) 50vw" type="image/jpeg"> <img class='image' src='image.jpg' alt='Image'> </picture> */ // Render an image using custom set rules and sizes // These rules will render 'portrait' versions of the image for tablet and mobile // Note the advanced use of the `srcset` option passing both `rules` and image `options` // WebP is disabled // Picture is disabled echo $image->render([ 'srcset' => [ 'rules' => '320x569, 640x1138, 768x1365, 1024, 1366, 1600, 1920', 'options' => [ 'upscaling' => true, 'hidpi' => true, ], ], 'sizes' => '(orientation: portrait) and (max-width: 640px) 50vw', 'picture' => false, ]); // <img src='image.jpg' alt='' srcset='image.320x569-srcset-hidpi.jpg 320w, image.640x1138-srcset-hidpi.jpg 640w, image.768x1365-srcset-hidpi.jpg 768w, image.1024x0-srcset-hidpi.jpg 1024w, image.1366x0-srcset-hidpi.jpg 1366w, image.1600x0-srcset-hidpi.jpg 1600w, image.jpg 1920w' sizes='(orientation: portrait) and (max-width: 768px) 50vw' loading="lazy"> TextformatterPageimageSource Bundled with this module is a Textformatter largely based on TextformatterWebpImages by Ryan Cramer. When applied to a field, it searches for <img> elements and replaces them with the default output of Pageimage::render() for each image/image variation. Assuming a default set of 480, 960 and lazy loading enabled, here are some examples of what would be returned: Example <figure class="align_right hidpi"> <a href="/site/assets/files/1/example.jpg"> <img alt="" src="/site/assets/files/1/example.300x0-is-hidpi.jpg" width="300" /> </a> </figure> WebP enabled <figure class="align_right hidpi"> <a href="/site/assets/files/1/example.jpg"> <img alt="" src="/site/assets/files/1/example.300x0-is-hidpi.webp" width="300" srcset="/site/assets/files/1/example.300x0-is-hidpi.webp 480w" sizes="100vw" loading="lazy" /> </a> </figure> <picture> enabled <figure class="align_right hidpi"> <a href="/site/assets/files/1/example.jpg"> <picture> <source srcset="/site/assets/files/1/example.300x0-is-hidpi.webp 480w" sizes="100vw" type="image/webp"> <source srcset="/site/assets/files/1/example.300x0-is-hidpi.jpg 480w" sizes="100vw" type="image/jpeg"> <img alt="" src="/site/assets/files/1/example.300x0-is-hidpi.jpg" width="300" loading="lazy" /> </picture> </a> </figure> Because the variation is small - 300px wide - the srcset only returns the source image variation at the lowest set width (480w). If the source image was > 1000px wide, there would be a variation at both 480w and 960w. PageimageSrcset This module is built upon work done for PageimageSrcset, which can be considered a first iteration of this module, and is now deprecated. Migration PageimageSource is a simplified version of PageimageSrcset with a different approach to rendering. Most of the features of the old module have been removed. If you were just using $image->srcset(), migration should be possible, as this functionality is essentially the same albeit with some improvements to image variation generation.
  11. 10 points
    Just to add, if the points from @ryan and @horst aren't enough (they should boost import times quite noticeably) you could try dropping the FULLTEXT keys on the relevant fields' tables before the import and recreating them afterwards (ALTER TABLE `field_fieldname` DROP KEY `data` / ALTER TABLE `field_fieldname` ADD FULLTEXT KEY `data` (`data`)). Finally, a big part of MySQL performance depends on server tuning. The default size for the InnoDB buffer pool (the part of RAM where MySQL holds data and indexes) is rather small at 128MB. If you have a dedicated database server, you can up that to 80% of physical memory to avoid unnecessary disk access.
  12. 10 points
    corporate.blue-tomato.com is a new Website about the Corporate behind Blue Tomato. You can find informations about the history and values of Blue Tomato and also all open job positions. Used Modules ProCache ProcessGraphQL (used with React on the job overview page for searching/filtering the jobs) ProFields: Combo ProFields: RepeaterMatrix (Used for some kind of a "PageBuilder") SeoMaestro TemplateEngineSmarty / TemplateEngineFactory
  13. 9 points
    I think this is an important topic and @pideluxe makes some useful suggestions. The subsequent discussion provides insights into what is attractive about PW and what people think is missing. I am a relative newcomer to PW and am only an amateur - in two senses: (a) as a retiree, I can do stuff for for friends and family without commercial pressures and (b) I have never had any training (and it shows 😉 ). What attracted me to PW was the ability to build custom apps integrated into a website and give others the ability to manage the content. Having used WP (aargh - customisation is a nightmare and the admin is really clunky), CodeIgniter (well-designed, but hard work!) and some simple CMS solutions, I wanted something that was easy to use and took out a lot of the hard coding work, but was flexible and capable. PW ticked all the boxes. As well as straight CMS, I have now built two quite complex apps - a club management system and a self-catering cottage management system. PW has been a great tool for the job. So the question is, why is it not more widely known and used? I think there are two issues, both of which have been mentioned by others: The project does appear to be very reliant on Ryan (albeit maybe less than previously owing to some of the excellent contributors to the ecosystem). This was not a problem for me (in fact a positive, because it has meant a clear vsion for the project), but is a negative point for some. My daughter, who is tech director of an e-marketing company, prefers WP even though it is technically inferior because of the greater assurance of continuity and the larger pool of people with relevant skills. The OP suggests some ways in which this perception could be changed, but I suspect that may not be possible without @ryan's active involvement. Over time, the user group has become more technical so that the appeal is narrowing. In some ways, this is a consequence of PW's success in enabling some pretty sophisticated sites/apps - this has attracted technophiles whose needs have further driven it in this direction. This has happened without any active decision on anyone's part. Some may feel very comfortable with this and think that it is a perfectly viable niche. I am not so sure - the risk is that the ease-of-use aspects are increasingly downplayed so that ultimately the comparison is with, say, Laravel, rather than WP. The bottom line is that PW's initial attraction of being both a 'simple' CMS and a sophisticated app-building tool risk it ending up being between a rock and a hard place. That would be a great shame because IMHO it is better, technically, than the competition. You may infer from my comments above that I think that part of the 'problem' is that the project is largely technically-driven - inevitable given the nature of those involved. This is not a critcism, just a fact of life. But perhaps the community needs to get more input from outside and think about PW's marketplace more broadly. FWIW, I agree with many of the suggestions above, including Pretty much all of @OllieMackJames's initial post. BTW, this one - is one of the few things that actually irritates me about PW, which is why I am trying to do something about it (see https://processwire.com/talk/topic/25307-oh-no-not-another-migration-module/, but I really think something like this should be in the core).
  14. 9 points
    Admin Restrict Branch Select is an add-on / companion module for Admin Restrict Branch. With this module enabled, you can manually select more than one branch parent per user via the branch_parent field, and users with more than one option selected will be able to switch between those while editing site content. Switching between branches is done via a select field injected at the top of the page tree. Note that users are still limited to one branch at a time: this module will not make it possible to view multiple branches at the same time. When the module is installed, it will automatically update the branch_parent field if deemed necessary, i.e. if it isn't yet configured to allow selecting multiple pages. You can make changes to the field later, if you want to restrict selectable options by template, use asmSelect instead of PageListSelectMultiple, etc. This is an early beta release, so be sure to test carefully before enabling this module in production! https://processwire.com/modules/admin-restrict-branch-select/ https://github.com/teppokoivula/AdminRestrictBranchSelect
  15. 9 points
    @MrSnoozles @teppo This is not a limiting factor in scalability at least. First off, at least here, the file-based assets are delivered by Cloudfront CDN, so they aren't part of the website traffic in the first place (other than to feed the CDN). If you wanted scalability then you'd likely want a CDN serving your assets whether using S3 or not. But a CDN isn't a necessary part of the equation in our setup either. File systems can be replicated just like databases. That's how this site runs on a load balancer on any number of nodes. Requests that upload files are routed to node A (primary), but all other requests can hit any node that the load balancer decides to route it to. The other nodes are exact copies of the node A file system that update in real time. This is very similar to what's happening with the DB reader (read-only) and writer (read-write) connection I posted about above, where the writer would be node A and there can be any number of readers. Something like S3 doesn't enhance the scalability of any of this. Implementing S3 as a storage option for PW is still part of the plan here, but more for the convenience and usefulness than scalability. You can already use S3 as a storage option in PW if you use one of the methods of mapping directories in your file system to it. But I'm looking to support for it in PW more natively than that. It is admittedly more complex than the DB stuff mentioned above. For instance, we use PHP's PDO class that all DB requests go through, so intercepting and routing them is relatively simple. Whereas in PHP, there is no PDO-type class that the file system API is built around, and instead it is dozens of different procedural functions (which is just fine, until you need to change their behavior). In addition, calls to S3 are more expensive than a file system access, so doing something as simple as getting an image dimensions is no longer a matter of a simple php getimagesize() call. Instead, it's more like making an FTP connection somewhere, FTP'ing the file to your computer, getting the image dimensions, storing them somewhere else, then deleting the image. So meta data like image dimensions needs to be stored somewhere else. PW actually implemented this ability last year (meta data and stored image dimensions). So we've already been making small steps towards S3-type storage, but because the big picture is still pretty broad in scope to implement, it's more of a long term plan. Though maybe one of my clients will tell me they need it next week, in which case it'll become a short term plan. 🙂
  16. 8 points
    101aandd.com New site mostly designed by the client and implemented in Processwire. Mostly concentrating on small subtle animations.
  17. 8 points
    Thanks. The v0.3.0 update to HannaCode is actually a major refactoring of the module so there were a few things in HannaCodeDialog that needed updating for compatibility. The new v0.4.0 of HannaCodeDialog should be compatible with HannaCode v0.3.0 and it requires HannaCode >= v0.3.0.
  18. 8 points
    Bern hard u.can does $wire->addHook('/mymodule/{foo}/{bar}(/{baz})?/', function($e) { return "<pre> foo: $e->foo bar: $e->bar baz: $e->baz "; }); @ bern berna hard @ @bernhard
  19. 8 points
    After looking into the code, adding support for a parallel uploads limit wasn't too hard, I think. If you're willing to try, I've created a fork of the current PW dev branch and added that functionality. This lets you set a configurable limit for how many uploads happen at once, the rest is queued and processed as soon as others finish.
  20. 7 points
    Hi, A bit of background. I am creating a website which lets you navigate through a protein database with 20 million proteins grouped into 50 thousand categories. The database is fixed in size, meaning no need to update/add information in the near future. Queries to the database are pretty standard. The problem I am currently having is the time it takes to create the pages for the proteins (right now around a week). Pages are created reading the data from a csv file. Based on previous posts I found on this forum (link1, link2) I decided to use $database transactions to load the data from a php script (InnoDB engine required). This really boosts performance from 25 pages per second to 200 pages per second. Problem is performance drops as a function of pages created (see attached image). Is this behavior expected? I tried using gc_collect_cycles() but haven't noticed any difference. Is there a way to avoid the degradation in performance? A stable 200 pages per second would be good enough for me. Pseudo code: $handle = fopen($file, "r"); $trans_size = 200 // commit to database every _ pages try { $database->beginTransaction(); for ($i = 0; $row = fgetcsv($handle, 0, " "); ++$i) { // fields from data $title = $row[0]; $size = $row[1]; $len_avg = $row[2]; $len_std = $row[3]; // create page $page = new Page(); $page->template = "protein"; $page->title = $title; $page->size = $size; $page->len_avg = $len_avg; $page->len_std = $len_std; $page->save(); if (($i+1)%$trans_size == 0) { $database->commit(); // $pages->uncacheAll(); // gc_collect_cycles(); $database->beginTransaction(); } } $database->commit(); } I am quiet new to process wire so feel free to criticize me 🙂 Thanks in advance
  21. 7 points
    @fedeb Glad that moving the $parent outside the loop helped there. The reason it helps is because after a $pages->save() is the automatic $pages->uncacheAll(), so the auto-assigned parent from the template is having to be re-loaded on every iteration. By keeping your own copy loaded and assigning it yourself, you are able to avoid that extra overhead in this case. Avoid getting repeaters involved. I wouldn't even experiment with it here. That will at minimum triple the number of pages (assuming every protein page could have a repeater). Repeaters would be just fine if you were working in the thousands-of-pages territory, but in the millions-of-pages territory, it's not going to be worth even attempting. Using a ProFields table field would be the best alternative if you needed it to be queryable data. If you didn't need it to be queryable data (groupID, start, end, sequence), I would leave them as they are, space-separated in a plain textarea field — they can easily be parsed out at runtime so you can access them as as properties of the page. (If that suits your need, let me know and I'll get into how that can be done). When working at large scale, it's also always good to consider custom building a Fieldtype module for the purpose too (that's another topic, but we can get into it too). For your groupID, if the same groupID is referenced by multiple proteins, and there is more information about each "group" (other than just an ID) then I think it would make sense for it to be a Page reference field. What is the max number of groupID+start+end+sequence rows that a protein can have? If there is a natural limit and it's not large, then that would open up some new storage possibilities too. Another optimization you can make in your loop: $page->sort = $i; This prevents it from having to detect and auto-assign a sort value based on the quantity of children the parent page has. For the $page->name, if each page will have a unique "protein-name" then you might also consider using that rather than the ("protein" . $i), as it will be more reflective of the page than a generic index number.
  22. 7 points
    I think you could try the generators here while you are playing with CSV. For .eg <?php function getRows($file) { $handle = fopen($file, 'rb'); if ($handle === false) { throw new Exception('open file '.$file.' error'); } while (feof($handle) === false) { yield fgetcsv($handle); } fclose($handle); } // allocate memory for only a single line in the csv file // do not need the entire csv file is read into memory $generator = getRows('../data/20_mil_data.csv'); // foreach ($it as $row) {print_r($row);} while ($generator->valid()) { print_r($generator->current()); //$generator->current() is your $row // playing with ProcessWire here $generator->next(); } $generator->rewind(); // http://php.net/manual/en/class.generator.php That's always my #1 choice while working with big datasets in PHP.
  23. 7 points
    This module is sort of an upgrade to my earlier ImageToMarkdown module, and might be useful to anyone working with Markdown in ProcessWire. Copy Markdown Adds icons to images and files that allow you to copy a Markdown string to the clipboard. When you click the icon a message at the top left of the screen notifies you that the copying has occurred. Screencast Note: in the screencast an EasyMDE inputfield is used to preview the Markdown. It's not required to use EasyMDE - an ordinary textarea field could be used. Usage: Images When you hover on an item in an Images field an asterisk icon appears on the thumbnail. Click the icon to copy an image Markdown string to clipboard. If the "Description" field is populated it is used as the alt text. You can also open the "Variations" modal for an image and click the asterisk icon to copy an image Markdown string for an individual variation. Usage: Files When you hover on an item in a Files field an asterisk icon appears next to the filename. Click the icon to copy a link Markdown string to the clipboard. If the "Description" field is populated it is used as the link text, otherwise the filename is used. https://github.com/Toutouwai/CopyMarkdown https://processwire.com/modules/copy-markdown/
  24. 7 points
    Funding Processwire, Here are my thoughts, I think it could be a really good cash cow for many people, but only if they focus on something so many people out there want. Mind you I am not a developer, but an end user who needs developers to build stuff for me. Two of my sites are: www.rugpijnweg.nl and www.backpaingoodbye.com both made by people here on the forum. For me, like many others, my sites have to make me money. Therefore speed is important, hence I use procache on all my sites. Building blocks are important for easy building of pages that generate the action I need. Backpaingoodbye is good with that, all repeatermatric things in there. What I love about processwire is the speed and that anything can be built. The last part is also the problem, if I want something new, it has to be built. What I think processwire needs is something like www.thrivethemes.com, which is a separate company with an offering on top of wordpress, like there are more, but I particularly like many of the things they are making. What I hate about my sites with processwire, is that if I have some changes added to a site of mine, it just is a stupid nightmare to port new stuff to other sites that use the same profile. I guess rockmigrations would be great for that, but there you go, I need a developer to run things again, and I now have 4 sites running the same profile. For that reason I am now even considering moving over to wordpress, I got me a subscription to thrivethemes, but I HATE wordpress with a vengeance... all these stupid updates all the time. Thrivethemes itself is buggy and does not make sense, it produces slow sites, but hey I can get a lot of functionality. The underlying problem with processwire and its funding is that it is IMHO too much tech oriented, brilliant minds who build incredible things, but the huge market out there is just forgotten, and wordpress gobbles it up. And then people build stuff on top of wordpress and make a very good living. So here's what I believe would work: - get a group of people together and decide on & build a basic site structure that has all the fields anyone needs, with upgrade paths in place should the structure be extended - on top of this site structure build a storehouse of repeateable blocks with a page builder like thrive themes is using, but then better, more processwire like - then have the designers go crazy on developing a number of front end variations that change the look and feel of the whole site, just by pointing the whole show to a different css file - have some developers working on creating more building blocks focused on different niches, par example the coaching business would require agenda booking, same for hairdresser salons. - this way it would be possible to offer niche specific sites, including hosting I have ideas how this could work, and would like to hear if people are interested to develop these ideas further Let me know
  25. 7 points
    One of my first projects with ProcessWire. Online for a while, but still up to date and currently new features are being developed again. The most interesting feature of the site is the integration of the external tool and the PDF creation of the registration form with integrated API connection to always be able to directly output the most current dates. External Service: SEMCO ( https://www.semcosoft.com/de/ ) Used Modules: HannahCode Pages2PDF AIOM+ MarkupSitemap XML and some others In Development: Shop with Padloper Site: https://www.school-dynamic.de/ At this point I like to say Processwire makes it so easy for me to solve even more complicated tasks and get impressive results. Because I must confess, my PHP skills are unfortunately not yet where I would like to be.
  26. 7 points
    Hehe, There are days, and there are days like these. At 0.0.9 now, even refactored the code a bit and added syntax highlighting for embedded PHP snippets. I think I'll call it a day now though.
  27. 7 points
    And we love hearing from you. Have a great weekend everybody.
  28. 6 points
    Unless I'm forgetting something, the $pages->uncache($page); won't help here because $page is a newly created Page that wasn't loaded from the database. So it's not going to be cached either. Uncaching pages is potentially useful when iterating through large groups of existing pages. For instance, if you are rendering or exporting something large from the contents of existing pages, you might like to $pages->uncacheAll() after getting through a thousand of them to clear room for another paginated batch. Though nowadays we have $pages->findMany() and $pages->findRaw(), so there are fewer instances were you would even need to use uncache or uncacheAll, if ever. ProcessWire actually does an uncacheAll() internally after saving a page already. This is necessary because changes to a page or additions/deletions to the page tree may affect other pages, and we don't want any potential for old cached data to appear in future $pages->find() or other operations. Just one example is if we called $parent->children() before a save, and then after the save called it again, we'd want our new page to be in the children rather than having it return the previously cached value. There are a lot of similar cases, so the safest bet is for PW to uncache the results of future page get/find operations after a save as the default behavior. So that's the way it's always done it. As far as I can tell from fedeb's example (and often other with import operations), it may be better to tell PW to skip this "uncacheAll-after-save" behavior. That's because imports often involve Page reference fields, and you don't want PW to have to reload referenced pages after every save. So you could potentially reduce overhead by telling it not to uncache after save, i.e. $pages->save($page, [ 'uncacheAll' => false ]); I'm not sure if fedeb's import involves loading of any other pages, whether for page reference fields, or anything else. So it may not matter one way or the other here, but wanted to mention it just in case. I know about ProcessWire tuning, but not about MySQL server tuning. When dealing with 20 million rows that seems like getting into the territory where optimizations to the DB configuration deserve a lot of focus, so I would bet that BitPoet's suggestions are going to make the most difference.
  29. 6 points
    @fedeb That's the largest quantity of pages I've heard of anyone creating in ProcessWire, by a pretty large margin. So you are in somewhat uncharted territory. But that's really cool you are doing that. I would be curious how different the graph would be if you split it up into batches so that you aren't creating more than a certain quantity per execution/runtime. For instance, maybe you create 10k in one execution and another 10k in the next, etc., or something like that. Would the same slowdown still occur? If so, I would start to think it might be the database index and increased overhead in maintaining that index as the quantity increases. On the flip side, if restarting the process to create each set in batches solves the slowdown, then I would think it might be memory or resource related. A couple things you can do to potentially (?) improve your page creation time: 1. At the top of your code (before the loop) put: $template = $templates->get('protein'); Then within the loop set: $page->template = $template; 2. I don't see a parent page assignment. How are you doing that? Double check that you aren't asking PW to load the parent page every time in the loop and instead handle it like with the template in #1 above. 3. What kind of fields are on your "protein" template? Depending on their type, there may be potential optimizations. Especially if any are Page references. Can you paste in a line or two from the CSV? 4. If you can assign a $page->name = "protein" . $i; rather than having PW auto-generate a name from the title, that will save some resources too.
  30. 6 points
    @markus_blue_tomato Sorry to hear that, I hope they have it available soon. I figured we'd be the last to get it here because the for-profit healthcare system in the US doesn't often lend itself well to public health, unless you are wealthy. (Just getting a covid test was $400). Luckily it seems the vaccine isn't being handled by the healthcare companies, and it's free. My parents got their vaccine at an appliance store drive-through, my wife got hers at the grocery store, and I got mine at the office of some technology company in our town square. That might sound sketchy but they are all legitimate and it seems to be working well for once.
  31. 6 points
    Thanks for showing those modules some love Ryan - much appreciated!
  32. 6 points
    I hadn't developed a website for a while, but here we are. It's a very simple minimalist website to showcase the latest work of Dominican Artist Patricia Abreu Mota. Site: https://patriabreu.com Modules: Procache ❤️ Seo Maestro Profiler Pro Lister Pro ProcessRedirects
  33. 5 points
    @AndZyk Alright, I had some fun with it. Here's an improved script for the asset export, which can handle nested repeater and matrix repeater fields: /** * Get a flat array of all images on the given page, even in nested repeater / repeater matrix fields. * * @var Page $page The page to get the images from. * @return Pageimage[] Array of Pageimage objects. */ function getImages(Page $page): WireArray { $images = new WireArray(); $fields = $page->fields->each('name'); foreach ($fields as $field) { $value = $page->get($field); $type = $page->fields->{$field}->getFieldType(); if ($value instanceof Pageimage) { $images->add($value); } elseif ($value instanceof Pageimages) { $images->import($value->getArray()); } elseif ($value instanceof RepeaterMatrixPageArray || $value instanceof RepeaterPageArray) { foreach ($value as $repeaterPage) { $images->import(getImages($repeaterPage)); } } elseif ($value instanceof RepeaterMatrixPage || $value instanceof RepeaterPage) { $images->import(getImages($value)); } } return $images; } $images = getImages($page); // create target folder for the page assets $targetDir = $config->paths->assets . 'export/' . $page->name . '/'; $files->mkdir($targetDir, true); // move all images to the target folder foreach ($images as $image) { $name = $image->basename(); $target = $targetDir . $name; $src = $image->filename(); $files->copy($src, $target); } This could be extended in any number of ways: Handle file fields as well as images. Handle any other fields and dump them in the target folder as JSON. Handle native Page properties. At some point you got a complete page export module. Might want to look into the pages export module if you don't want to write all that stuff yourself 🙂
  34. 5 points
    Whats about a #5: $pages->uncache($page) after the $database->commit(); to additionally free some memory.
  35. 5 points
    Food Allergens Module A simple List of Food Allergens My needs were to provide a simple list of food allergens for our clients with restaurant related activity. The idea was to simply output the list (to speed up the data entry) without leaving the food menu editing, eg. opening another page in new tab or window. This isn't a perfect solution, but it works fine for my needs and I decided to share the base idea. This could also be easily used to show little notes or short "vademecum", not only for the list of food allergens. --- Main features The basis All moves from a short editing of the module in this tutorial: How to create custom admin pages by @bernhard First of all it creates an empty admin page, with a dedicated permission to let safe-user to see it (this permission has to be created as a new ones, manually or by the module). Once the page is created, I have hooked its behaviour into the ready.php, to show the content (basically a list). A step further With the tips of @bernhard, @Soma (and many others), see here , the magic happens. The new page will be shown as a panel, so editors will not abandon their data entry to have a quick view to the list. A little further Why scroll to the top of the page to click a link? The next step was to create a sticky button only in the food menu pages. Again with a @bernhard tip I moved into the customization of this simple module and the related hook. --- How to use this module After installed, it creates the page /admin/page/allergens/ and the module is to be setted up. The first field is a CKEditor with multi-language. This is the place where to write the informations that will be shown into the page. The next field is a simply text-area where to place a bit of JS that will be appended to the markup of the text (omit the 'script' tags). I also putted a checkbox with a silly statement: this to think at least twice on the safety of the written JS. Now comes the first way to display the link to the page Field Note with Link. Enable and save it. The module will display a new row with 4 selects (1 standard and 3 ASM): View mode (to show the page as Panel or as Modal PopUp); Templates to select: select one or more and save before proceed, so the asm-select of the pages will be populated showing all the pages of the selected templates. Pages to select: also here select at least one and save before proceed to populate the asm-select for fields only with the ones that belong to the selected pages. Select the fields where to place the note and save again. That's all: now you will find into the notes of the selected fields the link "See the List of Allergens". At the same way, the option for the sticky button, but with a plus The field select is obviously unnecessary, but you could play with the last row: the inline styles to fix your sticky button where you like. Here you could set the sticky position of the <div> and the absolute ones of the <a>. Video Explanation In these screencasts you could see a custom JS that show a "copy" button near a "hanna-code" call. This because I've set a specific one for each allergen to show up a tooltip in the front end. Registrazione #33.mp4 Registrazione #34.mp4 --- Last but not the least Actually it works fine for my needs, even if it's much improvable: I'm working on the permissions creation, the uninstall section, a separate configs and defaults and how to include the hook into the module leaving free the ready.php. According to a simpler uninstall. Also I would make the link text as a dynamic text field, so it will be more flexible. I always learn a lot here, so I would share my code for whom it could be interested. I removed the hanna code references, but I provide you the html list of the allergens, English and Italian too, so you can paste them into the "source" of the CKEditor field to have a ready to use module. Obviously you are free to modify the code as per your needs. Please, keep in mind that I'm not a pro coder and I beg your pardon for my verbosity (speaking and coding). 😉 I hope be helpful or for inspiration. Bye ready.phpList-ITA.htmlList-ENG.htmlAllergens.module README.md
  36. 5 points
    Thanks for sharing this. 👍 Another possible way to approach this is to use a single Hanna tag for all allergens and then use Hanna Code Dialog to create an interface where a site editor can select allergen types by label. You could use a page per allergen type and use the (multi-language?) title field for the label and an integer field for the allergen number. Then use a hook to set the options for the dialog. $wire->addHookAfter('HannaCodeDialog::buildForm', function(HookEvent $event) { // The Hanna tag that is being opened in the dialog $tag_name = $event->arguments(0); // The form rendered in the dialog /* @var InputfieldForm $form */ $form = $event->return; if($tag_name === 'allergen') { /* @var InputfieldCheckboxes $f */ $f = $event->wire('modules')->InputfieldCheckboxes; $f->name = 'type'; $f->id = 'type'; $f->label = 'Type'; foreach($event->wire()->pages->find("parent=/references/allergens/") as $allergen) { $f->addOption($allergen->allergen_number, $allergen->title); }; $form->add($f); } }); Or maybe if you are using a Repeater for your menu items you don't need Hanna Code and could just use a Page Reference field to select allergen pages per menu item.
  37. 5 points
    Well, in this case, it's Teppo who is amazing 🙂
  38. 5 points
    With help from Elabx, I'm now able to see it again on FF. The HTTP was being redirected to HTTPS so it was failing to load. Problem solved.
  39. 5 points
    Also interested in this. My impression is that offloading assets to Amazon S3 / Google Cloud Storage etc. is a relatively common need, and there's currently no bulletproof solution. Some third party modules have attempted to, but I'm not sure if any of them really solve all the related needs (image management etc.) — and while remote filesystems such as EFS are easier to work with, they also come with certain downsides (such as a higher price point). I seem to recall that Ryan mentioned something about this a while ago too 🙂
  40. 5 points
    This is a great feature for larger sites. Really excited about this. Speaking of scaling: I would imagine a limiting factor right now would be, that the uploaded assets are bound to the local drive. Is that a problem for your setup / would proccesswire.com benefit if it was possible to store files on S3? And is there a post somewhere explaining your setup on AWS?
  41. 5 points
    RF3 got a very nice little update today 😍 Aggregations Often we need to calculate sums or averages of table data quickly and efficiently. RockFinder3 makes that easy as well: $avg = $rockfinder->find("template=cat") ->addColumn('weight') ->getObject("SELECT AVG(weight)"); db($avg); $cats = $rockfinder ->find("template=cat") ->addColumns(['title', 'weight']); $cats->dump(); // dump finder data to tracy $obj = $cats->getObject("SELECT SUM(`weight`) AS `total`, COUNT(`id`) AS `cats`, SUM(`weight`)/COUNT(`id`) AS `avg`"); db($obj); // dump result of aggregation What happens behind the scenes is that RockFinder3 gets the current SQL query of the finder and adds that as FROM (...sql...) AS tmp to the query that you provide for aggregation. This is the resulting SQL query of the example above: SELECT SUM(`weight`) AS `total`, COUNT(`id`) AS `cats`, SUM(`weight`)/COUNT(`id`) AS `avg` FROM ( SELECT `pages`.`id` AS `id`, `_field_title_605cab16f38ce`.`data` AS `title`, `_field_weight_605cab16f3993`.`data` AS `weight` FROM `pages` LEFT JOIN `field_title` AS `_field_title_605cab16f38ce` ON `_field_title_605cab16f38ce`.`pages_id` = `pages`.`id` LEFT JOIN `field_weight` AS `_field_weight_605cab16f3993` ON `_field_weight_605cab16f3993`.`pages_id` = `pages`.`id` WHERE (pages.templates_id=44) AND (pages.status<1024) GROUP BY pages.id ) AS tmp You can even provide a suffix for your query to do things like GROUP BY etc: $rf = $rockfinder->find("template=cat|dog"); $rf->addColumns(['created']); $rf->dump(); db($rf->getObjects( "SELECT COUNT(id) AS `count`, DATE_FORMAT(created, '%Y-%m-%d') AS `date`", "GROUP BY DATE_FORMAT(created, '%Y-%m-%d')" ));
  42. 5 points
    I've reworked this slightly, based on the helpful suggestion of @Kiwi Chris. The individual migration items are now entered in a repeater field and so the sequence can take into account any dependencies. Each item can be either 'new', 'changed' or 'removed'. When a migration is uninstalled, the sequence is automatically reversed and 'new' items are changed to 'removed' and vice versa. Example below shows a migration testing a page reference dependency (it works!). This is the appearance in the source database (pre-export): If you click 'Preview' you see what changes are proposed to export (see below). This feature also operates to review (in the target database) what changes will happen on install or uninstall - or, if the install has failed, what changes remain (either by modifying the migration or applying a manual fix if all else fails). Export is shown as 'no object' in the above, because the migration has not yet been exported. I think this all seems to work as designed, but am grateful for any futher thoughts on the design. I will now work on tidying the code a bit and doing a bit of documentation. There may still be a few bugs. There are some at the moment that I can't pin down but they are cosmetic rather than fundamental - I think I may need some help from those who understand the inner workings of PW better ( @adrian ?). Promises of help will encourage me to get on and release some code 😉 Meanwhile, I am happy to answer any further questions.
  43. 4 points
    It's possible using the native permission system by creating page-edit-lang-[language] permissions, i.e. both page-edit-lang-default and page-edit-lang-de (or however German language is named in this PW instance) and only giving page-edit-lang-de to your translators (your regular editors roles gets both). To prevent translators from changing non-multilanguage fields, you also need to create a page-edit-lang-none permission and assign that to the regular editor roles, but not the translator role. This blog post gives a good introduction to the underlying concept.
  44. 4 points
    Code is now posted at https://github.com/MetaTunes/ProcessDbMigrate Many thanks once more to those who gave me ideas and help in doing my first 'proper' module - in particular @Kiwi Chris, @bernhard and @adrian. Please test and feedback. But be gentle in your criticism of the code 😜. Most importantly - this is a proof of concept only at this stage and it makes changes to your files and databases so please do not use on production sites and do back up everything beforehand - use at your own risk! To install: Place the ProcessDbMigrate folder in your site/modules directory. Make sure your environment meets the requirements - you need @Robin S's FieldtypeRuntimeOnly to be installed first. The earliest PW version I have tested it with is 3.0.148, but it might work on earlier 3.0.xxx versions. Please let me know if it works with earlier versions. Having satisfied the dependencies, install the module. You will see that there is an extensive help.md file - please read it, particularly if you get stuck. @bernhard asked for a screencast - I will do that next - hopefully it will make things clearer.
  45. 4 points
    I'm in the former category - to now, but a couple of projects have got me thinking how the second option might be useful to me as well. I've built a couple of sites (or I'd prefer to call the apps) on top of ProcessWire that have potentially a much wider market than just the clients I've built them for, however I'm just a single developer and having to manage hosting, billing, deployment, won't scale well the way I'm currently working, and although if I take the time I should be able to automate nearly everything, there's also the issue of discoverability, whereas if there were an option where people could people just click to sign up and choose an app of their choice with installation and billing all taken care of I think there could be a use case. At least, I think the modules directory needs to support commercial modules from any developer, and also at install time, I think the choice of site profiles should be able to query the modules website and offer paid or free profiles other than just those bundled with ProcessWire. Obviously ProcessWire should get a percentage of any sales, just like big app stores.
  46. 4 points
    This not really a deciding point, but about awareness that constants (as they are always static) are, just like static properties, subject to early binding. A lot of code (even mine, I have to admit) uses self:: to reference class constants and static properties, which is going to cause headache whenever such a class is extended and a constant or static property is changed. To be inheritance safe, any code that references such a value needs to use static:: instead of self:: (or address the property through the object instance). A short illustration: <?php class A { const one = 'ONE'; const two = 'TWO'; public static $three = 'THREE'; public static $four = 'FOUR'; public $five = 'FIVE'; const six = 'SIX'; function showMe() { echo self::one . PHP_EOL; echo static::two . PHP_EOL; echo self::$three . PHP_EOL; echo static::$four . PHP_EOL; echo $this->five . PHP_EOL; echo $this::six . PHP_EOL; } } class B extends A { const one = 'UNO'; const two = 'DUE'; public static $three = 'TRE'; public static $four = 'QUATTRO'; public $five = 'CINQUE'; const six = 'SEI'; } $b = new B(); $b->showMe(); That in mind, I guess the decision depends on a few questions: Is the code that is using the value executed often? In that case, constants have a small but perhaps noticeable performance advantage. If not, does my IDE of choice favor one over the other between constants and static properties? May the naming be changed at run time for an individual instance of the class? In that case, the actual value bearers have to be instance properties. For the later case, I like to use a mixed approach where my initial values are declared as constants (though a static property would be just as good) since they jump the eye: <?php class TestClass { const CLASS_PREFIX_DEFAULT = 'tpl_'; public function __construct() { $this->class_prefix = static::CLASS_PREFIX_DEFAULT; } //... } I try to avoid static access through $this:: (last line in the showMe() method above) as much as possible for my own sanity, as my head wants to make an immediate connection to instance access once it sees $this, and I'll likely end up with expressions like $this::$staticVariable, which to me looks too much like $class->$memberName, yet behaves differently.
  47. 4 points
    Hello @creativeguy, First, my condolences to you and his family. I applaud you for carrying on with the sites you and he built together. Second, don't worry about the adult theme. I have a number of client sites that are adult only. (Also nothing dark :D) Lastly, since you are versed in PHP, why don't we see if we can get you up and running. This forum is full of knowledgeable people that are more than willing to help get you set up. Let's start at the beginning. When you say that you cannot make a local copy work. Did you "copy" all of the files from document root to your localhost? What error message(s) are you getting when you attempt to access localhost? Do the messages indicate a problem connecting to the database? Do you stay in an endless loop trying to login? Also, an FYI, The config.php will be different on localhost than on the server, and possibly the .htaccess file as well. Give us some more information to go on so we know where to look. Best regards,
  48. 4 points
    Awesome! Great to hear that! Sometimes you just have to stare at the code a bit of time until it clicks, keep on it! Let me know if I can help you with something else.
  49. 4 points
    Just did a quick test and it looks great - thanks for sharing this - I think it will be a great solution for many folks.
  50. 4 points
    Recently needed to add something similar to a site I've been working on, and ended up with a slightly different approach. Can't share the code right now, but here's the general concept: Store all allowed branches in user profile, similar to the screenshot above. Hook into ProcessPageList::execute and add a select option for choosing which branch is "active". Hook into AdminRestrictBranch::getBranchRootParentId and check if a branch GET param was provided and matches one of those stored in user profile; if yes, store the value in session. Return either the first branch from user profile, or the value from session. Technically it's only restricting the user to one branch at a time, so there are some limitations, but in my specific case that's actually preferable: this lets users focus on one section of the site at a time 🙂
This leaderboard is set to UTC/GMT+00:00
  • Create New...