Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 09/19/2017 in all areas

  1. There's no change in referencing. A PHP variable referencing a page is always an in-memory object. When you call $page->save() for the first time, PW executes an INSERT on the pages table and populates that page object's id with the last inserted id. last_insert_id is scoped to the current database connection, so other PHP threads / browser sessions inserting pages can never influence each other. There's no need to worry.
    3 points
  2. Do you want something like this? Moving a page before Admin > Pages? Then try using $pages->insertBefore(), no need to change page status at all. $anchor = $pages(3); // `Pages` page $sortee = $pages(1354); // page to move before `Pages` $pages->insertBefore($sortee, $anchor); <?php /** * Sort one page before another (for pages using manual sort) * * Note that if given $sibling parent is different from `$page` parent, then the `$pages->save()` * method will also be called to perform that movement. * * @param Page $page Page to move/sort * @param Page $sibling Sibling that page will be moved/sorted before * @param bool $after Specify true to make $page move after $sibling instead of before (default=false) * @throws WireException When conditions don't allow page insertions * */ public function insertBefore(Page $page, Page $sibling, $after = false) { /* ... */ } EDIT: Moving before Tree works too $anchor = $pages(8); // `Tree` page $sortee = $pages(1354); // page to move before `Tree` $pages->insertBefore($sortee, $anchor);
    2 points
  3. This is actually so useful that I need to add to AOS (if you don't mind) Removing toolbar items is easy with your code but AOS may add extra buttons via CKEditor plugins. I found out that listing them in "removePlugins" is enough, so this can be used to remove extra plugins with ease: $this->addHookBefore('Field(inputfieldClass=InputfieldCKEditor)::getInputfield', function(HookEvent $event) { // do not show modified data on Field edit page if ($this->wire('process') != 'ProcessPageEdit') { return; } $field = $event->object; if($this->wire('user')->hasRole('editor')) { $field->toolbar = 'Bold, Italic'; $field->removePlugins = 'div, justify'; // plugins to remove $field->formatTags = 'p;h2;h3;h4'; // allowed format tags, separated with semicolon } });
    2 points
  4. Repeater items are actually pages, and stored under /admin/repeaters/. If a template includes a repeater field, and you create a page using that template, a new repeater parent page is created under /admin/repeaters to house new repeater items for that specific page and a specific field. Changing the page's name or parent later on doesn't have an effect, because repeater parents use page and field ids for referencing which repeater items belong to which page and which field. <?php // FieldtypeRepeater.module /** * Return the repeater parent used by $field, i.e. /processwire/repeaters/for-field-name/ * * Auto generate a repeater parent page named 'for-field-[id]', if it doesn't already exist * * @param Field $field * @return Page * @throws WireException * */ protected function getRepeaterParent(Field $field) { $parentID = (int) $field->get('parent_id'); if($parentID) { $parent = $this->wire('pages')->get($parentID); if($parent->id) return $parent; } $repeatersRootPage = $this->wire('pages')->get((int) $this->repeatersRootPageID); $parentName = self::fieldPageNamePrefix . $field->id; // we call this just to ensure it exists, so template is created if it doesn't exist yet if(!$field->get('template_id')) $this->getRepeaterTemplate($field); $parent = $repeatersRootPage->child("name=$parentName, include=all"); if(!$parent->id) { $parent = $this->wire('pages')->newPage(array('template' => $repeatersRootPage->template)); $parent->parent = $repeatersRootPage; $parent->name = $parentName; $parent->title = $field->name; $parent->addStatus(Page::statusSystem); $parent->save(); $this->message("Created '$field' parent: $parent->path", Notice::debug); } if($parent->id) { if(!$field->get('parent_id')) { // parent_id setting not yet in field $field->set('parent_id', $parent->id); $field->save(); } } else { throw new WireException("Unable to create parent {$repeatersRootPage->path}$parentName"); } return $parent; } If you change the template and the new template does not have the same repeater field, then the repeater parent and all its items are deleted /** * Delete the given Field from the given Page * * @param Page $page * @param Field $field Field object * @return bool True on success, false on DB delete failure. * */ public function ___deletePageField(Page $page, Field $field) { $result = parent::___deletePageField($page, $field); $this->deletePageField = $field->get('parent_id'); $fieldParent = $this->wire('pages')->get((int) $field->get('parent_id')); // confirm that this field parent page is still part of the pages we manage if($fieldParent->parent_id == $this->repeatersRootPageID) { // locate the repeater page parent $parent = $fieldParent->child('name=' . self::repeaterPageNamePrefix . $page->id . ', include=all'); if($parent->id) { // remove system status from repeater page parent $parent->addStatus(Page::statusSystemOverride); $parent->removeStatus(Page::statusSystem); $this->message("Deleted {$parent->path}", Notice::debug); // delete the repeater page parent and all the repeater pages in it $this->wire('pages')->delete($parent, true); } } return $result; } As for your second question, I wouldn't worry as long as everything works fine
    2 points
  5. When you save a new page with $p->save(), page is saved to the database and last insert id is assigned to the page. <?php // /wire/core/PagesEditor.php protected function savePageQuery(Page $page, array $options) { // ... // save the page to the database // ... if($result && ($isNew || !$page->id)) $page->id = $database->lastInsertId(); if($options['forceID']) $page->id = (int) $options['forceID']; return $result; } Since fields and pages are uncoupled (referencing takes place by page ids), once the page is saved, its ID becomes available and its fields are ready to save as well. <?php // /wire/core/PagesEditor.php /** * Save individual Page fields and supporting actions * * triggers hooks: saved, added, moved, renamed, templateChanged * * @param Page $page * @param bool $isNew * @param array $options * @return bool * */ protected function savePageFinish(Page $page, $isNew, array $options) { // ... // save each individual Fieldtype data in the fields_* tables foreach($page->fieldgroup as $field) { $name = $field->name; if($options['noFields'] || isset($corruptedFields[$name]) || !$field->type || !$page->hasField($field)) { unset($changes[$name]); unset($changesValues[$name]); } else { try { $field->type->savePageField($page, $field); } catch(\Exception $e) { $error = sprintf($this->_('Error saving field "%s"'), $name) . ' - ' . $e->getMessage(); $this->trackException($e, true, $error); } } } // ... } There are a lot of missing parts, but feel free to read PagesEditor class, the source is quite readable. Also, you don't have to explicitly call $page->referenceField->save(), saving page already takes care of that. <?php namespace ProcessWire; $p = new Page(); $p->template = 'page_template'; $p->parent = $parentPageOrItsId; // parent id is enough $p->addStatus(Page::statusUnpublished); $p->field = $someValue; $p->reference_field = $somePage; // $p->reference_field->save(); // not necessary $p->save();
    2 points
  6. It does run find() on every loop, but PW caches pages as it fetches them from the DB, so subsequent calls to the same page will be made in memory without touching the db again. From /wire/core/PagesLoaderCache.php /** * Cache the given page. * @param Page $page * @return void */ public function cache(Page $page) { if($page->id) $this->pageIdCache[$page->id] = $page; }
    2 points
  7. NOTE: This thread originally started in the Pub section of the forum. Since we moved it into the Plugin/Modules section I edited this post to meet the guidelines but also left the original content so that the replies can make sense. ProcessGraphQL ProcessGraphQL seamlessly integrates to your ProcessWire web app and allows you to serve the GraphQL api of your existing content. You don't need to apply changes to your content or it's structure. Just choose what you want to serve via GraphQL and your API is ready. Warning: The module supports PHP version >= 5.5 and ProcessWire version >= 3. Links: Zip Download Github Repo ScreenCast PW modules Page Please refer to the Readme to learn more about how to use the module. Original post starts here... Hi Everyone! I became very interested in this GraphQL thing lately and decided to learn a bit about it. And what is the better way of learning a new thing than making a ProcessWire module out of it! For those who are wondering what GraphQL is, in short, it is an alternative to REST. I couldn't find the thread but I remember that Ryan was not very happy with the REST and did not see much value in it. He offered his own AJAX API instead, but it doesn't seem to be supported much by him, and was never published to official modules directory. While ProcessWire's API is already amazing and allows you to quickly serve your content in any format with less than ten lines of code, I think it might be convenient to install a module and have JSON access to all of your content instantly. Especially this could be useful for developers that use ProcessWire as a framework instead of CMS. GraphQL is much more flexible than REST. In fact you can build queries in GraphQL with the same patterns you do with ProcessWire API. Ok, Ok. Enough talk. Here is what the module does after just installing it into skyscrapers profile. It supports filtering via ProcessWire selectors and complex fields like FieldtypeImage or FieldtypePage. See more demo here The module is ready to be used, but there are lots of things could be added to it. Like supporting any type of fields via third party modules, authentication, permissions on field level, optimization and so on. I would love to continue to develop it further if I would only know that there is an interest in it. It would be great to hear some feedback from you. I did not open a thread in modules section of the forum because I wanted to be sure there is interest in it first. You can install and learn about it more from it's repository. It should work with PHP >=5.5 and ProcessWire 3.x.x. The support for 2.x.x version is not planned yet. Please open an issue if you find bugs or you want some features added in issue tracker. Or you can share your experience with the module here in this thread.
    1 point
  8. Hello, I found the following bug: If you have two or more different text fields on a page, which use the same CKEditor-custom-js-style-set and one of these text-fields is located in a repeater-field, the style-set doesn’t work anymore. Has anybody discovered this problem before?
    1 point
  9. This week: A hurricane, no electricity, a new version of ProcessWire on the dev branch, and a new version of ProDrafts that adds repeater support and workflow support. https://processwire.com/blog/posts/processwire-3.0.75-and-a-new-version-of-prodrafts/
    1 point
  10. Just something I was trying out recently that might be useful to someone... With the following hook added to /site/ready.php you can adjust the CKEditor toolbar that a particular role gets for a particular field. Use the name of your CKEditor field in the hook condition. $this->addHookBefore('Field(name=my_ckeditor_field)::getInputfield', function(HookEvent $event) { $field = $event->object; // Define toolbar for a particular role if($this->user->hasRole('editor')) $field->toolbar = 'Format, Bold, Italic, -, NumberedList, BulletedList, Outdent, Indent'; }); Or what I find useful on some sites is adding extra toolbar buttons for superuser only that you don't trust editors to use. $this->addHookBefore('Field(name=my_ckeditor_field)::getInputfield', function(HookEvent $event) { $field = $event->object; // Add extra buttons for superuser only if($this->user->isSuperuser()) $field->toolbar .= ', Table, TextColor'; }); You could use the same technique to selectively define other CKEditor settings such as 'stylesSet', 'customOptions', 'extraPlugins', etc.
    1 point
  11. Hi all, I have just committed a major new version (2.0.0) on the dev branch (https://github.com/adrianbj/TableCsvImportExport/tree/dev). This version has breaking changes to the frontend export/import API methods!! Changes include: changed API methods - please see ReadMe for details: https://github.com/adrianbj/TableCsvImportExport/blob/dev/README.md module config settings to: set defaults for import and export parameters (delimiter, enclosure, etc) can users override the default parameters can users can select columns/order in exported CSV can users use an InputfieldSelector interface to filter table rows (this is an enhanced version of the built-in Table "Find" interface available when you have pagination enabled) I would like to make this the stable (master) version as soon as possible because the old API methods were not well constructed so I would really appreciate testing of this new version if you have any time. Thanks! PS - has anyone out there actually been using the API methods?
    1 point
  12. I just ran into this same issue. I tried to make a field dependent on one of the roles and it broke the profile, even though neither the roles field or the dependent field were visible in the profile at all. I created a copy of the ProcessProfile module under /site/modules/ and replaced this: if(count($form->getErrors())) { $this->error($this->_("Profile not saved")); return; } With this: $errors = $form->getErrors(); if(count($errors)) { if(count($errors) == 1 && strpos($errors[0], 'showIf') !== false) { // Everything is okay } else { $this->error(implode('; ', $errors)); // Show the actual errors instead of just a generic message return; } } This way if there is only one error message and it is related to a 'showIf' field dependency, the form will save anyway.
    1 point
  13. Wow, this is exactly what I am looking for. Great thanks!
    1 point
  14. No reason it would break at all. It's quite robust and a lot of corner cases are handled in the module source. But, regardless of your setup or page count, make frequent backups of your DB. http://modules.processwire.com/modules/cronjob-database-backup/
    1 point
  15. Thanks @adrian Yesterday I actually "only" did some preliminary tests with around 50 users. It worked well but today I had to do the "real thing". I found that it was only about 33 users I could always create without timeout, more than that sometimes worked sometimes not. Since I had to deal with only about 150 users, I created them in groups of 33 or so, as I did not want to waste time with dealing with the timeout. My other issue was that I had to deal with duplicate user names. The batcher – in such a case – fails with mysql error (...Duplicate entry 'super_duplex_user' for key 'name_parent_id'...) which is not too elegant I cleaned up the CSV to tackle this but was wondering if the script could look for them in advance? Is that out of the scope of this action?
    1 point
  16. Thanks for all the tips. I am aware of limit @mr-fan but thanks anyway. This is just a demo for now but will start writing some real content asap. Looking forward to being able to give something back to the community. Although my guides will probably be nothing new to seasoned users, they will make it easier for people new to PW, or maybe to persuade people choosing a CMS/CMF that PW is super easy to use even for non/beginner coders. Anyway, back from work now so can continue. -EDIT- I added a Datetime field to the blog posts with no output so I can choose the post date on each one rather than use the created date i.e. I can backdate them to make it look like I've been doing this for ages... seriously though, now it shows the two latest posts per category and I can manipulate the dates better (for HTML/CSS output) from the field timestamp. I'll probably stick that in a function as the date will be displayed on multiple templates. // category-index.php <?php namespace ProcessWire; ?> <div class="container"> <div class="row"> <?php $categories = $pages->get("/categories/")->children; foreach ($categories as $category): ?> <div class="col"> <h2><a href="<?= $category->url; ?>"><?= $category->title; ?></a></h2> <ul class="list-unstyled"> <?php $posts = $pages->find("template=blog-entry, categorySelect=$category, sort=-postDate, limit=2"); foreach ($posts as $post): ?> <li><a href="<?= $post->url; ?>"><?= $post->title; ?></a></li> <?= $post->summary; ?> <?php endforeach; ?> </ul> </div> <?php endforeach; ?> </div> </div> Seems to work ok: Thanks for all the help. Maybe my first post should be "How to make a blog with processwire"...
    1 point
  17. MySQL usually listens to loopback interface (127.0.0.1) and only responds to requests coming from the machine itself, like PHP or NodeJS etc (unless you're using a convoluted multi-server DB architecture and have configured PW to connect another machine on the network). So you dont have to change 127.0.0.1 to localhost. In fact leaving it that way will eliminate a (non-zero but very small) delay caused resolving host names to their IP addresses.
    1 point
  18. Bumped to 0.2.4-beta: Fixes schema changes (per ThePixelDeveloper's recent commits). Note that the sitemap doesn't render using the XML viewer, and it's still to be tested in Search Console. A few changes to ModuleConfig. Adding image fields to the sitemap is now only possible if your site contains image fields. Hook priority is changed (from after to before). This is mostly due to SystemNotifications showing a 404 for the sitemap.xml route. add X-SitemapRetrievedFromCache header for debugging purposes. PS, I'm working on a new docs site as well. It'll be powered by PW, and will have docs for all my modules. On a separate note: Jumplinks is on the mantleshelf for a little while as I'd like to do a refactor to make the code a little more 'lean' and my schedule is not what it once was. There are a few things bugging me with regards to the way it's put together at the moment, and it feels a little chunky. Sorry for the delay on that one, guys...
    1 point
  19. you can simple use things like $posts = $pages->find("template=blog-entry, categorySelect=$category, limit=10"); or your example with count() to be more efficient on big datasets... http://cheatsheet.processwire.com/selectors/built-in-page-selector-properties/limit-n/
    1 point
  20. Thank you for your replies that clarifies the situation completely. Processwire is genius but equally it's forum contributors.
    1 point
  21. So I quickly knocked this up before work. Changed path to /category-index/category-entry/ (have template files for each now) and to render the /categories/ page: // category-index.php <?php namespace ProcessWire; ?> <div class="container"> <div class="row"> <?php $categories = $pages->get("/categories/")->children; foreach ($categories as $category): ?> <div class="col"> <h2><a href="<?= $category->url; ?>"><?= $category->title; ?></a></h2> <ul class="list-unstyled"> <?php $posts = $pages->find("template=blog-entry, categorySelect=$category"); foreach ($posts as $post): ?> <li><a href="<?= $post->url; ?>"><?= $post->title; ?></a></li> <?= $post->summary; ?> <?php endforeach; ?> </ul> </div> <?php endforeach; ?> </div> </div> How is this in terms of efficiency? Like, if there were 10,000 posts. I saw code in another post, something like like: if (! $pages->count("template=blog-entry, categorySelect=$category")) continue; { // Do stuff } Is this a situation where @Robin S module would come in handy? Currently the page renders lie so: (some pages are repeated because they have multiple categories selected but this is fine)
    1 point
  22. @szabesz I found the idea in your links and made it to work perfectly for my needs so far:
    1 point
  23. @szabesz I must have copied here one of the tests I ran. so $i = 1 and at the end of the loop I am increasing it: $i++; I edited the example code. I thought to give a try of YAML, but the initial idea to get the nutrition into a text field is to simply copy the values from a specific program that we use to generate the totals for a specific group of ingredients. @kongondo Initially I started with a repeater, but I find it to be too much to add 10-12 values in the repeater. I do use the repeater for the recipe steps where I need to have images, text, step timing etc. and it works great, but for the nutrition I think it is much easier in my case to copy/paste the generated values. Will see the chessboard examples for a hint. I might also edit the CSS to avoid the insertion of the row div for left and right block. I find it to be much better coded if all the boxes are using the same class and adding two keeps them on the same row, but the third shows on the next one and so fort. This way a simple FOR loop would do the trick super easy. The question is more like if it ever happens to deal with such scenario how to find a suitable working example
    1 point
  24. Hi, It's a bit a middle of the night over here to understand all the wodoo you are exercising here but we have a nice topic dealing with some examples, have you seen it? Also: ($i & 1) but where is $i coming from in the first place?
    1 point
  25. Who-hoooo, I've been dreaming of this! Thank you very much, Sir!
    1 point
  26. Thank you @abdus and @bernhard for sharing your ways. It is always interesting to find out some ways to have an easier and lighter approaches. Will try the next theme to accomplish the multiple use of single fields and setting up the differences in the code.
    1 point
  27. You could also try ersch's PagesSum module for this aspect.
    1 point
  28. Here are my generic versions WireArray::map wire()->addHookMethod('WireArray::map', function (HookEvent $e) { $func = $e->arguments(0); if (!is_callable($func)) throw new WireException('first argument must be a callable function'); $mapped = array_map($func, $e->object->getArray()); $e->return = $mapped; }); WireArray::reduce wire()->addHookMethod('WireArray::reduce', function (HookEvent $e) { $func = $e->arguments(0); $initial = $e->arguments(1) ?? []; if (!is_callable($func)) throw new WireException('first argument must be a callable function'); $reduced = array_reduce($e->object->getArray(), $func, $initial); $e->return = $reduced; }); WireArray::forEach wire()->addHookMethod('WireArray::forEach', function (HookEvent $e) { $func = $e->arguments(0); if (!is_callable($func)) throw new WireException('first argument must be a callable function'); $foreached = []; foreach ($e->object as $i => $item) $foreached[] = $func($item, $i); $e->return = $foreached; }); Tests $mapped = $pages->find('id>0, parent=2')->map(function($page) { return [ 'title' => $page->title, 'id' => $page->id ]; }); $reduced = $pages->find('template=post')->reduce(function($carry, $item){ $carry[] = $item->id; return $carry; }, []); $foreached = $pages->find('template=post')->forEach(function($item){ echo $item->id . '<br>'; }); dump($mapped); dump($reduced); dump($foreached); Which outputs array (4) 0 => array (2) title => "Pages" (5) id => 3 1 => array (2) title => "Setup" (5) id => 22 2 => array (2) title => "Modules" (7) id => 21 3 => array (2) title => "Access" (6) id => 28 array (8) 0 => 1024 1 => 1026 2 => 1028 3 => 1031 4 => 1033 5 => 1058 6 => 1062 7 => 1065 1024 1026 1028 1031 1033 1058 1062 1065 array (8) 0 => null 1 => null 2 => null 3 => null 4 => null 5 => null 6 => null 7 => null 29.59ms, 0.06 MB
    1 point
  29. @bernhard, I've found a way. Hooking ProcessPageEdit::buildForm works wire()->addHookAfter('ProcessPageEdit::buildForm', function (HookEvent $e) { /** @var ProcessPageEdit $edit */ $templates = ['post', 'basic']; $edit = $e->object; $page = $edit->getPage(); if (!in_array($page->template->name, $templates)) return; /** @var InputfieldForm $form */ $form = $e->return; /** @var InputfieldImage $imageField */ $imageField = $form->children->findOne('name=images'); if (!$imageField) return; $imageField->maxFiles = 1; }); When I try to add more images, field just replaces the first one, not allowing more than one. If there were multiple images before, they disappear after page save, only the first one remains.
    1 point
  30. Hi Sérgio and Robin, thanks a lot for helping out. So the Connect Page Fields module seems to be interesting for the page reference approach – I'll check that out later today. For now, I took the repeater approach, and your three bullets were essential in achieving the goal, @Robin S. That's how I do it: 1. I search for date repeaters by treating them as template (by putting "_repeater" before the field name); I can now sort and limit the array to my tasting 2. I set "check_access=0" so that all users can see the results 3. I get field data from the page the repeater is living on by using the getForpage() method. $allEvents = $pages->find("template=repeater_event_repeater_datetime, event_datetime>today, sort=event_datetime, limit=14, check_access=0"); foreach ($allEvents as $event): echo "<h4>$event->event_date</h4>"; echo "<h5>{$event->getForpage()->event_title}</h5>"; echo "<p>{$event->getForpage()->event_desc}</p>"; endforeach; So far, that works great. Since there are some tricks involved I didn't know before, I'll keep checking, if there are any "side effects".
    1 point
  31. Hi @mvdesign. So sorry that I could not respond earlier. I decided to make an introduction video for this module to help people that are trying to use it. But then, I never made a screencast video before, and on top of that, the last time I spoke english was 2011. So I had to take dozens of try-outs till I got something watchable. So here is the video. It shows how you would create/update pages with this module. The video is far from OK, so I will probably record another one after I get some feedback. Until then please refer to this video to learn about how the module works.
    1 point
  32. I have a page field "office" that should appear only on users with the role "broker". So, I put under Visibility for the field, Show this field only if roles*=1075. It doesn't show without the asterisk. But when I try to update a user with an office selected, I get error message up top: "Profile not saved". Anyone know why? Update: See replies #5 and 6
    1 point
×
×
  • Create New...