-
Posts
16,772 -
Joined
-
Last visited
-
Days Won
1,530
Everything posted by ryan
-
Pete is right that if you had fields you didn't want to be editable to your client, then you probably wouldn't keep them as fields at all. Instead, you'd handle such things from your template file. Though I think there is value in having field-level access, and so Soma is also right that it'll be coming to ProcessWire soon. In fact, it's a real simple fit with the existing system. I suppose the only reason we don't have it yet is just that it hasn't really come up as a common need, and we tend to be pretty cautious about adding too much (always balancing simplicity with capability). That's a great module example that Soma put together there too, seems like a good way to solve it at least until the capability is in the core.
-
Is that a valid URL? I'm not sure a variable name in a URL can have a slash in it, but I could be wrong. However, I don't think that the Redirects module will get the opportunity to ever see that URL. /index.php is a file that already exists (ProcessWire's /index.php). So requesting it directly sidesteps the whole Apache rewrite engine which maps non-existing file requests to ProcessWire. I think that the only way you could setup a redirect for a URL like this is by using Apache Rewrite rules in your .htaccess file.
-
Good point, thanks for finding that. You are right that they should use $video->filename rather than $video->setFilename()
-
I've actually got a lot of updates coming for the PageLinkAbstractor module that I was working on in the last couple weeks. Beyond what it was doing before, it now also keeps track of links to deleted pages and deleted images or files. When it detects a broken link, it logs it and optionally emails you about it. It also keeps an alert at the top of the admin screen telling you about it with a link where you can go review it. Once you fix the broken links, it detects they've been fixed and clears the log of fixed errors. Here's a couple of scenarios that it covers: 1. Page /about/ links to /products/widget/ in the bodycopy (TinyMCE) field. The /products/widget/ page gets moved to the trash or deleted. Since /products/widget/ is gone (or at least, not accessible to anyone) the link is broken. 2. Page /about/ has a photo embedded in the bodycopy (TinyMCE) from the /events/bbq-fest/ page. That photo gets deleted from the /events/bbq-fest/ page, which causes a broken image to appear on the /about/ page. Both of those errors would be automatically detected, logged, and emailed to you, so that you can fix them. It's keeping track of this stuff so that internal broken links don't ever get to persist. With these additions, the module is also being renamed to LinkMonitor, LinkNotifier or LinkPolice (still deciding), as I think PageLinkAbstractor is just a bit too technical sounding.
-
wire("config")->urls->admin in autoload module init() not available?
ryan replied to Soma's topic in General Support
The reason for this is that init() is called before any pages are loaded. This is so that you have the opportunity to hook into things like $pages->find and expect that your hook won't miss any pages. The admin URL is dependent upon what name you've given to your admin root page. I think it's actually the only $config->urls property that wouldn't be ready in init(). Given that, I could probably come up with a way for it to be populated before init() (like a direct SQL query)--I think I will do this. However, for now, I'm thinking that you probably want to use ready() rather than init(), since the requested page and admin page have been loaded by that time. When you use ready(), the API is fully ready without exception. But for some autoload modules, the events they wanted to hook into have already occurred, or their hook was attached after $page was already loaded by $pages->find(), etc. I think it's best to start with ready(), and if it's not doing what you need, switch to init(). Also, this may be obvious after the above, but if your module needs to access any kind of data or function from the loaded $page, then ready() is the only way to go. -
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).