-
Posts
17,151 -
Joined
-
Days Won
1,668
Everything posted by ryan
-
Okay i think the problem might be the 766 permission. Most likely Apache is running on a different account than yours. 766 will be fine for config.php, but I would suggest changing /site/assets/ to 777, because it sounds like PHP requires "x" in addition to "rw" in order to be able to mkdir().
-
Adam is right on this. The fact that the installer is finding that the directories are writable, but mkdir() and chmod() are failing indicates something must be disabled. But the way around this is to create the directories and make them writable yourself. Let us know if you need help identifying the directories to create and/or how to make them writable. Thanks, Ryan
-
Strange, I've not heard of this before. But can you check these things: Make sure there isn't actually a directory called /ProcessWire/ (leftover from the zip). If there is, delete it. Try doing the login page with a trailing slash, I.e /processwire/ rather than /processwire. Try a fresh install, just in case. If none of these do it, can you post what PHP ver and your phpinfo result if possible. Thanks
-
Hey guys, this has been posted: https://github.com/ryancramerdesign/ProcessWire/commit/4bc0e1136b067bd663426a544a207aab6704147d Includes the expanded JS title->url converter too. thanks, Ryan
-
Thanks for locating the problem. I broke it earlier this week and fixed it but looks like I must have not committed the updated js file. I will post this when I get back to my computer, within the next hour or so. Thanks!
-
A page can have multiple parents in the hierarchy (parent, grandparent, great grandparent, etc.) of course, but each page only has one direct parent. On the other hand every page can have any number of relations which are also pages. You could think of them as parents or categories but the use/terminology depends on what the relations are used for. Pages are essentially the same thing as nodes in Drupal. Though I don't like the term 'node' because it confuses the heck out of my clients (the people that ultimately have to use the CMS day to day). You can use a page for whatever you want, it doesn't have to be a literal page on the site. Though every page has a URL, which you can think of as it's GUID. Whether you choose to render content at that URL or not is up to you. I prefer URLs as a globally-unique identifier (GUID) because that's the way search engines treat them, possibly even penalizing the same content at multiple URLs (a part of my full time job is search accessibility and optimization). But of course ProcessWire will let you pull content from anywhere and do whatever you want to with it, but it at least associates that content as having a primary association with a page's URL. You are right that you can select multiple pages using any of it's properties, and then process them in any way you want. To generate a site map, see these: http://processwire.com/talk/index.php/topic,26.0.html http://processwire.com/api/include/ Note that there is definitely overhead with what ProcessWire does in translating selectors to queries, finding them, and creating resulting Page objects. I think it's well worth it. But if you are used to selecting just what you want in SQL and generating a giant site map directly from that, you might be disappointed in the performance if dealing with lots and lots of pages (and/or lots of autojoined fields on those pages). If I'm generating a site map for a large site, I'm usually caching the output so that it doesn't have to be generated on each view. While I've done my best to optimize all of this, I'm sure there is still lots of room for optimization. For instance, I am pushing an update today or tomorrow for better caching selectors and their resulting PageArrays. I'm always tweaking this to make it better/faster, and the more eyes on it, I think the better performance we'll get out of it. Yes, namespacing is badly needed for those that plan to include ProcessWire for it's API from other apps and CMSs. But to do that, we have to drop PHP 5.2 support, and I don't think it's safe to do that quite yet. Thanks, Ryan
-
I came to the same conclusion as Adam looking at this. I've not seen the DOCUMENT_ROOT show as something different from checking the dirname(__FILE__) before. But clearly it's possible for that to happen, since we're seeing it here. So it's something that ProcessWire should probably be on the look out for. But I think it might be best for me to do more research and make sure this isn't an anomaly just specific to your server. So in the short term, I do think it's best to set the rootURL manually in the /index.php file. But will do more research here, unless anyone else knows if this is a known situation on some servers. Thanks for the debugging you did in finding this.
-
This topic has been moved to FAQs. [iurl]http://processwire.com/talk/index.php?topic=44.0[/iurl]
-
Publish/Unpublish by date or by manual means.
ryan replied to moondawgy's topic in Wishlist & Roadmap
I will add this to the roadmap as a module (most likely a Fieldtype). But also wanted to follow up with the strategy that I use, and the reason why the need for a publish/unpublish by date has not come up before. It's because this is so easily accomplished with the API. It also gives you more control over how you want to react to such dates. Here's how: Lets say I added a datetime field for publish date and/or expire date to the page's template. That template checks it's dates before it outputs anything. For example, lets assume we added a datetime field to the job_advert template called "expire_date". When we edited a job advert, we selected an expire_date of February 1, 2011. The job_advert template might have code like this: <?php if(time() > $page->getUnformatted('expire_date')) { throw new PageNotFoundException(); // or print "sorry this posting has expired" if you don't want a 404 } // print the job advert Note that the getUnformatted() function above is necessary because ProcessWire will return a formatted date according to the format you specified when you created the field, "January 21, 2011" as an example. So using getUnformatted() is a way to ensure it returns a timestamp that you can use for comparison with other timestamps (like that returned by PHP's time() function). (Btw, that getUnformatted() function works with any field, returning text fields without entities or runtime markup, etc.) Lets say that you also wanted to move the job advert to the trash since we now know it's expired: <?php if(time() > $page->getUnformatted('expire_date')) { $pages->trash($page); // move it to the trash throw new PageNotFoundException(); } // print the job advert Next lets assume that you didn't move the page to the trash, but you are just leaving it where it is and not disabling it or anything. If that's the case, you'll want to check that it's not expired when you links to your job adverts. So here's what your list_job_adverts template might look like: <?php $now = time(); $jobs = $page->children("expire_date>$now"); foreach($jobs as $job) { // print the job listing, being confident that it's not expired } If you had both expire_date and publish_date fields, then you'd find those jobs with something like this: <?php $now = time(); $jobs = $page->children("expire_date>$now, publish_date<=$now"); -
Nicolasbui, Thanks for your feedback. I am impressed at the level with which you've looked into ProcessWire. I am sure there is lots of room for optimization and improvement in many parts of the software, as the code is all quite young. And finding ways to optimize it is one of the most satisfying parts in my opinion. It's also one of those things that I am very thankful to have people like yourself helping me with. I would like the software to be as optimized as possible. I agree with you in theory about using a nested set model. While I've not spent a lot of time using the nested set model, I am fascinated by it. But let me explain why I believe the adjacency tree model is preferable in ProcessWire. Then, please correct me if you think I'm wrong on any points. • I view the nested set model as a compromise for the structure because it would take it far away from the methodology of the API. Currently, the structure matches the terminology and methodology of the API, maintaining a good level of consistency between DB and API. I believe this makes it more accessible for one to understand should they want to break out of the API and into MySQL. While it's rare for someone to need that in producing a site, I think it may be more common for people developing modules. • With the queries used for page rendering and common API use, there are very few in ProcessWire that would actually benefit from the nested set model (at least from my understanding of it, and in the sites I produce). And, I may be wrong on this, but I can only identify one page render dependent query that would see a possible performance boost, and this query is most commonly executed only once per request. Not to mention, it's already quite fast, executing in 0.0003s (average, on my server with a page 3 levels deep). • On the sites that I run, the database is rarely the bottleneck. Of course, this will depend on how you are using the API. But most of ProcessWire's queries avoid the tree entirely and are instead focused on the fields that make up a page. • Should the query mentioned above ever become a bottleneck due to a change in the API or new needs, etc., the plan is to index page paths in the database or in the file system cache. While a compromise to support speed over normalization, the result would be faster than the nested set model. Of course, it would also be more expensive when moving a page, but an okay compromise. • I think the nested set model may show measurable performance improvement in ProcessWire with sites that have very deep hierarchies. However, such a level of depth is unusual on web sites and not that practical to manage. I don't believe I've ever gone more than 4 or 5 levels deep in a hierarchy, even on a very large site. Even if you see a URL deeper than that (at least on my sites), there's a good chance it's one or more urlSegments used for branching and not an actual page in the hierarchy. These are the reasons why I think the current model is preferable in ProcessWire, but there may be more benefits to the nested set model applicable to ProcessWire than I realize. Let me know your thoughts. I'm always interested in creating a new branch to experiment if it makes sense. Thanks again for taking the time to look at ProcessWire in this depth. Thanks, Ryan
-
Thanks for your report, I think this makes sense and will put it on the issues to fix list. I guess that I've never had to revert back to blank for this, but it's clear we need to be able to do that.
-
I agree with Adam. It does sound to me like something is going on with the apache configuration because where would /var/www/ be coming from if it didn't exist? I'm guessing it must be somewhere in the apache config. If you'd like, post your phpinfo or email it to me, and that may provide some answers.
-
Some questions (Plus tutorials on how to support multiple languages)
ryan replied to Snakehit's topic in General Support
Not yet, but we'll be making language packs for the admin, and it'll be pretty easy to do (well, the code side of it). The language part I'll need help with. I wasn't sure how many would be interested, so that wasn't high on the list before, but there does seem to be interest so it will probably be implemented sooner rather than later. -
I totally agree, I think that's a good suggestion. I'm adding it to my list.
-
Sounds like we were typing at the same time. Your reply was waiting right after I posted. It sounds like you are talking about just the image sizes used in the rich text editor. In that case, ProcessWire is not resizing the source image (it's creating copies), so you are good there. But my understanding is that you think it should limit the size of the image that shows up in the textarea, so that you aren't trying to scale an image back that's 5x bigger than your screen. I think that makes sense, good idea.
-
Thanks for the feedback! In Mozilla-based browsers (and in IE8) you can resize directly in the editor. In webkit based browsers, you can't resize in the editor because Webkit apparently doesn't support selectable divs (or whatever method is used by TinyMCE). However, you can still resize easily just by clicking the image, clicking the image icon, and then the popup should still let you resize. Then click "insert image" and it'll put in the image resized and resampled at your dimension. Regardless of what method you use, the result will be the same, and the code it runs through on the back end is the same. Regarding image size, I always recommend uploading images in the largest possible size you might ever need, so that all of your resizes are starting from a really high quality source. Typically you don't actually use them at that size in your output. Instead, you specify the dimension, ProcessWire will create the resized version and cache it for future use. For example, this is how you might print out a gallery of thumbnails that links to larger sizes, like you might use with lightbox script: <?php foreach($page->images as $image) { $large = $image->width(500); $thumb = $image->size(100, 100); echo "<a href='{$large->url}'><img src='{$thumb->url}' /></a>"; } You can create as many size variations of an image as you want. I believe this is preferable to having it resized to some target dimension in the admin because all of your size variations can start from the source.
-
Kristof, thanks for your message. Good to hear from Belgium! If you are looking for front-end user interaction like you see with Drupal, that's something that ProcessWire doesn't emphasize very heavily at this stage. In part, because I am a fan of Drupal for that type of site. That will change with time as more and more ProcessWire modules become available. On the other hand, you certainly can do any type of front-end features that you want if you don't mind some development. An example can be found on the Templates and API forum where a user asked about how to save contact form submissions as pages. And there was also a question about how to setup pages that users could login to: http://processwire.com/talk/index.php/topic,22.0.html http://processwire.com/talk/index.php/topic,17.0.html This sort of stuff is pretty easy to do in ProcessWire, but to do these things you have to think of ProcessWire as a tool that you can make it do whatever you want, rather than as a ready-to-go solution. With time, we will provide lots of good starting points for these things, including multiple site profiles.
-
Two things to try: On your first server, check that .htaccess is working. You can tell by editing it and putting in some junk characters on the first line, like "alfkjeafkljaeglk". Then save. Load the site. If you get a 500 Internal Server Error, then your htaccess is being read by Apache. If you don't, then Apache isn't reading your .htaccess file. I'm guessing that's the case. To fix it, add this to the entry in your httpd.conf vhosts section: AllowOverride All OR, copy all of ProcessWire's .htaccess directives and just put them in your vhost section of httpd.conf. I recommend doing AllowOverride All so you don't have to go manually update your httpd.conf when you do upgrades. On the second server, we can find what the problem by editing /site/config.php and changing $config->debug = false, to $config->debug = true. Reload and it'll tell you what's up. Paste the error in here. Thanks, Ryan
-
It sounds like you might not have Apache mod_rewrite, or you maybe missing the /.htaccess file in your ProcessWire/site root dir. Also, check to make sure that you don't have an empty dir called "ProcessWire" off of your installation. If you do, just remove it. That can happen if you move all the files out of the default dir it unzips to, which I sometimes do... and because it's the same name as the default admin login URL, it can show you a 404 rather than the admin page. Let me know what you find.
-
Some questions (Plus tutorials on how to support multiple languages)
ryan replied to Snakehit's topic in General Support
The admin and error messages use language, and we will be getting more language packs for those in the future. For everything else, ProcessWire is language agnostic and doesn't use language. It doesn't currently provide any specific tools for handling multiple languages (though we are looking at that). But for now, I think there are a lot of approaches you could take, and Adam's approach looks like a good one. Here's another one below. One of the main differences from Adam's example is that this one keeps separate fields for each language on one page, rather than separate pages for each language. Which approach is better will depend on your needs. Using field names and URL segments to display different languages Use the $input->urlSegment API variable to check if the last segment of the URL is "es" or "fr" or "en", and have your template output the corresponding field. For example, lets say you had a page called /about/ and these are the language versions you supported (and their corresponding URL): /about/ - The default language page, we'll assume English /about/es/ - The Spanish About Page /about/fr/ - The French Language Page The /about/ page is used as an example, but this could be for any and/or all pages in your site. You could set it up for one template and use it site-wide. You would create fields corresponding to the languages that you want to support, and add them to your template. For instance, you might have a field called "body_en" for English, and then one called "body_es" for Spanish, and "body_fr" for French. If you are dealing with lots of fields, you may want to separate them with ProcessWire's FieldsetTab field type, which will place each language version on a separate tab, compartmentalizing each language on it's own screen. Then you would enable URL segments for your template(s) in the admin (Setup > Templates > Advanced). That ensures that your /about/ page won't show up as a 404 page when someone accesses /about/es/ (because the es/ doesn't actually exist). The API code in your template might look like this: <?php // other languages that we support in addition to English $languages = array('es', 'fr'); // check if the current page's URL ends with a language if(in_array($input->urlSegment1, $languages)) { // A known language was specified in the URL, so save it in our session. // Now that language will be the default even if URL doesn't have a language specified. $session->language = $input->urlSegment1; } if($session->language) { // Get the body field for our custom language $body = $page->get("body_" . $session->language); } else { // get the body field for our default language (English) $body = $page->body_en; } // Output the body text echo "<div id='bodycopy'>$body</div>"; That will ensure that the correct language is always displayed. They would only have to hit the /about/es/ page once (or any /es/ page), and the rest of the site would then assume it's displaying Spanish regardless of the URL. Likewise, if they hit /about/fr/ next, the site is now in French. If you preferred it, you could relegate this functionality to some gateway page where they had to select a language. But I prefer it this way because they can make language changes on the fly (not that people do that, hehe). Optional: Locking a language to a URL It may be that you want to lock your language setting to a URL so that English always displays at /about/ and never Spanish or French. Likewise, the only time you'd see Spanish is if you were at the /es/ version of a given page. The advantage of this approach is that you may get 3 different language versions of your site indexed in Google, and it will be possible for you to use template caching. (You wouldn't be able to use template caching in the previous example because it's session-dependent rather than URL-dependent). To do this, you would just need to pay attention to the language setting when you create your navigation, and you would update the example above to show the "body" field according to the validated $input->urlSegment1 and not $session->language. Here's how you might create language-aware navigation: <?php // determine our language (or reuse the other example) if(in_array($input->urlSegment1, array('es', 'fr'))) $language = $input->urlSegment1; else $language = 'en'; $topnav = $pages->find("parent=/"); foreach($topnav as $item) { $url = $item->url; // if language isn't English, add a language segment to the end of the URL if($language != 'en') $url .= $language; echo "<li><a href='$url'>{$item->title}</a></li>"; } Optional: Redirecting to the selected language If you didn't want to always consider language when drawing navigation, you could also just set your pages to redirect to the proper language version if they access the generic /about/ page when the language is Spanish. You'd want to store your language in $session->language again, like in the first example, and perhaps use that instead of the $language variable in the second example. <?php if($session->language && !$input->urlSegment1) { $session->redirect($page->url . $session->language); } I'm not exactly sure what you mean by indent lines. Can you attach a screenshot example or rough mockup of what you mean? While ProcessWire doesn't yet have a public-site form collection feature, making a contact form is a pretty easy thing to do. See this post for an example: http://processwire.com/talk/index.php/topic,22.0.html Though I think in most cases, you would probably want to email the submitted form rather than saving it to a page, i.e. mail("you@company.com", "Contact Form Submitted", $message); For an image gallery, Adam is right on. Also see this: http://processwire.com/talk/index.php/topic,16.0.html -
Good question. You can use the built-in append() and/or prepend() functions, i.e. $menu_items->prepend($home); I believe the unshift syntax will also work: $menu_items->unshift($home); You don't have to worry about modifying the site tree because you would have to actually save a $page in order to modify the site tree. In addition, none of what you are trying to do here is modifying any pages, so even if you did save a page, it wouldn't modify anything. PageArrays are runtime dynamic arrays, and aren't saved anywhere unless they are attached to something like the result of a Page reference Fieldtype. See /wire/core/Array.php and /wire/core/PageArray.php for all the traversal and modification methods (there are a lot). For the most part, they are patterned after jQuery traversal methods, but I included alternate names like unshift() and shift() for people that prefer PHP function names. Just for fun, here's your example all bundled on one line. foreach($pages->find("parent=/navigation/")->prepend($pages->get('/')) as $item) { ... Or even shorter: foreach($pages->find("parent=0|/navigation/") as $item) { ... The selector above is saying to find all pages that have no parent (i.e. "0") or have a parent called /navigation/. Both examples above return the same result. That won't work because the function only accepts one param (a selector). But because all the pages you are selecting there have the same parent, this would work: $pages->find("parent=/, name=navigation|page|item2");
- 1 reply
-
- 2
-
-
Adam, writing this up got me to thinking that I may be able to write a tool that would automate most of deployment, by creating an installation profile. The tool would be in the ProcessWire admin, and when you run it, it would create a installation profile of your site. Then, you would just upload the profile to your server, and then load it in your browser (which would initiate the installer). I kind of like this idea because it would handle any file permissions and export + import of the database automatically. Are you aware of any other CMSs that do something like this? (I'm not). I'll put more thought into this.
-
MIGRATING A SITE FROM DEV TO LIVE FOR THE FIRST TIME STEP 1: Copy all the files from dev to live Copy all the files from your dev installation to the live installation. Whether you use FTP, rsync, scp or some other tool doesn't matter. What does matter is that you copy everything and that you retain the file permissions–More specifically, make sure that /site/assets/ and everything in it is writable to the web server. Here are some sample commands that would do it (replace 'local-www' and 'remote-www' with your own directories): Using rsync: rsync --archive --rsh=/usr/bin/ssh --verbose /local-www/* user@somehost.com:remote-www/ Using scp: scp -r -p /local-www/* user@somehost.com:remote-www/ Using FTP/SFTP/FTPS: I think this will depend on the FTP client that you are using as to whether it will retain the permissions of the files you transfer. Hopefully it will do that by default, if not, check if there is a setting you can enable. If not, then you may have to adjust the permissions manually in /site/assets/ and the dirs/files in there. Make sure /.htaccess got copied over too Depending on how you copied the files in step 1, it may or may not have included the /.htaccess file. Make sure that gets copied over, as many copying tools will ignore hidden files by default. STEP 2: Transfer the MySQL Database from Dev to Live a. Export dev database Export ProcessWire's database on your development server to a MySQL dump file. PhpMyAdmin or mysqldump are the most common ways to do it. Personally I use PhpMyAdmin because it's so simple, but here is how you would do it with mysqldump: mysqldump -u[db_user] -p[db_pass] [db_name] > site.sql b. Create live database Create a new MySQL database and database user on the live server and make note of the DB name, DB host, DB user and DB pass, as you'll need them. c. Import dev database to live Import the MySQL dump file you exported from your dev server. Most web hosts have PhpMyAdmin, so that's what I use to import. If you have SSH access, you can also import with the mysql command line client, i.e. mysql -u[db_user] -p[db_pass] -h[db_host] [db_name] < site.sql d. Update /site/config.php On the live server, edit the /site/config.php file. At the bottom you will see the database settings. Update these settings to be consistent with the database/user you created, then save. Most likely you will only be updating these settings: $config->db_name = "database name"; $config->db_user = "database user name"; $config->db_pass = "database user password"; $config->db_host = "database host, most commonly localhost"; STEP 3: Test the Site Your site should now be functional when you load it in your browser. If not, then enable debug mode: a. Turn on debug mode (only if you get an error) Edit /site/config.php and look for $config->debug = false, and change it to $config->debug = true. Then load the site in your browser again and it should give you more details about what error occurred. If you aren't able to resolve it, contact Ryan. b. Browse the site Assuming your site is now functional, browse the site and the admin and make sure that everything looks as it should. If it looks like stylesheets aren't loading or images are missing, it may be displaying cached versions from your dev site. If that's the case, you need to clear your cache: c. Clear your cache (optional) Login to the ProcessWire admin and go to Modules > Page Render > and then check the box to "clear page render disk cache", then click save. This should resolve any display issues with the site's pages. d. Practice good housekeeping Make sure that you've removed /install.php and /site/install/, and check to make sure that /site/config.php is not writable. While this isn't absolutely necessary, it's good insurance.
-
Publish/Unpublish by date or by manual means.
ryan replied to moondawgy's topic in Wishlist & Roadmap
moondawgy, I expanded on your question with a better answer here: http://processwire.com/talk/index.php/topic,35.0.html -
This article introduces multiple concepts in ProcessWire, as goes far beyond the stated subject. The subject of "unpublishing a page" serves as an ideal context, so I encourage you to read the entire article (particularly strategy #2) as it introduces the concepts of role inheritance and API-level page access, among other things. To unpublish a page without permanently deleting it, use any one of the following 4 strategies (whatever suits your needs best). STRATEGY #1: MOVE THE PAGE TO THE TRASH This is the most straightforward strategy, but does involve moving the page. I don't permanently delete pages very often, so I use the trash as more of an archive. When you delete a page in the ProcessWire admin, it just moves the page to the Trash. You can still move it back out at any time (as long as you haven't emptied it first). Pages in the trash are not web accessible and don't appear in search results, so it's a good place to unpublish your page. To unpublish your page in this manner, just delete it by clicking it's 'delete' tab, checking the confirmation box, and saving it. The page will be waiting for you in the trash. You can also trash pages directly in the Page List view: 1. Go to the Page List view by clicking "Pages" in the top right corner. 2. Click the Trash page to open it. 3. Click the page you want to unpublish, you should see a 'move' link. 4. Click the 'move' link, and then drag the page into the trash. From the API you can trash pages by calling $pages->trash($page); STRATEGY #2: REMOVE THE GUEST ROLE FROM THE PAGE: This strategy includes a few factors to consider, but has the benefit of not having to move the page. First off, here's how you do it: 1. Edit the page and click on it's 'Settings' tab. 2. Locate the 'Roles' section near the bottom. 3. Uncheck the 'guest' role. 4. Consider checking the 'hidden' status too (see the section further down). 4. Save. The 'guest' role implies anonymous site users. If there are any other roles still checked, those are the only roles that can view the page. If no roles are checked, then only the 'superuser' role can view the page. Note that the page can still appear in API function results (like in your navigation), so you should also check the 'hidden' status too if you don't want that (see section further down) OR check access in the API (see the API section further down). Note about Role Inheritance This note is only applicable to a Page that has children. Page roles are inherited through the site tree, so unchecking a 'guest' role on /about/company/ unchecks it on any children and grandchildren, and so on. For example, if you removed the 'guest' role from /about/company/, then these page would also be inaccessible to 'guest': /about/company/staff/ /about/company/staff/mike/ But you can still turn the 'guest' role (or any other role) back ON at any of the descending pages. For instance, you could turn the 'guest' role back on at /about/company/staff/ and that page would be accessible, even if it's parent isn't. Because roles are inherited, enabling 'guest' at /about/company/staff/ also enables it for /about/company/staff/mike/, but the same rules about inheritance apply no matter where you are in the site structure. As a result, removing the 'guest' role from the homepage would unpublish the entire site from public access, unless any other pages had specifically set a different behavior. Note about Roles and the API In the ProcessWire API, the results of any page selection functions like get(), find(), children() and siblings() do not consider roles. If the page is in your site tree, you can still select it in the API. This is so that you can continue to use assets from a page in the API even if the page isn't a URL on your site. 'Page' is just an abstract term that doesn't have to refer to a page on your site... You might be using a page for storage of shared image assets, site configuration or other items that you don't want to be directly accessible via a URL, but you DO want to utilize in the API. As a result, if you don't want a page showing up in navigation or search results you can do one of 2 things: 1. Check the box for the 'hidden' status (described in the section further down). 2. Or, when generating your navigation, check if the page is viewable before outputting the link via $page->isViewable(). For example: <?php foreach($page->children() as $child) { if($child->isViewable()) echo "<a href='{$child->url'}'>{$child->title}</a> "; } I don't like having to check access from the API, so recommend doing #1 over #2. In the near future, automatic access checking will be an option you can optionally enable with API functions (should you want to). STRATEGY #3: USE THE HIDDEN STATUS Another alternative to unpublishing (via removing the 'guest' role) is to enable the 'hidden' status in the page editor. You may want to use this instead of–or in addition to–removing the 'guest' role, depending on your needs. This strategy doesn't technically unpublish the page unless you combine it with strategy #2. But this strategy is often a better solution then just unpublishing, as there is no danger of breaking any on-or-off site links. 1. Edit the page you want to hide and click on it's 'Settings' tab. 2. Locate the 'Status' section. 3. Check the box for 'Hidden'. 4. Save. A hidden page is excluded from the API find(), children() and siblings() results, which essentially excludes it from any of your dynamically generated navigation. It's not excluded from get() results, so the page can still be accessed by it's URL or loaded individually from the API. This means the page isn't going to show up in your navigation or search results, but loading the URL directly will still load the page. STRATEGY #4: JUST DON'T DO IT (IF YOU DON'T HAVE TO) As a best practice, I always encourage my clients not to delete or unpublish pages if they don't have to. For example, let old news items drift further down the list rather than deleting them... someone, somewhere may be linking to it. Every link to your site carries value, even if the page being linked to isn't recent. At least the user gets what they expected and can explore further in your site. And Google will still count that link as a vote in favor of your pagerank. Delivering a 404 page is less likely to result in that outcome for the user or your site. Of course, there are lots of good reasons to delete or unpublish a page, so I'm just speaking about the situations where you don't necessarily have to delete it. Another alternative is to setup 301 redirects for pages that you delete or unpublish, but I won't cover that here unless someone wants me to.