-
Posts
17,100 -
Joined
-
Days Won
1,642
Everything posted by ryan
-
If you keep the structure above, then you would consider the parent ($page->parent) to be the 'primary sub-category' and the grandparent ($page->parent->parent) to be the 'primary category'. In that case, you'd probably want to consider your categories page reference to be 'secondary categories'. I think this structure works fine if you consider the primary category/subcategory to be mostly static once published and more important than the secondary ones.
-
Testing for the first element in a wirearray
ryan replied to thetuningspoon's topic in General Support
I don't think it matters much here. The object===object is probably about the same thing as an id==id integer comparison, when it gets down into the low level code that it's written in. That's because object===object is comparing that two objects are the same instance, which translates to: do they occupy the same place in memory? (1234567==1234567) But there is a good reason to use the id==id over object===object in ProcessWire: $about1 = $pages->get('/about/'); $about2 = $pages->get('/about/'); echo $about1 === $about2 ? "Same Instance " : "Different Instance "; This should output "Same Instance". But then try this: $about1 = $pages->get('/about/'); $about1->set('title', 'About Test')->save(); // can be any page that is changed then saved $about2 = $pages->get('/about/'); echo $about1 === $about2 ? "Same Instance " : "Different Instance "; This should output "Difference Instance". Why? The entire memory cache is cleared when you save a page where one or more fields changed, or if you delete a page. It doesn't matter if it was $about1, or some other page, the memory cache is wiped. Since the code above kept it's own copy of the /about/ page before it was saved, there are now two copies of /about/ in memory: your copy and ProcessWire's copy. For this reason, you may prefer to compare the 'id' property if the code you are working with is manipulating any pages or interacting with modules that do. But for most front-end situations, it doesn't matter. -
That's not far off from what I'd been working on with versioning, which was to store page versions in flat JSON encoded strings in a DB table (though could just as easily be stored as text files). Any ProcessWire page can be reduced to a PHP array of basic types (strings, integers, floats, arrays), which means it can be reduced to a JSON string really easily. This lends itself well to version storage, except for assets where only references are encoded (like files and page references). But I have to admit that the more I think about Antti's proposal to store this at the field-level, the more Iike it. I would create another interface that could optionally be added to a Fieldtype's implements section in the class definition (FieldtypeWithVersions or something like that), and give the individual fieldtypes responsibility for if, how and where they store their versions. That would enable us to get up and running with versions quickly (for text fields at least). It would also seem to provide a nicer level of control to the user, as you don't have to keep track of what is changing in multiple fields at once. And, it would let you to enable versions on fields that you want it, omitting on field where you don't, making things more efficient. Lastly, it would leave the page-structure out of versions completely (where the page lives in the tree), which struck me as a potentially dangerous problem with versions. It could work something roughly like this: You hover over the revisions label and get a summary of recent revisions. You click on one, it could pop up a modal showing you the revision (maybe with a compare tool) and a button to revert to it or cancel. It just seems like a really easy-to-use solution for the user.
-
Niklas, Thanks this has been hugely helpful. Your test scripts and database really saved me a lot of time and helped me to get a handle on the issue quickly. Actually your do.php was a pretty outstanding example of using the API with a shell script for automation. I think that I've now got it fixed, though need more eyes on it to be sure. But here are the page creation times with the fixes implemented: Setup 1 do.php test BEFORE populate .. /countries/test7807394638/ saved in 0.008997 seconds .. /countries/test1201072181/ saved in 0.010174 seconds .. /countries/test4845661424/ saved in 0.010104 seconds do.php test AFTER populate .. /countries/test13939a4379/ saved in 0.010184 seconds .. /countries/test8963721816/ saved in 0.010156 seconds .. /countries/test8775327378/ saved in 0.009929 seconds Setup 2 do.php test-repeater BEFORE repeater-populate .. /countries-repeater/test1328343619/ saved in 0.027560 seconds .. /countries-repeater/test1178892434/ saved in 0.029362 seconds .. /countries-repeater/test1854160826/ saved in 0.029181 seconds do.php test-repeater AFTER repeater-populate .. /countries-repeater/test2117723102/ saved in 0.031516 seconds .. /countries-repeater/test1829907000/ saved in 0.031705 seconds .. /countries-repeater/test1610663476/ saved in 0.031419 seconds I didn't take comparisons before, as I went in straight to work once I confirmed there was an issue. However, you had mentioned it would take measurable seconds before, so I'm thinking this is a major improvement? I'm also not surprised by the very slight increase in time after repeater-populate, given that repeater-populate added more than 30,000 pages to the system. One thing is for sure, doing 'do.php populate' is exponentially faster than before. Page names have random numbers in them to prevent ProcessWire's from consuming time in a loop trying to make the page name unique (skewing the numbers). So rather than making each page name 'test', I made it 'test'.mt_rand(); The repeater test pages take at least 2x as long to create as the non repeater test pages because the repeater tests are populating a repeater item on every page creation, which the other test isn't doing. Basically it's creating 2x the number of pages on each call with the repeater test. The Fix ProcessWirePagesParentsFix.zip Can you try replacing your /wire/core/Pages.php and /wire/core/PageFinder.php with the files attached? Please let me know your results. These fix it for me, but this is something with broad scope and it definitely needs more eyes on it. I honestly don't know what I was thinking when I wrote the piece of code you originally quoted from Pages.php. It may have worked in terms of producing accurate results, but had a real legibility problem, and of course the bottleneck problem. Part of the issue was that pages_parents didn't originally come with ProcessWire, so it was added in later. We had to deal with situations where people might not yet have a pages_parents index, which led to some code that doesn't make as much sense in today's context. I went ahead and rewrote that part, among some other details. Test Package Because some of this logic had to be rewritten, I'm naturally concerned about breaking stuff. So I put together a quick test package that runs through various tests to confirm that it's returning all the results it should. This test script assumes the setup you've described with the /countries/ and cities below them. It tries moving around a few pages and executing find()s and confirming it's returning the right pages. I'm including here just in case you or anyone else wants to help test or can think of other tests we should perform with it. This should be placed in /site/ like the do.php script. #!/usr/bin/php <?php include("../index.php"); /** * Perform a find test calling $parent->find($selector) * * If the quantity found is different from the provided $expectedCount * then the test fails. * * @param Page|string $parent Parent page to call find() from, either Page or path (string) to it. * @param string $selector Selector string to use in the find() * @param int $expectedCount Quantity required to PASS the test. * */ function findTest($parent, $selector, $expectedCount) { $expectedCount = (int) $expectedCount; if(is_string($parent)) $parent = wire('pages')->get($parent); echo "\n\t+ pages.get({$parent->url}).find($selector); "; $matches = $parent->find($selector); $numMatches = (int) count($matches); if($numMatches == $expectedCount) { echo "PASS ($expectedCount)\n"; } else { echo "*****FAILED***** ($numMatches != $expectedCount)\n"; } } /** * Move $page to $parent * * @param Page $page * @param Page|string $parent May be parent page object or path to it (string) * */ function movePage($page, $parent) { if(!is_object($parent)) $parent = wire('pages')->get($parent); echo "\n\n----------------------------------------------------------"; echo "\nMoving {$page->path} to {$parent->path}{$page->name}/\n"; $page->parent = $parent; $page->save(); } /** * Start the tests * */ $page = wire('pages')->get('name=country-97'); $selector = "name={$page->name}, has_parent!=/countries-repeater/"; movePage($page, '/'); findTest('/', $selector, 1); findTest('/countries/', $selector, 0); movePage($page, '/countries/'); findTest('/', $selector, 1); findTest('/countries/', $selector, 1); movePage($page, '/countries/country-99/'); findTest('/', $selector, 1); findTest('/countries/', $selector, 1); findTest('/countries/country-99/', $selector, 1); movePage($page, '/countries/country-99/city-1/'); findTest('/', $selector, 1); findTest('/countries/', $selector, 1); findTest('/countries/country-99/', $selector, 1); findTest('/countries/country-99/city-1/', $selector, 1); findTest('/countries/country-99/city-2/', $selector, 0); findTest('/countries/country-96/', $selector, 0); findTest('/countries/country-95/city-1/', $selector, 0); movePage($page, '/countries/'); findTest('/', $selector, 1); findTest('/countries/', $selector, 1); findTest('/countries/country-99/', $selector, 0); findTest('/countries/country-99/city-1/', $selector, 0); findTest('/countries/country-99/city-2/', $selector, 0); findTest('/countries/country-96/', $selector, 0); findTest('/countries/country-95/city-1/', $selector, 0); $countries = wire('pages')->get('name=countries'); movePage($countries, '/about/'); findTest('/countries/', $selector, 0); findTest('/about/', $selector, 1); findTest('/about/countries/country-99/', $selector, 0); findTest('/about/countries/', $selector, 1); movePage($countries, '/'); echo "\n"; Thanks again for your help in finding and debugging this issue.
-
PageAutoComplete fieldtype and hidden pages bug
ryan replied to tiagoroldao's topic in General Support
Okay I think I tracked down what the issue was. Looks like ProcessPageSearch just didn't support include=hidden directives in the AJAX query. I've updated it so that it does, if you want to try the latest. Let me know if this works from that end too? -
Testing for the first element in a wirearray
ryan replied to thetuningspoon's topic in General Support
I think it might be a little better to use a counter just because that is native PHP with no function call (i.e. always faster/more efficient). And you should be able to get that counter right out of the foreach(). foreach($newsChildrenArray as $cnt => $newsChildPage) { $newsImg = $newsChildPage->images->first(); if($cnt == 0 && $newsImg) { $newsImg = $newsImg->size(157,151); // big } else if($newsImg) { $newsImg = $newsImg->size(77,71); // small } // the rest… } -
I'm not sure that I understand the question? But you did say that you want a categories Page reference on the article template. That makes me think that you probably don't want the articles structured under the subcategories and instead maybe they should all be under /articles/ ? Then you'd select categories/subcategories from the Page reference field using something like PageListSelectMultiple or PageAutocomplete. That would at least solve the issue of articles that need multiple categories/subcategories (though not sure if this is something you needed or not?)
-
If I understand correctly, you want to change the rendering context at runtime if certain conditions are met. You have a few options. 1. You could set your $video to have a different template file before calling $video->render(); /site/templates/home.php $video->template->filename = 'teaser.php'; echo $video->render(); 2. Or you could throw up a flag that your video.php template file knows to look for: /site/templates/home.php $input->whitelist('teaser', 1); echo $video->render(); …and in your video.php: /site/templates/video.php if($input->whitelist('teaser')) { include('teaser.php'); } else { // render full video page } The advantage here is that all your video rendering code is together in one file. 3. Or you could include your teaser.php from home.php manually: /site/templates/home.php $_page = $page; $page = $video; // substitute $page before including teaser.php include('./teaser.php'); $page = $_page; // return $page to it's original state 4. Or you could isolate the teaser rendering to a function (perhaps included from another file): /site/templates/functions.inc function renderTeaser($page) { return "<p><a href='{$page->url}'>{$page->title</a><br />{$page->summary}</p>"; } /site/templates/home.php include('./functions.inc'); echo renderTeaser($video); This last option is the one I'd be most likely to take as I think it's probably one of the lowest overhead, it's easily reusable across different templates, and requires very little consideration wherever you use it. Here's another way you can do the same thing, by actually adding a renderTeaser() function to the Page class at runtime: /site/templates/functions.inc function renderTeaser(HookEvent $event) { $page = $event->object; $event->return = "<p><a href='{$page->url}'>{$page->title</a><br />{$page->summary}</p>"; } $this->addHook('Page::renderTeaser', null, 'renderTeaser'); And now you can do this: echo $video->renderTeaser(); Btw that line that has $this->addHook(); has null as the 2nd argument because the function is outside of a class. Some people create auto-loading modules to contain rendering functions, in which case that 2nd argument is the module's object instance.
-
ProcessWire's admin is designed for trusted users. I would avoid a setup that allows any user on the internet to register and then gain access to your admin. I'm not aware of any CMS that I would consider for this type of permission escalation for untrusted users (though possibly Drupal). Instead, I would take a framework approach, whether with ProcessWire or another framework. When users are untrusted, you really want to jail them, limit everything, and lock it down. Kind of the opposite of what you want to do for trusted users. If they are going to be uploading files to the system, you want to limit the size and quantity, so one user can't go fill up the hard drive or launch DDOS attacks by uploading massive files and getting them stuck in GD resizes. Anything uploaded should be stuck in a non-web accessible quarantine area that is only delivered to the authenticated user via a passthru script with a forced mime type. You kind of have to expect the worst when you open up any kind of editing or upload tool to untrusted users. But this is relatively easy to do when you are using a framework (like ProcessWire) and coding for a specific purpose with clear limits.
-
It all sounds good to me. I'm not against it. Though I don't use PHPDoc or any editor that supports it, so the comments you see in the code have always been for my reference (though I think the style is similar to PHPDoc). I'll be honest, I just can't think in code when I'm surrounded by a IDE that has lots to look at and is trying to do stuff for me. So I stick to VIM, full screen. I haven't figured out how to bring in a pull request without files getting deleted or tabs/linebreaks getting screwed up, so I'd prefer to just make any changes here manually. It sounds like what you are wanting is a @property tag for the class properties in Page.php? I'm happy to do this, but have a concern: most of these properties are not API accessible. You don't want these showing up in your IDE unless you are modifying or extending the Page class (which nobody has done yet as far as I know). Likewise, I think we need to be careful with phpdoc for any PW class properties if the intention is to have them show in your IDE. The reason for this is that the vast majority of API accessible properties are handled through PHP magic methods and not actually properties of the classes themselves. If you use the properties of the classes, you'd be getting an very incorrect view of the API. On the other hand, PHPDoc should be totally fine for public functions. Though there are many public functions that are not part of the public API (meaning, PW classes use them internally, but you shouldn't), so relying on an IDE for this stuff would again give an incorrect view of the API that could get one into trouble. I think it's better to use the cheatsheet and PW API documentation for this stuff. Though maybe there are also some phpdoc tags that can help clarify if a public function is for internal (core use).
-
That makes sense as to why Page::render is getting called twice, and your hook is getting called twice. It seems to all be the proper behavior. So like you found, you just need to avoid adding your script twice. I think the way you are doing it is good. Though here's another alternative that might be slightly faster: public function addScripts(HookEvent $event) { static $completed = false; if($completed) return; // ... code to add stuff to the rendered output $completed = true; }
-
The page history module may yet make it into 2.3, but not as a full blown versioning system. However, it may be able to do some of what you mentioned with text fields. But the point of the page history module is more about seeing what changed, when, and by whom. Though tracking this info crosses over into the territory of versioning in many respects. I guess I think of full blown versioning as including all page data (files, repeaters, etc.), but it's a difficult prospect without actually making clones of the pages themselves (like Rob mentioned). Maybe I should stop trying to think about these things and focus on the stuff that can be versioned efficiently. Your idea of a field history is compelling, I need to give some more thought to it.
-
PageAutoComplete fieldtype and hidden pages bug
ryan replied to tiagoroldao's topic in General Support
PageAutocomplete actually uses the ProcessPageSearch AJAX API which doesn't allow "include=all" except from Superuser. This is for security reasons. Try changing it to "include=hidden" which should at least enable you to show the hidden (though not unpublished) pages. -
That extra "?it" parameter is coming from ProcessWire's RewriteRule at the bottom of your .htaccess. Somehow that RewriteRule is getting included in your redirect. But I'm also a loss to know why.
-
Nik, thanks for your efforts in debugging this. I'm impressed by the depth to which you've gone into this part. This is a pretty amazing first post. As you might have guessed, the whole point of this code is to enable the has_parent selector (used by any $page->find() command) to support isolation of results to the structure within. The logic is a little tricky (and makes my head hurt) though thankfully the amount of code behind it is small. I think you are definitely on to something here, and I need to get to where you are. Just to confirm, it sounds like what we have here is an unnecessary bottleneck, but not something more? (i.e. nothing causing incorrect values to be returned from API functions?) You mentioned a noticeable slowdown of seconds when creating a page. I've not experienced this before, despite what I think may be similar structures and scale on my own sites, so the first thing I need to do is reproduce the problem that led you into this. Is it possible for me to get a copy of the database that you are using where the problem appears? (email ryan at this domain or PM me). Or if not, can I see the site map (like one generated by this script). You mentioned countries, cities, states in a hierarchy, which I can certainly figure out how to re-create. But I've already got structures like that on almost every site I make, some which run into the tens of thousands of pages, so I may be missing something key. The closer I can get to the data/structure you are actually working with, the better I can understand and isolate it. Once I fully understand it from your context, it sounds like I'll be able to reproduce it in any context. Have you tried making any code changes consistent with the items you brought up in an attempt to fix? If so, it'd be helpful for me to get a look at that too. Once I can reproduce something similar to what you are getting, I'm going to put a debug timer at the beginning and end of the code segment you pasted, just to confirm that this is the place where the bottleneck is. You may have already done this, in which case let me know and I'll skip that part. Thanks again for your efforts here, Ryan
-
There's a couple spots that it could go, but right now I'm thinking you'd want it in CommentForm.php in the processInput() function, after this line: $this->page->save($pageFieldName); If you are working off the most recent version of the comments module, a findComments() function was added a couple weeks ago. This example assumes your field name is 'comments' and you want to get comment ID 1234: $comment = FieldtypeComments::findComments('comments', 'id=1234')->first();
-
Welcome to the forums Thomas. It's not possible to physically resize an image on another server, but you certainly can resize it with CSS or width and/or height attributes in the <img> tag. But if you want an image you can physically manipulate, then it has to be on a file system that ProcessWire can write to. You can tell ProcessWire to pull an image from another server like this: $page->video_image = 'http://domain.com/path/to/file.jpg'; $page->save(); Once you've got the image in there, you can resize it like in your example. I'm assuming from the non-plural name 'video_image' that you've defined the field to only hold 1 image rather than many. Though the snippet above should work either way. Note you may need to add a $page->of(false); before the code example above if you are executing it from a template file. ProcessWire delivers pages to templates in an output-ready state where entities are encoded and such (something you wouldn't want when saving a page). So you just have to disable output formatting to put the page in a state where it can be saved, otherwise ProcessWire will throw an error. This isn't usually necessary in other API contexts, outside of template files.
-
I'm not sure why you are getting that behavior with the redirects. I don't personally use this style of redirect, so don't know the options available. It does appear that it's getting your redirects mixed in with ProcessWire's rewrite rules. When you use a RewriteRule redirect, you add the 'L' option to it to prevent processing any more rules, like this: RewriteRule ^about-us/?$ /about/ [R=301,L] So I'm thinking you need the equivalent of the 'L' option in your redirects, but I don't know what the equivalent is for that type. If it were me, I'd just use the above style of Rewrite Engine rules, rather than Redirect/RedirectMatch. Though if you prefer the type you are using, I bet there would be a solution in Apache's Redirect/RedirectMatch docs. However, I find these a little difficult to wade through sometimes.
-
Forum post for now, but that's short term. We'll be having a more official modules directory here soon. I plan to manually take everything from the forum to populate the modules directory initially.
-
Absolutely, you are right about that. It's in that unusual state of there not being enough there for me to want to advertise it too far beyond the forum. But the only way to get more there is to advertise it. Sinnut's addition really helped I think. We will definitely include this in the new site's navigation (initially as part of the docs/support navigation).
-
I would like to provide this for sure. But so far I haven't seen that there is any demand for it. I'm sure the time will come. Well if they didn't think it was somehow incomplete, there wouldn't be any real incentive to have a paid package? But I know what you are saying, and that's why I'm not interested in having anything but the pure open source and free ProcessWire. On the other hand, I do like the idea of support packages and commercial modules-- they are special add-ons (separate from the core software) for people that want them. I don't think that having paid support options devalues anything else. The way I see it, it just is a form of insurance… a form of insurance that doesn't exist at present. None of us are obligated to support anything. We do it because we like to. But that's not a good enough answer for a company that depends on their web site. They want to know that if something happens that they can't solve, there is somebody obligated to help them in a timely manner. You can find that here in the forum, but it's based purely on the goodwill of the people here. I think it works now because our community is a nice size where it's easy to keep up with everything. When and if we get to the point of having 10x as many daily posts as we get now, it won't technically be possible for any individual to keep up with them all, at least not nearly as quickly. At that point, I think having more support options becomes a benefit to the perception of the project and helps attract big scale users that might otherwise look elsewhere. So it doesn't hurt to explore how best to handle the growth, even if we don't necessarily need it now. Here are some examples of commercial support for other platforms: WordPress: http://vip.wordpress.com/our-services/ Drupal: http://www.acquia.com/acquia-network-subscriptions MODX: http://modx.com/complete-maintenance-and-support/ Symphony: http://getsymphony.com/get-support/
-
I've found the column choice valuable for getting a different view on things. When you've got 4 columns, you can see a lot at once, but maybe you don't want to get too much into details or copy/paste because, depending on length, you may get wrapping like this: $page ->setOutputFormatting(true|false ) That's perfectly okay and expected, but for me the advantage of being able to reduce columns is that I can eliminate wrapping when I want/need to.
-
@jbroussia: Thanks, I have looked into this and it is a bug. I think it may be possible to make repeater fields cloneable, but they are kind of a unique situation and the factors involved in cloning them are more broad and complex than with others fields. So my solution for this now is going to be to have it detect when it's being cloned and throw an exception to prevent it from happening. Longer term, I should be able to figure out when I've gone 1-2 days to wrap my head around it. @Pete: He's talking about cloning fields rather than cloning pages. We did fix the issue with cloning pages that have repeaters. But cloning actual fields (Setup > Fields > some_repeater > Advanced > Copy/Clone) is a different thing. Hope to find a way to make these cloneable. But since this is the first time it's come up, I don't think there's a lot of demand for cloning repeater fields. Unlike cloning pages with repeaters, which is probably a more common occurrence.
-
Actually it's not going to be in 2.3, but it is a long-term work in progress. Versioning that is respectful of resources is rather complicated, especially when it gets into things like automatically created versions with file-based assets, repeaters, and the like. The system I'm working on keeps the version data outside of the pages system and page tree. But I'm a long way off from figuring out all the details.
-
I'm enjoying the animation here. Though if it were slow I probably wouldn't. But seems to be flying (in Chrome 20): very fast and smooth and just plain fun to use. I get a foundation building block feeling out of this (especially switching in/out of 4-col), and it relates well to ProcessWire as a builders tool. Sometimes it's hard to tell with animation whether it's best for long term. I remember my first exposure to the things going on in OS X and then iOS made me think I'd get annoyed with it. But it grew on me and made me enjoy using it more. Some of the animation in Windows was quite the opposite. Time will tell what the case is with the cheatsheet, and I may be wrong, but thinking it may grow on folks that aren't sure about it. Some of this is eye candy, but it's of the kind that attracts people and makes us look good, IMO. Either that, or I'm just really impressed because I've never seen anything like this before.