Jump to content

ryan

Administrators
  • Posts

    16,484
  • Joined

  • Last visited

  • Days Won

    1,464

Everything posted by ryan

  1. To unpublish by manual means (without deleting or moving the page), edit a page, click on it's 'settings' tab and uncheck the 'guest' role from the access section. There isn't a tool in the CMS to automate unpublishing by date. Though it could easily be implemented with a little API code (let me know if I can provide an example?). However, I always encourage my clients not to delete or unpublish pages if they don't have to, unless they plan to setup 301 redirects. 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. Delivering a 404 page is less likely to result in that outcome. 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 to unpublishing is to enable the "hidden" status in the page editor (also on the Settings tab). A hidden page is excluded from 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.
  2. I agree, and the system is designed for it, just haven't implemented yet. Stay tuned.
  3. That's a good idea, though I'm not sure that I've found an ideal method. I should probably be using Git for this, but I don't yet know it well enough. When I referred to deployment in the other messages, I was talking about deploying the source code to Git, rather than deploying a staging to production site. But here's how I do it. For live sites, I use rsync to deploy my staging files to the server's files. i.e. at the OS X command line: rsync --archive --rsh=/usr/bin/ssh --verbose site/templates/* user@somehost.com:www/site/templates/ This is made easier if your SSH keys are setup so that you don't have to type your password every time you do this. I selectively rsync the /site/templates/ or /wire/ dirs, depending on what I'm upgrading. If I'm upgrading the version of ProcessWire, then I rsync the /wire/ dir. If I'm upgrading the site's functionality (usually via it's templates, stylesheets and scripts) then I'll rsync /site/templates/. I don't do full-site rsyncs because the version of data on my dev server is usually older than the version of data on the live site. Though I do daily backups, I don't mirror my dev copy's data with the server's... unless there's a reason to do so. My deployments are usually code related, not database related. So to safely deploy upgrades to a live site, I specifically avoid rsyncing any files in /site/assets/ or any database files. Instead, I just keep rotating backups of those and manually upgrade my dev site to the latest version of that data whenever it makes sense to do so. Even though I'm using rsync, which makes this all a lot nicer, quicker and more secure, there's not a major difference between this method and just FTPing the files. So you can see I don't have a perfect system for this. I'm interested to learn other people's methods.
  4. What you suggest should work just fine. Using your example, here's how I'd code it in your /site/config.php: Solution 1: <?php // If your dev/staging isn't 'localhost' then replace it with // whatever $_SERVER['HTTP_HOST'] is on your dev server: if($_SERVER['HTTP_HOST'] == 'localhost') { // staging or dev server $config->dbHost = 'localhost'; $config->dbName = 'pw2_blank'; $config->dbUser = 'root'; $config->dbPass = 'root'; $config->dbPort = '3306'; } else { // live server $config->dbHost = 'something'; $config->dbName = 'db_name'; $config->dbUser = 'db_user'; $config->dbPass = 'db_pass'; $config->dbPort = '3306'; } Solution 2: Another solution, and the one I use is to maintain a separate config file called /site/config-dev.php. ProcessWire looks for this file, and if it sees it, it'll use that rather than /site/config.php. If you just keep this file on your staging/dev server, and not on your live server, then that solves it for you. I also added that file to my .gitignore file so that it doesn't ever get deployed, while /site/config.php does. To get started with this method, just copy your /site/config.php to /site/config-dev.php (on your staging/dev server) and change the database settings in the config-dev.php version.
  5. Just wanted to add a couple more notes to this: Using a 'summary' field I think it's more common on this type of page that you would display a summary of the news story rather than the whole news story... and then link to the full news story. To display a summary, you could have a separate 'summary' field in your template, which would be just a regular textarea field where you would have a 1-2 sentence summary of the article. And you would display this 'summary' field rather than 'body' field in the news_index template. If you wanted to autogenerate a summary from the 'body' field (rather than creating a new 'summary' field), you could just grab the first sentence or paragraph of the body and use that as your summary. This is how I usually do something like that: <?php // make our own summary from the beginning of the body copy // grab the first 255 characters $summary = substr($story->body, 0, 255); // truncate it to the last period if possible if(($pos = strrpos($summary, ".")) !== false) { $summary = substr($summary, 0, $pos); } That's a really simple example, and you may want to go further to make sure you are really at the end of a sentence and not at an abbreviation like "Mr." In the example that Moondawgy posted, it makes sense to autogenerate a summary (if he needed it). But in other cases, the 'body' can be quite long (and take up a lot of memory), and it makes more sense to maintain a separate summary field if you have to keep a lot of pages loaded at once. This is really only an issue once you get into hundreds of pages loaded at a time. It's not an issue in these examples, but I just wanted to point it out. Autojoin Using the 'autojoin' optimization can increase performance on fields that get used a lot. Not using it can reduce the page's memory footprint. What is more desirable in each instance depends on your situation. In this news section example, the date and body fields would benefit from having 'autojoin' turned ON. See this page for an explanation: http://processwire.com/talk/index.php/topic,32.0.html
  6. What does autojoin do? Using the 'autojoin' optimization can increase performance on fields that get used a lot. Not using it can reduce the page's memory footprint. What is more desirable in each instance depends on your situation. What sites should use autojoin? Autojoin is most applicable with larger sites. On smaller sites, there may be no benefit to using it or not using it. But it's good to know what it's for regardless. Where do you control autojoin? Autojoin is controlled per-field. You can turn it on by editing each field under Setup > Fields > [your field], and you'll see it under the 'Advanced' heading. When should you use autojoin? Autojoin causes the field's data to be loaded automatically with the page, whether you use it or not. This is an optimization for fields that you know will be used most of the time. Fields having their data loaded with the page can increase performance because ProcessWire grabs that data in the same query that it grabs the Page. Autojoin is a benefit for fields that are always used with the Page. This is best explained by an example. Lets say that you have a template for individual news stories called news_story. The news_story template has these fields: title date summary body sidebar We'll assume that when you view a page using the news_story template, all of the fields above are displayed. Fields that should have autojoin ON: Now consider a separate news_index template that displays ALL of the news stories together and links to them. But it only displays these fields from each news story: title* date summary In this case, the 3 fields above would be good to autojoin since they are used on both the news_index and news_story templates. If your title, date and summary fields didn't have autojoin turned on, then ProcessWire wouldn't go retrieve the value from the database until you asked for it it (via $page->summary, for example). Because the news_index template displays all the stories at once, and always uses the title, date and summary fields, it will perform better with title, date and summary having autojoin ON than with it OFF. In this case, it reduces the query load of the news_index template by 3 for each news story. To take that further, if it were displaying 20 news stories, that would mean 60 fewer queries, which could be significant. Fields that should have autojoin OFF: Now lets consider the body and sidebar fields, which are only used on the news_story template: body sidebar It would be desirable to leave autojoin OFF on those fields because there is no reason for the body and sidebar to be taking up space in memory when they are never used on the news_index template. While it might mean 2 fewer queries to view a news story, that is not significant and certainly not a worthwhile tradeoff for the increased memory footprint on the news_index template. Keeping autojoin OFF reduces a page's memory footprint. Conclusion Using the 'autojoin' optimization can increase performance on fields that get used a lot. Not using it can reduce the page's memory footprint. What is more desirable in each instance depends on your situation. But if your situation doesn't involve lots of pages or data, then you don't need to consider autojoin at all (and can generally just leave it off). Additional Notes Not all fields have autojoin capability. You won't see the option listed on fields that don't have the capability. *The title field has autojoin on by default, so you don't need to consider that one. It was included in the examples above because I thought it's omission might cause more confusion than it's inclusion. Be careful with multi-value fields that offer autojoin capability (page references and images, for example). Because MySQL limits the combined length of multiple values returned from a group in 1 query, autojoin will fail on multi-value fields that contain lots of values (combined length exceeding 1024 characters). If you experience strange behavior from a multi-value field that has autojoin ON, turn it OFF. If you want to play it safe, then don't use autojoin on multi-value fields like page references and images.
  7. I forgot to reply to your question about sorting by the "team" field. If that's the way you always want them to sort by default, then I would suggest just setting that as your sort field when you edit the page (in the Children tab). When you do that, they'll always be sorted that way on any children() call, unless you've specified a different sort. But if you want to keep them alphabetical in the admin, and sorted by Team followed by the person's full-name (stored in the 'title' field) on the front end, you can have ProcessWire sort them for you when you load the staff members: $staff = $pages->get("/about/staff/people/")->children("sort=team, sort=title"); If "team" is a page reference, like in the optional section in my previous message above, then your selector will look a little different: $staff = $pages->get("/about/staff/people/")->children("sort=team.name, sort=title"); The selectors above are saying to sort by team first, and the person's full-name (stored in the 'title' field) second. In the second example, "team" is a page reference. Since a page contains lots of different fields, we have to tell it which one, so we're telling it to sort by the "team" page's "name" field.
  8. This example assumes that you've already setup your fields and added the pages just as they are in the link you posted. It further assumes that each staff member is a page living under /about/staff/. An example would be: /about/staff/jemma-weston/ I'm going to assume these are the field names: title (person's first/last name) image (person's photo) team (per your link) job_title (per your link) email (per your link) To output the staff listing, you would do something like this: <?php echo "<ul class='staff_listing'>"; foreach($page->children as $person) { if($person->image) { // make 150px wide thumbnail $image = $person->image->width(150); $img = "<img src='{$image->url}' alt='{$person->title}' />"; } else { // make some placeholder (optional) $img = "<span class='image_placeholder'>Image not available</span>"; } echo " <li> $img <ul class='staff_detail'> <li><strong>Name:</strong> {$person->title}</li> <li><strong>Team:</strong> {$person->team}</li> <li><strong>Job Title:</strong> {$person->job_title}</li> <li><strong>Email: {$person->email}</li> </ul> </li> "; } echo "</ul>"; Optional Another scenario might be where you want the "team" or "job_title" fields to be from a pre-selected list, and to themselves be pages that the user could click on to view everyone in the "Audit and Accounts" team (for example). The way you would set that up is to create a new structure of pages that has all of the Team types (and/or Job Titles), like this: /about/staff/teams/audit-and-accounts/ /about/staff/teams/business-services/ /about/staff/teams/practice-management/ ...and so on... I would use a new template for your team types, something like team_type.php. The only field it needs to have is a title field. At the same time, I would move your staff members into their own parent like /about/staff/people/. In your staff template, you would make the "team" field be of the field type called "Page". When you create it, it will ask you from what set of pages you want it to use, and you can tell us to use /about/staff/teams/. It'll ask you if you want it to hold one page or multiple pages (PageArray), and you'll want to choose Page (one page). Likewise, where it asks you to select what type of input field you want to use, choose a "Select" or "Radio Buttons" field (and not one of the multiple select types). Add that new "team" field to the staff template, and when you add/edit a staff member, you'll select a team rather than type one in. Your markup to output that field in the staff listing would instead be like this: <li><strong>Team:</strong> <a href='{$person->team->url}'>{$person->team->title}</a></li> Your team_type.php template would list the staff members just like your main /about/staff/ page did (it might even be good to include your staff_list template to do it for you, or convert it to a reusable function), except that it would load the staff members like this: <?php $staff = $pages->get("/about/staff/people/")->children("team=$page"); foreach($staff as $person) { // ... } The main thing to note in the example above is the "team=$page" portion, which is essentially telling ProcessWire to pull all staff members that have a "team" field that points to the current page. So if you are viewing the /about/staff/teams/audit-and-accounts/ page, then it's going to load all staff members that are in that team.
  9. It looks like your news page example displays 4 stories per page, and it displays the full bodycopy for each of them. Assuming the same structure and fields of the pages you posted, you'll need to create two templates: 1. news_index.php 2. news_story.php You can call those templates above whatever you want, but just note that we'll use a separate template for news stories and news items. Your news_story template will contain the following fields: 1. title 2. date (date) 3. body (textarea) And it's markup might look like this (excluding headers, footers, etc.): /site/templates/news_story.php: <h1><?=$page->title?></h1> <div id='bodycopy'> <?=$page->body?> </div> Not much action there. Your news_index is where most of the action will happen. In Admin > Setup > Templates > news_index > Advanced, check the box for "Page Numbers" to turn them on. Save. Here is what the code in your news_index.php might look like: /site/templates/news_index.php: <h1><?=$page->title?></h1> <?php // start the news stories list echo "<ul>"; // get the stories for this page $stories = $page->children("limit=4, sort=-date"); // note if you set the stories to sort by date descending on the /news/ page // in the admin, then you can omit the "sort=-date" above. // cycle through each story and print it in a <li> foreach($stories as $story) { echo " <li><a href='{$story->url}'>{$story->title}</a> <p><strong>Date:</strong> {$story->date}</p> {$story->body} </li> "; } echo "</ul>"; // get values for our placemarker headline $start = $stories->getStart(); $end = $start + count($stories); $total = $stories->getTotal(); $num = $input->pageNum; $lastNum = ceil($total / $stories->getLimit()); // output the placemarker headline echo "<h4>Showing $start - $end of $total Article/s | Page $num of $lastNum</h4>"; // output pagination links echo $stories->renderPager(); Lets say that you don't want the pagination links that renderPager produces, you can always modify it's output by passing params to it. See the page about pagination here: http://processwire.com/api/modules/markup-pager-nav/ But if you want your literal "previous" and "next" buttons like on your site, then you'll want to insert your own logic to do that. Something like the next example. Note I'm reusing the vars I set in the previous example here for brevity. This snippet would replace the renderPager() method in the previous example. <?php // make the previous link if($num > 2) $prevLink = "./page" . ($num-1); else if($num == 2) $hrefLink = "./"; // page 1 else $prevLink = "./page" . $lastNum; // last page // make the next link if($num >= $lastNum) $nextLink = "./"; // page 1 else $nextLink = "./page" . ($num+1); // output the prev/next links: echo "<p><a href='$prevLink'>Previous</a> <a href='{$nextLink}'>Next</a></p>"; Disclaimer: the examples on this page are just written off the top of my head and are not actually tested examples. You'll likely have to tweak them. In particular, you may have to add or subtract 1 in a few places to get the right numbers. If you end up adapting this, please let me know of any errors I have here so that I can correct them.
  10. I like the potential of selecting multiple items and then being able to manipulate them as a group. For instance, like in a file system where you draw a selection around multiple files and then double click them, or move them, delete, etc. Though I think of pages on a web site as very different from files on a filesystem, because moving (where the URL changes) or deleting pages should be discouraged. One could break a whole lot of on-or-off-site links very easily. Still, this would be really handy in some cases, so I will be thinking about ways to implement it, perhaps combined with the tool described below… Actually ProcessWire 1 had something like this, called the "find change pages tool". But it was very different in how you selected pages. The way it worked is that it presented a comprehensive search engine to you, which covered every possible field that a page could have (it would create a selector for $pages->find behind the scenes). Then it would present the results and give you the option of modifying any field on the group of found pages. That field could be internal (like template or parent) or could be any of your custom fields. I haven't yet built this in PW2 because it's need doesn't come up so often, and when it does, it is so simple to do from an API shell script. But I know that the majority of people would not want to go create a shell script to modify pages in bulk, so this tool will make a reappearance in PW2 at some point. Thanks, Ryan
  11. See the guide and examples in the ProcessWire MarkupPagerNav module information at: http://processwire.com/api/modules/markup-pager-nav/
  12. Creating a sitemap is fairly easy in ProcessWire. The strategy we use is to get the page where we want the sitemap to start (like the homepage), print out it's children, and perform the same action on any children that themselves have children. We do this with a recursive function. Below is the contents of the sitemap.php template which demonstrates this. This example is also included in the default ProcessWire installation, but we'll go into more detail here. /site/templates/sitemap.php <?php function sitemapListPage($page) { // create a list item & link to the given page, but don't close the <li> yet echo "<li><a href='{$page->url}'>{$page->title}</a> "; // check if the page has children, if so start a nested list if($page->numChildren) { // start a nested list echo "<ul>"; // loop through the children, recursively calling this function for each foreach($page->children as $child) sitemapListPage($child); // close the nested list echo "</ul>"; } // close the list item echo "</li>"; } // include site header markup include("./head.inc"); // start the sitemap unordered list echo "<ul class='sitemap'>"; // get the homepage and start the sitemap sitemapListPage($pages->get("/")); // close the unordered list echo "</ul>"; // include site footer markup include("./foot.inc"); The resulting markup will look something like this (for the small default ProcessWire site): <ul class='sitemap'> <li><a href='/'>Home</a> <ul> <li><a href='/about/'>About</a> <ul> <li><a href='/about/child1/'>Child page example 1</a> </li> <li><a href='/about/child2/'>Child page example 2</a> </li> </ul> </li> <li><a href='/templates/'>Templates</a> </li> <li><a href='/site-map/'>Site Map</a> </li> </ul> </li> </ul> Note: to make this site map appear indented with each level, you may need to update your stylesheet with something like this: ul.sitemap li { margin-left: 2em; } The above sitemap template works well for a simple site. But what if you have some pages that have a "hidden" status? They won't appear in the sitemap, nor will any of their children. If you want them to appear, then you would want to manually add them to the what is displayed. To do this, retrieve the hidden page and send it to the sitemapListPage() function just like you did with the homepage: <?php // get the homepage and start the sitemap // (this line is included here just for placement context) sitemapListPage($pages->get("/")); // get our hidden page and include it in the site map sitemapListPage($pages->get("/some-hidden-page/")); What if your sitemap has thousands of pages? If you have a very large site, this strategy above may produce a sitemap with thousands of items and take a second or two to generate. A page with thousands of links may not be the most helpful sitemap strategy to your users, so you may want to consider alternatives. However, if you've decided you want to proceed, here is how to manage dealing with this many pages in ProcessWire. 1. First off you probably don't want to regenerate this sitemap for every pageview. As a result, you should enable caching if your template in: Admin > Setup > Templates > Sitemap > Advanced > Cache Time. I recommend setting it to one day (86400 seconds). Once you save this setting, the template will be rendered from a cache when the user is not logged in. Note that when you view it while still logged in, it's not going to use the cache… and that's okay. 2. Secondly, consider adding limits to the number of child pages you retrieve in the sitemapListPage function. It may be that you only need to list the first hundred child pages, in which case you could add a "limit=100" selector to your $page->children call: <?php // this example takes place inside the sitemapListPage function. // loop through the children, recursively calling this function for each: foreach($page->children("limit=100") as $child) sitemapListPage($child); 3. Loading thousands of pages (especially with lots of autojoined fields) may cause you to approach the memory limit of what Apache will allow for the request. If you are hitting a memory limit, you'll know it because ProcessWire will generate an error. If that happens, you need to manage your memory by freeing groups of pages once you no longer need them. Here's one strategy to use at the end of the sitemapListPage function that helps to ensure the memory allocated to the child pages is freed, making room for another thousand pages. <?php function sitemapListPage($page) { // ... everything above omitted for brevity in this example ... // close the list item echo "</li>"; // release loaded pages by telling the $pages variable to uncache them. // this will only uncache pages that are out of scope, so it's safe to use. wire('pages')->uncacheAll(); }
  13. Right now it doesn't have anything that does this automatically. I left it out because I didn't want to assume what action you would want to take with duplicate page names, or what format you would want to use in generating a unique name. But perhaps I should add it as an optional module.
  14. The URL name ($page->name) has to be unique for all sibling pages (i.e. pages having the same parent). I don't usually bother with checking unless ProcessWire throws an error about it during the import. If it looks like there are going to be duplicates, then here's how you'd ensure uniqueness by adding a number to the end of the URL name and keep incrementing it until it's unique: Replace this: $skyscraper->name = $building; $skyscraper->save(); With this (overly verbose for explanation purposes): <?php // just to turn on the forum syntax highlighting // Converts "Sears Tower" to "sears-tower" when setting the name $skyscraper->name = $building; // Retrieve the URL name, which should be "sears-tower" $name = $skyscraper->name; // a counter incremented each time a duplicate is found $n = 0; // find the first non-duplicate name while(count($parent->children("name=$name")) > 0) { $n++; $name = $skyscraper->name . $n; // i.e. sears-tower1, sears-tower2, etc. } // set the page's name to be one we know is unique, i.e. sears-tower1 $skyscraper->name = $name; // now when ProcessWire checks uniqueness before saving, it won't throw an error. $skyscraper->save();
  15. ProcessWire provides a 404 page that you can modify and customize like any other (and you'll see it in your Page List). ProcessWire will automatically display that 404 page when someone attempts to access a non-existent URL. If one of your templates is configured to show a 404 page when the user doesn't have access to a page (in Templates > Template > Advanced Settings), then it'll show your 404 page in that instance as well. There may be instances where you want to trigger 404 pages on your own from your templates. To do this, use the following PHP snippet: throw new PageNotFoundException(); Once you do that, processing of your template will stop and ProcessWire will send a 404 header and display the default 404 page instead (thereby passing control to your 404 template). Please note: you should throw the PageNotFoundException before outputting any content in your template.
  16. I exported a CSV file from freebase.com that contained all the skyscraper fields I wanted to use. Then I created a simple shell script to import it from the CSV. Note that I only used a shell script for convenience, you could just as easily do this from a ProcessWire template file if you preferred it or needed to do this from Windows, etc. Below is a simplified example of how to do this. The example is fictional and doesn't line up with the actual structure of the skyscrapers site, nor does it attempt to create page relations or import images. If you are interested in how to do that, let me know and I'll keep expanding on the example in this thread. But I wanted to keep it fairly simple to start. First, here is the contents of a CSV file with each line having a skyscraper building name, city, and height. /skyscrapers/skyscrapers.csv (Building, City, Height): Sears Tower, Chicago, 1400 John Hancock Tower, Chicago, 1210 Empire State Building, New York City, 1100 IBM Building, Atlanta, 860 Westin Peachtree, Atlanta, 790 Next, create a new template in ProcessWire and call it "skyscraper". Create a text field for "city", and an integer field for "height" and add them to the skyscraper template. Create a page called "/skyscrapers/" in ProcessWire, that will serve as the parent page for the skyscrapers we'll be adding. Here is the command-line script to load the ProcessWire API, read the CSV data, and create the pages. As I mentioned above, this could just as easily be done from a template, where the only difference would be that you wouldn't need the shebang (#!/usr/local/bin/php -q) at the beginning, nor would you need to include ProcessWire's index.php file. /skyscrapers/import_skyscrapers.sh: #!/usr/local/bin/php -q <?php // include ProcessWire's index file for API access // (this isn't necessary if you are doing this from a template file) include("./index.php"); $fp = fopen("./skyscrapers.csv", "r"); $template = wire('templates')->get("skyscraper"); $parent = wire('pages')->get("/skyscrapers/"); while(($data = fgetcsv($fp)) !== FALSE) { // create the page and set template and parent $skyscraper = new Page(); $skyscraper->template = $template; $skyscraper->parent = $parent; // set the skyscraper fields from the CSV list($building, $city, $height) = $data; $skyscraper->title = $building; $skyscraper->city = $city; $skyscraper->height = $height; // set the URL name, i.e. Sears Tower becomes "sears-tower" automatically $skyscraper->name = $building; // save the skyscraper $skyscraper->save(); echo "Created skyscraper: {$skyscraper->url}\n"; } To do the import, make the script executable and then run it from the command line: chmod +x .import_skyscrapers.sh ./import_skyscrapers.sh OR, if you are doing this from a template file, then load the page (that is using this template) in your web browser, and that will execute it. The output should be: Created skyscraper: /skyscrapers/sears-tower/ Created skyscraper: /skyscrapers/john-hancock-tower/ Created skyscraper: /skyscrapers/empire-state-building/ Created skyscraper: /skyscrapers/ibm-building/ Created skyscraper: /skyscrapers/westin-peachtree/ If you go into the ProcessWire admin, you should see your skyscrapers. I used an example of CSV file for simplicity, but the same method applies regardless of where you are pulling the data from (web service feeds, etc). For the actual skyscrapers demo site, I used Freebase's web services feed to pull the data and images, etc.
  17. ProcessWire's Database class is extended from PHP's mysqli, so when you access the database you are dealing with a mysqli connection and all the functions and info in the PHP manual applies. From all templates, the $db instance is scoped locally, so you can do this (example): $result = $db->query("SELECT id, name, data FROM some_table"); while($row = $result->fetch_array()) print_r($row); From modules or any class extended from Wire, the database is scoped via $this->db: $result = $this->db->query("SELECT id, name, data FROM some_table"); while($row = $result->fetch_array()) print_r($row); From outside ProcessWire classes or in any regular function or non-ProcessWire class, the database can be accessed from the wire(...) function: $result = wire('db')->query("SELECT id, name, data FROM some_table"); while($row = $result->fetch_array()) print_r($row); Intended for use outside of ProcessWire templates and classes, that wire() function also provides access to all of ProcessWire's API variables, like $page, $pages, and others... i.e. wire('pages')->find('selector'); Also I should mention that I've never needed to use the $db from my templates. While it's there and ready for you to use, it's always preferable (not to mention easier and safer) to use ProcessWire's API for accessing any of it's data. If you need to create a connection to another database, then make a new mysqli connection (or PDO, regular mysql, etc, according to your preference). ProcessWire does not try to replace PHP's database functions. Good question, I've written a reply, but going to paste in a new thread so that the subject line matches the content.
  18. Thanks Jim! Glad that worked. Sorry for the typos. I have corrected the typos in the original post.
  19. I think your idea is a good way to go. Setting up a page structure should be a fine place to maintain submitted forms. And it should be fairly easy to do. Just be aware that in your templates, you have full unrestricted access to the API. As a result, you shouldn't include anything like: where the page is saved (i.e. parent page or template) in the data that is user-submitted, unless it's well validated. Ideally, the data that gets saved should go exclusively in custom fields that you've created, rather than any built-in ProcessWire fields. For all custom fields that contain text, make sure you enable the entities text formatting filter. You'll see the text formatting filters in the field settings when you create it. I also recommend performing some basic validation/sanitization of the data before adding it to the page's fields. Granted there is built-in validation, but I always recommend sanitizing/validating data when it's first accessed, even if it will be done again later automatically. Lastly, make sure the parent page where you'll be storing these contact submissions has the guest role removed (via the 'settings' tab on the page). You don't want these submissions being accessible to anyone but you. Here's a basic example to get started. This assumes that you are storing submissions in a child page of /form-results/ and that you have a template called "contact_submission" that contains the fields we are populating. We're populating the name, title (with the fullname field), email and comments fields into a page, then saving it. /site/templates/contact-form.inc <form action='/contact/' method='post'> <p> <label>Full Name</label> <input type='text' name='fullname' /> </p> <p> <label>E-Mail</label> <input type='email' name='email' /> </p> <p> <label>Comments</label> <textarea name='comments'></textarea> </p> <p> <input type='submit' name='submit' value='Submit' /> </p> </form> /site/templates/contact.php <?php if($input->post->submit) { // create a new Page instance $p = new Page(); // set the template and parent (required) $p->template = $templates->get("contact_submission"); $p->parent = $pages->get("/form-results/"); // populate the page's fields with sanitized data // the page will sanitize it's own data, but this way no assumptions are made $p->title = $sanitizer->text($input->post->fullname); $p->email = $sanitizer->email($input->post->email); $p->comments = $sanitizer->textarea($input->post->comments); // PW2 requires a unique name field for pages with the same parent // make the name unique by combining the current timestamp with title $p->name = $sanitizer->pageName(time() . $p->title); if($p->fullname && $p->email) { // our required fields are populated, so save the page // you might want to email it here too $p->save(); echo "<h2>Thank you, your submission has been received.</h2>"; } else { // they missed a required field echo "<p class='error'>One or more required fields was missing.</p>"; include("./contact-form.inc"); } } else { // no form submitted, so render the form include("./contact-form.inc"); }
  20. For pages that you want to automatically direct to the first child, I would make them use a template that has this in it's code: <?php if($page->numChildren) $session->redirect($page->child()->url); If you add a new child page, and they aren't sorted by date added (descending) then the newly added page isn't likely to be the first child. If no default sort is selected, then it'll add it as the last child. So if you wanted to redirect to last page (rather than the first) then you'd want to do this: <?php if($page->numChildren) $session->redirect($page->child("sort=-sort")->url); By the way "sort" is just a name representing the order that pages have been dragged/dropped to. That's the default value if a page doesn't have another field selected as it's default sort field. The minus sign "-" in front of it represents descending sort. Without the minus sign, it would be ascending. Since you want to select the last page, that's why it's "-sort" rather than "sort". Or if you wanted to make it select the most recent page added by date: <?php if($page->numChildren) $session->redirect($page->child("sort=-created")->url); The above is also descending since most recent added would be the highest date.
  21. As for adding a class to the current page based on the current's page URL name and the parent's URL name, you have many options. Here's how I might do it, by adding a class to the body tag that would make it possible to target any element on a page in your CSS: <body class='<?php echo "page_{$page->name} parent_{$page->parent->name}"; ?>'> If you were on a page with URL "/news/press/", the result would look like this: <body class='page_press parent_news'> Rather than assigning body classes based on the current page and current parent, I usually find it's more convenient to assign a class based on the current section, or "root parent page" as it's called in ProcessWire. Typically these section pages are those that appear in the top navigation. Having every page identify it's section via a <body> class means we can easily highlight the current section in the navigation with our CSS. So here's how I might output the body tag: <body id="section_<?php echo $page->rootParent->name?>"> If we were on the page /news/press/, the result would be: <body id="section_news">
  22. Dynamic navigation is just a matter of finding the group of pages you want to output, and then loop through them to print the links. For example, if you wanted to print the top navigation: <ul id='topnav'> <?php foreach($pages->find("parent=/") as $p) echo "<li><a href='{$p->url}'>{$p->title}</a></li>"; ?> </ul>
  23. The skyscraper example is probably not the best one to look at. I built that SkyscraperList.php just to maximize the efficiency and appeal to coders, but it probably made it a lot less clear to designers. It's not necessary to take that approach. The templates that come with the basic/default site are probably a better place to start. As far as how to output lists of content, typically you'd follow a process like this: 1) Find the pages you want to output, typically with $pages->find(...) 2) Loop through the results and output the fields you want. Example: <?php $news_items = $pages->find("parent=/news/"); echo "<ul>"; foreach($news_items as $item) echo "<li><a href='{$item->url}'>{$item->title}</a></li>"; echo "</ul>"; If you wanted ProcessWire to do the above for you, you could do this: <?php echo $pages->find("parent=/news/")->render(); (basically added the "render" to the end of the pages find) I prefer to generate my own markup for clarity and control, so don't recommend the shortcuts (like above), especially when you are learning. But just wanted to let you know they are there. These two pages provide more detail and examples of how to output lists: http://processwire.com/api/templates/ http://processwire.com/api/selectors/ With the documentation, I've tried to balance it between the needs of designers and developers, so if you see something you don't understand, don't feel bad about skipping it ... it will probably make sense later, or feel free to reply with questions or post in the forum. With regard to lists of images, I haven't yet finished this part of the documentation, but it is similar to working with pages. However, you would need to have an images field to work with in your page's template. I will assume you have one called "images" (like in the demo site). If we wanted to print the images from the current page as 100x100 pixel thumbnails linked to full size versions, we would do this: <?php foreach($page->images as $img) { $t = $img->size(100, 100); echo "<a href='{$img->url}'><img src='{$t->url}' alt='{$t->description}' /></a>"; }
  24. The user functions in ProcessWire are probably the least developed part so far, but I think it can still accomplish what you want pretty well. Since ProcessWire assigns permissions to roles rather than users, I think the best bet is instead to create one role for clients, and then we'll check if they have access on the actual page by comparing the page name to the user name. 1. To start, create a new role in Access > Roles, call it "client" (or whatever), and give it just "ProcessPageView" permission. 2. Next create a new page that will be the parent of the protected client pages, i.e. "/clients/". Once the page is created, click on the "settings" tab of that page, uncheck the "guest" role, and check the "client" role. Save. 3. Now create a new template and associated page that will serve as the login form. I'd call the template "login" and the page "/login/". The logic for the login template should be something like the following: /site/templates/login.php <?php if($input->post->login_submit) { // process submitted login form $name = $sanitizer->username($input->post->login_name); $pass = $input->post->login_pass; if($session->login($name, $pass)) $session->redirect("./"); else echo "<h2>Login failed, please try again.</h2>"; } if($user->isSuperuser()) { // display links to all the client pages echo $page->children()->render(); } else if($user->isLoggedin()) { // redirect to client page, if it exists $private = $pages->get("/clients/{$user->name}/"); if($private->id) $session->redirect($private->url); else echo "<p>Your page is not yet setup.</p>"; } else { // display the login form include("./login_form.inc"); } I split the login form into a separate file, though you could mix it into the login.php file above too. Below is the markup for the login form: /site/templates/login_form.inc <form action='/login/' method='post'> <p><label>Username <input type='text' name='login_name' /> </label></p> <p><label>Password <input type='password' name='login_pass' /> </label></p> <p><input type='submit' name='login_submit' value='Login' /></p> </form> 4. Now create the client template, that will serve as an individual user's private page. I would call it "client", and insert logic like the following: /site/templates/client.php <?php if($user->name != $page->name && !$user->isSuperuser()) throw new PageNotFoundException("No access"); // if we made it here, you are good to display their photos 5. Now setup your client pages and client users. Create each client page under /clients/, and make it use the client.php template we setup above. The client's username and page URL name must match, so if your client's username is michelle, then her client page should be /clients/michelle/. Also, when you create each client's user account, give each one the "client" role we created in step 1. 6. I think that will do it. Let me know if this makes sense or if you have any questions. Also, I'm doing this all off the top of my head (while also checking the API code), so let me know if you find I'm off on any parts too.
  25. To interact with the images field. Create a new field (under Setup > Fields) and call it whatever you want, like "images" for example. Set the field type as "image". Add this field to a template (under Setup > Templates). Then edit a page using that template and upload some images. In your template code: ProcessWire will create your images at any size on the fly and then keep a cache of them. For instance, lets say we want to cycle through all the images, create a large image at 500 pixel width with proportional height, and a thumbnail at 100x100, and then have the thumbnail link to the large: <?php foreach($page->images as $image) { $large = $image->width(500); $thumb = $image->size(100, 100); echo "<a href='{$large->url}'><img src='{$thumb->url}' alt='{$thumb->description}' /></a>"; }
×
×
  • Create New...