Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by dragan

  1. I guess this should do the trick (delete orphaned images) // before the function definition: $imageFields = array(); // hold all image field-names // replace/insert in my above function this: // tell PW that all the data needs to be saved foreach ($copy->fieldgroup as $field) { if ($field->type !== "FieldtypeImage") { if ($copy->hasField($field)) $copy->trackChange($field->name); } else { $imageFields[] = $field->name; // here } } // test it: $pg = $pages->get(23506); // clone a page $newClone = cloneWithoutFiles($pg); $id = $newClone->id; // echo "new page id = $id <br>"; $pg = $pages->get($id); $pg->of(false); foreach($imageFields as $f) { $pg->$f->deleteAll(); $pg->save($f); } Perhaps there's a cleaner way to do all this... it would be nice to have this as an option built into the core function!
  2. Basically, you could clone the clone function (ha!) from the core and alter it slightly: https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/core/Pages.php#L1733 function cloneWithoutFiles(Page $page, Page $parent = null, $recursive = true, $options = array()) { $defaults = array( 'forceID' => 0, 'set' => array(), 'recursionLevel' => 0, // recursion level (internal use only) ); if (is_string($options)) $options = Selectors::keyValueStringToArray($options); $options = array_merge($defaults, $options); if ($parent === null) $parent = $page->parent; if (count($options['set']) && !empty($options['set']['name'])) { $name = $options['set']['name']; } else { $name = wire()->pages->names()->uniquePageName(array( 'name' => $page->name, 'parent' => $parent )); } $of = $page->of(); $page->of(false); // Ensure all data is loaded for the page foreach ($page->fieldgroup as $field) { // changed: if ($field->type !== "FieldtypeImage") { if ($page->hasField($field->name)) $page->get($field->name); } } /** @var User $user */ $user = wire()->wire('user'); // clone in memory $copy = clone $page; $copy->setIsNew(true); $copy->of(false); $copy->setQuietly('_cloning', $page); $copy->setQuietly('id', $options['forceID'] > 1 ? (int)$options['forceID'] : 0); $copy->setQuietly('numChildren', 0); $copy->setQuietly('created', time()); $copy->setQuietly('modified', time()); $copy->name = $name; $copy->parent = $parent; if (!isset($options['quiet']) || $options['quiet']) { $options['quiet'] = true; $copy->setQuietly('created_users_id', $user->id); $copy->setQuietly('modified_users_id', $user->id); } // set any properties indicated in options if (count($options['set'])) { foreach ($options['set'] as $key => $value) { $copy->set($key, $value); // quiet option required for setting modified time or user if ($key === 'modified' || $key === 'modified_users_id') $options['quiet'] = true; } } // tell PW that all the data needs to be saved foreach ($copy->fieldgroup as $field) { // changed: if ($field->type !== "FieldtypeImage") { if ($copy->hasField($field)) $copy->trackChange($field->name); } } wire()->pages->cloneReady($page, $copy); wire()->cloning++; $options['ignoreFamily'] = true; // skip family checks during clone try { wire()->pages->save($copy, $options); } catch (\Exception $e) { wire()->cloning--; $copy->setQuietly('_cloning', null); $page->of($of); throw $e; } wire()->cloning--; // check to make sure the clone has worked so far if (!$copy->id || $copy->id == $page->id) { $copy->setQuietly('_cloning', null); $page->of($of); return wire()->pages->newNullPage(); } // copy $page's files over to new page // changed: // basically, you could delete this block altogether if (PagefilesManager::hasFiles($page)) { // $copy->filesManager->init($copy); // $page->filesManager->copyFiles($copy->filesManager->path()); } // if there are children, then recursively clone them too if ($page->numChildren && $recursive) { $start = 0; $limit = 200; $numChildrenCopied = 0; do { $children = $page->children("include=all, start=$start, limit=$limit"); $numChildren = $children->count(); foreach ($children as $child) { /** @var Page $child */ $childCopy = wire()->pages->clone($child, $copy, true, array( 'recursionLevel' => $options['recursionLevel'] + 1, )); if ($childCopy->id) $numChildrenCopied++; } $start += $limit; wire()->pages->uncacheAll(); } while ($numChildren); $copy->setQuietly('numChildren', $numChildrenCopied); } $copy->parentPrevious = null; $copy->setQuietly('_cloning', null); if ($options['recursionLevel'] === 0) { // update pages_parents table, only when at recursionLevel 0 since parents()->rebuild() already descends if ($copy->numChildren) { $copy->setIsNew(true); wire()->pages->parents()->rebuild($copy); $copy->setIsNew(false); } // update sort if ($copy->parent()->sortfield() == 'sort') { wire()->sortPage($copy, $copy->sort, true); } } $copy->of($of); $page->of($of); $page->meta()->copyTo($copy->id); $copy->resetTrackChanges(); wire()->pages->cloned($page, $copy); wire()->pages->debugLog('clone', "page=$page, parent=$parent", $copy); return $copy; } // basic usage just as with original function: $pg = $pages->get(23506); cloneWithoutFiles($pg); Besides the three places marked with // changed, I also had to replace $this-> with wire()-> to use it in a template file. Images (files) are not copied to the new page, however, the fields are. It wouldn't be too hard to delete these after you've created the clone (after initial clone-save).
  3. @Carsten The echo '<pre>' part comes too early. Change the first few lines to: <?php namespace Processwire; ini_set('max_execution_time', 60 * 5); // 5 minutes, increase as needed include("./index.php"); echo '<pre>'; // rest below unchanged: // Add a leading back slash to the class so it can access the global namespace $dir = new \DirectoryIterator(wire('config')->paths->files); Tried the script like this and it works just fine (PW 3.0.205)
  4. That's weird. Laragon keeps working fine for me, with several PHP versions, one of them being 8.1.8. There are far better tools than phpMyAdmin. Try HeidiSQL.
  5. @ryanThis looks awesome! At first glance, the only thing missing for me is VAT. Do you plan to add that later? If not, how easy would it be to add this?
  6. PHP? Really? All you basically need is some admin CSS. #PageEditTabs { position: fixed; top: 261px; left: 0; flex-direction: column; } ^ this surely doesn't look fancy, but it could serve as a starting point...
  7. You're welcome ? Your current implementation of your DL has display: none on the entire definition list (inline styles). I understand this is work in progress, but display: none will be completely ignored by screen readers. I would put the class .sr-only not just on the h2, but on the entire section instead. I just now, on second sight, realized that unter "map options" you have a lot more stuff going on, in an accordion, with all kinds of filters. Previously I just looked at the first accordion item "library floor". As you have correctly mentioned, complex visuals that convey information, need special attention re: a11y. There are no quick shortcuts or golden recipes that help you out. It's all about context. You have put a tremendous amount of work into exploring the building, making sure the user can filter / find everything. This whole "map options" thing is sure a great help for "regular" users who are not visually impaired. Kudos to that. Now, if you look at the big picture, you will realize that all these fine-grained options don't do anything for the non-visual users. They will most likely be confused. If you wanted to build the entire filter/options functionality with a11y in mind, you would need to invest a lot of work. You'd have to use aria-live to inform the user that something has changed in the map after the user made a new selection. Currently, if the user selects the "Glasby Room" radio button, the visually impaired persons don't get the information that regular users get (by toggling/removing the inline display:none). You would have to map these with either an aria-label right at the input level, or with aria-describedby or some such. (I strongly encourage you to test it out yourself with a screen reader - don't rely on automated tests like Lighthouse - they all have their limits, especially when it comes to context) I would personally visually hide all filter stuff under "map options", but provide a table in each accordion item. Tables are well-supported by assistive technologies, while DLs are only so-so. If budget is tight (and it always is tight for accessibility-related work) I'd opt for one big .sr-only table, and use aria-hidden on both the SVG and the filters. Just my 0.02.
  8. @BrendonKozThanks for the interesting write-up. Generally speaking, with maps or chart graphics, there are two main ways to ensure accessibility: One is described neatly in this article: https://css-tricks.com/accessible-svgs/#aa-interactive-images e.g. the use of role=group vs. role=img, using role=list role=listitem, tabindex etc. In your map, you could turn your three floor groups into three lists. The other strategy is to output an unstyled + visually hidden table or ul/ol somewhere that represents the floors and rooms/areas. You would then simply put aria-hidden=true on the entire SVG. This strategy is certainly the most efficient. However, if you want to enable focusing areas with the keyboard (for people who can't use the mouse), you'd have to alter your SVG further, like the above example.
  9. Thanks for sharing! We have taken over a trainwreck of a Craft site from another agency recently, and amongst a million other nuisances, the image resizer plugin they've chosen is end-of-life. Plus, the authors are fond of uploading huge images. I just might steal that line of code to bring the overall size down considerably...
  10. The future looks bright! I'm very excited about the preview edition of GitHub Copilot. Finally someone putting AI to good use. Some of these suggestions are downright scary, they're so great. Write some code, a comment, then hit TAB. Boom. This is like Intellisense, but on steroids, and then some. Anyone else tried it? Care to share your experiences?
  11. Perhaps you can use RockFinder and try a "group by" query.
  12. Try browser extensions? e.g. https://chrome.google.com/webstore/detail/office-editing-for-docs-s/gbkeegbaiigmenfmjfclcdgdpimamgkj
  13. As far as I understand, Headless CMS and SSG aren't the same thing really. So which one do you really want? If you'd like to use an SPA framework like React, you'd probably want headless. If you'd like to automagically create static pages, it's a typical SSG setup. For headless, I'd try the AppApi module. For SSG, I'd use Pro Cache. I'm not sure what to make of your example comparison with WP and taxonomies. In PW, you create your own taxonomy or data model. It's easy to create JSON output from your PW instance. Use URL segments, and handle each /json request to your own script. It's ridiculously easy to create JSON feeds (just a bunch of URLs that match your selector). Is that all you really need? Sorry if I misunderstand you, but there's way more to a headless CMS than just having a URL feed. Again, I don't get what you really want in the end. If you need static sites, what good are "JSON feeds" for you?
  14. Did you check server logs? I would try to delete everything inside site/assets/cache and try again. Put your site briefly into debug mode and see if you can spot anything, e.g. with Tracy Debugger.
  15. My IDE does ? PHPStorm has a tab that checks code comments starting with @todo - VSCode has probably something similar as a plugin I guess.
  16. What is an "external template"? You mean a regular frontend template? I'm not quite sure what you're asking: Reverse all child pages only? Or keep the page order as in the admin, but reverse the images per page only? If the latter, you could do something like: $imgs = $page->images; if ($imgs) { $gmi = array(); foreach ($imgs as $img) { array_push($gmi, array($img->url, $img->description, $img->title)); } $images = array_reverse($gmi); foreach ($images as $img) { $content .= "<h2>{$img[2]}</h2><img src='{$img[0]}' alt='{$img[1]}'>"; } } Basically just array_reverse()
  17. You could use CSS: https://processwire.com/talk/topic/20837-inputfield-repeater-matrix-item-duplicator/?do=findComment&comment=180498
  18. https://octo.github.com/projects/repo-visualization Interesting... This is PW's repo visualized: https://octo.github.com/projects/repo-visualization#explore-for-yourself (enter a repo and you'll get an interactive SVG)
  19. There's already a password reset module built into PW. 2FA can be disabled for any individual account as needed. I have a PW installation, where I activated 2FA for myself as superuser. Recently, my phone for some reason deleted several apps - one of them being Google Authenticator. I know how to reset a user password, but that won't help me in that particular situation. @ryan Is there an API method to deactivate 2FA for a certain user? In the docs, I only see $user->hasTfa().
  20. https://pdfmyurl.com/ claims to support HTML5 + CSS3, but I haven't tested it so far.
  21. Well, if you have the budget, you could use one of the commercial services out there which have an API. This article suggests using Chrome in headless mode, but I have no idea how difficult it is to install, and what the server requirements are: https://peterdev.pl/2019/01/11/picking-a-php-tool-to-generate-pdfs/
  22. Not sure if that's what you're after: In the JSON of your post (let's say it has ID 19199) -> https://yoursite.com/wp-json/wp/v2/posts/19199 you should see featured_media: 19360 this gives you the image details: -> https://yoursite.com/wp-json/wp/v2/media/19360
  23. @picarica Could you please use a more civilized language in here? I'm no PW forum admin, but I'm pretty sure the F*** word is not welcome here. If you have a problem with that particular module, you should post your question in the respective support forum instead:
  24. @modifiedcontent you can do $users->find("roles=superuser"); $users->find("roles=superuser")->first; yes, PW doesn't limit the number of superusers to just one single user.
  25. I don't think so... the trick with PW hooks is to find out which hook to use, and whether to use before or after. Page::loaded seems to be the wrong method to hook into for what you want to achieve. Here are some code-bits I used in the past, and they all work as expected (inside site/ready.php) $this->addHookAfter('Pages::saveReady', function (HookEvent $event) { $page = $event->arguments[0]; // ---------- $pages->addHookAfter("ProcessPageEdit::buildForm", function (HookEvent $event) { $page = $event->object->getPage(); $form = $event->return; // ---------- $this->addHookBefore('Pages::saved(template=invoice, parent=23444)', function(HookEvent $event) { $page = $event->arguments(0);
  • Create New...