{"id":25,"date":"2016-03-17T10:50:58","date_gmt":"2016-03-17T10:50:58","guid":{"rendered":"http:\/\/www.librarypi.com\/?page_id=25"},"modified":"2016-04-01T01:05:37","modified_gmt":"2016-04-01T01:05:37","slug":"lp-stage-3","status":"publish","type":"page","link":"https:\/\/www.librarypi.com\/index.php\/lp-stage-3\/","title":{"rendered":"LP Stage 3"},"content":{"rendered":"<p>In stage 3 we are going to add some very basic authentication and registration.\u00a0 Basically, the site will support only one user, an administrator.\u00a0 Only that user will be able to add or delete books.<\/p>\n<p>Note that you can download a zip file of a Raspberry Pi disk image backup of Stage\u00a03 here ( <em>NOTE: you will need to run <strong>sudo raspi-config<\/strong> to expand file space<\/em>):<br \/>\n<div class='w3eden'><!-- WPDM Link Template: Default Template -->\n\n<div class=\"link-template-default card mb-2\">\n    <div class=\"card-body\">\n        <div class=\"media\">\n            <div class=\"mr-3 img-48\"><img decoding=\"async\" class=\"wpdm_icon\" alt=\"Icon\"   src=\"https:\/\/www.librarypi.com\/wp-content\/plugins\/download-manager\/assets\/file-type-icons\/zip.svg\" \/><\/div>\n            <div class=\"media-body\">\n                <h3 class=\"package-title\"><a href='https:\/\/www.librarypi.com\/index.php\/download\/library-pi-stage-3-image\/'>Library Pi Stage 3 Image<\/a><\/h3>\n                <div class=\"text-muted text-small\"><i class=\"fas fa-copy\"><\/i> 1 file(s) <i class=\"fas fa-hdd ml-3\"><\/i> 528 MB<\/div>\n            <\/div>\n            <div class=\"ml-3\">\n                <a class='wpdm-download-link download-on-click btn btn-primary ' rel='nofollow' href='#' data-downloadurl=\"https:\/\/www.librarypi.com\/index.php\/download\/library-pi-stage-3-image\/?wpdmdl=170&refresh=69e5f729c43a01776678697\">Download<\/a>\n            <\/div>\n        <\/div>\n    <\/div>\n<\/div>\n\n<\/div><\/p>\n<p>Or, you can also just download the Zip file with the Stage\u00a03 PHP files here (you still need to create the database, and setup lp_dbconf.php):<br \/>\n<div class='w3eden'><!-- WPDM Link Template: Default Template -->\n\n<div class=\"link-template-default card mb-2\">\n    <div class=\"card-body\">\n        <div class=\"media\">\n            <div class=\"mr-3 img-48\"><img decoding=\"async\" class=\"wpdm_icon\" alt=\"Icon\" src=\"https:\/\/www.librarypi.com\/wp-content\/plugins\/download-manager\/assets\/file-type-icons\/zip.svg\" \/><\/div>\n            <div class=\"media-body\">\n                <h3 class=\"package-title\"><a href='https:\/\/www.librarypi.com\/index.php\/download\/php-files-for-stage-3\/'>PHP Files for Stage 3<\/a><\/h3>\n                <div class=\"text-muted text-small\"><i class=\"fas fa-copy\"><\/i> 1 file(s) <i class=\"fas fa-hdd ml-3\"><\/i> 313.96 KB<\/div>\n            <\/div>\n            <div class=\"ml-3\">\n                <a class='wpdm-download-link download-on-click btn btn-primary ' rel='nofollow' href='#' data-downloadurl=\"https:\/\/www.librarypi.com\/index.php\/download\/php-files-for-stage-3\/?wpdmdl=171&refresh=69e5f729c5a831776678697\">Download<\/a>\n            <\/div>\n        <\/div>\n    <\/div>\n<\/div>\n\n<\/div><\/p>\n<p>SQL script for stage 3<br \/>\n<div class='w3eden'><!-- WPDM Link Template: Default Template -->\n\n<div class=\"link-template-default card mb-2\">\n    <div class=\"card-body\">\n        <div class=\"media\">\n            <div class=\"mr-3 img-48\"><img decoding=\"async\" class=\"wpdm_icon\" alt=\"Icon\" src=\"data:image\/svg+xml;base64,CiAgICAgICAgICAgIDxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDAgNDAiPgogICAgICAgICAgICAgICAgPGRlZnM+CiAgICAgICAgICAgICAgICAgICAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudCIgeDE9IjAiIHkxPSIwIiB4Mj0iMCIgeTI9IjEiPgogICAgICAgICAgICAgICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjMjY5ZGVmIiBvZmZzZXQ9IjAiLz4KICAgICAgICAgICAgICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzI2YmRlZiIgb2Zmc2V0PSIxIi8+CiAgICAgICAgICAgICAgICAgICAgPC9saW5lYXJHcmFkaWVudD4KICAgICAgICAgICAgICAgIDwvZGVmcz4KICAgICAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgICAgIDxyZWN0IGZpbGw9InVybCgjZ3JhZGllbnQpIiB4PSIwIiB5PSIwIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHJ4PSIzIiByeT0iMyIvPgogICAgICAgICAgICAgICAgICAgIDx0ZXh0IHg9IjUiIHk9IjE5IiBmb250LWZhbWlseT0iQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxM3B4IiBsZXR0ZXItc3BhY2luZz0iMSIgZmlsbD0iI0ZGRkZGRiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0c3Bhbj5TUUw8L3RzcGFuPgogICAgICAgICAgICAgICAgICAgICAgICA8dHNwYW4geD0iNiIgeT0iMjgiPl88L3RzcGFuPgogICAgICAgICAgICAgICAgICAgIDwvdGV4dD4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9zdmc+CgoJCQk=\" \/><\/div>\n            <div class=\"media-body\">\n                <h3 class=\"package-title\"><a href='https:\/\/www.librarypi.com\/index.php\/download\/sql-stage-3\/'>SQL Stage 3<\/a><\/h3>\n                <div class=\"text-muted text-small\"><i class=\"fas fa-copy\"><\/i> 1 file(s) <i class=\"fas fa-hdd ml-3\"><\/i> 1.21 KB<\/div>\n            <\/div>\n            <div class=\"ml-3\">\n                <a class='wpdm-download-link download-on-click btn btn-primary ' rel='nofollow' href='#' data-downloadurl=\"https:\/\/www.librarypi.com\/index.php\/download\/sql-stage-3\/?wpdmdl=178&refresh=69e5f729c6de41776678697\">Download<\/a>\n            <\/div>\n        <\/div>\n    <\/div>\n<\/div>\n\n<\/div><\/p>\n<h3>Datamodel<\/h3>\n<p>We&#8217;re going to need some new tables in our datamodel: lp_user and lp_book_del.\u00a0 lp_user will contain a simple ID, login\/email, and password for user authentication, and lp_book_del will serve as a &#8216;recycle bin&#8217; for deleted books so that we can delete them when it&#8217;s convenient.<\/p>\n<p><em>NOTE: The password is stored un-encrypted, which is a bad practice.\u00a0 I am leaving it this way for simplicity of demonstration.\u00a0 The password should use some salt with an MD5 hash so that if anyone steals or compromises the database their passwords aren&#8217;t exposed.\u00a0 I am also not enforcing any sort of minimal requirements for weak password.<\/em><\/p>\n<pre>create table if not exists lp_user(\r\n\u00a0 id integer\u00a0 NOT NULL AUTO_INCREMENT,\r\n\u00a0 email varchar(254),\r\n\u00a0 password varchar( 32 ),\r\n\u00a0 connection_key varchar( 32 ),\r\n\u00a0 PRIMARY KEY PK_lp_used (id)\r\n);<\/pre>\n<pre>create table if not exists lp_book_del (\r\n\u00a0 id integer,\r\n\u00a0 ukey varchar(32),\r\n\u00a0 PRIMARY KEY PK_lp_book_del (id)\r\n);<\/pre>\n<p>&nbsp;<\/p>\n<h3>Authentication<\/h3>\n<p>We are going to add a new <em>lp_user<\/em> table to hold our login information.\u00a0 Since our LP-&gt;HTMLPageTop function is called at the top of every site page, we are going to add all the steps for authentication, which include Registration, Login, and Logout.\u00a0 HTMLPageTop will call 2 new functions to support this interaction: UserProcess() and UserHTML().<\/p>\n<ul>\n<li>When there are no users in the lp_user table, the HTMLPageTop method will call UserHTML() to output a Registration form allowing one user to enter a username and password.<\/li>\n<li>If there is one user in lp_user, the HTMLPageTop method will call UserHTML() to output a login form.<\/li>\n<li>If there is a cookie value that indicates a user has logged in, HTMLPageTop will call UserHTML() to output a Logout button.<\/li>\n<\/ul>\n<p>So, the UserHTML() method outputs the proper type of form based on the authentication status.\u00a0 Examples:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-152\" src=\"http:\/\/www.librarypi.com\/wp-content\/uploads\/2016\/03\/auth1.png\" alt=\"auth1\" width=\"842\" height=\"138\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-154\" src=\"http:\/\/www.librarypi.com\/wp-content\/uploads\/2016\/03\/auth3.png\" alt=\"auth3\" width=\"521\" height=\"135\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-153\" src=\"http:\/\/www.librarypi.com\/wp-content\/uploads\/2016\/03\/auth2.png\" alt=\"auth2\" width=\"174\" height=\"129\" \/><\/p>\n<p>The HTMLPageTop method also calls the UserProcess() method.\u00a0 This new method will check the input parameters from the forms, and either register the admin user, or attempt a login.\u00a0 If a login is successful, then a cookie is set that can be checked upon the next page view (to remember the user already logged in).<\/p>\n<p>With the login process, the LP class will now have a UserID variable.\u00a0 Pages and code can check this variable to determine if the admin is logged in (&gt;zero) or not (=zero).\u00a0 Look at the example of how the new HTMLPageTop makes use of this to only show the Upload menu link to the admin user:<\/p>\n<pre>if( $this-&gt;UserID == 0 )\r\n<span style=\"color: #ff0000;\">  $Upload<\/span> = \"\";\r\nelse\r\n<span style=\"color: #ff0000;\">  $Upload<\/span> = \"&amp;nbsp;&amp;nbsp;&lt;a href=upload.php&gt;Upload&lt;\/a&gt;\";\r\n\u2026\r\necho \"&lt;!DOCTYPE HTML PUBLIC ...&gt;\\n\".\r\n  \"&lt;html lang=\\\"en\\\"&gt;\\n\" .\r\n\u00a0 \"&lt;head&gt;\\n\" .\r\n\u2026\r\n\u00a0 \"&lt;div id=lpmenu&gt;&lt;a href=index.php&gt;Home&lt;\/a&gt;<span style=\"color: #ff0000;\">$Upload<\/span>&lt;span style=\\\"float:right;\\\"&gt;\" \r\n  . <span style=\"color: #ff0000;\">$this-&gt;UserHTML()<\/span> \r\n  . \"&lt;\/span&gt;&lt;\/div&gt;\";<\/pre>\n<p>We can see that the Upload menu link is now only output if a user is logged in.\u00a0 Just to be safe that nobody is trying a malicious hack of the URL, the <em>AddBook<\/em> method also checks for user access:<\/p>\n<pre>function AddBook( $Title )\r\n{\r\n\u00a0  if( $this-&gt;UserID == 0 ) <span style=\"color: #ff0000;\">die( \"Access denied\" );<\/span>\r\n\u2026<\/pre>\n<h3>UserHTML<\/h3>\n<p>The UserHTML method in the LP class will output the different HTML forms for Registration, Login, and Logout, and is called from HTMLPageTop.\u00a0 Take a look at the <em>if<\/em> tests that determine the current authentication status, and output the needed HTML in $Ret:<\/p>\n<pre>function UserHTML()\r\n{\r\n\u00a0\/\/ Output either Register, Login, or Logout\r\n\u00a0$Ret = \"\";\r\n\u00a0if( $this-&gt;UserID &gt; 0 ) <span style=\"color: #ff0000;\">\/\/ Must have just logged in<\/span>\r\n\u00a0\u00a0$Ret = \"&lt;form method=post&gt;&lt;input type=hidden name=act value=logout&gt; &lt;input type=submit value=Logout&gt;&lt;\/form&gt;\";\r\n\u00a0if( strlen( $Ret ) == 0 ) \r\n\u00a0{\r\n\u00a0\u00a0$result = mysqli_query( db(), \"select count(*) Cnt from lp_user\");\r\n\u00a0\u00a0if( $result === false )\r\n\u00a0\u00a0\u00a0return ( \"Error: Can't read user data.\u00a0 Did you create the SQL tables in the correct database?&lt;br&gt;\" );\r\n\u00a0\u00a0if ( $row = mysqli_fetch_array( $result ) )\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0if( $row[\"Cnt\"] == 0 ) <span style=\"color: #ff0000;\">\/\/ If there are no users, it's a register<\/span>\r\n\u00a0\u00a0\u00a0\u00a0$Ret = \"&lt;form method=post&gt;$this-&gt;UserErr Register: EMail: &lt;input type=text name=email maxlength=254&gt;\"\r\n\u00a0\u00a0\u00a0\u00a0.\" Password: &lt;input type=password name=pass1 &gt;\u00a0 again: &lt;input type=password name=pass2 &gt;\"\r\n\u00a0\u00a0\u00a0\u00a0.\"&lt;input type=hidden name=act value=register&gt;\"\r\n\u00a0\u00a0\u00a0\u00a0.\"&lt;input type=submit value=Register&gt;&lt;\/form&gt;\";\r\n\u00a0\u00a0}\r\n\u00a0\u00a0$result-&gt;close();\r\n\u00a0}\r\n\u00a0if( strlen( $Ret ) == 0 )\u00a0 <span style=\"color: #ff0000;\">\/\/ else, there are users, but nobody logged in, so login:<\/span>\r\n\u00a0\u00a0$Ret = \"&lt;form method=post&gt;$this-&gt;UserErr EMail: &lt;input type=text name=email maxlength=254&gt;\"\r\n\u00a0\u00a0\u00a0.\" Password: &lt;input type=password name=pass1 &gt;\"\r\n\u00a0\u00a0\u00a0.\"&lt;input type=hidden name=act value=login&gt;\"\r\n\u00a0\u00a0\u00a0.\"&lt;input type=submit value=Login&gt;&lt;\/form&gt;\";\r\n\u00a0return\u00a0 $Ret;\r\n}<\/pre>\n<p>Again, the HTMLPageTop function that is called on every page will in turn call the above UserHTML to output the correct authentication form.<\/p>\n<h3>UserProcess<\/h3>\n<p>The UserProcess method will process the user&#8217;s data from the registration form, login form, or logout button:<\/p>\n<ul>\n<li>When a user registers, their name and password are added to the lp_user table.\u00a0 Only one user can register.<\/li>\n<li>When a user logs in, their name and password is checked against the lp_user table.\u00a0 If found, a cookie is created and stored in lp_user so that the authentication is remembered.<\/li>\n<li>When a user logs out, their cookie is cleared and the lp_user table also has the cookie cleared.<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<pre>\u00a0function UserProcess()\r\n\u00a0{\r\n\u00a0\u00a0$Act = isset( $_POST[\"act\"] ) ? $_POST[\"act\"] : \"\";\r\n\u00a0\u00a0$ConnKey = isset( $_COOKIE[ 'connkey' ] ) ? $_COOKIE[ 'connkey' ] : '';\r\n\u00a0\u00a0if( $Act != \"logout\" &amp;&amp; strlen( $ConnKey ) &gt; 0 ) <span style=\"color: #ff0000;\">\/\/ Reconnect using cookie?<\/span>\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0$result = mysqli_query( db(), \"select * from lp_user where connection_key = '$ConnKey'\");\r\n\u00a0\u00a0\u00a0if( $result === false )\r\n\u00a0\u00a0\u00a0\u00a0die( \"Error: Looks like user table is not setup\" );\r\n\u00a0\u00a0\u00a0if ( $row = mysqli_fetch_array( $result ) )\r\n\u00a0\u00a0\u00a0\u00a0$this-&gt;UserID = $row[\"id\"];\r\n\u00a0\u00a0\u00a0$result-&gt;close();\r\n\u00a0\u00a0}\r\n\u00a0\u00a0if( $this-&gt;UserID &gt; 0 )\r\n\u00a0\u00a0\u00a0return;\r\n\u00a0\u00a0$EMail = str_replace( \"'\", \"\", isset( $_POST[\"email\"] ) ? $_POST[\"email\"] : \"\" );\r\n\u00a0\u00a0$Pass1 = str_replace( \"'\", \"\", isset( $_POST[\"pass1\"] ) ? $_POST[\"pass1\"] : \"\" );\r\n\u00a0\u00a0if( $Act == \"register\" ) <span style=\"color: #ff0000;\">\/\/ Was the Registration form submitted?<\/span>\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0$Pass2 = str_replace( \"'\", \"\", isset( $_POST[\"pass2\"] ) ? $_POST[\"pass2\"] : \"\" );\r\n\u00a0\u00a0\u00a0if( strcmp( $Pass1, $Pass2 ) != 0 || strlen( $Pass1 )==0 || strlen( $EMail) == 0 )\r\n\u00a0\u00a0\u00a0\u00a0$this-&gt;UserErr = \"&lt;span class=err&gt;Verify email &amp; password&lt;\/span&gt;\";\r\n\u00a0\u00a0\u00a0else{\r\n\u00a0\u00a0\u00a0\u00a0mysqli_query( db(), \"insert ignore into lp_user set id=1, email='$EMail', password='$Pass1'\" ); \/\/ Only 1 user!\r\n\u00a0\u00a0\u00a0\u00a0$Act = \"login\";\r\n\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0} \r\n\u00a0\u00a0if( $Act == \"login\" ) <span style=\"color: #ff0000;\">\/\/ Was the Login form submitted?<\/span>\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0$result = mysqli_query( db(), \"select * from\u00a0 lp_user where email='$EMail' and password='$Pass1'\" );\r\n\u00a0\u00a0\u00a0if ( $row = mysqli_fetch_array( $result ) )\r\n\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0$this-&gt;UserID = $row[\"id\"];\r\n\u00a0\u00a0\u00a0\u00a0$Key = $this-&gt;UKey();\r\n\u00a0\u00a0\u00a0\u00a0setcookie( \"connkey\", $Key );\r\n\u00a0\u00a0\u00a0\u00a0mysqli_query( db(), \"update lp_user set connection_key = '$Key' where email='$EMail' and password='$Pass1'\" );\r\n\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0else\r\n\u00a0\u00a0\u00a0\u00a0$this-&gt;UserErr = \"&lt;span class=err&gt;Invalid login&lt;\/span&gt;\";\r\n\u00a0\u00a0\u00a0$result-&gt;close();\u00a0\u00a0\u00a0\r\n\u00a0\u00a0}\r\n\u00a0\u00a0else if( $Act == \"logout\" ) <span style=\"color: #ff0000;\">\/\/ Was the logout form\/button submitted?<\/span>\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0$this-&gt;UserID = 0;\r\n\u00a0\u00a0\u00a0setcookie( \"connkey\", null );\r\n\u00a0\u00a0\u00a0mysqli_query( db(), \"update lp_user set connection_key = '' where key='$ConnKey'\" );\r\n\u00a0\u00a0}\r\n\u00a0}<\/pre>\n<h3>Security<\/h3>\n<p>Now with authentication, we&#8217;re going to make some additional changes:\u00a0 only the admin user can upload books, and will also be able to\u00a0delete books.<\/p>\n<p>In order to only show the Uploads option to the admin user, we&#8217;ve already seen this code that was added to the HTMLPageTop method:<\/p>\n<pre>if( $this-&gt;UserID == 0 )\r\n<span style=\"color: #ff0000;\"> $Upload<\/span> = \"\";\r\nelse\r\n<span style=\"color: #ff0000;\"> $Upload<\/span> = \"&amp;nbsp;&amp;nbsp;&lt;a href=upload.php&gt;Upload&lt;\/a&gt;\";<\/pre>\n<p>If the UserID is zero. then nobody is logged in and we won&#8217;t present the link to the upload page.\u00a0 If the UserID is &gt; zero, then it&#8217;s the admin that&#8217;s logged in, and we can display the upload link.<\/p>\n<p>For deleting books, look at this change to the BookList method:<\/p>\n<pre>while ( $row = mysqli_fetch_array( $result ) )\r\n{\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0if( $this-&gt;UserID == 0 )<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0\u00a0\u00a0$Del = \"\";<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0else<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0\u00a0\u00a0$Del = \"&amp;nbsp;&lt;a href=# onclick=\\\"if( confirm( 'Are you sure you want to delete this?' ) ) window.location='index.php?act=del&amp;bk=\" . $row[\"ukey\"] . \"';\\\"&gt;[DELETE]&lt;a&gt;&amp;nbsp;&amp;nbsp;\";<\/span>\r\n\u00a0\u00a0$Pages = $row[\"Pages\"];\r\n\u00a0\u00a0$ToDo = $row[\"ToDo\"];\r\n\u00a0\u00a0if( $ToDo == 0 )\r\n\u00a0\u00a0\u00a0\u00a0$Det = \"$Pages pages\";\r\n\u00a0\u00a0else\r\n\u00a0\u00a0\u00a0\u00a0$Det = \"$ToDo pages to process\";\r\n\u00a0\u00a0$Ret .= \"<span style=\"color: #ff0000;\">$Del<\/span>&lt;a href=\\\"reader.php?bk=\" . $row[\"ukey\"] . \"\\\"&gt;\".$row[\"title\"] . \" [$Det]&lt;\/a&gt;&lt;br \/&gt;\";\r\n}<\/pre>\n<p>In the above code, if the Admin user is logged in, then the $Del variable is initialized with a link that will lead the user to delete the book.\u00a0 And now look again at the change to HTMLPageTop:<\/p>\n<pre>if(<span style=\"color: #ff0000;\"> $this-&gt;UserID == 0<\/span> )\r\n  $Upload = \"\";\r\n<span style=\"color: #ff0000;\">else<\/span>\r\n{\r\n  $Upload = \"&amp;nbsp;&amp;nbsp;&lt;a href=upload.php&gt;Upload&lt;\/a&gt;\";\r\n\u00a0\u00a0<span style=\"color: #ff0000;\">$Act = isset( $_GET[\"act\"] ) ? $_GET[\"act\"] : \"\";<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0if( $Act == \"del\" )<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0{<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0  $UKey = isset( $_GET[\"bk\"] ) ? $_GET[\"bk\"] : \"\";<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0\u00a0\u00a0$this-&gt;DeleteBook( $UKey );<\/span>\r\n<span style=\"color: #ff0000;\">\u00a0\u00a0}<\/span>\r\n}<\/pre>\n<p>So when the UserID &gt; zero (admin logged in) not only does the page show the &#8216;Upload&#8217; option, but it also checks for the &#8220;del&#8221; delete operation and calls a new DeleteBook method.<\/p>\n<h3>DeleteBook<\/h3>\n<p>Remember how we have a scheduled crontab job, that is calling the proc.php page every minute?\u00a0 Well, if the user wants to delete a book while the proc.php script is in the middle of OCR&#8217;ing it, then we can have remnant files and possible errors.\u00a0 So here&#8217;s how we&#8217;re going to avoid that:<\/p>\n<ul>\n<li>When a book is deleted, it&#8217;s not really deleted.\u00a0 It&#8217;s record is moved into another table named lp_book_del.<\/li>\n<li>Since the book isn&#8217;t in lp_book anymore, the OCR code won&#8217;t see it and won&#8217;t process anymore pages in it.<\/li>\n<li>The proc.php script will now also check for records in lp_book_del to be purged from the system.\u00a0 Since this is the same method that invokes the OCR process, we can make sure that the delete doesn&#8217;t occur in the middle of an OCR.<\/li>\n<\/ul>\n<p>So here is the DeleteBook method in the LP class:<\/p>\n<pre>function DeleteBook( $UKey )\r\n{\r\n\u00a0\u00a0if( $this-&gt;UserID == 0 ) die( \"Access denied\" );\r\n\u00a0\u00a0$UKey = str_replace( \"'\", \"\", $UKey );\r\n\u00a0\u00a0$result = mysqli_query( db(), \"select * from lp_book where ukey = '$UKey'\" );\r\n\u00a0\u00a0if ( $row = mysqli_fetch_array( $result ) )\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0 $BookID = $row[\"id\"];\r\n\u00a0\u00a0\u00a0 mysqli_query( db(), \"<span style=\"color: #ff0000;\">insert into lp_book_del select id, ukey from lp_book where id = $BookID<\/span>\" );\r\n\u00a0\u00a0\u00a0 mysqli_query( db(), \"<span style=\"color: #ff0000;\">delete from lp_book where id = $BookID<\/span>\" );\r\n\u00a0\u00a0}\r\n\u00a0\u00a0$result-&gt;close();\r\n}<\/pre>\n<p>Of course, there is a &#8216;real&#8217; method to delete the book and related files, and it&#8217;s called PurgeBooks:<\/p>\n<pre>function PurgeBooks()\r\n{\r\n\u00a0\u00a0$result = mysqli_query( db(), \"<span style=\"color: #ff0000;\">select * from lp_book_del<\/span>\" );\r\n\u00a0\u00a0if ( $row = mysqli_fetch_array( $result ) )\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0 $UKey = $row[ \"ukey\"];\r\n\u00a0\u00a0\u00a0 $BookID = $row[\"id\"];\r\n\u00a0\u00a0\u00a0 $this-&gt;deleteDir( $_SERVER[\"DOCUMENT_ROOT\"].\"\/uploads\/$UKey\/\" );\r\n\u00a0\u00a0\u00a0 mysqli_query( db(), \"<span style=\"color: #ff0000;\">delete from lp_page_word<\/span> where page_id in (select id from lp_page where book_id = $BookID)\" );\r\n\u00a0\u00a0\u00a0 mysqli_query( db(), \"<span style=\"color: #ff0000;\">delete from lp_page<\/span> where book_id = $BookID\" );\r\n\u00a0\u00a0\u00a0 mysqli_query( db(), \"<span style=\"color: #ff0000;\">delete from lp_book_del<\/span> where id = $BookID\" );\r\n\u00a0\u00a0}\r\n}<\/pre>\n<p>And finally, we see how proc.php (which is called every minute) is modified to include the above code:<\/p>\n<pre>\u00a0function DoProcess() {\r\n\u00a0\u00a0$lp = new LP();\r\n\u00a0\u00a0$lp-&gt;OCRFiles();\r\n\u00a0\u00a0$lp-&gt;AddOCRRecords();\r\n\u00a0\u00a0<span style=\"color: #ff0000;\">$lp-&gt;PurgeBooks();<\/span>\r\n\u00a0} \/\/ end of function DoProcess()<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In stage 3 we are going to add some very basic authentication and registration.\u00a0 Basically, the site will support only one user, an administrator.\u00a0 Only that user will be able to add or delete books. Note that you can download&#8230;<br \/><a class=\"read-more-button\" href=\"https:\/\/www.librarypi.com\/index.php\/lp-stage-3\/\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":4,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-25","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/pages\/25","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/comments?post=25"}],"version-history":[{"count":20,"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/pages\/25\/revisions"}],"predecessor-version":[{"id":186,"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/pages\/25\/revisions\/186"}],"wp:attachment":[{"href":"https:\/\/www.librarypi.com\/index.php\/wp-json\/wp\/v2\/media?parent=25"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}