-
Posts
45 -
Joined
-
Last visited
Everything posted by Ovi
-
public function init() { die("foo"); $this->addHookAfter("CommentFormWithRatings::processRatingInput", $this, 'copyAverageRating'); } displays foo when viewing any page, so the module loads properly. public function copyAverageRating($event) { die("foo"); $page = $event->arguments[0]; // we're interested in product pages only if($page->template->name != 'product') return; // copy the average rating into a field called average_rating $page->average_rating = $page->product_comments->averageRating; } The above does nothing when adding a comment. die() never gets called. I double checked all my naming, i don't know where the issue is. Any idea where to look next?
-
Thanks, can't believe i missed that. But the problem's still here, it just that the error's gone. i tried all i could think of but i'm certain now that $this->addHookAfter("CommentRatings::processRatingInput", $this, 'copyAverageRating'); is not triggering like it should. So my module's not working. (even though i have ___processRatingInput() in Antti's code) Basically i need a hook that would execute the copyAverageRating function each time a comment is added. There's no hook in PW for when a comment is added, as far as i know. @horst Thanks for the suggestion, but it's a little hard for me to do since i don't understand Antti's module. If i did, i would have written it myself Everything's a confusing mess for me and each time i try to follow the code i get lost. Besides, all i need is to "do something" after a comment has been added. It looks like a module might be the simplest solution to that, if i can get a working hook, that is. Halp?
-
Hello everyone. My problem today: i have a custom module built by Antti Peisa which hooks into the comments module that comes with PW and adds a ratings field. Antti did an awesome job with it, except i'm not able to sort pages by their average rating. that happens because the average rating is not stored into any db field, it's just calculated at runtime, on request. since Antti unfortunately doesn't have any time to help me with this beyond a quick pointer, i'm posting here. please see the bottom of this post for Antti's comment ratings module. My attempted solution: build a module that stores the average rating for a comments field in another field, as an integer or float: i built the module lke this class PageAverageRating extends WireData implements Module { public static function getModuleInfo() { return array( // bla bla, removed for brevity ); } public function init() { $this->addHookAfter(CommentRatings::processRatingInput, $this, 'copyAverageRating'); } public function copyAverageRating($event) { $page = $event->arguments[0]; // we're interested in product pages only if($page->template->name != 'product') return; // copy the average rating into another field, if there is a rating if(count($page->product_comments) > 0) { $page->average_rating = $page->product_comments->averageRating; } else { $page->average_rating = 0; } } } i obviously had to hook it to the moment a comment gets added. Didn't really know how to do that. Antti said: so i turned processRatingInput into ___processRatingInput to make it hookable - i doesn't seem to have worked, see below Where i'm stuck: when i run the module above with the hook: $this->addHookAfter(CommentRatings::processRatingInput, $this, 'copyAverageRating'); i get an error saying: Error: Undefined class constant 'processRatingInput' (line 16 of ..../public_html/site/modules/PageAverageRating.module) Other things i've tried: Since this should be a method and not a property, i tried: $this->addHookAfter(CommentRatings::processRatingInput(), $this, 'copyAverageRating'); i get an error saying: Error: Undefined class method 'processRatingInput' (line 16 of ..../public_html/site/modules/PageAverageRating.module) Also i've tried hooking into the CommentFormWithRatings class which extends the CommentForm class and actually contains the processRatingInput() method: $this->addHookAfter(CommentFormWithRatings::processRatingInput, $this, 'copyAverageRating'); Error: Class 'CommentFormWithRatings' not found (line 16 of ..../public_html/site/modules/PageAverageRating.module) I'm attaching the module that Antti built here - it's going to be public anyway. CommentFormWithRatings.php CommentRatings.module Can anybody please help me out ? I feel like the village idiot for not managing to figure this out. How do i make this hook work? Big thanks!
-
I have difficulties to find the right selector...
Ovi replied to titanium's topic in General Support
Seems like all of the solutions here are shorter than mine and so they are better from a KISS perspective. Like i said i'm a beginner who just learned to use the hammer so many problems are starting to look like nails to me But just wanted to mention that with my solution you should be able to use renderPager() since you're not working with in-memory arrays. I would be really curious to know how DaveP's solution plays out and how it compares in processing speed with mine - right now i have no idea which would be faster. BTW, if you're looking to filter out all the pages who don't have the intro field from the module, just replace: // we can specify a certain template that this module should run for. As it is, it will run for all pages. Just uncomment the line blow and replace "template_name_here" with the name of your template //if($page->template->name != 'template_name_here') return; with: if (!$page->intro) return; -
I have difficulties to find the right selector...
Ovi replied to titanium's topic in General Support
Hello. Best way i can think of is writing a custom module that actually stores the length of that field in a separate, int field. Try this: <?php class PageIntroLength extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Get Intro Length for filtering', 'version' => 100, 'summary' => 'Stores the intro length into a field', 'singular' => true, 'autoload' => true, ); } public function init() { $this->pages->addHookBefore('save', $this, 'stroreIntroLength'); } public function stroreIntroLength($event) { $page = $event->arguments[0]; // we can specify a certain template that this module should run for. As it is, it will run for all pages. Just uncomment the line blow and replace "template_name_here" with the name of your template //if($page->template->name != 'template_name_here') return; // get the length of the intro and store it in a field named intro_length. YOU WILL HAVE TO CREATE THIS FIELD AND ATTACH IT TO THE TEMPLATE YOURSELF FOR THIS TO WORK. $page->intro_length = strlen($page->intro); } } Save this as PageIntroLength.module and upload it to your modules directory. Install the module (click check for new modules first.) Don't forget to create the field (of type integer) that will store the length. You must name it "intro_length " without the quotes or this won't work. Make the field hidden so the editors don't see it. After you've done all that, each time you save a page, the intro_length field will get populated with an integer that represents the number of characters in the intro field. Then you can filter like this: $pages->find(intro_length < 10); This code is untested, and i am very much a beginner, so if you have any problems just post here. Please let me know how it works! -
Wow, i feel like a dumbass now . Sorry i completely overlooked the .count method available in the PW selectors. Thanks for your help guys!
-
Thanks for your repply Horst! I'm using the comments module http://processwire.com/api/fieldtypes/comments/ Would your suggestion work with that?
-
Hi everyone, i have another sorting conundrum. After finding out how to sort pages by a repeater's field value - see http://processwire.com/talk/topic/2884-sorting-based-on-repeater-field-values/ (thanks Nik) now i have another issue. I guess the title pretty much says it all. I need to sort a collection of pages (products) by most comments (reviews). According to the method i learned for sorting by a repeater's field, i would need to: create a hidden field (let's call it "commentsCount") write a short module that counts all the comments and stores that value in the hidden field as an integer sort the pages by that field, like: $pages->find("template=product, sort=commentsCount"); The problem is that i have nothing to hook that module into. In the case of sorting by a repeater's field it was ok hooking into the Save event because you need to save a page to change a repeater's values. But here there's a problem since people can add comments without anybody saving the page and there's no "vent hook available for when a comment is added. Antti suggested that this is the method that needs to be hook-able but isn't. So in conclusion: sorting products by how many reviews they have is a very important functionality of an online store, any suggestions on how to solve this? Thanks! EDIT: My only idea so far is hooking the module into something like the save event and then running a cron job that triggers the save event on all pages once per day, thus updating their number of comments count. It's a terrible idea. There has to be a better way to do this...
-
@Nik Thanks so much for that, the code is working perfectly and it all makes perfect sense now! So amazingly easy! I will use the same method on other sorting issues i have that would not work out of the box with PW's $pages->find() method. I still have one problem though: I already have a lot of products in the site, and since the module hooks on the "Save" event, the field won't populate unless i go to each page and save it. There are a LOT of pages . What would be a smart method of going about this? Can i trigger the Save event on all pages that have the product template, or can i change to hook somehow? @horst Thanks for the pointers, i've taken your advice and have begun reading up on hooks and the API documentation in general, but snippets of code like Nik's one above really make a world of difference in shining some light on the whole thing. EDIT: Eureka! I managed it myself: <?php $allProducts = $pages->find("template=product"); foreach ($allProducts as $p) { $p->setOutputFormatting(false); $p->save(); echo "page ".$p->url." saved!"; } ?> Done and done. Now all the product pages have the 'first_version_price' field populated! Thanks for all the help everyone, you rock!
-
@Soma & Nik Thanks for the offer, i could always use any help i can get! What you're saying is more like what it first thought about when i discussed this briefly with Antti Peisa, however it seemed to me that it would be too difficult to code (i wish i knew Module Development but i don't - and this seems a lot like a custom module). I think it should probably be something similar to: http://modules.processwire.com/modules/fieldtype-concat/. Btw if you have any tips on where to start learning module development i'd be grateful - besides "look at the hello world module" and "see how other modules are done" , that doesn't help me unfortunately. I can write PHP up to and including creating and using my own classes, and extending existing ones - but i'm a beginner and can't figure out how i should work with a huge thing like PW or make sense of existing modules. @kongondo Thanks for spotting that! i jumbled things up when i put together the code for the forums. Lines 54 to 54 should be changed from: // set the scope of the query if ($page == $page->rootParent) { // this is a category, we need the categories from all it's children subcategories $categoryScope = $page->rootParent->category_field_name; }else{ // this is a sub-category, we only need the products from itself $categoryScope = $page->rootParent->category_field_name; } // do the PW query $products = $pages->find("template=product, ".$categoryScope."=$page->children"); to: // set the scope of the query if ($page == $page->rootParent) { // this is a category, we need the categories from all it's children subcategories $categoryScope = "=$page->children"; }else{ // this is a sub-category, we only need the products from itself $categoryScope = "=$page"; } // do the PW query $products = $pages->find("template=product, ".$page->rootParent->category_field_name.$categoryScope); I already changed that in my original post. Btw if you're interested: category_field_name is a hidden text field i created and attached to the category-page template and it stores the name of a Page Field which references all the subcategories of that category. It's part of the structure system i came up with to be able to have products in multiple categories and sub-categories without creating duplicate URLs, but still maintaining a sensible structure in the backend. Let me know if you need more info on this - won't go into it any further here because this is not the topic of this thread.
-
Thanks for looking into this Nik! It'm curious if usort is faster in PW. See this link for a performance test done by someone on StackOverflow http://stackoverflow.com/questions/1462503/sort-array-by-object-property-in-php. They're saying: Of course this may not be the case at all for PW.
-
Hi guys, i came across a similar problem, and i just wanted to add my solution here since it doesn't use usort. It uses the Quicksort algorithm which is a lot faster, and seeing as PW is built to be lightning fast and scale very well, i think speed is an important factor here. My (supposedly faster) solution here: http://processwire.com/talk/topic/3788-sorting-pages-by-repeater-fields-first-entry/?p=37032
-
Thanks Nik, but i came up with my own solution meanwhile. The good part is that i don't user uSort i use an implementation of the QuickSort algorithm, which should be 3 times faster than uSort. Useful Links: explanation of Quicksort algorithm: http://en.wikipedia.org/wiki/Quicksort php implementation of Quicksort by Iskren Stoianov: http://istoyanov.blogspot.ro/2007/11/quicksort.html My setup: i have an online store with products. all products have the same template products have price variations depending on quantity (small box of product vs big box of the same product) - it is not a matter of shipping more of the same product, the diferent sizes come from the manufacturer (cosmetic products) product variations are stored in a repeater called "product_variations" which has the fields "size" and "price" categories and subcategories share the same template. The difference is that categories have sub-categories as children. The challenge: sorting the products by price. Since there can be multiple prices for a product (one for each variation) i want to sort the products by the FIRST variation's price. The solution: function quickSortProducts( &$array, $sort_order="ASC" ){ if ($sort_order!= "ASC" && $sort_order != "DESC") { trigger_error("Invalid paramenter ".$sort_order." . Expecting ASC or DESC", E_USER_ERROR); } $cur = 1; $stack[1]['l'] = 0; $stack[1]['r'] = count($array)-1; while( $cur != 0 ){ $l = $stack[$cur]['l']; $r = $stack[$cur]['r']; $cur--; while( $l < $r ){ $i = $l; $j = $r; $tmp = $array[(int)( ($l+$r)/2 )]; // partion the array in two parts. // left from $tmp are with smaller values, // right from $tmp are with bigger ones while( $i <= $j ){ if ($sort_order == "DESC") { while( $array[$i]->product_versions[0]->price > $tmp->product_versions[0]->price ) $i++; while( $tmp->product_versions[0]->price > $array[$j]->product_versions[0]->price ) $j--; }else{ while( $array[$i]->product_versions[0]->price < $tmp->product_versions[0]->price ) $i++; while( $tmp->product_versions[0]->price < $array[$j]->product_versions[0]->price ) $j--; } // swap elements from the two sides if( $i <= $j){ $w = $array[$i]; $array[$i] = $array[$j]; $array[$j] = $w; $i++; $j--; }// end if };// end while ( $i <= $j ) if( $i < $r ){ $cur++; $stack[$cur]['l'] = $i; $stack[$cur]['r'] = $r; } $r = $j; }; // end while ( $l < $r ) }// end while( $cur != 0 ); }// end function quickSortProducts This function does all the sorting. Next i put together my query. Since categories and sub-categories share the same template, i need to figure out what the scope of the query is and i do that below. If you don't have the same setup just do whatever query you need to. This part has nothing to do with the sorting, just putting it here for completeness: // set the scope of the query if ($page == $page->rootParent) { // this is a category, we need the categories from all it's children subcategories $categoryScope = $page->rootParent->category_field_name; }else{ // this is a sub-category, we only need the products from itself $categoryScope = $page->rootParent->category_field_name; } // do the PW query $products = $pages->find("template=product, ".$categoryScope."=$page->children"); And finally, after we've done the query and we have our collection of product pages, we sort it by calling the custom function: if ($input->urlSegment1 == "price-ascending") { quickSortProducts( $products, "ASC") }elseif($input->urlSegment1 == "price-descending"){ quickSortProducts( $products, "DESC") }
-
Thanks for your detailed response, I was aware of most of that, but the thing is, i need to sort by a specific entry in the repeater field (namely the first one - to keep it simple), not filter the set. This $products = $pages->find("template=product, [more criteria here] ,sort=-product_versions[0].price"); doesn't work. I would need something like: $price = $product->product_versions->first()->price; but usable in a selector. Also, when trying the line below, just to see what it does (since it couldn't possibly work since it doesn't know which product version to pick): $products = $pages->find("template=product, [more criteria here] ,sort=-product_versions.price"); i get the error: Error: Exception: Unknown column '_sort_product_versions_price.price' in 'order clause' (in /home/trb/public_html/wire/core/Database.php line 118) "_sort_product_versions_price.price"? Really? I don't get it, sorry... Thanks for your help so far!
-
Hi everyone, My current conundrum is this: i have a list of products for a site i'm working on. They all have the same template and live in the same folder. The products have variations based on size, and each variation has it's own price. I want to be able to sort products by their lowest price (out of any variation), ascending and descending. i've tried something like: $products = $pages->find("template=product, [more criteria here] ,sort=-product_versions[0]->price"); where product_versions is the name of the repeater and "price" is obviously the name of the repeater's field that contains the price i want to sort by. This apparently is not allowed: Error: Exception: Unknown Selector operator: '' -- was your selector value properly escaped? (in /home/trb/public_html/wire/core/Selectors.php line 165) How can i do this? I've seen existing posts on repeaters and sorting on the forums, but none that addressed this issue. And i think it's a pretty vital one. Thanks! EDIT: I tried the following with no success (just for the hell of it, to see what it would do): $products = $pages->find("template=product, [more criteria here] ,sort=-product_versions"); definitely not what i need (does sort the products in some weird way though)
-
Come on, no one? Is everyone so busy?
-
Hello, I need something that is probably quite simple for anyone that is familiar with developing modules in PW. I need to augment the FieldtypeComments module with a 5-star rating field. That's all. The user would see an additional drop-down field when they post the comment that allows them to select a value from 1 to 5 stars. I intend to make the resulting module available publicly for the community after it is developed, as i think it is an essential part of the module collection that is sorely missing at the moment. Please contact me on Skype ( my ID is ovidiu.savescu ) with offers on how much this would cost. Thanks!
-
Hello everyone! What i want to do is simple in concept, but i don't really know what's the best way to go about it. What i want to do is this: when editing pages in the admin that have a certain template (let's say the "Product Category" template) i want to add a listing of all the products that belong to that product category. Products, in this case, are pages with a template of "Product" which contain a page field which is able to select one or more of the category pages. Just to clarify, products are NOT children of product categories, because one product can be in multiple categories. Instead, products are located under a "Products" parent folder. This would help a lot with visualizing what products are in what categories without having to look at the front-end of the website. Can anybody point me in the right direction of how i could do this? Thanks in advance!
-
Hello, I'm going to build an online store using Processwire and FoxyCart. The products will most likely appear in multiple categories, so to prevent duplicate content problems as well as madness when managing the products in the back-end, all the products will appear under one parent: "Products" with categories pages pulling children from that parent and filtering them according to a category field. My problem is that i need an easy way for the client to view the products in the backend, since the normal page tree doesn't work that well for this purpose (it would just be a very long list of product names...) So i'm thinking of a "Products" page in the admin section that would allow the client to search / filter the products based on certain criteria like category, tags, number of ratings, etc. I know how i would do this in the front end, but what's the easiest way of achieving it in the back-end? I don't have any experience with building modules forProcesswire but i know my way around simple to medium PHP. Should i try and use Soma's jquery data table module? https://github.com/somatonic/DataTable Or should i try using the hack described here: http://processwire.com/talk/topic/2386-easy-way-of-creating-custom-admin-pages/ Or is it better if i start a module from scratch to learn the ropes? Any advice is welcome! Thanks!
-
Hello, I'm having a really weird issue with image fields. When i save the page, the description does not save. The description saves fine for one image field that's already on the page (for selecting the banner image of the page) but even if i duplicate that image field and just give it another name (leaving everything else as it is) the new field does not save the description, while the original one does. Any ideas on this? Seems like a bug with Processwire - remember, i'm not talking about displaying the description in the front-end, but about the description field not retaining its value in the backend once i save the page. Thanks!