Jump to content

Robin S

  • Content Count

  • Joined

  • Last visited

  • Days Won


Everything posted by Robin S

  1. All good if another way is added to open files in PhpStorm, but I use the editor:// protocol with PhpStorm and never had any problem with it. It's been a long time since I set it up but I expect I followed the PhpStorm docs and I don't remember it being difficult.
  2. Yes, there are core permissions built in for this purpose. https://processwire.com/docs/user-access/permissions/#user-admin-permissions https://processwire.com/blog/posts/new-user-admin-permissions-automatic-version-change-detection-and-more-2.6.10/#new-user-admin-permissions
  3. It all depends on how your original image was created. Was it saved at maximum quality? Was it passed through some sort of optimisation process/service (TinyJPG, CompressOrDie, etc) that compressed the image and reduced the filesize? Your PW resizer settings are asking for maximum quality, minimum compression, and therefore you can expect relatively large filesizes. And the PW resizer is not going to produce filesizes as small a specialised optimisation service, particularly one that you customise the settings of per image.
  4. @Orkun, do you know about $page->listable()? It determines whether a page may appear in Page List (and also PageListSelect inputfields, which is probably something you want too). So you could do: $wire->addHookAfter('Page::listable', function(HookEvent $event) { /* @var Page $page */ $page = $event->object; if(!$page->editable() && !$page->addable()) $event->return = false; }); You can hook ProcessPageListRender::getNumChildren to customise the child count. But in some cases depending on what is determining a page's listable status you might decide it's not worth the trouble. Here's one way you might do it (in /site/ready.php): // Work out which templates are editable or addable for the user and store them on the $user object // Doing this here to avoid doing it repeatedly in the getNumChildren hook if(!$user->isSuperuser() && $page->template == 'admin') { $user_roles_ids = $user->roles->explode('id'); $allowed_templates = []; foreach($templates as $template) { $edit_add_roles = array_merge($template->editRoles, $template->addRoles); if(count(array_intersect($user_roles_ids, $edit_add_roles))) $allowed_templates[] = $template->name; } $user->allowed_templates = $allowed_templates; } // Set a selector to correct the children count $wire->addHookBefore('ProcessPageListRender::getNumChildren', function(HookEvent $event) { $user = $event->wire('user'); if($user->isSuperuser()) return; $selector = 'template=' . implode('|', $user->allowed_templates); $event->arguments(1, $selector); }); Be aware that there are some other areas you probably want to consider if you are hiding pages in Page List. Here are some pointers to get you started: Pages that can be found in the admin search - look at manipulating the selector by hooking ProcessPageSearch::findReady Pages that can be found in Lister - look at manipulating the selector by hooking ProcessPageLister::getSelector
  5. Try hooking after ProcessPageEdit::buildForm. ProcessPageEdit::buildFormContent only contains the page template fields and not the fields from the Children, Settings, etc tabs. Another hurdle is that the value of InputfieldPageAutocomplete is always an array, and ProcessPageEdit expects the value of the parent_id field to be an integer. This seems to be working: $wire->addHookAfter('ProcessPageEdit::buildForm', function(HookEvent $event) { /* @var InputfieldWrapper $form */ $form = $event->return; $orig_pi_field = $form->getChildByName('parent_id'); $attrs = [ 'collapsed', 'required', 'label', 'icon', 'id', 'name', 'value', ]; $new_pi_field = $event->wire('modules')->get('InputfieldPageAutocomplete'); $form->insertAfter($new_pi_field, $orig_pi_field); $form->remove($orig_pi_field); foreach($attrs as $attr) { $new_pi_field->$attr = $orig_pi_field->$attr; } $new_pi_field->maxSelectedItems = 1; }); $wire->addHookAfter('ProcessPageEdit::processInput', function(HookEvent $event) { /* @var InputfieldWrapper $form */ $form = $event->arguments(0); if($form->id !== 'ProcessPageEditSettings') return; $pi_field = $form->getChildByName('parent_id'); if(!$pi_field || !$pi_field->isChanged()) return; $parent_id = $pi_field->value; $parent_id = (int) reset($parent_id); /* @var Page $page */ $page = $event->object->getPage(); $page->parent_id = $parent_id; });
  6. I also have this question. Which is the recommended branch to use? @Soma
  7. The method that sets the filename is here: https://github.com/processwire/processwire/blob/655c4cdd245fa4990d010c06ccfcfd37a04c0fde/wire/core/Pagefiles.php#L554-L599
  8. As an alternative to step 2 and adding the matched images to cloned fields, in step 6 you could copy the matched files to the page's folder in /site/assets/files/. That's probably the easier approach. Edit: here's some API code you could execute in the Tracy console... // Find all image fields $image_fields = $fields->find("type=FieldtypeImage"); // Find all templates that contain those image fields $tpls = new TemplatesArray(); foreach($image_fields as $image_field) { /* @var Field $image_field */ $tpls->add($image_field->getTemplates()); } // The page that contains the unorganised images - update ID to suit $unorganised = $pages(1287); // Find all pages with missing images $pages_with_missing_images = $pages->find("template=$tpls, id!=$unorganised"); // Loop over those pages foreach($pages_with_missing_images as $p) { // Get the directory for the page $page_dir = $p->filesManager()->path; // Loop over the image fields foreach($image_fields as $image_field) { // Continue if the page doesn't contain the image field if(!$p->fields->has($image_field)) continue; // Get images as Pageimages object regardless of formatted value foreach($p->getUnformatted($image_field->name) as $image) { // Look for a matching image among the unorganised images $match = $unorganised->images->get("name={$image->basename}"); if($match) { // Copy to the page directory if a match is found $files->copy($match->filename, $page_dir); } else { // Display (or log) a message about unmatched images d("No match found for image '{$image->basename}' in field '{$image_field->name}' on page '{$p->title}'"); } } } }
  9. First thing would be check with your host to see if they have a backup of the file system. If they can't help you then I suggest... 1. Copy the site to your local machine. Everything else that follows you do locally - don't do anything else on the live site because you only risk losing more data. 2. Create clones of all your image fields (Add New Field > Type > Clone existing field...). Add them to the same templates that your original fields are on. This is so that when you add the new images you keep them separate from the existing field values. 3. Create another new image field that will hold all the unorganised images. Create a new template and add the image field to the template. Create a new page using the template - we'll title this "Unorganised Images". 4. Add all the unorganised images to the new page. Personally I would do this using the API together with glob or DirectoryIterator but you might be able to do it via the admin if you allow a lot of memory and a set a long max execution time. This step will sanitize the image filenames. 5. Create a DB backup now in case you make some errors in the next step and need to recover to this state. 6. Use an API script (using Admin Actions, in the Tracy console, or just in a template file) to loop over all pages that have image fields (apart from your page with the unorganised images), and loop over all the images in those image fields. For each image in any of your original image fields, look for an image of the same name in your Unorganised Images page. If you find a match then add the image to the clone of that image field. You might want to log the page title / image field name / image filename for any images that can't be matched. 7. Check the results. When you've done the best you can then you can delete the original image fields, rename the cloned image fields according to the original field names, delete the page/template/field used in Unorganised Images, and redeploy to the live site.
  10. There is a noTrim setting for InputfieldText and inputfields that extend it. Hooking InputfieldText::render is too late to set this, but you could hook Field::getInputfield if you want leading/trailing whitespace not to be trimmed for all text fields. // Include leading/trailing whitespace in inputfields for all text fields $wire->addHookAfter('Field::getInputfield', function(HookEvent $event) { /* @var Inputfield $inputfield */ $inputfield = $event->return; if(!$inputfield instanceof InputfieldText) return; $inputfield->noTrim = true; }); Before: After:
  11. I like it! Not only is it easier but it makes it less likely that the setting of the $wire variable will be accidentally left behind after debugging is finished.
  12. Hi all, Just wanted to share a tip that can be useful if you want to debug a core file before Tracy has been loaded. Suppose you were debugging ProcessWire::getHttpHost() and for some reason wanted to dump the $host variable just before it is returned. If you add the line... bd($host, 'host'); ...then you'll get a fatal error when the code runs because Tracy has not yet been loaded and so the bd() function is undefined. When debugging core files you'll find a number of places like this where the Tracy dump functions are not available. A workaround for this is to set the variable that you want to dump as a custom property on the Wire object. You want to be careful not to use a property name that clashes with any existing Wire properties or API variables - adding a prefix like "td_" is a safe bet. $this->wire()->set('td_host', $host); Now in /site/ready.php you can dump the variable that you stored on the Wire object: bd($wire->td_host, 'host'); ...or shorter (thanks @adrian)... bd($td_host, 'host');
  13. $page->parents() can take a selector: foreach($page->parents('template!=myTemplate') as $item) { // ...
  14. Your site visitors would have to download two copies of every image if you do this server-side. These days a CSS filter is the way to go: .bw-image:hover { filter:grayscale(100%); } And who cares about IE (or use a polyfill). But if you're sure you want to do this in PW and set a destination filename... $is = new ImageSizer('/path/to/filename.ext'); $is->getEngine()->convertToGreyscale('/path/to/filename_new.ext');
  15. @bernhard's post gives you the clue - it's to do with PW's built-in caching. $page->children() is a method that queries the database. You want to minimise the number of requests to the database because going to the DB is a lot slower than working with objects in memory. In your code you call $page->children() five times, so the expectation is that this would mean five trips to the DB... except that PW tries to help you out and caches the results to avoid unnecessary DB queries. But the exact workings of this caching are undocumented (as far as I know) and so I'd say it's best practice not to rely on it. It would be better to consciously minimise the DB requests in your code - it would make things clearer (i.e. avoid the confusion you're getting from the behind-the-scenes caching) and it's a good habit to get into for when you're working with other PHP projects that might not cache DB query results like PW does. You could rewrite your code like this: $layout->setTemplate('sidebar'); $selector = [ 'template' => 'product', 'sort' => 'title', 'categories' => page('id'), 'limit' => 9 ]; // Just get the children from the DB once // I assume you need all the children at some point later in the code because you dump page()->children() in your example. // If you don't need all the children then just get the categories directly with page()->children('template=category') $children = page()->children(); // Get the categories from the children PageArray (happens in memory) $categories = $children->find('template=category'); // If there are any categories then modify $selector if($categories->count) { $categories->add(page()); $selector['categories'] = $categories; } bd($children); bd($categories); And this might be getting into the zone of micro-optimisation but you could consider if you actually need full Page objects in a PageArray or if you only need the IDs (reduces the memory footprint): $category_ids = page()->children('template=category', ['findIDs' => 1]);; if(count($category_ids)) { $category_ids[] = page()->id; $selector['categories'] = $category_ids; }
  16. Possible reasons... 1. Your /site/ready.php is missing this code: https://github.com/ryancramerdesign/regular/blob/master/site-regular/ready.php 2. The child pages you are looping over do not (all) use a template named "category". The hook code linked to above attaches a numPosts() method only to pages using the template "category".
  17. This doesn't make much sense to me because the guest role should always be the most restricted role in the site. Making a role more restricted than the guest role isn't effective because the user could just log out and then access the protected content.
  18. Not why it wouldn't work for you - it works for me. Tracy Debugger is a useful tool that can help you debug. Now that I see you are deleting the page being saved you don't need to worry about making the fields not required - you can delete the page and redirect all within the before ProcessPageEdit::processInput hook. When you redirect you interrupt anything else that would normally happen such as evaluating the input for empty required fields and saving the page so these things will not happen. $wire->addHookBefore('ProcessPageEdit::processInput', function(HookEvent $event) { /* @var InputfieldWrapper $form */ $form = $event->arguments(0); // Only for the ProcessPageEdit form if($form->name !== 'ProcessPageEdit') return; $page = $event->object->getPage(); // Use $page to check for specific templates here... // Check field values in POST $input = $event->wire('input'); if($input->post->delete && $input->post->delete_confirm === 'DELETE') { $event->wire('pages')->delete($page); // Delete user also if needed $event->message('Your message here'); $event->wire('session')->redirect($event->wire('config')->urls->admin); } });
  19. There's this module which was created to work around that exact issue: https://modules.processwire.com/modules/fieldtype-page-ids/
  20. You have to hook before the input is processed and make those fields not required. Example: $wire->addHookBefore('ProcessPageEdit::processInput', function(HookEvent $event) { /* @var InputfieldWrapper $form */ $form = $event->arguments(0); // Only for the ProcessPageEdit form if($form->name !== 'ProcessPageEdit') return; $page = $event->object->getPage(); // Use $page to check for specific templates here... // Check field values in POST $input = $event->wire('input'); if($input->post->delete && $input->post->delete_confirm === 'DELETE') { // Get normally required fields and make them not required $first_name = $form->getChildByName('first_name'); $first_name->required = false; // Repeat for other required fields... // Do your delete/notification/redirect actions here // Or if you need the page to actually save when deleting then do actions in separate Pages::saved hook or similar } });
  21. @CrazyEnimal, please insert your code inside a code block in forum posts. You can use SQL in ProcessWire when it suits you. That's what the $database API variable is for. Here is one way you could get a listing of manufacturers with the number of occurrences within a selection of cars. // Get the IDs of the cars $car_ids = $pages->findIDs("parent=$parent, template=page_car, dealer=$dealer_id"); // Get table for manufacturer Page Reference field $table = $fields->get('manufacturer')->getTable(); // Get manufacturers that are selected in the car pages $query = $database->query("SELECT data FROM $table WHERE pages_id IN (" . implode(',', $car_ids) . ")"); $manufacturer_ids = $query->fetchAll(\PDO::FETCH_COLUMN); // Count how many times each manufacturer occurs in the results $manufacturer_occurrences = array_count_values($manufacturer_ids); // Sort the results in order of occurrences, highest to lowest arsort($manufacturer_occurrences); // Get the manufacturer pages $manufacturers = $pages->getById(array_keys($manufacturer_occurrences)); // Output a list of manufacturer titles and the number of occurrences foreach($manufacturers as $manufacturer) { echo "<p>{$manufacturer->title} ({$manufacturer_occurrences[$manufacturers->id]})</p>"; }
  22. Assuming the categories are assigned to locations by a Page Reference field named "categories": $locations = $pages->find("template=location"); foreach($locations as $location) { foreach($location->categories as $category) { echo "<marker title='$location->title' category='$category->title'/>"; } }
  23. In PW you cannot have multiple sibling pages with exactly the same name. That's why the pages are automatically renamed - otherwise you would see an error message.
  24. Maybe it's a matter of rethinking this in light of what the real objective is. I'm guessing that when the notify_user checkbox is checked and the page is saved, something happens - some sort of action that notifies a user. You're saying that you want to force the notify_user checkbox to be in a checked state if the categories field is changed, presumably to force the action to happen. So why not just do the action if the categories field has changed? $pages->addHookAfter('saveReady', function(HookEvent $event) { $page = $event->arguments(0); // If the notify_user checkbox is checked or the categories field value has changed... if($page->notify_user || $page->isChanged('categories')) { // Do the notify user action... } });
  • Create New...