Source Code for /public/searchindex.json
[
{
"id": "\/",
"kind": "page",
"title": "Home",
"body": "Welcome to Dan Q's PHP Cookery Class\nLong ago, it was commonplace for personal websites to be\nmildly dynamic. Most of their content would be static\nHTML, but it'd be \u2728enhanced\u2728 with a sprinkle of server-side code (often written in PHP, or sometimes Perl).\nWhere personal websites exist today, they often fall into the extreme ends of the scale. They're\neither completely static - often from a static site generator like Jekyll - or else powered by a\nheavyweight CMS system like WordPress.\nI think that's a shame, so I wrote this \"cookery class\" to teach fellow Web Revivalists how to write PHP code on their\nown websites; to add a bit of dynamic magic and unlock a world of creative possibilities.\nIf you're ready to get started, use the navigation bar to\nstart with the introduction, where we'll check that you've got everything you need to start\nwriting PHP code, and get a basic understanding of how PHP works before we move on to\ncook our first dish!\nPhilosophy of the Class\nThis is not a recipe book. A recipe would be like \"How to make a Guestbook\". You'll find recipes here, and you can follow\nthem if you like, but the real purpose of this class is to teach you how to make your own recipes.\nLearning a programming language like PHP is harder than just copy-pasting code from a recipe, but it's worthwhile because\nwhen you're done you'll be able to modify other people's recipes, to invent your own dishes, and\nto continue your learning journey.\nThere are lots of guides to learning PHP, but this one comes specifically from the focus of Web Revivalism. The\nexamples it gives are all geared towards building and enhancing a personal website as you migrate from a completely static\nHTML site to a \"mildly dynamic\" PHP-powered one.\nThis site itself is written in PHP. I've made the full source code available:\nyou can view the source code of any file right here, or you can browse\nthe GitHub repository. All the code is released into the public\ndomain, so you're free to use it for your own projects and you don't have to give credit.\nAbout the Author\nI'm Dan Q. I've been developing for the Web since the mid-1990s, and writing \"mildly dynamic\"\nsites since about the the same time. My first \"mildly dynamic\" features were contact forms and hit counters implemented in Perl,\nbut the turn of the millenium I'd moved on to PHP. My career has taken me in and out of writing PHP daily, but it always holds a\nspecial place in my heart for its gentle learning curve and the simplicity of starting a new project."
},
{
"id": "\/01-intro\/",
"kind": "page",
"title": "Lesson 1: Introduction",
"body": "Objectives\n\nWork out whether you're ready to start writing PHP\nUnderstand the difference between static HTML and PHP-enhanced HTML\n\nKnow that when you see a "
},
{
"id": "\/02-hello-world\/closed-on-mondays.php",
"kind": "code",
"title": "closed-on-mondays.php",
"body": "<?php\r\n\/*\r\n * date('w') returns the day of the week as a number:\r\n * 0 is Sunday, 1 is Monday ... 6 is Saturday etc.\r\n *\/\r\nif( date('w') === '1' ) {\r\n \/*\r\n * This code tells the browser to show a \"403\" error page.\r\n * 403 is the error code for \"Forbidden\"!\r\n *\/\r\n header('HTTP\/1.1 403 Forbidden');\r\n\r\n echo \"This page can't be read on Mondays\";\r\n\r\n \/*\r\n * The die(); command stops the PHP script immediately!\r\n * No code after this will be run or sent to the browser.\r\n *\/\r\n die();\r\n}\r\n?>\r\n\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <title>The Non-Monday Page<\/title>\r\n<\/head>\r\n<body>\r\n <h1>It's not a Monday<\/h1>\r\n <p>\r\n It's not a Monday, so you can read this page! Lucky you!\r\n <\/p>\r\n<\/body>\r\n<\/html>"
},
{
"id": "\/02-hello-world\/hello-world.php",
"kind": "code",
"title": "hello-world.php",
"body": "<?php\r\necho 'HELLO WORLD backwards is ';\r\necho strrev( 'HELLO WORLD' );"
},
{
"id": "\/02-hello-world\/",
"kind": "page",
"title": "Lesson 2: Hello World",
"body": "Objectives\n\nTest that PHP is working on your server\nOpen and close PHP blocks with \nUse the echo command to print out text\nUse the strrev function to reverse a string\nUse the date function to get the current date\nUse an if statement to do something conditionally\n\nHello World\nFind an out-of-the-way place on your webserver and create a file called hello-world.php.\nInside it, put the following code:\nVisit your new page in your browser, e.g. at http:\/\/yoursite.example.com\/hello-world.php.\nYou should see the text \"HELLO WORLD backwards is DLROW OLLEH\".\nIf so: hurrah! PHP is working!\n(Help! PHP isn't working!)\nWhat did I just create?\nThe code you wrote did the following:\nBegan a PHP block with \nand it'll tell you!\nClosed on Mondays!\nYou could use this to close a page to visitors on Mondays. Here's closed-on-mondays.php:\nSee how we can mix-and-match between HTML and PHP code in the same file. Only the HTML (and anything\nechod by PHP) will be sent to the browser.\nIn this example, we use an if statement to check if today is Monday. If it is, the code inside the\nif block's { ... } is executed. That code sets the HTTP status code to 403 (which means\n\"Forbidden\"), prints a message, and then runs the die(); command to stop anything else being done."
},
{
"id": "\/02-hello-world\/today.php",
"kind": "code",
"title": "today.php",
"body": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>What day is it?<\/title>\r\n<\/head>\r\n<body>\r\n <h1>Happy <?php echo date('l'); ?>!<\/h1>\r\n <p>\r\n Today is <?php echo date('l'); ?>. That's my favourite day of the week!\r\n <\/p>\r\n<\/body>\r\n<\/html>"
},
{
"id": "\/03-lists-and-loops\/7-times-table.php",
"kind": "code",
"title": "7-times-table.php",
"body": "<h1>The 7-times table<\/h1>\r\n<p>\r\n <?php\r\n \/\/ $i is our variable (it's a bit like algebra)\r\n \/\/ It'll go from 1 to 10, adding one (++) each time\r\n for( $i = 1; $i <= 10; $i++ ) {\r\n \/\/ Print out the question...\r\n echo \"7 \u00d7 $i = \";\r\n \/\/ ...and the answer...\r\n echo ( 7 * $i );\r\n \/\/ ...and a HTML line break\r\n echo \"<br>\";\r\n }\r\n ?>\r\n<\/p>"
},
{
"id": "\/03-lists-and-loops\/",
"kind": "page",
"title": "Lesson 3: Lists and Loops",
"body": "Objectives\n\nCreate a variable and assign it a value\nUse a for loop to repeat a block of code a certain number of times\nUse a foreach loop to iterate through each item in an array\nUnderstand that arrays can be indexed by numbers (the default) or by strings\nUse a glob() function to turn a list of files into an array\n\nLooping\nPHP has several ways of looping through the same code several times.\nA for loop lets you specify the number of times the loop should run. The\nsyntax is a little gnarly if you've not come across for loops in any\nlanguage before, but you'll get to grips with it with practice.\nLet's create a new file called 7-times-table.php to show off how well we\nknow our 7-times table:\nHere's what's happening:\nOur for loop creates a variable called $i and assign it the value 1\nWhile the value of $i is less than or equal to 10, the code inside the for loop is executed, and then the value of $i is increased by 1 (++)\n\nWe print out the question \"7 \u00d7 $i = \"\n\n\nSee how PHP lets us use the variable right in the middle of the string: this is called\nstring interpolation,\nand it works for strings in double quotes (\") but not for strings in single quotes (')\n\n\n\nThen we calculate the answer ( 7 * $i ) and echo it out\nFinally, we echo a HTML line break ()\nDoing your 7-times table up to ten sevens isn't so impressive, but what if you made it go to a hundred sevens. Or a thousand?\nIt's possible to put loops inside other loops. Can you work out how to print out all the 1-times tables, 2-times tables,\n3-times tables, etc., up to 12-times-12? You'll need a loop inside another loop, with the \"outer\" loop using a different\nvariable name than the \"inner\" loop.\n(\ud83c\udfc1 view solution)\nA basic gallery\nAnother kind of loop is the foreach loop. This iterates through each item in an array.\n(An array is a kind of list of values.) Arrays in PHP can be defined either using square brackets ([])\nor the array() function: either way, the items in the array are separated by commas (,).\nHere I've made an array listing different fruits. For each fruit\nin the array, I exit my PHP code (?>) to start writing HTML code. I write an \ntag for each fruit, using the fruit's name as part of the filename and to help write the alt-text:\nLoops are great when you want to do a very similar thing multiple times, like when you're outputting a list\nof links or the images in a gallery.\nInstead of showing each image, could you make an ordered list ( and ) of\nhyperlinks (...) to each fruit's image?\n(\ud83c\udfc1 view solution)\nBy default, an array in PHP is indexed by numbers (starting from 0). If you wanted to print the name of third fruit\nin the array, you could write . But it's also possible to index an array by\nsome other identifier, such as a string. This means it's possible to make an array like this:\nMaking a navigation menu\nLet's use what we know to write some PHP that prints out a navigation menu for our site. A navigation menu is a perfect\nexample of something that may require the same repetitive structure several times over:\nThis site uses a navigation menu like this, but with a few enhancements. If you're curious, you can\nview the source code to see how it's done (look\nin the add_navbar() function).\nBecause we're working in PHP code, we can edit our array dynamically. For example, we could add a link to\nthe page we made that's closed on Mondays to our navigation menu... but only\nif it's not a Monday.\nTo do this, we use an if statement to check if today is Monday. If it isn't, we use PHP's\n[]= operator to add a new link to our array (another option would be the\narray_push() function):\nThe same approach could be used to add menu items that are only visible to logged-in users, for example.\nYou'd still want to check if they're logged-in when they visit the page behind the link, of course, to prevent\nthem from simply typing its URL into their browser's address bar!\nLooping through files\nLet's revisit our fruit gallery from before, but instead of manually specifying the list of fruits, we'll use\nPHP's glob() function. It'll\nproduce an array of all the files in the img\/fruit\/ directory, which we can then loop through\n(using foreach) to display each image:\nNow if we add a new .jpg file into the img\/fruit\/ directory, it will automatically be\nadded to the gallery.\nWhat if there were both .jpg and .webp images in the directory? Can you work out how\nto loop through both types of image using a single loop?\narray_merge() might be useful.\nWhat about if you want to keep the fruit in alphabetical order, regardless of their file extension?\nThen you might want to use the sort() function.\n(\ud83c\udfc1 view solution)\nShowing a random quote\nPHP provides the array_rand() function\nwhich selects a random key from an array. Let's use that to show a random quote to our visitors:\nCould you use this technique to show a random image, and apply a random CSS effect to it?\n\ud83c\udfc1 view solution"
},
{
"id": "\/03-lists-and-loops\/loop-through-files.php",
"kind": "code",
"title": "loop-through-files.php",
"body": "<?php\r\n\/\/ Get a list of all the .jpg files in the img\/fruit directory:\r\n$files = glob('..\/img\/fruit\/*.jpg');\r\n\r\n\/\/ Loop through each file and display it as an image:\r\nforeach( $files as $file ) {\r\n ?>\r\n\r\n <img src=\"<?php echo $file; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black;\">\r\n\r\n <?php\r\n}"
},
{
"id": "\/03-lists-and-loops\/loop-through-images.php",
"kind": "code",
"title": "loop-through-images.php",
"body": "<?php\r\n$images = [\r\n 'apple', 'bananas', 'blackberries', 'cherry', 'kiwi',\r\n 'lemon', 'orange', 'pear', 'pineapple', 'watermelon'\r\n];\r\n\r\nforeach( $images as $image ) {\r\n ?>\r\n\r\n <img src=\"\/\/php.danq.dev\/img\/fruit\/<?php echo $image; ?>.jpg\"\r\n alt=\"<?php echo $image; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black;\">\r\n\r\n <?php\r\n}"
},
{
"id": "\/03-lists-and-loops\/navigation-menu-2.php",
"kind": "code",
"title": "navigation-menu-2.php",
"body": "<?php\r\n\/**\r\n * This example uses the Daily Life Sepia Icons by Asam Munir.\r\n * Were this a real site, you'd want to credit Asam in the text\r\n * of the page somewhere, not just in a comment!\r\n *\r\n * https:\/\/www.svgrepo.com\/collection\/daily-life-sepia-icons\/\r\n *\/\r\n\r\n \/\/ Set up an array of links - each link is itself an array with\r\n\/\/ a title, icon, and href:\r\n$links = [\r\n [\r\n 'title' => 'Home', 'icon' => 'home.svg',\r\n 'href' => '\/'\r\n ],\r\n [\r\n 'title' => 'Blog', 'icon' => 'megaphone.svg',\r\n 'href' => '\/blog'\r\n ],\r\n [\r\n 'title' => 'My Itch.io games', 'icon' => 'controller.svg',\r\n 'href' => 'https:\/\/dan-q.itch.io\/'\r\n ],\r\n [\r\n 'title' => 'Ideas bin', 'icon' => 'light-bulb.svg',\r\n 'href' => '\/ideas'\r\n ],\r\n [\r\n 'title' => 'Code snippets', 'icon' => 'code.svg',\r\n 'href' => '\/my-code-snippets'\r\n ],\r\n [\r\n 'title' => \"Awards I've won!\", 'icon' => 'award.svg',\r\n 'href' => '\/awards.html'\r\n ],\r\n];\r\n\r\n\/\/ If it's NOT Monday, add a link to the Not-Monday page:\r\nif( date('w') !== '1' ) {\r\n $links[] = [\r\n 'title' => 'The Special Page!', 'icon' => 'star.svg',\r\n 'href' => '\/closed-on-mondays.php', 'class' => 'secret'\r\n ];\r\n}\r\n?>\r\n\r\n<nav>\r\n <ul>\r\n <?php foreach( $links as $link ) { ?>\r\n <li class=\"<?php echo $link['class']; ?>\">\r\n <a href=\"<?php echo $link['href']; ?>\">\r\n <img\r\n src=\"\/img\/icons\/<?php echo $link['icon']; ?>\"\r\n alt=\"\">\r\n <?php echo $link['title']; ?>\r\n <\/a>\r\n <\/li>\r\n <?php } ?>\r\n <\/ul>\r\n<\/nav>"
},
{
"id": "\/03-lists-and-loops\/navigation-menu.php",
"kind": "code",
"title": "navigation-menu.php",
"body": "<?php\r\n\/**\r\n * This example uses the Daily Life Sepia Icons by Asam Munir.\r\n * Were this a real site, you'd want to credit Asam in the text\r\n * of the page somewhere, not just in a comment!\r\n *\r\n * https:\/\/www.svgrepo.com\/collection\/daily-life-sepia-icons\/\r\n *\/\r\n\r\n\/\/ Set up an array of links - each link is itself an array with\r\n\/\/ a title, icon, and href:\r\n$links = [\r\n [\r\n 'title' => 'Home', 'icon' => 'home.svg',\r\n 'href' => '\/'\r\n ],\r\n [\r\n 'title' => 'Blog', 'icon' => 'megaphone.svg',\r\n 'href' => '\/blog'\r\n ],\r\n [\r\n 'title' => 'My Itch.io games', 'icon' => 'controller.svg',\r\n 'href' => 'https:\/\/dan-q.itch.io\/'\r\n ],\r\n [\r\n 'title' => 'Ideas bin', 'icon' => 'light-bulb.svg',\r\n 'href' => '\/ideas'\r\n ],\r\n [\r\n 'title' => 'Code snippets', 'icon' => 'code.svg',\r\n 'href' => '\/my-code-snippets'\r\n ],\r\n [\r\n 'title' => \"Awards I've won!\", 'icon' => 'award.svg',\r\n 'href' => '\/awards.html'\r\n ],\r\n];\r\n?>\r\n\r\n<nav>\r\n <ul>\r\n <?php foreach( $links as $link ) { ?>\r\n <li>\r\n <a href=\"<?php echo $link['href']; ?>\">\r\n <img\r\n src=\"\/img\/icons\/<?php echo $link['icon']; ?>\"\r\n alt=\"\">\r\n <?php echo $link['title']; ?>\r\n <\/a>\r\n <\/li>\r\n <?php } ?>\r\n <\/ul>\r\n<\/nav>"
},
{
"id": "\/03-lists-and-loops\/random-quote.php",
"kind": "code",
"title": "random-quote.php",
"body": "<?php\r\n$quotes = [\r\n [\r\n 'quote' =>\r\n \"People don't verify quotes they read on the Internet.\",\r\n 'by' => 'Abraham Lincoln'\r\n ],\r\n [\r\n 'quote' => 'Do or do not, there is no try.',\r\n 'by' => 'Yoda'\r\n ],\r\n [\r\n 'quote' => \"Snakes. Why'd it have to be snakes?\",\r\n 'by' => 'Indiana Jones'\r\n ],\r\n [\r\n 'quote' => \"\r\n I've wrestled with reality for 35 years, Doctor, and I'm\r\n happy to state I finally won out over it.\r\n \",\r\n 'by' => 'Elwood P. Dowd'\r\n ],\r\n [\r\n 'quote' => 'At the beep, the time will be ' . date('g:i a'),\r\n 'by' => 'The Speaking Clock'\r\n ],\r\n];\r\n\r\n$random_quote_id = array_rand($quotes);\r\n$random_quote = $quotes[ $random_quote_id ];\r\n\r\n?>\r\n<blockquote>\r\n <p>\r\n <?php echo $random_quote['quote']; ?>\r\n <\/p>\r\n <cite>\r\n <?php echo $random_quote['by']; ?>\r\n <\/cite>\r\n<\/blockquote>\r\n\r\n<p>\r\n <a href=\".\/random-quote.php\">\ud83d\udd04 Get another quote<\/a>\r\n<\/p>"
},
{
"id": "\/03-lists-and-loops\/string-keyed-array.php",
"kind": "code",
"title": "string-keyed-array.php",
"body": "<?php\r\n$person = [\r\n 'name' => 'Dan Q',\r\n 'website' => 'https:\/\/danq.me\/'\r\n];\r\n\r\necho '<a href=\"';\r\necho $person['website'];\r\necho '\">';\r\necho $person['name'];\r\necho \"'s website!\";\r\necho '<\/a>';"
},
{
"id": "\/04-includes-and-functions\/cap-img-demo.php",
"kind": "code",
"title": "cap-img-demo.php",
"body": "<?php\r\n\/*\r\n * Load our cap_img() function so we can make a gallery. We use\r\n * `require_once` so that we don't accidentally load it twice,\r\n * and so that if it fails to load the page stops here!\r\n *\/\r\nrequire_once('cap-img.php');\r\n?>\r\n\r\n<p>\r\n Things in my fruitbowl:\r\n<\/p>\r\n\r\n<div class=\"gallery\">\r\n <?php\r\n cap_img( '\/img\/fruit\/apple.jpg', 'A red apple',\r\n 'From the tree in my back yard.' );\r\n\r\n cap_img( '\/img\/fruit\/bananas.jpg', 'Two ripe bananas',\r\n \"They're my favorite fruit.\" );\r\n\r\n cap_img( '\/img\/fruit\/plums.webp', 'A bowl of purple plums',\r\n 'I enjoy making jam every Autumn.' );\r\n\r\n cap_img( '\/img\/fruit\/kiwi.jpg', 'A halved kiwi fruit',\r\n 'I wish these grew in my part of the world.' );\r\n ?>\r\n<\/div>"
},
{
"id": "\/04-includes-and-functions\/cap-img.php",
"kind": "code",
"title": "cap-img.php",
"body": "<?php\r\n\/\/ ( this is the file cap-img.php )\r\n\r\n\/**\r\n * Renders a captioned image. Takes the following parameters:\r\n * - $url: The URL of the image to display.\r\n * - $alt: The alt text for the image.\r\n * - $caption: The caption to be displayed below the image.\r\n * - $max_width: The maximum width of the image in pixels;\r\n * optional - defaults to 100 (pixels).\r\n *\/\r\nfunction cap_img( $url, $alt, $caption, $max_width = 80 ) {\r\n ?>\r\n <figure>\r\n <a href=\"<?php echo $url; ?>\">\r\n <img src=\"<?php echo $url; ?>\"\r\n alt=\"<?php echo $alt; ?>\"\r\n style=\"max-width: <?php echo $max_width; ?>px;\r\n height: auto;\">\r\n <\/a>\r\n <figcaption><?php echo $caption; ?><\/figcaption>\r\n <\/figure>\r\n <?php\r\n}"
},
{
"id": "\/04-includes-and-functions\/captioned-image-function.php",
"kind": "code",
"title": "captioned-image-function.php",
"body": "<?php\r\n\/**\r\n * Renders a captioned image. Takes the following parameters:\r\n * - $url: The URL of the image to display.\r\n * - $alt: The alt text for the image.\r\n * - $caption: The caption to be displayed below the image.\r\n * - $max_width: The maximum width of the image in pixels;\r\n * optional - defaults to 100 (pixels).\r\n *\/\r\nfunction cap_img( $url, $alt, $caption, $max_width = 80 ) {\r\n ?>\r\n <figure>\r\n <a href=\"<?php echo $url; ?>\">\r\n <img src=\"<?php echo $url; ?>\"\r\n alt=\"<?php echo $alt; ?>\"\r\n style=\"max-width: <?php echo $max_width; ?>px;\r\n height: auto;\">\r\n <\/a>\r\n <figcaption><?php echo $caption; ?><\/figcaption>\r\n <\/figure>\r\n <?php\r\n}\r\n?>\r\n\r\n<p>\r\n Things in my fruitbowl:\r\n<\/p>\r\n\r\n<div class=\"gallery\">\r\n <?php\r\n \/* Now we'll use our function to display a gallery: *\/\r\n cap_img( '\/img\/fruit\/apple.jpg', 'A red apple',\r\n 'From the tree in my back yard.' );\r\n\r\n cap_img( '\/img\/fruit\/bananas.jpg', 'Two ripe bananas',\r\n \"They're my favorite fruit.\" );\r\n\r\n cap_img( '\/img\/fruit\/plums.webp', 'A bowl of purple plums',\r\n 'I enjoy making jam every Autumn.' );\r\n\r\n cap_img( '\/img\/fruit\/kiwi.jpg', 'A halved kiwi fruit',\r\n 'I wish these grew in my part of the world.' );\r\n ?>\r\n<\/div>"
},
{
"id": "\/04-includes-and-functions\/",
"kind": "page",
"title": "Lesson 4: Includes and Functions",
"body": "Objectives\n\nSeparate your site into smaller, reusable parts, and include them where they're needed\nWrite your own functions to encapsulate repetitive tasks\nShare functions across multiple pages with include or require\n\nSharing your navbar across the site\nAs we saw with loops, PHP is very helpful when you want to reduce repetition in your code. This in turn reduces the\nrisk of mistakes, and makes it easier to re-use the same code in multiple places.\nOne powerful way to do this is with PHP's include directive. This lets you load the contents of a PHP\n(or HTML) file into another PHP file.\nWith this, we can re-use the navigation menu we made in Lesson 3\non every page of our site, like this:\nThis shares your navigation menu across every page of your site, including any logic embedded within it\n(e.g. to conditionally show or hide menu items). Updating the menu\nin just one place updates it everywhere.\nA common technique is to put reusable part of your site, like your header and footer, into separate files for reuse.\nWhy not take a look at the source code for the header and\nthe footer of this site!\n\nPHP has several functions for including files, with slight differences between them. They are:\n\n\ninclude - includes the file if it exists\nrequire - includes the file; stops execution if it doesn't exist\ninclude_once - includes the file if it hasn't been included yet and it exists\nrequire_once - includes the file if it hasn't been included yet; stops execution if it doesn't exist\n\n\nPurists get into arguments about which is better, but it usually only matters if your included file defines its own\nfunctions (in which case including it twice can cause an error).\n\nReusing common patterns with functions\nSo far, you've been using standard functions like strrev (to reverse a string) and date (to get the\ncurrent date). You can write your own functions and they're a great to way to encapsulate reusable code.\nLet's define a function for adding a captioned image, where clicking on it links to the full-size image:\nSharing a function across multiple pages\nTo make that function available throughout our site, we can move it to a different file and then include\nor require it from the files that need it.\nThe shared file with the function:\nA page that depends on it:\nThe files you include can come from anywhere on your server, including from outside the directory (folder)\nwhere your publicly-accessible files are stored. On this site, for example, the \"footer\" can be\nviewed as if it were a standalone page by going direcly to\nhttps:\/\/php.danq.dev\/inc\/footer.php! How could you\nmitigate this by moving the footer into a different directory?\n(\ud83c\udfc1 view solution)"
},
{
"id": "\/04-includes-and-functions\/navigation-menu-include.php",
"kind": "code",
"title": "navigation-menu-include.php",
"body": "<?php\r\ninclude('..\/03-lists-and-loops\/navigation-menu.php');\r\n?>\r\n\r\n<article>\r\n <h2>Page title<\/h2>\r\n <p>\r\n Page content goes here.\r\n <\/p>\r\n<\/article>"
},
{
"id": "\/05-accepting-input\/fruits-and-filters.php",
"kind": "code",
"title": "fruits-and-filters.php",
"body": "<?php\r\n\/**\r\n * Returns a sorted array of all of the fruit images in the\r\n * img\/fruit directory.\r\n *\/\r\nfunction get_fruits() {\r\n \/\/ Get a sorted list of fruits like we've done before:\r\n $jpgs = glob('..\/img\/fruit\/*.jpg');\r\n $webps = glob('..\/img\/fruit\/*.webp');\r\n $files = array_merge( $jpgs, $webps );\r\n sort( $files );\r\n \/\/ Return the list to whichever code called get_fruits()\r\n return $files;\r\n}\r\n\r\n\/**\r\n * Returns an array of CSS filters that can be applied to an\r\n * image.\r\n *\/\r\nfunction get_filters() {\r\n return [\r\n 'filter: grayscale(100%)',\r\n 'filter: blur(2px)',\r\n 'filter: brightness(1.5)',\r\n 'filter: contrast(1.5)',\r\n 'filter: hue-rotate(90deg)',\r\n 'filter: invert(1)',\r\n 'filter: sepia(1)',\r\n 'transform: rotateZ(90deg);',\r\n 'transform: skewX(-15deg);',\r\n ];\r\n}\r\n\r\n\/**\r\n * Returns true if the user provided a fruit_id and filter_id.\r\n * Returns false otherwise.\r\n *\/\r\nfunction user_requested_a_fruit_and_filter() {\r\n \/\/ If they haven't provided a fruit_id or filter_id, false:\r\n if( ! isset( $_POST['fruit_id'] ) ) return false;\r\n if( ! isset( $_POST['filter_id'] ) ) return false;\r\n \/\/ Othewise, true!\r\n return true;\r\n}\r\n\r\n\/**\r\n * Shows the requested fruit with the applied CSS filter.\r\n *\/\r\nfunction show_a_filtered_fruit() {\r\n \/\/ Note that we don't ask the user to send us the filename\r\n \/\/ but the NUMBER of the fruit (and filter), in its list.\r\n \/\/ This is a strong validation step - the user can't inject\r\n \/\/ anything we didn't expect them to!\r\n $fruit_id = intval( $_POST['fruit_id'] );\r\n $filter_id = intval( $_POST['filter_id'] );\r\n $fruit = get_fruits()[ $fruit_id ];\r\n $filter = get_filters()[ $filter_id ];\r\n\r\n \/\/ Output the fruit image with the filter applied:\r\n ?>\r\n <img src=\"<?php echo $fruit; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black; <?php echo $filter; ?>;\">\r\n <?php\r\n}\r\n?>\r\n\r\n<h1>Fruity Filters<\/h1>\r\n\r\n<?php if( user_requested_a_fruit_and_filter() ) { ?>\r\n <h2>\r\n Here's your filtered fruit:\r\n <\/p>\r\n <?php show_a_filtered_fruit(); ?>\r\n<?php } ?>\r\n\r\n<h2>\r\n <?php if( user_requested_a_fruit_and_filter() ) { ?>\r\n Filter another fruit!\r\n <?php } else { ?>\r\n Filter a fruit!\r\n <?php } ?>\r\n<\/h2>\r\n<form method=\"post\" action=\"fruits-and-filters.php\">\r\n <p>\r\n <label for=\"fruit\">Fruit:<\/label>\r\n <select id=\"fruit\" name=\"fruit_id\">\r\n <?php foreach( get_fruits() as $id => $fruit ) { ?>\r\n <option value=\"<?php echo $id; ?>\">\r\n <?php echo basename( $fruit ); ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <p>\r\n <label for=\"filter\">Filter:<\/label>\r\n <select id=\"filter\" name=\"filter_id\">\r\n <?php foreach( get_filters() as $id => $filter ) { ?>\r\n <option value=\"<?php echo $id; ?>\">\r\n <?php echo $filter; ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <button type=\"submit\">Submit<\/button>\r\n<\/form>"
},
{
"id": "\/05-accepting-input\/",
"kind": "page",
"title": "Lesson 5: Accepting Input",
"body": "Objectives\n\nUnderstand the difference between GET and POST requests\nKnow to use htmlspecialchars() before outputing anything that came from a user\nAccept input from a user and display it back to them\nAct upon user input: do different things based on what they submitted\nValidate user input to ensure it's among the expected values\nThink about how JavaScript fetch() requests might be integrated with PHP backend processing\nMake a fun quiz!\n\nOne area where PHP really shines is when you want to accept input from your visitors. Feedback forms, commenting,\nguestbooks, search boxes, user-generated content and more all work best using server-side code.\nThere are security implications when working with user input. As you go through the lesson, keep an eye out for\nwarnings like this one with information about the relevant dangers; or if you're familiar them skim the\nappendix on security for a summary of the risks.\nGET and POST requests\nUser input comes in many forms - the web address they've typed in, the contents of forms, even that's in their\ncookies or their browser's name and version number. But for the time being we're going to focus on the data they\nsupply via GET and POST requests.\nGET parameters\nIf a web address contains a question mark (?), then everything after it (up until any # symbols)\nare called the \"query string\". If it has any ampersands (&) or semicolons (;), PHP will\nsplit the query string into variables and values around these. Those variables all look like\n$_GET['variable_name'].\nIf you've spotted that $_GET must be an array, well done!\nConsider the following partial web address:\n\/05-accepting-input\/whats-your-name.php?name=Dan&pronoun=him\nWhen the user visits that page, whats-your-name.php will have access to the following variables:\n$_GET['name'] will be 'Dan'\n$_GET['pronoun'] will be 'him'\nPOST parameters\nPOST parameters are sent in the body of the request, rather than in the URL. They're associated with\nforms, but they can be sent in other ways too.\nJust because the user can't easily see their POST parameters doesn't mean the y can't tamper with them.\nThey can, so you have to be just as careful with them as with any other user input. We'll look at how to stay safe\nin a little bit.\nThe parameters sent in a POST request are stored in the $_POST array.\nIf you don't care what kind of input you're getting, the $_REQUEST array holds both the\nGET and POST arrays (sometimes among other data).\nAsking the user's name\nLet's start by asking the user's name and repeating it back to them. Create a new file called\nwhats-your-name.php with the following code:\nHere's how it works:\n\nOn the first visit to the page, neither $_GET['name'] nor $_GET['pronoun']\nare set, so we skip over the initial block of code and start rending the HTML.\n\n\nThis means that $greeting is not set, so skip ahead to rendering \"Hi! Who are you?\" and the form.\n\n\nThe HTML has two attributes:\n\n\nmethod=\"get\" means that the form will be submitted using a GET request; that is -\nthe user's input will appear in the address bar as part of the URL.\n\n\naction=\"whats-your-name.php\" means that the form will be submitted to the whats-your-name.php\npage - i.e. the user will come back to the same page, but now with the GET parameters added to the URL.\n\n\n\n\nThe form has two fields with a name=\"...\" attribute. These will become the names of the variables once they're\npassed to the PHP script.\n\n\nWhen they submit the form they'll come back to the same page again, but this time $_GET['name'] and\n$_GET['pronoun'] will be set. The code will make a variable called $greeting containing a\nstring that acknowledges the user's name and the subject pronoun associated with the value they picked from the dropdown.\n\n\nThe page will then render the greeting, and a link to the same page again so the user can have another go!\n\nWe use htmlspecialchars() to escape any HTML that the user entered, making it safe to display in the browser.\nThis prevents Cross-Site Scripting (XSS) attacks. If we didn't do this, a malicious user could enter HTML code that could\nrun their JavaScript on our page. If they could trick another user into visiting a page which they'd\ntampered with, they could potentially steal their cookies or other sensitive data.\nTry going to ?name=%3Cem style%3D%22color%3Ared%3B%22%3Ea wannabe hacker%3C%2Fem%3E&pronoun=them\nand see that the page is protected. If you want to, try removing the htmlspecialchars() function and see what happens!\nMore info in the appendix.\nIt's probably easier to experience than explain, so go introduce yourself and see\nwhat happens!\nIn this instance, we didn't perform any validation of the user's input. You can see the effect of this by editin the URL\nin the address bar to change your pronoun to one that isn't in the list! Try e.g.\n?name=Dan+Q&pronoun=that+sloppy+coder!\nIf that's something you care about, you'll need to make sure your code checks that the user's input is valid before using it.\nMore info in the appendix.\nCould you add a \"(no pronouns)\" option to the dropdown? A user who selects \"(no pronouns)\" should see their own name used a second\ntime, rather than \"him\"\/\"her\"\/\"them\" etc.\n(\ud83c\udfc1 view solution)\nFruits and filters revisited\nIn lesson 3 we came up with the idea for\na web page that would randomly select a fruit and a CSS effect and combine them.\nLet's revisit that, but this time we'll let the user choose which fruit and which CSS effect they want to use. This time,\nwe'll use a form with a POST request:\nIn general, the rule is that idempotent requests (ones that don't change anything, where if you did the same thing\ntwice it would work the same way) should use a GET request, POST requests should be used for\nrequests that change things. So a search form should use a GET request because searching for the same thing\ntwice should yield the same results, but a login form should use a POST request because it's likely to result\nin a cookie being set or changed (plus: users don't like their passwords appearing in the URL bar in case somebody's standing\nbehind them!). We're ignoring that \"best practice\" guideline for now, but you should be aware of it.\nMore info in the appendix.\nHere's how it works:\n\nWhen the page loads, the user_requested_a_fruit_and_filter() is used to check if a request has been made.\nIt looks for the $_POST variables that are set by submitting the form.\n\n\nIf the user has requested a fruit and a filter, the show_a_filtered_fruit() function is used to display\nthe fruit image with the filter applied. It uses get_fruits() and get_filters() to get the list of\nfruits and filters, then finds the correct-numbered fruit and filter from the list, based on the user's request. Then it\ndisplays that image.\n\n\nEither way, the page then renders a form with two (dropdown) fields. Each lists the relevant\nfruits\/filters, but the value=\"...\" of each is not the fruit\/filter name but its\nindex (position) in the list. This means a malicious user can't tamper with the form by changing the value into a fruit or\nfilter of their choice: it has to be one that's in the list.\n\nUsers who are allowed to select from a list of files on your server are a special case for security.\nIf we accepted the file name from the user, and we allowed PHP to read and send the file (e.g. with\nreadfile()), the user could tweak\nthe request to ask for any file on your system, e.g. \/etc\/passwd or \/home\/user\/my-secrets.txt.\nOur program is safe because we're only passing ID numbers around and then using those to look up fruits from the approved list,\nbut there's a relevant section in the appendix if you want to let your users\nspecify paths to files on your server.\nWouldn't it be nice if the boxes retained the values that were last selected, when the page reloads?\nTo do that, you'll need to add a selected attribute of the tags. Tip: everything you get\nfrom the user is a string, so you'll need to convert it to an integer before comparing it to the ID number.\n(\ud83c\udfc1 view solution)\nIf you enjoy JavaScript, you'll be pleased to know that you can integrate JavaScript and PHP together. Can you make it so that,\nif JavaScript is enabled, the form is submitted using a fetch() request, and if PHP sees this come in then it\nreturns some JSON to tell JavaScript what to update on the page? It should still work even with JavaScript disabled.\n(\ud83c\udfc1 view solution)\nLet's make a quiz!\nQuizzes are a great example of something for which using PHP would be better than using JavaScript, because it means that the\nuser can't cheat my looking at the source code. Let's make a quiz that tests the user's PHP knowledge with three questions,\nand then tells them their score at the end!\nCreate a new file called quiz.php:\nHere's how it works:\n\nIf the user isn't submitting the form, we show the quiz. We loop through an array and show each question and the candidate\nanswers associated with it. The array also contains the correct answer, but that's never sent to the user's\nbrowser.\n\n\nThe name=\"...\" of an is used by PHP to choose where it appears in the $_POST\narray; we use a special syntax with square brackets to tell PHP to store the answers given by the user in an array with\neach answer being numbered after the number of the question it belongs to.\n\n\nOnce submitted, we score the quiz by looping through the questions again and comparing the correct answer to the\none the user gave. If they match, we increment the score.\n\n\nThroughout, we use the as a convenient shorthand for \"the URL of\nthe current page\".\n\nCould you give the user a ranking based on their score, using a series of if statements? Or even a\nswitch statement? Or perhaps you\ncould show their score as a percentage?\nMaybe you would like to tell the user which questions they got wrong, so they can learn from their mistakes?"
},
{
"id": "\/05-accepting-input\/quiz.php",
"kind": "code",
"title": "quiz.php",
"body": "<?php\r\n\/\/ Let's store the questions in an array for easy modification:\r\n$questions = [\r\n [\r\n 'question' =>\r\n 'Which of these PHP globals might contain user input?',\r\n 'options' => [\r\n '$_GET',\r\n '$_POST',\r\n '$_COOKIE',\r\n 'All of the above!'\r\n ],\r\n 'correct_answer' => 'All of the above!',\r\n ],\r\n\r\n [\r\n 'question' =>\r\n 'How would I reverse a string in PHP?',\r\n 'options' => [\r\n 'reverse()',\r\n 'strrev()',\r\n 'string_reverse()',\r\n \"You can't reverse a string in PHP\"\r\n ],\r\n 'correct_answer' => 'strrev()',\r\n ],\r\n\r\n [\r\n 'question' =>\r\n 'How would I loop through the items in a PHP array?',\r\n 'options' => [\r\n 'foreach()',\r\n 'each()',\r\n 'array_each()',\r\n 'for $item of $array'\r\n ],\r\n 'correct_answer' => 'foreach()',\r\n ],\r\n];\r\n\r\n\/**\r\n * Display a single question and it's options:\r\n *\/\r\nfunction show_question($number, $data){\r\n echo '<p>' . $data['question'] . '<\/p>';\r\n echo '<ul>';\r\n foreach( $data['options'] as $option ){\r\n echo '<li><label>';\r\n \/\/ Notice how we assemble the name of this field - like:\r\n \/\/ <input name=\"answer[1]\" value=\"some answer\">\r\n \/\/ PHP recognises the name with square brackets as an\r\n \/\/ array and gives you one at the other end!\r\n echo '<input type=\"radio\"';\r\n echo ' name=\"answer[' . $number. ']\"';\r\n echo ' value=\"' . $option . '\">';\r\n echo $option;\r\n echo '<\/label><\/li>';\r\n }\r\n echo '<\/ul>';\r\n}\r\n\r\n\/**\r\n * Show the form with the questions:\r\n *\/\r\nfunction quiz($questions){\r\n ?>\r\n <h1>Quiz time!<\/h1>\r\n <form method=\"post\" action=\"<?php echo $_SERVER['REQUEST_URI']; ?>\">\r\n <?php\r\n foreach( $questions as $number => $question ){\r\n show_question( $number, $question );\r\n }\r\n ?>\r\n <input type=\"submit\" name=\"submit\" value=\"See score\">\r\n <\/form>\r\n <?php\r\n}\r\n\r\n\/**\r\n * Score the user's answers and give them their results:\r\n *\/\r\nfunction results($questions){\r\n \/\/ Make a variable to keep their score in:\r\n $score = 0;\r\n \/\/ Loop through each question, and see if they got it right:\r\n foreach( $questions as $number => $question ){\r\n \/\/ The correct answer comes from the questions array:\r\n $correct_answer = $question['correct_answer'];\r\n \/\/ Their answer comes from an array that was posted to us:\r\n $their_answer = $_POST['answer'][$number];\r\n \/\/ If they match, give them a point!\r\n if( $their_answer === $correct_answer ){\r\n $score++;\r\n }\r\n }\r\n \/\/ What's the best possible score? The number of questions:\r\n $max_score = count($questions);\r\n ?>\r\n <h1>Quiz results<\/h1>\r\n <p>\r\n You scored <?php echo $score; ?> out of <?php echo $max_score; ?>!\r\n <\/p>\r\n <p>\r\n <a href=\"<?php echo $_SERVER['REQUEST_URI']; ?>\">\r\n Have another go?\r\n <\/a>\r\n <\/p>\r\n <?php\r\n}\r\n\r\n\/\/ If the submit button was pressed (something with\r\n\/\/ name=\"submit\"), show the results, otherwise show the quiz:\r\nif( $_POST['submit'] ){\r\n results($questions);\r\n} else {\r\n quiz($questions);\r\n}"
},
{
"id": "\/05-accepting-input\/whats-your-name.php",
"kind": "code",
"title": "whats-your-name.php",
"body": "<?php\r\n\/\/ Check if we're provided a name and pronoun via GET params:\r\nif( isset( $_GET['name'] ) && isset( $_GET['pronoun'] ) ) {\r\n \/\/ Strip any HTML from the name and pronoun:\r\n $name = htmlspecialchars( $_GET['name'] );\r\n $pronoun = htmlspecialchars( $_GET['pronoun'] );\r\n \/\/ Construct a greeting string:\r\n $greeting = \"Today I met $name. I was so pleased to meet $pronoun!\";\r\n}\r\n?>\r\n\r\n<h1>\r\n Welcome to my friendly website!\r\n<\/h1>\r\n\r\n<?php if( isset( $greeting ) ) { \/* We have a greeting! *\/ ?>\r\n\r\n <h2>\r\n <?php echo $greeting; ?>\r\n <\/h2>\r\n <p>\r\n I wonder who I will <a href=\"whats-your-name.php\">meet next?<\/a>\r\n <\/p>\r\n\r\n<?php } else { \/* No greeting: show the form! *\/ ?>\r\n\r\n <p>\r\n Hi! Who are you?\r\n <\/p>\r\n <form method=\"get\" action=\"whats-your-name.php\">\r\n <label for=\"name\">Name:<\/label>\r\n <input type=\"text\" id=\"name\" name=\"name\" autofocus>\r\n <label for=\"pronoun\">Pronouns:<\/label>\r\n <select id=\"pronoun\" name=\"pronoun\">\r\n <option value=\"them\">they\/them<\/option>\r\n <option value=\"him\">he\/him<\/option>\r\n <option value=\"her\">she\/her<\/option>\r\n <option value=\"it\">it\/its<\/option>\r\n <option value=\"aer\">ae\/aer<\/option>\r\n <option value=\"eir\">ey\/em\/eir<\/option>\r\n <option value=\"faer\">fae\/faer<\/option>\r\n <option value=\"zir\">ze\/zir<\/option>\r\n <option value=\"hir\">ze\/hir<\/option>\r\n <\/select>\r\n <button type=\"submit\">Hi!<\/button>\r\n <\/form>\r\n\r\n<?php } ?>"
},
{
"id": "\/06-retaining-state\/",
"kind": "page",
"title": "Lesson 6: Sessions & Cookies",
"body": "Objectives\n\nUnderstand the concept of cookies and how they work\nUse a cookie to detect repeat visitors\nUse a session to track a user's login status\nExperience a game that uses a session to retain a user's progress\n\nWhat are cookies?\nThe Web was invented to be \"stateless\". Every page you visit would be a clean slate, with no memory\nof what came before.\n\nA traditional HTTP exchange: each request and response exists independently.\nBy the mid-1990s, it became clear that websites might benefit from some way of being able to \"track\"\na user from one page load to another, to enable things like login sessions and shopping carts.\nThe solution was cookies.\nWhen you visit a website, it can optionally send a string to your browser, called a cookie. If your\nbrowser accepts the cookie, it will send it back for every page it subsequently\nrequests from that site. It'll do this until either the cookie expires, the user deletes it, or\nthe website asks the browser to delete it.\n\nWhere the server sets a cookie, the browser returns it on each subsequent request. This allows the server to identify repeat visitors.\nBefore cookies became mainstream, imaginative developers came up with other ways to approximate their\nfunctionality, e.g. appending GET parameters to every internal link to \"pass on\" information\nto the next page, but this was not always reliable (e.g. if the visitor opened multiple\nbrowser windows pointing to the same website, for example).\nDetecting repeat visitors\nLet's have a go at implementing the thing from the diagram above - a web page that welcomes you with a different\nmessage if you've visited the page before. Here's welcome-back.php:\nVisit the page and you'll see the message saying it's your first visit.\nRefresh the page and you'll see it says \"welcome back\"!\nTry opening the page in a private browsing window and you'll see that it doesn't recognise you. A private browsing\nwindow keeps a separate cookie store from your regular browsing window, and usually deletes all cookies when\nit's closed.\nCould you extend this program so that it counts how many times the visitor has returned to the page?\n(\ud83c\udfc1 view solution)\nThere are lots of options for the setcookie() function.\nYou've seen the first three (name, value, and lifespan). The others, in order, are:\n\npath: which part of your website the cookie belongs to, by default it'll be the current directory\nyour code is in, but if you'd like your cookie to be available throughout your entire site, you can set it to '\/'.\n\n\ndomain: if your site spans multiple subdomains and you want your cookie to span them all, you'll need to se this. E.g.\nif your site spans cool.example.com and funny.example.com, a cookie will need to set a domain\nof example.com to be accessed from both of them.\n\n\nsecure: setting this to true tells browsers only to send the cookie over HTTPS connections.\n\n\nhttponly: cookies can also be manipulated using JavaScript, but if you set this to true then\nbrowsers won't let JavaScript access this cookie. That's useful if there's a chance that malicious JavaScript might end up\nbeing run on your site.\n\n\noptions: if you want to do advanced things with cookies, like configuring how they behave when people\nhotlink to your page, you'll need this.\n\n$_COOKIEs are example of user input, which means that\nusers can tamper with them.\nTry it for yourself: after\nvisiting the \"visit counter\" page,\nopen your browser's Developer Tools (F12 or Ctrl+Shift+I in most browsers), go to the \"Storage\" tab,\nand change the value of the \"visited\" cookie to a different number. Then visit the page again to see the change!\n\n(How can cookies be secured?)\n\nUsing what you learned in the previous lesson, could you ask the user for their name\nand then use it whenever they come back?\n(\ud83c\udfc1 view solution)\nLogging in (and staying logged in)\nThe easiest way to use cookies in PHP is with PHP's session functionality.\nSessions are a tamper-proof implementation of cookies (with, usually, a short life: until the user closes their browser) that provide\neasy-to-access variables to developers.\nJust call session_start() near the top of your script, and you can use $_SESSION to store and retrieve data.\nLet's make an admin section of a website that can be logged-into with a password. Here's login.php:\nLet's also make a members area page that's only accessible if you've logged in - members.php:\nHave a try at logging in and accessing the members area.\nTry accessing the members area without logging in, too (it shouldn't let you!).\nIf you use a session for authentication, be sure to check it on every page that needs protection! A\ncommon mistake is to e.g. check it on the main \"members area\" page, but then not check it on sub-pages of the\nmembers area. This means that anybody who can guess the address, or who has ever been logged-in before, can access\nthose sub-pages!\nLook at the new cookie using your browser's Developer Tools (F12 or Ctrl+Shift+I in most browsers). It'll be\ncalled PHPSESSID and will contain a long random string. If you tamper with it, your session will be invalidated: you'd\nhave to successfully guess the random string belonging to somebody already-logged-in! The actual information (e.g. whether you're\nlogged in or not) is kept securely on the server, in a file whose name is referenced by that cookie.\nPotion Finder game\nLet's make a game! I've hidden four different-coloured potions on different pages of the site. Your job is to find them all.\nWe'll use sessions to keep track of which potions you've found. Here's a page with more details,\nand a link to the source code.\n\nHere's the first one, to get you started:\n\nHere's how it works:\n\nYou may remember from Lesson 4 that this site includes a file called\ninc\/functions.php on every page. Well that\nfile includes another, called inc\/potion-finder.php,\nwhich contains all of the logic for this game.\n\n\nIt uses session_start() to start a session, and a variable called $_SESSION['potions'] to track which potions\nhave been found.\n\n\nIt also provides a convenience function called draw_potion(), which I've used throughout the site. This function\nchecks if you already have the specified potion and, if not, draws a picture of it with a link to collect it.\n\n\nTo prevent tampering with the links to instantly collect all the potions, each potion has a unique password which is included in its\nlink and checked when the link is clicked.\n\n\ninc\/potion-finder.php includes logic for showing a\npage with the details of the potions collected so far, if you put ?show_potions=true on\nthe end of a URL."
},
{
"id": "\/06-retaining-state\/login.php",
"kind": "code",
"title": "login.php",
"body": "<?php\r\n\/\/ Start the session:\r\nsession_start();\r\n\r\n\/\/ Check if the user has requested to log out (?logout=true):\r\nif( isset( $_GET['logout'] ) && $_GET['logout'] === 'true' ) {\r\n \/\/ Log the user out:\r\n session_destroy();\r\n \/\/ Refresh the page so we start a new session!\r\n header( 'Location: login.php' );\r\n die();\r\n}\r\n\r\n\/\/ Check if a password was submitted ($_POST['password']):\r\n if( isset( $_POST['password'] ) ) {\r\n \/\/ Check if the password is correct:\r\n if( $_POST['password'] === 'secret' ) {\r\n \/\/ Password is correct: log the user in!\r\n $_SESSION['logged_in'] = true;\r\n } else {\r\n \/\/ Password is incorrect: show an error message:\r\n echo \"<h2>Login failed<\/h2><p>Wrong password!<\/p>\";\r\n }\r\n }\r\n\r\n\/\/ Check if the user is logged in:\r\nif( $_SESSION['logged_in'] ) {\r\n \/\/ They are logged-in!\r\n ?>\r\n <h1>You're logged in!<\/h1>\r\n <ul>\r\n <li>\r\n <a href=\"members.php\">Go to the members area<\/a>\r\n <\/li>\r\n <li>\r\n <a href=\"login.php?logout=true\">Log out<\/a>\r\n <\/li>\r\n <\/ul>\r\n <?php\r\n} else {\r\n \/\/ They're not logged in!\r\n ?>\r\n <h1>Login<\/h1>\r\n <p>\r\n Once you log in, you'll be able to access\r\n <a href=\"members.php\">the members area<\/a>.\r\n <\/p>\r\n <form method=\"post\" action=\"login.php\">\r\n <label for=\"password\">Password:<\/label>\r\n <input type=\"password\" id=\"password\" name=\"password\" autofocus>\r\n <button type=\"submit\">Login<\/button>\r\n <\/form>\r\n <p>\r\n <small>\r\n \ud83e\udd2b Shh! The password is \"secret\".\r\n <\/small>\r\n <\/p>\r\n <?php\r\n}"
},
{
"id": "\/06-retaining-state\/members.php",
"kind": "code",
"title": "members.php",
"body": "<?php\r\n\/\/ Start the session:\r\nsession_start();\r\n\r\n\/\/ Check if the user is logged in:\r\nif( ! $_SESSION['logged_in'] ) {\r\n \/\/ They're not logged in - send them a HTTP 403 error code\r\n \/\/ (\"forbidden\") and information about logging in:\r\n header( 'HTTP\/1.1 403 Forbidden' );\r\n ?>\r\n <h1>Forbidden<\/h1>\r\n <p>\r\n You're not logged in, so you can't access this page.\r\n <a href=\"login.php\">Log in<\/a> to continue.\r\n <\/p>\r\n <?php\r\n die();\r\n}\r\n?>\r\n\r\n<h1>Members area<\/h1>\r\n<p>\r\n Welcome to the members area!\r\n<\/p>\r\n<p>\r\n If this was a real members area, there'd be all kinds of secret things here!\r\n<\/p>\r\n<ul>\r\n <li>\r\n <a href=\"login.php?logout=true\">Log out<\/a>\r\n <\/li>\r\n<\/ul>"
},
{
"id": "\/06-retaining-state\/welcome-back.php",
"kind": "code",
"title": "welcome-back.php",
"body": "<?php\r\n\/\/ See if a cookie is set:\r\nif( isset( $_COOKIE['visited'] ) ) {\r\n \/\/ The cookie is set, so we welcome the user back.\r\n echo \"\ud83d\udc4b Welcome back! You've visited this page before!\";\r\n\r\n \/\/ We could optionally set the cookie again here to renew it\/\r\n \/\/ extend its lifespan.\r\n\r\n} else {\r\n \/\/ Cookie is not set: welcome them!\r\n echo \"\ud83c\udf89 Welcome! This is your first visit.\";\r\n\r\n \/\/ We need to specify the number of SECONDS the cookie will\r\n \/\/ last for, so we need to do some arithmetic:\r\n $cookie_lifespan = 60 \/* seconds in a minute *\/\r\n * 60 \/* minutes in an hour *\/\r\n * 24 \/* hours in a day *\/\r\n * 7; \/* days (a week) *\/\r\n\r\n \/\/ Now we can set the cookie, which will last for a week.\r\n \/\/ Our cookie just sets visited=1 and will be accessible\r\n \/\/ on subsequent requests via $_COOKIE['visited'].\r\n setcookie( 'visited', '1', time() + $cookie_lifespan );\r\n}"
},
{
"id": "\/appendix-quine\/",
"kind": "page",
"title": "Appendix C: Source Code for This Site",
"body": "The source code for this site is available on GitHub,\nbut you can also browse it right here."
},
{
"id": "\/appendix-samples\/fruits-and-filters-js-enhanced.php",
"kind": "code",
"title": "fruits-and-filters-js-enhanced.php",
"body": "<?php\r\n\/**\r\n * Returns a sorted array of all of the fruit images in the\r\n * img\/fruit directory.\r\n *\/\r\nfunction get_fruits() {\r\n \/\/ Get a sorted list of fruits like we've done before:\r\n $jpgs = glob('..\/img\/fruit\/*.jpg');\r\n $webps = glob('..\/img\/fruit\/*.webp');\r\n $files = array_merge( $jpgs, $webps );\r\n sort( $files );\r\n \/\/ Return the list to whichever code called get_fruits()\r\n return $files;\r\n}\r\n\r\n\/**\r\n * Returns an array of CSS filters that can be applied to an\r\n * image.\r\n *\/\r\nfunction get_filters() {\r\n return [\r\n 'filter: grayscale(100%)',\r\n 'filter: blur(2px)',\r\n 'filter: brightness(1.5)',\r\n 'filter: contrast(1.5)',\r\n 'filter: hue-rotate(90deg)',\r\n 'filter: invert(1)',\r\n 'filter: sepia(1)',\r\n 'transform: rotateZ(90deg);',\r\n 'transform: skewX(-15deg);',\r\n ];\r\n}\r\n\r\n\/**\r\n * Returns true if the user provided a fruit_id and filter_id.\r\n * Returns false otherwise.\r\n *\/\r\nfunction user_requested_a_fruit_and_filter() {\r\n \/\/ If they haven't provided a fruit_id or filter_id, false:\r\n if( ! isset( $_POST['fruit_id'] ) ) return false;\r\n if( ! isset( $_POST['filter_id'] ) ) return false;\r\n \/\/ Othewise, true!\r\n return true;\r\n}\r\n\r\n\/**\r\n * Shows the requested fruit with the applied CSS filter.\r\n *\/\r\nfunction show_a_filtered_fruit() {\r\n \/\/ Note that we don't ask the user to send us the filename\r\n \/\/ but the NUMBER of the fruit (and filter), in its list.\r\n \/\/ This is a strong validation step - the user can't inject\r\n \/\/ anything we didn't expect them to!\r\n $fruit_id = intval( $_POST['fruit_id'] );\r\n $filter_id = intval( $_POST['filter_id'] );\r\n $fruit = get_fruits()[ $fruit_id ];\r\n $filter = get_filters()[ $filter_id ];\r\n\r\n \/\/ Output the fruit image with the filter applied:\r\n ?>\r\n <img src=\"<?php echo $fruit; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black; <?php echo $filter; ?>;\">\r\n <?php\r\n}\r\n\r\n\/**\r\n * This new block of code handles the possibility that\r\n * JavaScript was used to request a filtered fruit, rather than\r\n * a plain HTML form submission. If so, we return JSON to the\r\n * JavaScript so it can update the page with the new filtered\r\n * fruit.\r\n *\/\r\nif( isset( $_GET['js'] ) && ( $_GET['js'] === 'true' ) && user_requested_a_fruit_and_filter() ) {\r\n \/\/ If JavaScript was used to request a filtered fruit, we return JSON to the JavaScript so it can update the page with the new filtered fruit:\r\n echo json_encode([\r\n 'fruit' => get_fruits()[ intval( $_POST['fruit_id'] ) ],\r\n 'filter' => get_filters()[ intval( $_POST['filter_id'] ) ]\r\n ]);\r\n die(); \/\/ don't send the page's HTML; it'll break the JSON!\r\n}\r\n?>\r\n\r\n<h1>Fruity Filters<\/h1>\r\n\r\n<!--\r\n This block is always added ot the page, but it's only displayed if a fruit and filter was requested\r\n at page load (e.g. if JS was disabled). But because it's always added, it can be MADE visible by\r\n JavaScript in the event of a form submission using a fetch() request!\r\n-->\r\n<div id=\"results\" style=\"display: <?php echo( user_requested_a_fruit_and_filter() ) ? 'block' : 'none'; ?>;\">\r\n <h2>\r\n Here's your filtered fruit:\r\n <\/h2>\r\n <?php show_a_filtered_fruit(); ?>\r\n<\/div>\r\n\r\n<h2>\r\n <?php if( user_requested_a_fruit_and_filter() ) { ?>\r\n Filter another fruit!\r\n <?php } else { ?>\r\n Filter a fruit!\r\n <?php } ?>\r\n<\/h2>\r\n<form method=\"post\" action=\"fruits-and-filters-js-enhanced.php\">\r\n <p>\r\n <label for=\"fruit\">Fruit:<\/label>\r\n <select id=\"fruit\" name=\"fruit_id\">\r\n <?php foreach( get_fruits() as $id => $fruit ) { ?>\r\n <option value=\"<?php echo $id; ?>\">\r\n <?php echo basename( $fruit ); ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <p>\r\n <label for=\"filter\">Filter:<\/label>\r\n <select id=\"filter\" name=\"filter_id\">\r\n <?php foreach( get_filters() as $id => $filter ) { ?>\r\n <option value=\"<?php echo $id; ?>\">\r\n <?php echo $filter; ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <button type=\"submit\">Submit<\/button>\r\n<\/form>\r\n\r\n<script>\r\n \/* Here's our new JavaScript-enhancement to the form. *\/\r\n const results = document.getElementById('results');\r\n const resultsImage = results.querySelector('img');\r\n const form = document.querySelector('form');\r\n const submitButton = form.querySelector('button[type=\"submit\"]');\r\n\r\n form.addEventListener('submit', function(event) {\r\n \/\/ When we submit the form, JS can prevent the regular HTML submission process:\r\n event.preventDefault();\r\n\r\n \/\/ Then it can begin its own. Let's disable the submit button to prevent multiple submissions and make it clear to the user that something is happening:\r\n submitButton.disabled = true;\r\n submitButton.innerHTML = 'Please wait...';\r\n\r\n \/\/ Now we'll submit the form using a fetch() request and expect some JSON back:\r\n const url = this.action + '?js=true'; \/\/ let's add a GET var - js=true - to say this is a JS request (so PHP knows to send back JSON)\r\n fetch(url, {\r\n method: 'POST',\r\n body: new FormData(this)\r\n })\r\n .then(response => response.json())\r\n .then(data => {\r\n \/\/ The data we get back should look something like this:\r\n \/\/ {\r\n \/\/ \"src\": \"..\/img\/fruit\/apple.jpg\",\r\n \/\/ \"filter\": \"filter: grayscale(100%);\"\r\n \/\/ }\r\n \/\/ We can update our image with the new src and filter:\r\n resultsImage.src = data.fruit;\r\n resultsImage.style.cssText = 'transition: box-shadow 0.2s; border: 1px solid black;' + data.filter;\r\n\r\n \/\/ Let's apply a quick effect to the image so that the user knows it's changed:\r\n resultsImage.style.boxShadow = '0 0 12px yellow';\r\n setTimeout(()=>resultsImage.style.boxShadow = 'none', 250);\r\n \r\n \/\/ And ensure the results block is visible:\r\n results.style.display = 'block';\r\n })\r\n .catch(error => {\r\n \/\/ If there's an error, let's show it to the user:\r\n alert('Something went wrong: ' + error);\r\n })\r\n .finally(() => {\r\n \/\/ Whether it worked or not, let's re-enable the submit button so they can have another go:\r\n submitButton.disabled = false;\r\n submitButton.innerHTML = 'Submit';\r\n });\r\n });\r\n<\/script>\r\n\r\n<p>\r\n <a href=\"\/source-viewer.php?file=public\/appendix-samples\/fruits-and-filters-js-enhanced.php\">View source code for this page<\/a>\r\n<\/p>"
},
{
"id": "\/appendix-samples\/fruits-and-filters-retaining-selection.php",
"kind": "code",
"title": "fruits-and-filters-retaining-selection.php",
"body": "<?php\r\n\/**\r\n * Returns a sorted array of all of the fruit images in the\r\n * img\/fruit directory.\r\n *\/\r\nfunction get_fruits() {\r\n \/\/ Get a sorted list of fruits like we've done before:\r\n $jpgs = glob('..\/img\/fruit\/*.jpg');\r\n $webps = glob('..\/img\/fruit\/*.webp');\r\n $files = array_merge( $jpgs, $webps );\r\n sort( $files );\r\n \/\/ Return the list to whichever code called get_fruits()\r\n return $files;\r\n}\r\n\r\n\/**\r\n * Returns an array of CSS filters that can be applied to an\r\n * image.\r\n *\/\r\nfunction get_filters() {\r\n return [\r\n 'filter: grayscale(100%)',\r\n 'filter: blur(2px)',\r\n 'filter: brightness(1.5)',\r\n 'filter: contrast(1.5)',\r\n 'filter: hue-rotate(90deg)',\r\n 'filter: invert(1)',\r\n 'filter: sepia(1)',\r\n 'transform: rotateZ(90deg);',\r\n 'transform: skewX(-15deg);',\r\n ];\r\n}\r\n\r\n\/**\r\n * Returns true if the user provided a fruit_id and filter_id.\r\n * Returns false otherwise.\r\n *\/\r\nfunction user_requested_a_fruit_and_filter() {\r\n \/\/ If they haven't provided a fruit_id or filter_id, false:\r\n if( ! isset( $_POST['fruit_id'] ) ) return false;\r\n if( ! isset( $_POST['filter_id'] ) ) return false;\r\n \/\/ Othewise, true!\r\n return true;\r\n}\r\n\r\n\/**\r\n * Shows the requested fruit with the applied CSS filter.\r\n *\/\r\nfunction show_a_filtered_fruit() {\r\n \/\/ Note that we don't ask the user to send us the filename\r\n \/\/ but the NUMBER of the fruit (and filter), in its list.\r\n \/\/ This is a strong validation step - the user can't inject\r\n \/\/ anything we didn't expect them to!\r\n $fruit_id = intval( $_POST['fruit_id'] );\r\n $filter_id = intval( $_POST['filter_id'] );\r\n $fruit = get_fruits()[ $fruit_id ];\r\n $filter = get_filters()[ $filter_id ];\r\n\r\n \/\/ Output the fruit image with the filter applied:\r\n ?>\r\n <img src=\"<?php echo $fruit; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black; <?php echo $filter; ?>;\">\r\n <?php\r\n}\r\n?>\r\n\r\n<h1>Fruity Filters<\/h1>\r\n\r\n<?php if( user_requested_a_fruit_and_filter() ) { ?>\r\n <h2>\r\n Here's your filtered fruit:\r\n <\/p>\r\n <?php show_a_filtered_fruit(); ?>\r\n<?php } ?>\r\n\r\n<h2>\r\n <?php if( user_requested_a_fruit_and_filter() ) { ?>\r\n Filter another fruit!\r\n <?php } else { ?>\r\n Filter a fruit!\r\n <?php } ?>\r\n<\/h2>\r\n<form method=\"post\" action=\"fruits-and-filters-retaining-selection.php\">\r\n <p>\r\n <label for=\"fruit\">Fruit:<\/label>\r\n <select id=\"fruit\" name=\"fruit_id\">\r\n <?php foreach( get_fruits() as $id => $fruit ) { ?>\r\n <option\r\n <?php if( isset( $_POST['fruit_id'] ) && intval( $_POST['fruit_id'] ) === $id ) echo 'selected'; ?>\r\n value=\"<?php echo $id; ?>\"\r\n >\r\n <?php echo basename( $fruit ); ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <p>\r\n <label for=\"filter\">Filter:<\/label>\r\n <select id=\"filter\" name=\"filter_id\">\r\n <?php foreach( get_filters() as $id => $filter ) { ?>\r\n <option\r\n <?php if( isset( $_POST['filter_id'] ) && intval( $_POST['filter_id'] ) === $id ) echo 'selected'; ?>\r\n value=\"<?php echo $id; ?>\"\r\n >\r\n <?php echo $filter; ?>\r\n <\/option>\r\n <?php } ?>\r\n <\/select>\r\n <\/p>\r\n\r\n <button type=\"submit\">Submit<\/button>\r\n<\/form>\r\n\r\n<p>\r\n <a href=\"\/source-viewer.php?file=public\/appendix-samples\/fruits-and-filters-retaining-selection.php\">View source code for this page<\/a>\r\n<\/p>"
},
{
"id": "\/appendix-samples\/fruity-gallery-2.php",
"kind": "code",
"title": "fruity-gallery-2.php",
"body": "<?php\r\n\/\/ Get a list of all the .jpg files in the img\/fruit directory:\r\n$jpgs = glob('..\/img\/fruit\/*.jpg');\r\n\/\/ Get a list of all the .webp files in the img\/fruit directory:\r\n$webps = glob('..\/img\/fruit\/*.webp');\r\n\r\n\/\/ Combine both arrays into one:\r\n$files = array_merge( $jpgs, $webps );\r\n\r\n\/\/ Sort the array by filename:\r\nsort( $files );\r\n\r\n\/\/ Loop through each file and display it as an image:\r\nforeach( $files as $file ) {\r\n ?>\r\n\r\n <img src=\"<?php echo $file; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black;\">\r\n\r\n <?php\r\n}"
},
{
"id": "\/appendix-samples\/fruity-links.php",
"kind": "code",
"title": "fruity-links.php",
"body": "<?php\r\n$images = [\r\n 'apple', 'bananas', 'blackberries', 'cherry', 'kiwi',\r\n 'lemon', 'orange', 'pear', 'pineapple', 'watermelon'\r\n];\r\n\r\necho '<ol>';\r\nforeach( $images as $image ) {\r\n ?>\r\n\r\n <li>\r\n <a href=\"\/\/php.danq.dev\/img\/fruit\/<?php echo $image; ?>.jpg\">\r\n <?php echo $image; ?>\r\n <\/a>\r\n <\/li>\r\n\r\n <?php\r\n}\r\necho '<\/ol>';"
},
{
"id": "\/appendix-samples\/includes-from-outside-webroot.php",
"kind": "code",
"title": "includes-from-outside-webroot.php",
"body": "<h1>Preventing direct access to includes<\/h1>\n<p>\n Following up on <a href=\"\/04-includes-and-functions\/\">Lesson 4<\/a>, this page\n provides a demonstration of two ways to prevent your visitors from directly\n accessing files that you <em>intended<\/em> to be used only with\n <code>include<\/code> or <code>require<\/code> statements:\n<\/p>\n\n<h2>Solution 1: keep the files outside of the webroot<\/h2>\n<p>\n Below, we use: <code><?php include('..\/private\/sample-include.php'); ?><\/code>\n to include a file from outside the webroot. You can\n <a href=\"\/source-viewer.php?file=private\/sample-include.php\">view the source\n code of that file<\/a> (but only because this site explicitly allows you to,\n of course!). But there does not exist any web address that would load it directly.\n The only way to run it is from <em>this<\/em> page.\n<\/p>\n<?php include('..\/..\/private\/sample-include.php'); ?>\n\n<h2>Solution 2: have your include files \"halt early\" if accessed directly<\/h2>\n<p>\n If you can't, or don't want to, store your include files outside of the webroot,\n but still don't want them to be accessed directly by their web addresses, you\n can have them \"halt early\" if accessed directly.\n<\/p>\n<p>\n Below, we use: <code><?php include('sample-include-2.php'); ?><\/code> to\n load a file from within the webroot. If you try to access it directly, via\n <a href=\"https:\/\/php.danq.dev\/appendix-samples\/sample-include-2.php\">\n https:\/\/php.danq.dev\/appendix-samples\/sample-include-2.php\n <\/a>, you won't see anything, but when it's included from <em>this<\/em> page,\n it outputs four verses of \"10 Green Bottles\".\n <a href=\"\/source-viewer.php?file=public\/appendix-samples\/sample-include-2.php\">\n View the source code of that file\n <\/a> to see how that trick is performed!\n<\/p>\n<?php include('sample-include-2.php'); ?>"
},
{
"id": "\/appendix-samples\/",
"kind": "page",
"title": "Appendix A: Further Examples",
"body": "Additional samples of PHP code that didn't fit into the main lessons.\nRelated to Lesson 3:\n\nTimes tables square\n\n\nFruity links\n\n\nGallery of .webp and .jpg images\n\n\nRandom image with effect\n\nRelated to Lesson 4:\n\nIncludes from outside the webroot\n\nRelated to Lesson 5:\n\nFruits and filters retaining selection\n\n\nFruits and filters with JS-enhancement\n\nRelated to Lesson 6:\n\nCounting repeat visits in a cookie\n\n\nRemembering the user's name"
},
{
"id": "\/appendix-samples\/random-image-with-effect.php",
"kind": "code",
"title": "random-image-with-effect.php",
"body": "<?php\r\n\/\/ Get a list of all the .jpg files in the img\/fruit directory:\r\n$jpgs = glob('..\/img\/fruit\/*.jpg');\r\n\/\/ Get a list of all the .webp files in the img\/fruit directory:\r\n$webps = glob('..\/img\/fruit\/*.webp');\r\n\r\n\/\/ Combine both arrays into one:\r\n$files = array_merge( $jpgs, $webps );\r\n\r\n\/\/ Select a random file:\r\n$random_file_id = array_rand($files);\r\n$random_file = $files[ $random_file_id ];\r\n\r\n\/\/ A list of CSS effects to apply to the image:\r\n$effects = [\r\n 'filter: grayscale(100%)',\r\n 'filter: blur(2px)',\r\n 'filter: brightness(1.5)',\r\n 'filter: contrast(1.5)',\r\n 'filter: hue-rotate(90deg)',\r\n 'filter: invert(1)',\r\n 'filter: sepia(1)',\r\n 'transform: rotateZ(90deg);',\r\n 'transform: skewX(-15deg);',\r\n];\r\n\r\n\/\/ Select a random effect:\r\n$random_effect_id = array_rand($effects);\r\n$random_effect = $effects[ $random_effect_id ];\r\n\r\n\/\/ Tip: if you wanted to apply multiple different effects, you could use:\r\n\/\/ - `shuffle($effects)` to put the list of effects into a random order, then\r\n\/\/ - `$random_effects = array_slice($effects, 0, 2);` to choose the first two effects from the shuffled list, then\r\n\/\/ - `$style = implode(';', $random_effects);` to join the effects into a single string, separating them by semicolons\r\n\/\/ You could then `echo $style;` within the style=\"...\" attribute to apply the effects!\r\n\r\n?>\r\n<p>\r\n <img src=\"<?php echo $random_file; ?>\"\r\n width=\"200\"\r\n height=\"200\"\r\n style=\"border: 1px solid black; <?php echo $random_effect; ?>;\">\r\n<\/p>\r\n\r\n<ul>\r\n <li>The image that was displayed was: <code><?php echo basename($random_file); ?><\/code><\/li>\r\n <li>The effect that was applied was: <code><?php echo $random_effect; ?><\/code><\/li>\r\n<\/ul>\r\n\r\n<p>\r\n <a href=\"\/demo-harness.php?file=public\/appendix-samples\/random-image-with-effect.php\">\ud83d\udd04 Get another image\/effect<\/a>\r\n<\/p>"
},
{
"id": "\/appendix-samples\/remember-my-name-here-too.php",
"kind": "code",
"title": "remember-my-name-here-too.php",
"body": "<?php\r\n\/\/ Attempt to retrieve the user's name from their cookie -\r\n\/\/ First - check if we have a name for them:\r\n$has_name = isset( $_COOKIE['name'] );\r\n\/\/ Second - set a $name variable either to their name (if we have it) or \"Stranger\" if we don't:\r\n$name = $has_name ? $_COOKIE['name'] : 'Stranger';\r\n\r\n\/\/ Remember that the user's name is user input and therefore untrustworthy -\r\n\/\/ We'll need to use htmlspecialchars() to escape it and prevent XSS attacks!\r\n?>\r\n<h1>You found the second page, <?php echo htmlspecialchars( $name ); ?>!<\/h1>\r\n\r\n<p>\r\n This website isn't very interesting. You might as well go back to the\r\n <a href=\"\/demo-harness.php?file=public\/appendix-samples\/remember-my-name.php\">first page<\/a> again.\r\n<\/p>"
},
{
"id": "\/appendix-samples\/remember-my-name.php",
"kind": "code",
"title": "remember-my-name.php",
"body": "<?php\r\n\/\/ Check if the user is submitting a name:\r\nif( isset( $_POST['name'] ) ) {\r\n \/\/ If the user has submitted a name, store it in a cookie for 1 day:\r\n setcookie( 'name', $_POST['name'], time() + 60 * 60 * 24 * 1 );\r\n\r\n \/\/ Then reload the page and do nothing else\r\n header( 'Location: ' . $_SERVER['REQUEST_URI'] );\r\n die();\r\n}\r\n\r\n\/\/ Attempt to retrieve the user's name from their cookie -\r\n\/\/ First - check if we have a name for them:\r\n$has_name = isset( $_COOKIE['name'] );\r\n\/\/ Second - set a $name variable either to their name (if we have it) or \"Stranger\" if we don't:\r\n$name = $has_name ? $_COOKIE['name'] : 'Stranger';\r\n\r\n\/\/ Remember that the user's name is user input and therefore untrustworthy -\r\n\/\/ We'll need to use htmlspecialchars() to escape it and prevent XSS attacks!\r\n?>\r\n<h1>Hello, <?php echo htmlspecialchars( $name ); ?>!<\/h1>\r\n\r\n<p>\r\n Welcome to my website.\r\n <a href=\"\/demo-harness.php?file=public\/appendix-samples\/remember-my-name-here-too.php\">I made a second page<\/a>\r\n which will also address you by name; take a look!\r\n<\/p>\r\n\r\n<h2>What's your name?<\/h2>\r\n<form method=\"post\" action=\"<?php echo $_SERVER['REQUEST_URI']; ?>\">\r\n <label for=\"name\">Name:<\/label>\r\n <input type=\"text\" id=\"name\" name=\"name\" autofocus\r\n <?php if( $has_name ) { echo 'value=\"' . htmlspecialchars( $name ) . '\"'; } ?>\r\n >\r\n <button type=\"submit\">\ud83d\udc4b Hi!<\/button>\r\n<\/form>"
},
{
"id": "\/appendix-samples\/sample-include-2.php",
"kind": "code",
"title": "sample-include-2.php",
"body": "<?php\n\/**\n * This PHP file is within the webroot, in the 'public\/appendix-samples' directory on this webserver.\n * This means that it can be accessed directly from a web browser, like you can\n * for example with a URL like 'https:\/\/php.danq.dev\/appendix-samples\/sample-include-2.php'.\n *\n * However, we can use a trick to have it \"halt early\" if accessed directly, and only output\n * the four verses of \"10 Green Bottles\" if it's included from another file.\n *\/\n\n\/\/ If this file is accessed directly, halt execution and output a \"Forbidden\" error:\nif( $_SERVER['REQUEST_URI'] === $_SERVER['SCRIPT_NAME'] ) {\n header('HTTP\/1.1 403 Forbidden');\n die('This page cannot be accessed directly.');\n}\n\n\/\/ Otherwise, choose a random number from 6 to 10 and use that as the bit we're up to of\n\/\/ the \"10 Green Bottles\" song, counting down for four verses from that number:\n$bottles = rand(6, 10);\n\necho '<blockquote style=\"border: 1px solid black; padding: 1em; margin: 1em 0;\">';\n\n\/\/ Four times, print a verse and subtract one bottle:\nfor( $i = 0; $i < 4; $i++ ) {\n echo '<p>';\n echo \"{$bottles} green bottles hanging on the wall,<br>\";\n echo \"{$bottles} green bottles hanging on the wall,<br>\";\n echo \"And if one green bottle should accidentally fall,<br>\";\n $bottles--; \/\/ subtract one bottle!\n echo \"There'll be {$bottles} green bottles hanging on the wall.\";\n echo '<\/p>';\n}\n\necho '<\/blockquote>';"
},
{
"id": "\/appendix-samples\/times-tables-square.php",
"kind": "code",
"title": "times-tables-square.php",
"body": "<h1>Times tables square<\/h1>\r\n<p>\r\n This is a square of times tables, from 1 to 15.\r\n<\/p>\r\n\r\n<table>\r\n <tr>\r\n <th><!-- this cell intentionally left blank --><\/th>\r\n <?php\r\n \/\/ The top row of our table will have an empty column, then one column for each number from 1 to 15:\r\n for( $i = 1; $i <= 15; $i++ ) {\r\n echo '<th>' . $i . '<\/th>';\r\n }\r\n ?>\r\n <\/tr>\r\n\r\n <?php\r\n \/\/ We'll make 15 subsequent rows, using a variable called $row to count its number (1 to 15):\r\n for( $row = 1; $row <= 15; $row++ ) {\r\n \/\/ Each row number will start with a new row (<tr>) in the table:\r\n echo '<tr>';\r\n\r\n \/\/ The first cell in each row will be the number of the row:\r\n echo '<th>' . $row . '<\/th>';\r\n\r\n \/\/ Then we'll loop through 15 columns, using a variable called $col to count its number (1 to 15):\r\n for( $col = 1; $col <= 15; $col++ ) {\r\n \/\/ The value of the cell will be the product of the row and column numbers:\r\n echo '<td>' . ( $row * $col ) . '<\/td>';\r\n }\r\n\r\n \/\/ Each row will end with a new row (<tr>) in the table:\r\n echo '<\/tr>';\r\n }\r\n ?>\r\n<\/table>"
},
{
"id": "\/appendix-samples\/welcome-back-counter.php",
"kind": "code",
"title": "welcome-back-counter.php",
"body": "<?php\r\n\r\n\/\/ Set the visitor counter coookie to a specified value.\r\n\/\/ The cookie lifespan is 7 days.\r\nfunction set_or_update_visit_counter_cookie( $value ) {\r\n \/\/ Set a cookie for visited=?, where ? is the $value:\r\n setcookie( 'visited', $value, time() + 60 * 60 * 24 * 7 );\r\n}\r\n\r\n\/\/ See if a cookie is set:\r\nif( isset( $_COOKIE['visited'] ) ) {\r\n \/\/ Get the value of the cookie and force it to be an integer\r\n $number_of_visits = intval( $_COOKIE['visited'] );\r\n\r\n \/\/ The cookie is set, so we welcome the user back.\r\n echo \"\ud83d\udc4b Welcome back! You've visited this page \";\r\n echo $number_of_visits;\r\n echo \" time(s) before!\";\r\n\r\n \/\/ Increase the visit count by 1:\r\n $number_of_visits++;\r\n\r\n \/\/ Re-set the cookie using our function:\r\n set_or_update_visit_counter_cookie( $number_of_visits );\r\n} else {\r\n \/\/ Cookie is not set: welcome them!\r\n echo \"\ud83c\udf89 Welcome! This is your first visit.\";\r\n\r\n \/\/ Set the cookie using our function:\r\n set_or_update_visit_counter_cookie( 1 );\r\n}"
},
{
"id": "\/appendix-samples\/whats-your-name-2.php",
"kind": "code",
"title": "whats-your-name-2.php",
"body": "<?php\r\n\/\/ Check if we're provided a name and pronoun via GET params:\r\nif( isset( $_GET['name'] ) && isset( $_GET['pronoun'] ) ) {\r\n \/\/ Strip any HTML from the name and pronoun:\r\n $name = htmlspecialchars( $_GET['name'] );\r\n $pronoun = htmlspecialchars( $_GET['pronoun'] );\r\n\r\n \/\/ If they selected \"(no pronouns)\", use their own name:\r\n if( $pronoun === 'NONE' ) {\r\n $pronoun = $name;\r\n }\r\n\r\n \/\/ Construct a greeting string:\r\n $greeting = \"Today I met $name. I was so pleased to meet $pronoun!\";\r\n}\r\n?>\r\n\r\n<h1>\r\n Welcome to my friendly website!\r\n<\/h1>\r\n\r\n<?php if( isset( $greeting ) ) { \/* We have a greeting! *\/ ?>\r\n\r\n <h2>\r\n <?php echo $greeting; ?>\r\n <\/h2>\r\n <p>\r\n I wonder who I will <a href=\"whats-your-name-2.php\">meet next?<\/a>\r\n <\/p>\r\n\r\n<?php } else { \/* No greeting: show the form! *\/ ?>\r\n\r\n <p>\r\n Hi! Who are you?\r\n <\/p>\r\n <form method=\"get\" action=\"whats-your-name-2.php\">\r\n <label for=\"name\">Name:<\/label>\r\n <input type=\"text\" id=\"name\" name=\"name\" autofocus>\r\n <label for=\"pronoun\">Pronouns:<\/label>\r\n <select id=\"pronoun\" name=\"pronoun\">\r\n <option value=\"them\">they\/them<\/option>\r\n <option value=\"him\">he\/him<\/option>\r\n <option value=\"her\">she\/her<\/option>\r\n <option value=\"it\">it\/its<\/option>\r\n <option value=\"aer\">ae\/aer<\/option>\r\n <option value=\"eir\">ey\/em\/eir<\/option>\r\n <option value=\"faer\">fae\/faer<\/option>\r\n <option value=\"zir\">ze\/zir<\/option>\r\n <option value=\"hir\">ze\/hir<\/option>\r\n <option value=\"NONE\">(no pronouns)<\/option>\r\n <\/select>\r\n <button type=\"submit\">Hi!<\/button>\r\n <\/form>\r\n\r\n<?php } ?>\r\n\r\n<p>\r\n <a href=\"\/source-viewer.php?file=public\/appendix-samples\/whats-your-name-2.php\">View source code for this solution<\/a>\r\n<\/p>"
},
{
"id": "\/appendix-security\/",
"kind": "page",
"title": "Appendix B: Security",
"body": "Security principles you need to be aware of when making your website \"mildly dynamic\" with PHP are:\nDon't trust user input! (XSS)\nAny data that comes from a user, whether you find it in $_GET, $_POST, $_COOKIE,\nor even e.g. their browser's name and version number ($_SERVER['HTTP_USER_AGENT']) can not be trusted.\nIt can always be tampered with by a malicious user. So:\n\nIf you're sending (e.g. echo) user input back to the user, or to another user, you should escape\nit first:\n\n\nhtmlspecialchars() is the best solution in almost all circumstances if you're outputting to a web page, e.g.:\nThanks for visiting my site, !\n\n\nIf you're outputting into a HTML element, you must also make sure that the element's value is properly wrapped in quotes, e.g.:\n"
},
{
"id": "\/troubleshooting\/",
"kind": "page",
"title": "Troubleshooting",
"body": "Stuck? Here are some common problems:\n\nPHP isn't working"
},
{
"id": "\/troubleshooting\/php-not-working\/",
"kind": "page",
"title": "Troubleshooting: PHP isn't working",
"body": "If you tried the Hello World lesson and didn't get the expected result,\nthere are a few things you can check:\n\nCheck that your file has a .php extension. In some text editors, it's easy to accidentally save a file\nas .html, .txt or similar instead. (Make sure you're actually using a text editor, not a\nword processor!)\n\n\nCheck that PHP is enabled on your server. Many free\/cheap hosting plans don't include PHP (e.g. Neocities\ncan't run PHP code). Some hosts might require you to check a box to enable it. If you manage your own server, you\nmight meed to install and configure PHP.\n\n\n(If you're looking for a new host,\nthis guide might help.)\n\n\n\n\nIf asking for help online, be sure to include:\n\nThe code from your failing .php file\nThe full URL of the page\n\nThe way in which it's not-working e.g. 404 error, 500 error, blank page (use View Source to see if it's\nreally blank), can see PHP code, etc."
}
]