Jump to content

thetuningspoon

Members
  • Posts

    691
  • Joined

  • Last visited

  • Days Won

    6

Everything posted by thetuningspoon

  1. I actually find that ProcessWire plays pretty well with Git, certainly in comparison to WordPress. The main thing is to avoid installing modules via the admin UI (just download the module and put it in your repo). And of course exclude /assets/ from the repo (PW conveniently keeps all of this together). It would be nice to be able to exclude the wire folder and have Composer handle that instead, but I don't find it that big of a deal to keep the wire folder in Git. In the very rare case that a direct core mod is necessary for a project I'm working on, it's good to be able to make that change and commit it to the repo. The ability to maintain a master configuration file containing all of the meta data for the templates and fields in a project and to be able to put this into version control would be a real game changer, though. This is a real pain point we're running into on a lot of our projects, especially now that we have a couple systems that have multiple deployments and multiple developers working on them. Since we already have JSON export for templates and most field types (the options field export/import is still not fully functional), it doesn't seem like this is too far off. Maybe PW could cache a copy of the JSON configuration file and then if it gets updated externally, show a message in the admin that there are pending field/template changes to be applied, allowing the user to apply these changes with a single click. If it makes it easier/cleaner, there could be 2 config files, one for fields and one for templates (and maybe another for modules?) I'm curious how other systems handle this under the hood. Edit: Here's how Craft CMS does it: https://craftcms.com/docs/3.x/project-config.html#propagating-changes This sounds a lot like what I'm thinking. The command line option for applying changes is also a great idea since some changes could take a while to apply on large projects.
  2. You don't have to. For example, you could put the username and passwords into the config-env.php instead, and still have the rest of the advantages of sharing the config file. But if you manage your own git server securely then it shouldn't be a problem. I'm interested in hearing your thoughts if you think otherwise, though. In your example, are you not including the config file in git? I'm confused as to why you would be including both the dev and prod settings in a single config file if you aren't.
  3. What you've done with the first example looks super powerful. Nice work. The second approach is what we use. It's always a battle between giving the client more control vs. making it easier for them to manage (and making sure they don't mess up the site's aesthetic!). So we tend to give them more clearly defined components and then add options to them as they need them, or build out new components if they want something totally new. With the example of the text w/ image block, you could create a single block but add a radio button for which side the image goes on.
  4. Just wanted to share the new approach we've started using for our configs. Instead of multiple config files, we are using a single shared /site/config.php plus a custom /site/config-env.php file that is unique for every environment the site is deployed (or developed) in. The config-env.php file simply contains one $env variable with a string that defines which environment we're working in (e.g. "production" "staging" "dev") and we include this file within the usual /site/config.php file. The config-env.php file is excluded from git (gitignore) and from our deployment scripts, so it won't be overwritten or touched after it's set up. Using a file to determine the environment rather than the domain or server variables means that it will work regardless of where or how it's invoked (cli vs browser). Here's the basic format: Example /site/config-env.php <?php $env = 'production'; // A string identifying the current environment Example /site/config.php <?php if(!defined("PROCESSWIRE")) die(); setlocale(LC_ALL,'en_US.UTF-8'); ini_set('session.gc_probability', 1); ini_set("log_errors_max_len",0); /*** SITE CONFIG *************************************************************************/ /** * Set up all your defaults and settings that are shared by all environements below */ $config->debug = false; $config->adminEmail = 'example@example.com'; $config->advanced = true; $config->templateCompile = false; $config->uploadTmpDir = dirname(__FILE__) . '/assets/uploads/'; $config->chmodDir = '0775'; $config->chmodFile = '0664'; $config->timezone = 'America/New_York'; $config->defaultAdminTheme = 'AdminThemeUikit'; $config->installed = 1527694349; /** * Environment-specific configs */ if(!file_exists(__DIR__ . '/config-env.php')) { echo 'No /site/config-env.php file could be located. Please create a /site/config-env.php file with a single $env variable containing a string identifying the current environment. This file should NOT be committed to Git (add it to the /.gitignore file) and must be excluded from deployment.'; exit; } require(__DIR__ . '/config-env.php'); if(!isset($env)) { echo 'No $env variable found in /site/config-env.php'; exit; } switch($env) { /** * Production */ case 'production': $config->dbHost = '...'; $config->dbName = '...'; $config->dbUser = '...'; $config->dbPass = '...'; $config->dbPort = '3306'; $config->dbEngine = 'InnoDB'; $config->httpHosts = array('...'); break; /** * Staging */ case 'staging': $config->dbHost = '...'; $config->dbName = '...'; $config->dbUser = '...'; $config->dbPass = '...'; $config->dbPort = '3306'; $config->dbEngine = 'InnoDB'; $config->httpHosts = array('...'); break; /** * Developer One's Dev environment */ case 'dev1': $config->debug = true; $config->dbHost = '...'; $config->dbName = '...'; $config->dbUser = '...'; $config->dbPass = '...'; $config->dbPort = '3306'; $config->dbEngine = 'InnoDB'; $config->httpHosts = array('...'); break; /** * Developer Two's Dev environment */ case 'dev2': $config->debug = true; $config->dbHost = '...'; $config->dbName = '...'; $config->dbUser = '...'; $config->dbPass = '...'; $config->dbPort = '3306'; $config->dbEngine = 'InnoDB'; $config->httpHosts = array('...'); break; default: echo "No configuration found in /site/config.php for the environment string \"$env\""; exit; } If you wanted, you could make the $env variable $config->env instead, which would allow you to access it from anywhere else on the site using wire('config')->env
  5. Watch out with this. If you're running a cli script or cron job, the SERVER_NAME will not be set and you may end up running it on your live database instead of your development!
  6. This has been an intermittent issue for me with PW as long as I can remember.
  7. @Noel Boss For some reason this doesn't work with your change from using add() to using $page->set() (PW 3.0.178). I got it to work and figured out a way to clear the orphans message: $field->setOrphans(new PageArray()); $page->{$field->name}->add($page->children('include=all, check_access=0')); I also added include=all and check_access=0 to make sure all children get added. Here's the resulting full hook method: /** * Fill pagetable fields with children before editing…. * * @param HookEvent $event */ public function addChildrenToPageTableFieldsHook(HookEvent $event) { $field = $event->object; // on ajax, the first hook has no fieldname if (!$field->name) { return; } // get the edited backend page $editID = $this->wire('input')->get->int('id'); if (!$editID && $this->wire('process') instanceof WirePageEditor) { $editID = $this->wire('process')->getPage()->id; } $page = wire('pages')->get($editID); // disable output formating – without this, the ajax request will not populate the field $page->of(false); // you could also insert a check to only do this with sepcific field names… $field->setOrphans(new PageArray()); // Clear out the "orphans" notice $page->{$field->name}->add($page->children('include=all, check_access=0')); }
  8. We've been playing around with findRaw but it looks like it doesn't support getting fields from the parent page like it does for page fields. Any chance we can get that feature added? ? ?
  9. This sounds fantastic. A couple of our clients have been wishing for something like this and have tried using the clone/copy feature to try to achieve it, but then have issues such as the CkEditor fields pointing to the wrong page's files. Would this feature resolve that issue?
  10. Seems like it should be mentioned somewhere that the pageFileSecure settings only works with files added after it is enabled.
  11. Glad you're finding it useful! You are correct--I don't believe I ever added support for the read-only state of the various inputfields. Feel free to submit a pull request if you do work on this.
  12. Posted by: <?php if($page->insight_author): ?> <a href='<?= $page->insight_author->url ?>'><?= $page->insight_author->title ?></a> <?php endif ?> , <?php foreach ($page->insight_author->roles as $role): ?> <?= $role->staff_role ?> <?php endforeach ?> I would suggest something like the above, though there are certainly other templating styles. Like @monollonom said, I'm not exactly sure what role represents or how that is structured.
  13. @ryan I'm trying out the new join feature within a regular page selector. I believe you mentioned that it is possible to join subfields of page fields. I'm wondering if you can explain that in a bit more detail. Does this still all happen within a single SQL query? If I want to join the id and the title of a page in a page reference field, which of the following is the proper way to do this? $pages->find("template=foo, join=pageField|pageField.title"); $pages->find("template=foo, join=pageField.title"); // Can I join pageField.title without also joining the pageField? If so, would I still have access to the page id? $pages->find("template=foo, join=pageField.id|pageField.title") // Is joining pageField.id the same thing as joining pageField? I could answer these questions for myself if I knew of a good way of checking which data has already been loaded in the resulting page. What is the best way to double check what field data is already loaded on the page objects after performing a find (in order to make sure the join worked properly)? Edit: I just realized I wasn't thinking clearly when I wrote this. Being able to join subfields of page reference fields would require loading those pages as their own separate page objects, which would always include the page ID, name, modified, and created dates. I am still wondering whether doing either join=pageField or join=pageField.title would actually cause those pages to be preloaded as part of the find, or whether it would only lazy load them when first requested, per the normal PW behavior.
  14. This is great. Is it possible to hook after trash/delete in the page editor in order to redirect somewhere other than the page list now? This was something I had need to do in a recent project and had to do something pretty hacky to achieve the effect.
  15. No changes here. There's only so much that can be joined in one query per field, so autojoin is not always possible, especially on multi-value (FieldtypeMulti) fields with lots of values. It should be possible on most Page reference and options fields though, so long as they don't have huge amounts of selections. If there's need for it though, I may be able to have fields (where applicable) store a cache of data (like FieldtypeCombo does) that can be autojoined. I recall that in the past auto joining multi-value page fields would fail quietly and simply result in bad data being returned (like a single page instead of all pages). Can this be changed so that it throws an exception (or at least skips trying to auto join it) if you try to auto join a field that can't be auto joined? That would help avoid nasty issues for programmers who aren't familiar with the internal workings here.
  16. @Robin S That looks nice and clean. Would this actually make it appear non-editable, or just prevent the values from being saved?
  17. Here is a cleaner and more thoroughly tested version, which eliminates some of the code duplication. The Field::viewable hook is no longer needed. I'm not sure why the Field::editable is still necessary, but it doesn't work without it. /site/ready.php /** * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable. * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier. * * Individual field access settings, if enabled, will override this feature. */ $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) { $page = $event->arguments(0); if($page instanceof User) return; // Exclude any field types we don't want to make uneditable by default $excludedFieldtypes = [ 'FieldtypeFieldsetClose', 'FieldtypeFieldsetOpen', 'FieldtypeFieldsetTabOpen', 'FieldtypeFieldsetPage', 'FieldtypeFieldsetGroup', ]; $field = $event->object; $context = $event->arguments(1); $user = $event->wire('user'); if($context || in_array($field->type, $excludedFieldtypes)) return; // If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) { $field->setRoles('view', [$event->wire('config')->guestUserRolePageID]); // Set all roles to have view permission by passing in the guest role $field->setRoles('edit', []); $field->addFlag(Field::flagAccessAPI); $field->addFlag(Field::flagAccessEditor); $field->set('useRoles', true); $field->set('disabledByHook', true); } }); /** * Only the above Field::getInputfield hook should be required since it sets $field->setRoles('edit', []); but for some reason the main content tab will still appear editable unless we add this hook */ $this->wire()->addHookAfter('Field::editable', function() { $return = $event->return; $field = $event->object; // If the current was field disabled by our other hook then reflect that here as well if($field->disabledByHook) { $return = false; } $event->return = $return; });
  18. I figured out a way to do this ( @bernhard's original request, solution #3), but it wasn't easy. I added a page-edit-edit-fields permission which can be added or revoked per-template, and without which none of the fields are editable in the page editor. It required 3 hooks to make the permission work. /site/ready.php /** * Implements a custom page-edit-edit-fields permission. By default, all fields will be view only when the page-edit permission is granted. Adding the page-edit-edit-fields permission makes all fields on the page editable. * This could be accomplished by implementing access settings on every field of the template, but this makes it much easier. * * Individual field access settings, if enabled, will override this feature. * * Both this hook and the one below are required to make this work. */ $this->wire()->addHookAfter('Field::editable', 'fieldEditableHook'); function fieldEditableHook(HookEvent $event) { $return = $event->return; $page = $event->arguments(0); $user = $event->arguments(1); $field = $event->object; if($return === true && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already not editable or we're not checking page context if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user // If the current user does not have page-edit-edit-fields permission, disable edit if(!$user->hasPermission('page-edit-edit-fields', $page)) { $return = false; } } $event->return = $return; } $this->wire()->addHookAfter('Field::viewable', 'fieldViewableHook'); function fieldViewableHook(HookEvent $event) { $return = $event->return; $page = $event->arguments(0); $user = $event->arguments(1); $field = $event->object; if($return === false && $page !== null) { // No need to check our page-edit-edit-fields permission if the field is already viewable if(!$user) $user = $event->wire('user'); // If no user passed in, use the current user // If the current user does not have page-edit-edit-fields permission, enable viewable permission if(!$user->hasPermission('page-edit-edit-fields', $page)) { $return = true; } } $event->return = $return; } $this->wire()->addHookBefore('Field::getInputfield', function(HookEvent $event) { // Exclude any field types we don't want to make uneditable by default $excludedFieldtypes = [ 'FieldtypeLeafletMapMarker', ]; $field = $event->object; $page = $event->arguments(0); $user = $event->wire('user'); if(in_array($field->type, $excludedFieldtypes)) return; // If the current user does not have page-edit-edit-fields permission, disable edit regardless of the field's roles if(!$field->useRoles && !$user->hasPermission('page-edit-edit-fields', $page)) { $field->set('useRoles', true); //$field->addFlag(Field::flagAccess); // May not be necessary //$field->addFlag(Field::flagAccessAPI); // May not be necessary $field->addFlag(Field::flagAccessEditor); } });
  19. I'm curious what the rationale is for the default behavior of a page save uncaching all pages in the system, as this sometimes causes problems when editing and saving more than one page during the same request. I'm finding myself having to add ['uncacheAll' => false] to most of my page saves. But if there is some important reason for clearing the cache, I want to be aware of it!
  20. Hi @antpre. Making the module permissions-aware may be possible, but it would be a bit tricky since it would involve hooking the markup for each of the inputfield option types and injecting a data attribute to signify whether the page is editable/addable. If I get some free time I will see if this is possible. This sounds like it would be an issue with any modal window in the back end. Unfortunately, iframes are basically unusable on iOS. On all of my sites that use iframe modals, I use browser detection to disable them on iOS.
  21. @horst Thank you! The image access control was indeed the problem. At some point the client made a copy of the page and trashed the original, and the clone was still referencing the images on the original page. Is this how it's supposed to work? It seems like if the page clone clones all of the images on the page, it should also update the image references inside of its textareas ?
  22. This is a weird one. I have a page with a repeater matrix field on it. Several of the items have CKEditor fields with images in them. When I view the page while logged in, the images are visible. When I log out, all but one of them is stripped from the output. I double checked that the IDs of the repeater items remain the same--they do. The image tags are completely absent when I view source. I used to have ProDrafts installed, but have now remove it. I have ProCache installed, but temporarily disabled it to test this. I'm at a loss. Any ideas?
  23. Hi @bernhard, We've been running up against the limitations of ProcessWire's native method of handling subfields in selectors. Basically, every subfield in a selector results in a separate query that runs to return all IDs that match the subfield, and then those IDs are used in the where of the final sql query (as opposed to joining the tables and then doing a where on the data all in a single query). This fails at scale because the individual subfield queries start returning so many page Ids that memory is exhausted or MySQL even crashes. There are ways to work around this in some cases by using separate selectors instead of subfields, but it's not terribly elegant. My question is, can we use RockFinder to solve this problem? In this particular use case, we would still want to end up with Pages in the end. I am guessing that using subfields in a RockFinder selector would behave the same way as it does in native PW (I assume RockFinder just gets the final query that PW creates for it--which may have taken multiple queries to construct--and proceeds from there). But if the join function would allow us to limit results based on the values of subfields all in a single query, then that would be golden. At that point all we would want back from RockFinder would be an array of page ids, which we could pass to Pages::getById() to get the pages back. Perhaps a method call for doing this is worth putting into RockFinder? This would be much more efficient than using the callback since it would be one SQL query to get all the pages. Another thing which I mentioned back with RockFinder1 (and implemented locally) is the option of getting a WireArray of WireData objects instead of standard objects or a plain array. This is still way faster than Pages but allows you to use runtime selectors on the results. Anyway, thanks for your work on this and looking forward to your thoughts.
×
×
  • Create New...