-
Posts
16,784 -
Joined
-
Last visited
-
Days Won
1,537
Everything posted by ryan
-
I don't know for sure what Reiska means either, but @apeisa told me that was apparently my Finnish name and that it means something along the lines of handyman. I had a member-title spot open, so I put it there. But if it's translating anywhere as "god of all heavens" or something like that, then I need to change it. I'm okay with being a handyman or somebody that fixes things, but need to change it reads as something egotistical.
-
It's more efficient to do it in one find() operation, using the new options available. When using two separate find operations, all the pages from the first find() operation have to be loaded. When you do it in one find(), then those pages don't have to be loaded. So it can actually be a whole lot more efficient, especially if dealing with larger scales. There is still some room for optimization so I'll be working on that, but regardless they should be a lot more efficient than multiple find operations. Another good reason to use OR-groups (and sub-selectors, depending on the case) would be for pagination. You really can't achieve paginated results very easily if splitting into multiple finds.
- 367 replies
-
- 11
-
Continuing from my previous post in this thread about some selector enhancements available on the dev branch, we've got a couple more advanced options for use in selectors in case anyone is interested: OR-groups These let you specify multiple expressions and only one of them has to match in order for the selector to match. It's a way of saying "either this has to match OR that has to match". This is useful because selectors always assumed AND – meaning everything has to match. While you have always been able to use the pipe "|" to specify ORs for fields or values or both, the scope of it was just that field=value statement only. Now we have something new called OR-groups. These let you create multiple selector groups and only one of them has to match. You can specify OR-groups by surrounding selectors in parenthesis. An example demonstrates it best. Lets say that we wanted to find all "product" pages that were in stock, and either in a featured date range, or had a highlighted checkbox checked. Previously we would do like this with two separate find operations: $items = $pages->find("template=product, stock>0, featured_from<=today, featured_to>=today"); $items->add($pages->find("template=product, stock>0, highlighted=1")); Now we can do it in one find operation: $items = $pages->find("template=product, stock>0, (featured_from<=today, featured_to>=today), (highlighted=1)"); Above are two selectors surrounded in parenthesis. Only one of them has to match. You can specify as many of them as you want. This type of OR expression is something you couldn't previously do with selectors. Think of the parenthesis as a way of saying "this is optional". But of course, at least one of your parenthesized selectors has to match in order for the full selector to match. I'm guessing the above usage probably covers 99% of the situations where you might need it. But lets say that you want to have different combinations of OR expressions. You can create named groups that OR with each-other by specifying: foo=(selector1), bar=(selector2), foo=(selector3), bar=(selector4) In the above you'd replace "foo" and "bar" with names of your choice. And you'd replace the "selector" with any selector strings. Those foo/bar names aren't referring to fields, instead they are just named groups that you can name however you want. In that selector, at least one of the "foo" named selectors would have to match, and at least one of the "bar" named selectors would have to match. If you didn't use the foo/bar named groups here (but still used the parenthesis), then only one of the 4 selectors would be required to match. Sub-selectors Some of you are already familiar with these because it was committed to the dev branch a couple weeks ago (and I think may have been outlined elsewhere in the forums). Sub-selectors let you put a selector within a selector, enabling you to perform more complex matches that used to require you to use separate API calls. These can be used on the 'id' property of any field that maps to a page. The 'id' property is assumed when referring to a page reference or a parent, so it's not necessary to specify it unless you want to, i.e. "field" and "field.id" mean the same thing in this case. Sub-selectors are specified between [square brackets]. For example, lets say we are matching products and our product template has a "company" page field. Each company also has it's own page field where all the company locations are identified. Lets say we want to find all products that are made by a company that has more than 5 locations and at least one of those locations has "Finland" in the title. Previously we would have had to do it like this: $companies = $pages->find("template=company, locations>5, locations.title%=Finland"); $items = $pages->find("template=product, company=$companies"); That's easy enough. But now it's even simpler, as you can do it in one operation: $items = $pages->find("template=product, company=[locations>5, locations.title%=Finland]"); When you've got a "field=[value]" selector, any properties you refer to in "[value]" assume the "field", so "locations" above is referring to a property of the "company" field.
- 367 replies
-
- 33
-
Implement yet another way to access PW's globals
ryan replied to owzim's topic in Wishlist & Roadmap
Feasibly it's possible, but it's not desirable. Making wire('var') aware of context involves pre-compiling the template files to replace wire('var') with wire($this, 'var'). So it's something we do to provide backwards compatibility in template files. We won't be providing that backwards compatibility outside of template files, so people will have to use $this->var, $this->wire('var') or wire($this, 'var'); elsewhere. Also, a Static::syntax implies dealing with a framework that only has one context, which gives the appearance of a weakness that isn't there, something I think we'd want to avoid. While I'd really like to limit statics in ProcessWire as much as possible (just because they are more than often a bad programming practice), that syntax is perfectly fine for static functions where context doesn't matter or where you are passing the context into it. For example, $sanitizer functions would not need to have different behavior in different contexts. I've even seen some frameworks that do use statics for sanitization functions as well. In the end though, IMO they would still be better served by providing the same functions non-statically. The plan is that when you do this... $main = new ProcessWire('/site/'); $intranet = new ProcessWire('/site-intranet/'); ...everything in each of those two instances will be unique to those instances and whatever site files are stored in /site/ or /site-intranet/. That means that when a module is initialized, it is only initialized for the instance (whether main or intranet). When a module refers to $this->var, it's referring to API variables that are part of its instance only. This enables you to have multiple sites talking to each other. Currently this isn't possible precisely because PW uses statics for API variables behind the scenes. But the fact that we've kept that behind the scenes is a good thing because that means it doesn't matter how our API variables are stored. We can switch them to a stronger storage mechanism that would be tied to an instance. This is one reason why I deprecated the Wire::getFuel() syntax (that appeared in early versions of PW2) early-on... though you might still see it appear in a few core spots, which will need to be changed. But we've really tried to keep the public API clear of static calls so that the API would not have to change as PW continues to grow as a framework. -
You could install it in /wire/modules/ rather than /site/modules/. However, I don't recommend doing that because it'll get overwritten every time you upgrade the core, which would be a bigger problem than having multiple copies of it. Use symbolic links instead (for /site/modules/ProCache/) and you'll only have to keep one copy of the ProCache files.
-
Creating module to handle password validation
ryan replied to MadHatter's topic in Module/Plugin Development
As a basic test case, you could add this to your /site/templates/admin.php wire()->addHookAfter('InputfieldPassword::processInput', function($event) { $inputfield = $event->object; $password = $inputfield->attr('value'); $bad = array('test', 'abc', '123', 'password', 'unicorn'); if(in_array($password, $bad)) { $inputfield->error("That's not good enough of a password, try again"); $inputfield->attr('value', ''); } }); -
@kongondo the functionality was added a little while back and is already in the stable 2.4.0. See these four hooks in Pages.php.
-
Implement yet another way to access PW's globals
ryan replied to owzim's topic in Wishlist & Roadmap
Around PW 3.0, I'm looking to make PW multi-instance compatible so that you could have multiple ProcessWire instances (each connected to separate databases) running from the same PHP code. This opens a huge amount of flexibility. But it also means that any kind of static references will have to disappear because function calls like Wire::getFuel('var') and wire('var') make the assumption that there is only one instance of ProcessWire running. This is one of the bad things about static calls in general. When that time comes, we will be able to apply contexts to our template files so that calls like wire('var') still work. But the reality is that non-static calls like $this->wire('var'), $this->var, and $var are technically superior when it comes to a multi-instance environment. So my opinion would be that it's not good to introduce new static calling methods when they may soon be obsolete. -
There's a security problem here. You are placing an unsanitized variable into a selector. Think of that like SQL query injection, even if it's not quite that dangerous. But you want to avoid any instances where user input could start expanding your selector string. So always sanitize user input before using it. In this case, if you know that $min is always going to be an integer or a float, then make it an integer or a float: $min = (int) $input->get->min; // if integer $min = (float) $input->get->min; // if float If you are dealing with some type of string, then at minimum sanitize with $sanitizer->selectorValue(): $value = $sanitizer->selectorValue($input->get->value); Do all of this sanitization before placement in a selector. Lastly, it doesn't matter whether you use $_GET or $input->get (or $_POST or $input->post), but I prefer to use $input->get or $input->post because they remove the issue of PHP's magic quotes setting, which may cause unexpected slashes to appear in your input, depending on the server settings.
-
If anyone is interested in experimenting with this one, grab the latest dev branch. In your selector to $pages->find(), you can specify get_total=count to make it use the count method rather than the calc method. You can also specify get_total=0 to disable any kind of total counting. Though note that it's only counting totals if you have a "limit=n" (where n is any number), so don't bother trying to change the count method if you aren't using limit=n somewhere in your query. Example: // calculate total using count method $t = Debug::timer(); $items = $pages->find("template=basic-page, limit=1, get_total=count"); $total = $items->getTotal(); echo "<p>Found $total items in " . Debug::timer($t) . " seconds using count</p>"; // calculate total using calc method (default) $t = Debug::timer(); $items = $pages->find("template=basic-page, limit=1, get_total=calc"); $total = $items->getTotal(); echo "<p>Found $total items in " . Debug::timer($t) . " seconds using calc</p>"; // bypass any kind of counting $t = Debug::timer(); $items = $pages->find("template=basic-page, limit=1, get_total=0"); $total = $items->getTotal(); // will be 0 or 1 echo "<p>Found $total items in " . Debug::timer($t) . " seconds with total count disabled.</p>"; One thing is for certain: counting totals takes up significant overhead regardless of count method. For large scale usages, having the option to disable total counting with get_total=0 will be valuable for sure. For instance, if you wanted to retrieve the 3 newest news items, but didn't care how many there were total, get_total=0 would be worthwhile. Likewise, if you are using a "Next Page / Prev Page" type of pagination (rather than numbered pagination links) then you also wouldn't need the total count.
-
How do we emulate WordPress "post author selection" in Processwire?
ryan replied to Zahari M.'s topic in API & Templates
Or you could use this undocumented feature: Edit your /site/config.php and set $config->advanced = true; In your admin, go to Setup > Templates > your-post-template Click to the "System" tab. See the last field: "Allow the created user to be changed on pages?" Check that box and Save. Restore your config.php back to normal: Edit your /site/config.php again and set $config->advanced = false. Now you should be able to change the "Created User" on the Settings tab of any pages using that template.- 8 replies
-
- 16
-
I've updated PageFinder to support both the SQL_CALC_FOUND_ROWS and the COUNT(*) method so that I could compare them. I found some interesting results. My conclusion is that COUNT(*) is indeed faster in many instances, but SQL_CALC_FOUND_ROWS is faster in many other instances. Sometimes one is even twice as fast as the other, making me think we might need to build some logic into PageFinder to help it decide which method to use. Though I'm not yet certain why one is counting so much faster than the other at present. Here are the results. Numbers are in seconds. This was testing on a site with ~45k pages. 1. Selector: id>1 (matches 45786 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.030606 1.5386 COUNT: 0.026732 1.3451 WINNER 2. Selector: template=facility (matches 38008 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.197568 9.9118 COUNT: 0.183608 9.2142 WINNER 3. Selector: template=facility, title*=senior (matches 1207 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.053222 2.6943 WINNER COUNT: 0.08826 4.4469 4. Selector: template=facility, capacity>5 (matches 28616 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.347964 17.4320 COUNT: 0.208856 10.4764 WINNER 5. Selector: template=facility, capacity>5, zip!='' (matches 28145 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.61547 30.8082 COUNT: 0.407376 20.4028 WINNER 6. Selector: capacity>5, zip!='', limit=2 (matches 28145 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.435284 21.7988 WINNER COUNT: 0.903338 45.2016 7. Selector: capacity<5, zip!='', limit=2 (matches 4713 pages) AVERAGE TOTAL (50 finds) ------------------------------------------- CALC: 0.097256 4.8961 WINNER COUNT: 0.176476 8.8574 Here is the script I used to test: <pre><?php include("./index.php"); $timers = array(); $selector = "id>1, limit=2"; // change to desired selector $totalTimer = Debug::timer(); $totalRecords = 0; $max = 50; for($n = 1; $n <= $max; $n++) { $timer = Debug::timer(); $items = wire('pages')->find($selector); $totalRecords = $items->getTotal(); $elapsed = Debug::timer($timer); $timers[] = $elapsed; // clear the find() cache so it doesn't get reused on next find() wire('pages')->uncacheAll(); } $total = 0.0; foreach($timers as $elapsed) $total += (float) $elapsed; $average = $total / count($timers); echo "Selector:\t$selector\n"; echo "Average:\t" . $average . " seconds per find()\n"; echo "Total Time:\t" . Debug::timer($totalTimer) . " seconds for $max find()s\n"; echo "Total Pages:\t$totalRecords\n";
-
Thanks Hari, excellent observation and I will take a closer look at this. While COUNT(*) is very fast in that context, ProcessWire never executes such a query with no where or join conditions. If I recall that speed difference doesn't hold true once you are counting the results of a rather complex query (as most PW page queries are). Though if we can tweak a little more speed out of using a count rather than a select found_rows() then I'd be all for it. I've been through this consideration before and had settled on sql_calc_found_rows being the better option at the time, but it's been awhile and I'd like to revisit it. I'm now working on a project with apeisa where we deal with hundreds of thousands of pages and we're always looking for ways to optimize performance, so it seems like a good opportunity to benchmark this again. One thing to mention also is that the PW dev branch is more optimized in this respect. It is quite a bit more selective as to when it uses SQL_CALC_FOUND_ROWS, which ensures it's not putting that overhead on top of situations where it doesn't absolutely need it. You can also disable the behavior in your $pages->find() call by specifying the 'getTotal' option as false, which tells PageFinder not to attempt to determine the total number of matches. But specifying it wouldn't be necessary unless your selector string included a limit=n in it, as PW dev doesn't attempt the count unless you are using a limit (presumably for pagination). $options = array('getTotal' => false); $items = $pages->find("selector", $options);
-
VIM for most quick code editing. PhpStorm (in VIM mode) when I have time to sit and focus on coding. I enjoy using it quite a bit. But the overhead is definitely noticeable relative to using VIM on its own. TextWrangler for quick stuff. I especially like TextWrangler for search/replace with regular expressions, as it's more straightforward than doing it in VIM or PhpStorm.
-
wireRelativeTimeStr() has been there since PW 2.3 if I recall correctly. All the words in it are translatable via the translation tool pointed at /wire/core/Functions.php.
-
You are right the SR-80s really aren't comfortable for very long periods. Though they don't bother me too much. Mine are old enough that the foam surrounds have started to disintegrate (which is apparently normal) so I've got to order some new surrounds, or whatever they are called. The main reason I still use the Sr-80s is because they are open air and I can still hear when people need me (lots of interrupts when you've got little kids). Otherwise, I have a strong preference for the sound of the Vmoda headphones, but they really do isolate you from the rest of the world to the point where I'm no good to anyone else when I've got them on. I bought the Vmodas because Amazon had them on a $50 lightning deal last year (they are usually like $130), otherwise I wasn't in the market for them. I have tested out the Beats by Dre a couple of times just because they are always around in stores to try. Even on the $300 monsters, maybe they are comfortable, but the sound doesn't suit me (which is good, because I'd never spend that much). Maybe they've got bass, but it sounds and feels like plastic headphone bass rather than real bass, to my ears at least. These kinds of flavored cans (including Vmodas too) I suppose are more subjective to the individual than flat ones.
-
My brother has some Audio Technica ones and I like them. I haven't used them next to others, so don't have a good comparison, but I did find them very comfortable and with very nice sound. I personally use Grado SR-80s and Vmoda Crossfade LP. I'm mixed on the Grado headphones... they sound great, supposedly excellent, but I think for someone else's preferences. I love the sound of the Vmoda headphones though. They are heavy on the bass and soundstage for sure, but in a way that's real / you can feel, like you are in a movie theater... makes me forget I'm wearing headphones. Honestly though the best value I've seen for headphones is with these Panasonic earbuds. Both my wife and I use them to replace our mobile phone earbuds, and the incredible sound that comes out of those $8 earbuds is hard to believe.
-
Translatable File and Image Descriptions
ryan replied to Robert Zelník's topic in Multi-Language Support
File and image fields now support multiple languages for the description (in the current PW dev branch). The multi-language capability was built directly in the existing file/image fields, so nothing needs to be installed/modified–they simply become multi-language when you've got language support active. Thanks to stardesign for sponsoring this addition.- 11 replies
-
- 16
-
AutoLinks has been updated with the following additions: Multi-language support: You can now specify different sets of terms per language. Max linked terms option: You can now specify the maximum terms that will be linked in a block of copy. Markup for links: It will now let you control the markup for auto-generated links and you can specify different markup for internal and external links. Allowed tags whitelist: You can now specify a whitelist of tags where auto-generated links are allowed. For example, if you didn't want it generating links in headlines, you might tell it to only generate links in paragraphs <p> and list items <li>.
-
Probably not. The Table ProField is good for repeated tabular information. If your user profile needed to include a links, memberships, family members or something like that (each with multiple properties) then the Table field would be good. But the examples you listed all sound like they would still be best kept as separate fields to me. The only exception would be that I think you might potentially benefit from combining Hobbies, Dislikes, and About Me into a single Textareas field (similar to what @peterfoeng mentioned). This doesn't sound consistent with ProFields, or ProcessWire for that matter. If you are setting up a forum, your best bet us to use a forum software. We use IP.Board here and really like it, but there are plenty of other great forum packages that are smaller in scope too (I've heard good things about Vanilla). Also, I wouldn't worry about having too much power when it comes to forum software. Even if your needs are only basic, having a powerful forum software doesn't necessarily make it harder to implement. It just makes it more likely that it'll do what you need it to, when you need it to. The Page fieldtype won't be part of it, as Table isn't actually using Fieldtypes. But some kind of Page reference field will likely be added to Table.
-
Table Use this for tabular data, like rate tables or other things that you might typically represent in a spreadsheet. Use it for situations where you don't need the full-blown flexibility of repeaters, as it's technically more efficient with far less overhead than repeaters. Something like the Events Fieldtype could be very easily re-created via a Table field, but the potential uses are far broader. But for the most part, think tabular data when it comes to the Table field. Multipliers This is good for when you need a range of values (whether text, textarea, numbers, dates, etc.). If you are using repeaters with just one field in them, you might be a lot better off with a Multiplier. Like the Table field, Multipliers are very efficient and low overhead relative to something like Repeaters. Use Multipliers when you need to repeat a single input multiple times, optionally with a min and max number of inputs. Lets say you are building an employee directory, and each employee has between 1 and 3 email addresses. Rather than using 3 separate email fields, you would use 1 multiplier field and specify min=1 and max=3. Repeaters These are infinitely flexible in terms of what they represent, but each row of values is technically a page in the system. As a result, with the flexibility comes significant overhead. This is really only an issue when the quantity of repeater items gets high, or when you have lots (thousands) of pages using repeaters. I recommend repeaters for setting up things like homepage carousels. For example, if you go to the Villas of Distinction homepage, there are 3 separate repeaters in use on that page, each holding a photo, title, description, link. The client can have as many items in each of those sections as they want. Currently it looks like the first repeater as 6 items, the 2nd has 2, and the 3rd has 6. The possibilities of what can be represented with repeaters is endless, but look for potential alternatives when dealing with large quantities (whether large quantities of repeater items, or large quantities of pages using repeaters). PageTable This is one of the ProFields that is available for free (thanks to Avoine sponsorship) on the ProcessWire dev branch. Meaning, it'll be available for everyone to use as part of the core in ProcessWire 2.5. And you can use it now if you don't mind running the dev branch. PageTable has all the flexibility of repeaters, but with lower overhead from the admin/input perspective. Rather than trying to bundle all the inputs on one screen, PageTable shows you a table of items and you click on the item to edit it in a modal window. This enables it to be a lot more efficient from the admin UI perspective. It's also more flexible than repeaters are in terms of where you store your items. PageTable lets you choose where they should live, whether as children of the page being edited, or as children of some other parent page you designate. They might be a little more work to setup than repeaters, but I think that most situations where you need the flexibility of repeaters may be better served by PageTable. PageTable still can't compete with the speed and efficiency of Table or Multiplier, but consider using PageTable anywhere that you might have used Repeaters before. Repeaters and PageTable are fundamentally different from the admin UI/input perspective, so you'd want to compare them yourself to see what suits your individual input needs better. PageTable involves more clicking to create and edit items, making Repeaters potentially faster for entering data rapidly. But PageTable will scale much further in the admin UI than Repeaters will, so I would personally favor PageTable in more situations than Repeaters.
- 105 replies
-
- 44
-
Definitely still use for repeaters, but fewer for sure. I think it's all a good thing to have more options, as I expect repeaters will end up being used more for more appropriate situations rather than for everything.
-
We already have a dedicated Inputfield for this: InputfieldCheckboxes. Use that as your selection when creating a Page field, and you'll have exactly what you are talking about (I use it regularly for that purpose).
-
Multiplier and Table can be used as more efficient (and perhaps preferable UI) repeater replacements in a lot of instances. If either Multiplier or Table will serve your needs as well as repeater, they will be able to scale a lot better than repeater. File fields aren't supported by Table at present. I agree with Antti that PageTable is your best bet to replace repeaters for your particular need. While PageTable won't be as fast/efficient as Table, it will still be a lot better than repeaters in that respect. PageTable is actually very simple and I think you'll find it easy to get going without needing to read anything about it. From the API side you are just dealing with a PageArray like any other.
-
ProFields just comes in 1 edition, unlike FormBuilder and ProCache which come in either: Single, Professional, Developer or Agency editions. The ProFields edition is the equivalent of the Developer edition of FormBuilder and ProCache, in that you can use it in as many sites as you develop, for as long as you want to. I figured there wasn't much point to having individual site licenses with a multi-module package because chances are you won't use them all together on one site. It's more likely that folks will use one or some of the ProFields on some sites, and others on other sites. We want it to be a toolbox you've always got available to you when you need it.