Saturday, October 31, 2009

Chapter 7. Update Statements











 < Day Day Up > 







Chapter 7. Update Statements





This chapter covers the following exam topics:



  • The INSERT and REPLACE statements

  • The UPDATE statement

  • The DELETE and TRUNCATE statements

  • Handling duplicate key values

  • Using ORDER BY and LIMIT with UPDATE and DELETE statements



Questions on the material in this chapter make up approximately 10% of the exam.



This chapter discusses SQL statements that modify the contents of database tables:



  • INSERT adds new records to a table.

  • REPLACE is similar to INSERT, except that if a new record contains a value that duplicates a unique key value in an existing record, it deletes the old record first.

  • UPDATE modifies existing table records.

  • DELETE and TRUNCATE TABLE remove records from a table. DELETE can remove some or all records; TRUNCATE TABLE completely empties a table.



Another statement that modifies table contents is LOAD DATA INFILE, which reads records from a datafile and loads them into a table. It's discussed in Chapter 9, "Importing and Exporting Data."



Note that for purposes of discussion here, the term "update statement" is used in a collective sense to refer to various kinds of statements that modify tables. "UPDATE statement" refers specifically to statements that begin with the UPDATE keyword. Also, keep in mind the following terminology with regard to indexes:



  • "UNIQUE index" means specifically a unique-valued index created using the keyword UNIQUE.

  • "PRIMARY KEY" means specifically a unique-valued index created using the keywords PRIMARY KEY.

  • The term "unique-valued index" is a generic term meaning any index that contains only unique values. A UNIQUE index or a PRIMARY KEY is a unique-valued index.

  • The term "primary key" means a unique-valued index that cannot contain NULL values.



Much of the discussion in this chapter uses the following table as a source of examples:










CREATE TABLE people

(

id INT UNSIGNED NOT NULL AUTO_INCREMENT,

name CHAR(40) NOT NULL,

age INT NOT NULL,

PRIMARY KEY (id)

);


















     < Day Day Up > 



    20.5 Correcting compiler and linker errors



    [ Team LiB ]










    20.5 Correcting compiler and linker errors


    What if nothing seems to happen when you try to build and run your version of the Pop program? First make sure you can see the Output window, which is where your compiler errors are listed. If you can't find it, use a Windows | Other Windows | Output to open it [Windows | Output (Version 6.0)].


    Then resize the Output window so you can see what's in there. Double-clicking on a compiler error will call up the Source-Code window with the cursor on the line where the error is. Locate the erroneous line and see if there's an obvious typo.


    If not, go back and look at the error message. Read all of the error message. Think about what it says. Believe it or not, the message is trying to help you. In its own way, it's saying 'try this,' and not just, 'you're wrong.' If the message doesn't make sense to you, click on the error message and press F1 to get information about it.


    Go back to the Source-Code window and look at the line with the error again. If you're unsure about the usage of any of the API functions there, highlight them and press F1 to get their documentation.


    One thing that happens sometimes is that an error early in a code module causes a cascade of many more error messages further down. Generally, you should start trying to fix the errors from the beginning. If you fix a few errors and get to errors that don't seem to make sense, try to rebuild the program and/or recompile the module and see if some of them go away.


    If you have an error that refuses to go away, skip over it and fix some of the later errors if you can. Maybe while you're working your subconscious will figure out what was wrong up above.


    Let's repeat the key teaching we have to impart here:



    When fixing an error, READ the error message and THINK ABOUT what it says.



    One tends to not want to read an error message, as it's bad news, but this is a mistake. The message is trying to help you! An error message is not your enemy, it's your friend. It's trying to save you from your own mistakes!


    If you get a zillion error messages saying things aren't found, that means there are some files missing from your project file or from the directory where your project lives. Not being able to find windows.h, for instance makes a lot of errors. You may need to check that the directory setting is correct.


    Sometimes when working in a lab with the compiler on a remote server, your machine can't find the place on the server where the standard include files live. This may be what's going on if you get the zillion 'file not found' messages.


    Microsoft Visual Studio keeps the information about which directories it searches on the Tools | Options... | Projects | VC++ Directories sheet [this is the Tools | Options... | Directories sheet in Version 6.0]. These settings are not part of your project file, they are part of the individual compiler installation. The all-important Include directories setting is found by selecting Include Files in the Show Directories for combo box on this sheet. Make sure that one of the selections here corresponds to where your compiler has its include subdirectory. If you are working in a lab, your compiler may be on a remote drive with a funny letter name like K: or M:. You can use Windows Explorer to go out across the network and find where the Visual Studio Include directory lives.


    Even if you have the directories right, you will get a 'not found' linker error if there is some function you prototyped but forgot to add the code for. Look at the error closely to figure out which function is missing. The function name will be in a 'mangled' format, so it can be a little hard to decipher.


    Once in a while Visual Studio will get confused or lose track of what you've been doing, and even though you have properly defined all of your functions, it will say that it can't find one of them. This can happen if for some reason Visual Studio doesn't realize that it needs to recompile some files. The way to fix this situation is to use the Build | Rebuild All option to force a rebuild of all files.


    Invoking Build | Rebuild All can also help if Visual Studio seems unable to find a resource that you're fairly sure you've put in place. If the rebuild doesn't fix things, then the problem is probably you; that is, you need to recheck what you think you've done.






      [ Team LiB ]



      Hack 29. Add Support for Nonstandard File Extensions











       < Day Day Up > 





      Hack 29. Add Support for Nonstandard File Extensions





      Use the VSTweak power toy to tell Visual Studio

      how to handle file types that aren't supported by

      default
      .







      Creating custom

      file extensions allows you to specify your own file extensions that

      will be treated like another already existing file type in Visual

      Studio. This is very useful if you have created a custom file type

      for your application but want Visual Studio to treat it like a

      regular file.





      To create and manage custom file extensions, you can use a power toy

      called VSTweak. The VSTweak power toy is one of the more useful power

      toys for Visual Studio and is the subject of a number of different

      hacks in this book. The VSTweak power toy can be downloaded at

      http://workspaces.gotdotnet.com/vstweak.





      Once you have installed and launched VSTweak, click on the File

      Extensions Manager tab, shown in Figure 4-16.







      Figure 4-16. File Extensions Manager







      To add a new custom extension, you simply need to enter the custom

      extension in the Define Extension text box, select what kind of file

      this extension should be treated as in the Treat As drop-down, and

      then click the Add button shown in Figure 4-16. The

      extension .cxm will be added to Visual Studio

      and treated like an .xml file.





      You can also remove custom extensions by selecting the extension from

      the drop-down and clicking the Delete button. Changes made to custom

      extensions are made for all users of your

      machine, not just you.









      To edit custom file extensions, you must be logged in as

      administrator on your machine, or you must launch the VSTweak

      application by right-clicking on it and choosing Run As, then log in

      using an administrator account.








      Managing custom file extensions is very useful, especially when

      creating custom file types or when hacking Visual Studio to do

      something it was never intended to do (such as adding the ability to

      edit .php files [Hack #12] ).

















         < Day Day Up > 



        8.14 Determining Outgoing Interface with UDP



        [ Team LiB ]






        8.14 Determining Outgoing Interface with UDP


        A connected UDP socket can also be used to determine the outgoing interface that will be used to a particular destination. This is because of a side effect of the connect function when applied to a UDP socket: The kernel chooses the local IP address (assuming the process has not already called bind to explicitly assign this). This local IP address is chosen by searching the routing table for the destination IP address, and then using the primary IP address for the resulting interface.


        Figure 8.23 shows a simple UDP program that connects to a specified IP address and then calls getsockname, printing the local IP address and port.



        Figure 8.23 UDP program that uses connect to determine outgoing interface.

        udpcliserv/udpcli09.c




        1 #include "unp.h"

        2 int
        3 main(int argc, char **argv)
        4 {
        5 int sockfd;
        6 socklen_t len;
        7 struct sockaddr_in cliaddr, servaddr;

        8 if (argc != 2)
        9 err_quit("usage: udpcli <IPaddress>");

        10 sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

        11 bzero(&servaddr, sizeof(servaddr));
        12 servaddr.sin_family = AF_INET;
        13 servaddr.sin_port = htons(SERV_PORT);
        14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

        15 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

        16 len = sizeof(cliaddr);
        17 Getsockname(sockfd, (SA *) &cliaddr, &len);
        18 printf("local address %s\n", Sock_ntop((SA *) &cliaddr, len));

        19 exit(0);
        20 }


        If we run the program on the multihomed host freebsd, we have the following output:





        freebsd % udpcli09 206.168.112.96
        local address 12.106.32.254:52329

        freebsd % udpcli09 192.168.42.2
        local address 192.168.42.1:52330

        freebsd % udpcli09 127.0.0.1
        local address 127.0.0.1:52331


        The first time we run the program, the command-line argument is an IP address that follows the default route. The kernel assigns the local IP address to the primary address of the interface to which the default route points. The second time, the argument is the IP address of a system connected to a second Ethernet interface, so the kernel assigns the local IP address to the primary address of this second interface. Calling connect on a UDP socket does not send anything to that host; it is entirely a local operation that saves the peer's IP address and port. We also see that calling connect on an unbound UDP socket also assigns an ephemeral port to the socket.


        Unfortunately, this technique does not work on all implementations, mostly SVR4-derived kernels. For example, this does not work on Solaris 2.5, but it works on AIX, HP-UX 11, MacOS X, FreeBSD, Linux, and Solaris 2.6 and later.






          [ Team LiB ]



          Recipe 16.14. Automatically Discovering DRb Services with Rinda










          Recipe 16.14. Automatically Discovering DRb Services with Rinda




          Credit: James Edward Gray II



          Problem


          You want to distribute Ruby code across your local network without hardcoding the clients with the addresses of the servers.




          Solution


          Using Ruby's standard
          Rinda library, it's easy to provide zero-configuration networking for clients and services. With Rinda, machines can discover DRb services without providing any addresses. All you need is a running RingServer on the local network:



          #!/usr/bin/ruby
          # rinda_server.rb

          require 'rinda/ring' # for RingServer
          require 'rinda/tuplespace' # for TupleSpace

          DRb.start_service

          # Create a TupleSpace to hold named services, and start running.
          Rinda::RingServer.new(Rinda::TupleSpace.new)

          DRb.thread.join





          Discussion


          The RingServer provides automatic service detection for DRb servers. Any machine on your local network can find the local RingServer without knowing its address. Once it's found the server, a client can look up services and use them, not having to know the addresses of the DRb servers that host them.


          To find the Rinda server, a client broadcasts a UDP packet asking for the location of a RingServer. All computers on the local network will get this packet, and if a computer is running a RingServer, it will respond with its address. A server can use the RingServer to register services; a client can use the RingServer to look up services.


          A RingServer object keeps a service listing in a shared TupleSpace (see Recipe 16.12). Each service has a corresponding tuple with four members:


          • The literal symbol :name, which indicates that the tuple is an entry in the RingServer namespace.

          • The symbol of a Ruby class, indicating the type of the service.

          • The DRbObject shared by the service.

          • A string description of the service.


          By retrieving this TupleSpace remotely, you can look up services as tuples and advertise your own services. Let's advertise an object (a simple TupleSpace) tHRough the RingServer under the name :TupleSpace:



          #!/usr/bin/ruby
          # share_a_tuplespace.rb

          require '
          rinda/ring' # for RingFinger and SimpleRenewer
          require '
          rinda/tuplespace' # for TupleSpace

          DRb.start_service
          ring_server = Rinda::RingFinger.primary

          # Register our TupleSpace service with the RingServer
          ring_server.write( [:name, :TupleSpace, Rinda::TupleSpace.new, 'Tuple Space'],
          Rinda::SimpleRenewer.new )

          DRb.thread.join



          The SimpleRenewer sent in with the namespace listing lets the RingServer periodically check whether the service has expired.


          Now we can write clients that find this service by querying the RingServer, without having to know which machine it lives on. All we need to know is the name of the service:



          #!/usr/bin/ruby
          # use_a_tuplespace.rb

          require 'rinda/ring' # for RingFinger
          require 'rinda/tuplespace' # for TupleSpaceProxy

          DRb.start_service
          ring_server = Rinda::RingFinger.primary

          # Ask the RingServer for the advertised TupleSpace.
          ts_service = ring_server.read([:name, :TupleSpace, nil, nil])[2]
          tuplespace = Rinda::TupleSpaceProxy.new(ts_service)

          # Now we can use the object normally:
          tuplespace.write([:data, rand(100)])
          puts "Data is #{tuplespace.read([:data, nil]).last}."
          # Data is 91.



          These two programs locate each other without needing hardcoded IP addresses. Addresses are still being used under the covers, but the address to the
          Rinda server is discovered automatically through UDP, and all the other addresses are kept in the
          Rinda server.


          Rinda::RingFinger.primary stores the first RingServer to respond to your Ruby process's UDP packet. If your local network is running more than one RingServer, the first one to respond might not be the one with the service you want, so you should probably only run one RingServer on your network. If you do have more than one RingServer, you can iterate over them with Rinda::RingFinger#each.




          See Also


          • Recipe 16.12, "Creating a Shared "Whiteboard"

          • Recipe 16.18, "A Remote-Controlled Jukebox"

          • Eric Hodel has a Rinda::RingServer tutorial at http://segment7.net/projects/ruby/drb/rinda/ringserver.html













          Chapter 6 -- The Keyboard




          Chapter 6

          The Keyboard


          The keyboard and the mouse are the two standard sources of user input in Microsoft Windows 98, often complementing each other with some overlap. The mouse is, of course, much more utilized in today's applications than those of a decade ago. We are even accustomed to using the mouse almost exclusively in some applications, such as games, drawing programs, music programs, and Web browsers. Yet while we could probably make do without the mouse, removing the keyboard from the average PC would be disastrous.

          Compared with the other components of the personal computer, the keyboard has a positively ancient ancestry beginning with the first Remington typewriter in 1874. Early computer programmers used keyboards to punch holes in Hollerith cards and later used keyboards on dumb terminals to communicate directly with large mainframe computers. The PC has been expanded somewhat to include function keys, cursor positioning keys, and (usually) a separate numeric keypad, but the principles of typing are basically the same.

          Chapter 13. Data Grid











          Chapter 13. Data Grid




          Data consolidation is a typical problem that developers face when designing a web application GUI because handling large amounts of data is common practice in dynamic applications. Using a data grid to display large amounts of data is beneficial for consolidation and usability. Data consolidation and better usability occur naturally because the function of a data grid is to format the data into rows and columns, which ultimately creates a structured grid that is much easier to read. A data grid formats the data into rows of different content. Within each of the rows are columns that separate corresponding content.


          This way of structuring data is common practice among web applications. The component that we build in this chapter will make the process of structuring a data grid as easy as structuring the XML data that you will be passing it. Therefore, you will never have to touch the JavaScript objects after they are createdunless, of course, you would like to add additional functionality. This is because all the styling will be done in a CSS file in order to help you design the GUI for the grid and match any application that you are developing.















          Appendix B. X.509 Digital Certificates











           < Day Day Up > 





          Appendix B. X.509 Digital Certificates

          X.509, one of the most common formats for signed certificates, is used largely by Sun Microsystems, Microsoft, VeriSign, IBM, and many other companies for signing e-mail messages, authenticating program code, and certifying many other types of data. In its simplest form, an X.509 certificate contains the following data:

          • The certificate format version�X.509 V1, V2, or V3.

          • The certificate serial number.

          • The identifier of the signature algorithm�the algorithm the CA used to sign the certificate. The identifier consists of the algorithm ID and the parameters passed to the algorithm.

          • The X.500 name of the signer of the certificate. This entity is normally a CA. Using this certificate implies trusting the entity that signed this certificate. In some cases, such as root or top-level CA certificates, the issuer signs its own certificate.



          • The period of validity:

            begin date and end date. Each certificate is valid for only a limited amount of time, described by a start date and time and an end date and time. This period can be as short as a few seconds or almost as long as a century.



          • The name of the certified entity:

            The X.500 Distinguished Name of the entity whose public key the certificate identifies. This field conforms to the X.500 standard, so it is intended to be unique across the Internet (see Section 4.5.1.3 on page 120).



          • The public key of the certified identity:

            The public key of the entity being named, together with an algorithm identifier that specifies which public-key cryptosystem this key belongs to and any associated key parameters.



          • The signature:

            the hash code of all the preceding fields, encoded with the signer's private key. Thus, the signer guarantees that a given entity has a particular public key.

          All the data in a certificate is encoded using two related standards: Abstract Syntax Notation 1 and Distinguished Encoding Rules.













             < Day Day Up > 



            Section 10.7.&nbsp; Interval Literals









            10.7. Interval Literals


            Interval literals are similar to timestamp literals and are useful when you want to embed interval
            values as constants within your code. Interval literals take the following form:



            INTERVAL 'character_representation' start_element TO end_element



            where:



            character_representation


            Is the character string representation of the interval. See the section "Interval Conversions" for a description of how the two interval datatypes are represented in character form.


            start_element


            Specifies the leading element in the interval.


            end_element


            Specifies the trailing element in the interval.


            Unlike the TO_YMINTERVAL and TO_DSINTERVAL functions, interval literals
            allow you to specify an interval using any sequence of datetime elements from Table 10-2. There are only two restrictions:


            • You must use a consecutive sequence of elements.

            • You cannot transition from a month to a day within the same interval.


            Following are several valid examples:



            DECLARE
            A INTERVAL YEAR TO MONTH;
            B INTERVAL YEAR TO MONTH;
            C INTERVAL DAY TO SECOND;
            D INTERVAL DAY TO SECOND;
            BEGIN
            /* Some YEAR TO MONTH examples */
            A := INTERVAL '40-3' YEAR TO MONTH;
            B := INTERVAL '40' YEAR;

            /* Some DAY TO SECOND examples */
            C := INTERVAL '10 1:02:10.123' DAY TO SECOND;
             
            /* Fails in Oracle9i, Release 1 because of a bug */
            --D := INTERVAL '1:02' HOUR TO MINUTE;

            /* Following are two workarounds for defining intervals,
            such as HOUR TO MINUTE, that represent only a portion of the
            DAY TO SECOND range. */
            SELECT INTERVAL '1:02' HOUR TO MINUTE
            INTO D
            FROM dual;

            D := INTERVAL '1' HOUR + INTERVAL '02' MINUTE;
            END;



            In Oracle9i Database Releases 1 and 2, and, surprisingly, also in Oracle Database 10g Release 1, expressions such as INTERVAL '1:02' HOUR TO MINUTE that don't specify a value for each possible element will work from a SQL statement but not from a PL/SQL statement. Furthermore, you'll get an error about using the keyword BULK in the wrong context. This is a bug that we hope to see fixed in a future release.



            One very convenient thing that Oracle will do for you is to normalize interval values. In the following example, 72 hours and 15 minutes is normalized to 3 days, 0 hours, and 15 minutes:



            DECLARE
            A INTERVAL DAY TO SECOND;
            BEGIN
            SELECT INTERVAL '72:15' HOUR TO MINUTE INTO A FROM DUAL;
            DBMS_OUTPUT.PUT_LINE(A);
            END;



            The output is:



            +03 00:15:00.000000



            Oracle will normalize only the high-end value (hours in this example) of an interval literal. An attempt to specify an interval of 72:75 (72 hours and 75 minutes) results in an error.









              ACKNOWLEDGEMENTS









































              Prev don't be afraid of buying books Next






























              ACKNOWLEDGEMENTS



              A preface is not complete without acknowledging
              the other people that make a book possible. Being an author is like
              being at the peak of a pyramid. . . you are being supported (and
              your work made possible) in various ways by a multitude of other
              people. This is my chance to acknowledge and thank them. . . by
              name for the ones I'm aware of.



              Kent Beck for making TDD and XP household
              words—at least in my household—and for his support of
              this book.



              Miroslav Novak for first turning me on to this
              new way of programming that a bunch of smart people were talking
              about on something called a Wiki. Miroslav may be my junior in
              terms of time spent programming, but I've learned more from him
              than I sometimes care to admit.



              Patrick Wilson-Welsh for several things: for
              always reminding me of the big picture when I got mired down in the
              details of the moment; for being the best sounding board and copy
              editor that an author could ask for; and for having the courage to
              leave an established life in Washington, D.C. to move to small-town
              Canada to become my co-founder and first apprentice.



              Dave Thomas of "The Pragmatic Programmers"[URL
              55]for letting me use the macros he
              wrote for the book "The Pragmatic Programmer"[25]. That
              book was inspiring in its layout and typesetting as well as
              catalytic in bringing about a turning point in my thoughts about
              programming.



              Hand in hand with "The Pragmatic Programmer"
              went "Software Craftsmanship"[34] by Pete
              McBreen. I mean that literally,.. . I read them back-to-back. Pete
              provides a wonderful introduction to and discussion of software as
              a craft. A fabulous book, it was another contributing factor to my
              career-shaking epiphany (the third being XP). Thanks, Pete.



              Peter Coad, to whom I owe a great debt for
              taking me under his wing in many ways and helping me to get this
              project off the ground. I have to thank him also for letting me
              charge ahead with a TDD edition of The Coad Letter[URL 61].



              Paul Petralia, my acquisitions editor at
              Prentice Hall, and the fine crew that works with him. Thanks for
              letting us convince you that this book isn't about "Testing," and
              for believing in it whole heartedly once we had accomplished
              that.



              Craig Larman must be mentioned here for his
              encouragement, support, and advice. I still have great memories of
              spending a day with Craig at his home outside Dallas, discussing
              UML and Together[URL 34]and drinking homemade Chai.



              And a big thanks to Ron Jeffries for writing the
              foreword for me, as well as being generally supportive of my
              XP-related endeavors, specifically (well, what comes to mind as I
              write this) this book, and the TDD Coad Letter. Also, for doing so
              much to bring XP so far.



              Special thanks and a hearty acknowledgement to
              members of the TDD Yahoo! group that sent me their JUnit tips:
              Darren Hobbs, J. B. Rainsberger, and Derek Weber.



              Very special thanks to those that contributed to
              the book by writing and letting me use material on subjects that
              they are the experts in, specifically (in order of appearance):






              Mike Clark
              for the section on JUnitPerf,










              Jens Uwe
              Pipka
              for the section on the Daedalos extensions,










              Tim Bacon for
              the section on xmlUnit,










              Mike Bowler
              for the section on the Gargoyle extensions,










              Bryan Dollery
              for the section on IDEA,










              James Newkirk
              for the chapter on NUnit,










              Bob Payne for
              stepping in at the last minute with the chapter on PyUnit,










              Kay Pentecost
              for the chapter on vbUnit, and










              Scott Ambler
              for the appendix on agile modeling.











              Thanks to all the folks in the XP community who
              gave me feedback (in no particular order): Kay Pentecost, Edmund
              Schweppe, Aldo Bergamini, Mike Clark, Francesco Cirillo, and my
              friends, colleagues, and past co-authors: Randy Miller and Miroslav
              Novak. As with all authors, I'm sure I've missed someone. Sorry
              about that.



              I need to acknowledge and thank my reviewers as
              well: Alan Francis and William Wake.



              And yes, as Kent Beck says in the preface of his
              TDD book[9], it is
              cliché to thank our families, but they heartily deserve it. To
              my wife, Kate, for saying "I'll clean up the kitchen. You go
              write." To my kids, Tasha and Jason, for being understanding when I
              had to write, and for thinking that it's so cool to have a Dad who
              writes books. Finally, to my youngest child, Leah, who is too young
              to notice what I'm doing but simply smiles when she sees me and
              gives me a hug when I pick her up.



              This book was produced using a variety of open
              source software. All my computers run Redhat Linux. The manuscript
              was prepared using GNU Emacs, and typeset using . Image
              manipulation was done with Gimp. The xdvi previewer was used
              extensively. The PDF version was created using dvips, and ps2pdf.
              Several packages were used with , some off the shelf (lgrind, draft-copy, and fixme),
              several courtesy of Dave Thomas (for exercises, extended cross
              reference support, and url references), and several of my own
              (chapter heading quotes, story/task/test management, sidebars, and
              tips).















































              Amazon






              Programming with JDBC and SQL




              I l@ve RuBoard









              Programming with JDBC and SQL


              To start things off, we're going to create a table, insert some values, and query them.


              Import the DriverManager and Class classes to load the JDBC driver.



              >>> from java.sql import DriverManager
              >>> from java.lang import Class

              Load the driver using Class.forName, which dynamically loads a class into the Java Virtual Machine (JVM). (See the Java API documentation for more details.) For Microsoft Access via ODBC (or any ODBC database):



              >>> Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")
              <jclass sun.jdbc.odbc.JdbcOdbcDriver at -416861389>

              For InstantDB:



              >>> Class.forName("jdbc.idbDriver")
              <jclass jdbc.idbDriver at -282805662>

              Just as an HTML page has an HTTP URL, so a database has a JDBC URL. The last part of the URL for JdbcOdbcDriver refers to the DSN that we set up with the ODBC data source administrator in the last section. Enter this:



              >>> url = "jdbc:odbc:db_dev"

              The last part of the URL for InstantDB refers to the database properties file. Enter this:



              >>> url = "jdbc:idb=db_dev.prp"

              Now we can pass the URL to DriverManager's getConnection() method and pass the user name and password (both of which are blank, i.e., " "). Once we have a connection, we can create a table. Ours will have two columns, Name and Age, and we'll call it PERSON.


              Create the JDBC connection object.



              >>> connection = DriverManager.getConnection(url, "","")

              Create the JDBC statement object.



              >>> statement = connection.createStatement()

              Create an SQL DDL statement that defines a database table.



              >>> create_table = """CREATE TABLE PERSON (name VARCHAR(50), age INT)"""

              Execute the SQL DDL statement.



              >>> statement.executeUpdate(create_table)
              -1

              Close the connection. (Leave it open if you're going on to the next section.)



              >>> connection.close()

              Go to the database tools, and see that the table was created. The Microsoft Access version is shown in Figure 17-4. (For the InstantDB version, use SQLBuilder, which comes in the InstantDB examples directory.)


              Figure 17-4. The PERSON Table�MS Access Version



              Inserting Data


              Now we can start adding data to our table. If you've disconnected or restarted your JPython interactive session, you'll have to reconnect first.



              Reconnecting Your JPython Session


              Import DriverManager and Class.



              >>> from java.sql import DriverManager
              >>> from java.lang import Class

              For ODBC:



              >>> Class.forName('sun.jdbc.odbc.JdbcOdbcDriver')
              <jclass sun.jdbc.odbc.JdbcOdbcDriver ...>
              >>> url="jdbc:odbc:db_dev"

              For InstantDB:



              >>> Class.forName("jdbc.idbDriver")
              <jclass jdbc.idbDriver at -282805662>
              >>> url = "jdbc:idb=db_dev.prp"

              Get the connection, and create the statement.



              >>> connection=DriverManager.getConnection(url, "", "")
              >>> statement = connection.createStatement()


              Once you have a JDBC statement object, you have to create an insert format string in order to insert data. (Notice the single quotes around %s. They're important for proper SQL syntax.)



              >>> insert = "insert into Person (name, age) values ('%s', %d)"

              Pass a tuple containing name and age to the string format.



              >>> insert % ("Whitney", 3)
              "insert into Person (name, age) values ('Whitney', 3)"

              Add Whitney to the database.



              >>> statement.executeUpdate(insert % ("Whitney", 3))
              1

              Add Kiley.



              >>> statement.executeUpdate(insert % ("Kiley", 23))
              1

              Add Scott and Nick.



              >>> statement.executeUpdate(insert % ("Scott", 34))
              1

              >>> statement.executeUpdate(insert % ("Nick", 3))
              1

              Add Mary and Adam.



              >>> statement.executeUpdate(insert % ("Mary", 2))
              1

              >>> statement.executeUpdate(insert % ("Adam", 23))
              1

              Notice that after each addition the executeUpdate() method returns the number of rows affected.


              Go to your database management tools, and view PERSON. Figure 17-5 shows its Microsoft Access version; use SQLBuilder to see the InstantDB version.


              Figure 17-5. The PERSON Table with Data Added



              By the way, keep your connection open. If you've already closed it, see the sidebar on reconnecting.



              Accessing Data


              To access the names and ages in our table we query the results object (an instance of java.sql.ResultSet), which uses the following methods:



              • next()
                iterates through the table; returns 1 if there are more rows



              • getString()
                gets the name specified in the name string passed to it



              • getInt()
                gets the age specified in the age string passed to it





              >>> results = statement.executeQuery("select name, age from Person")

              Let's see how these methods work interactively.


              Move to the first row.



              >>> results.next()
              1

              Get the name.



              >>> results.getString("name")
              'Whitney'

              Get the age.



              >>> print results.getInt("age")
              3

              Define a function that can be used repeatedly to get the rest of the results.



              >>> def showResults(results):
              ... while(results.next()):
              ... name = results.getString("name")
              ... age = results.getInt("age")
              ... print name + " is " + `age` + " years old."
              ...

              Print the results.



              >>> showResults(results)
              Kiley is 23 years old.
              Scott is 34 years old.
              Nick is 3 years old.
              Mary is 2 years old.
              Adam is 23 years old.

              Here's how we filter our results to get, say, only persons under the age of four:



              >>> select = "select name, age from Person where age < 4"
              >>> showResults(statement.executeQuery(select))
              Whitney is 3 years old.
              Nick is 3 years old.
              Mary is 2 years old.

              or persons over the age of twenty:



              >>> select = "select name, age from Person where age > 20"
              >>> showResults(statement.executeQuery(select))
              Kiley is 23 years old.
              Scott is 34 years old.
              Adam is 23 years old.

              Functions like count(), sum(), and stdev() allow us to get aggregate data, such as the count of persons in the table.



              >>> results = statement.executeQuery("select count(name) as total from Person")
              >>> results.next()
              1
              >>> print results.getString("total")
              6

              See if you can figure out what the next two interactive sessions are doing. (They won't work with InstantDB as of v.3.12.)



              >>> r = statement.executeQuery("select stdev(age) as st from Person")
              >>> r.next()
              1
              >>> print "Standard deviation" + r.getString("st")
              Standard deviation=13.7501515143167
              >>> r = statement.executeQuery("select sum(age) as [sum] from Person")
              >>> r.next()
              1
              >>> print "Sum of ages " + r.getString("sum")
              Sum of ages 88.


              Removing and Changing Data


              The first thing you want to do is import the JdbcTour.py module from the InstantDB and Access directories so you can use its showResults() function in the next interactive sessions. (If you disconnected, refer to the sidebar on reconnecting.)


              Let's remove everyone over twenty. (Leave out the asterisk (*) in the method arguments if you're using InstantDB.)



              >>> statement.executeUpdate("delete * from Person where age > 20")
              3

              The executeUpdate() method returns 3, which means that three records were deleted.


              Now let's do some modifying. In this next session we'll show Whitney's current age and then change it. In other words, we'll update her record. Show Whitney's current age.



              >>> select = "select name, age from Person where name = 'Whitney'"
              >>> results = statement.executeQuery(select)
              >>> showResults(results)
              Whitney is 3 years old.

              Change it to four.



              >>> statement.executeUpdate("update Person set age = 4 where name = 'Whitney'")
              1

              Show Whitney's new age.



              >>> results = statement.executeQuery(select)
              >>> showResults(results)
              Whitney is 4 years old.


              SQL Subsets in InstantDB versus Microsoft Access


              InstantDB is a lightweight database that supports only a subset of the SQL syntax. Microsoft Access supports only a subset as well, but a larger one. The real problem is that the two databases don't always support the same subset.










                I l@ve RuBoard



                Practical Example - Login System for Dreamweaver Hotel
























































                Chapter 9 -

                Hand Coding Within Dreamweaver MX
                byGareth Downes-Powellet al.

                Wrox Press 2003





















































                Practical Example - Login System for Dreamweaver Hotel



                Now that we've looked at the new hand-coding features of Dreamweaver MX, we're going to create a login system, so that the staff at the Dreamweaver Hotel can log in and view client bookings.


                In this example, we're going to hand code some PHP to perform the following actions:




                • Read in the values from a custom-made login form.




                • Verify the correct type of data is contained in the form values.




                • Encrypt the password.




                • Verify the username and encrypted password against the values stored in the database.




                • Display either a failure notice or a success notice to the user, and set a session containing the user's status.




                We'll be using the idea of a session throughout this example, so it needs a little explanation here. A session starts when a user first browses to a page on a particular web site, and ends a certain length of time after they last request a page for that site. This means it can be used to keep track of a user throughout their visit to a site. In particular, we'll be using it to keep track of whether the user has logged in or not.




                Overview of Our Login System


                Listed below are the login pages we are going to be creating, and how they link to each other. The first three pages are to be stored in a folder called admin in the root site directory. The final page is to be stored in a folder called include, also in the root site directory.



                login.php


                This page allows users to log in. Only users with a valid username and password that are checked against the database can log in - they are then redirected to menu.php. Their status is saved in a session for checking on other pages. If their details are incorrect, they stay on the login.php page, and an error message is shown.





                menu.php


                This page shows a menu of options to users who have successfully logged in, and who have either "Staff" or "Admin" status. It redirects any non-logged in users back to the login.php page.





                create_user.php


                This page is linked to by menu.php, but can only be accessed by managers who have "Admin" status. It can be used to add a new user to the database, after validating the data and encrypting the user's password. It redirects any users with "Staff" status back to the menu.php page.






                dreamweaver_hotel_admin.php


                This is purely a PHP file; there is no HTML code. It contains our generic functions, which can be used on any page which has an include statement pointing to this file.








                The Database Users Table


                Our first step is to create a users table in the database, to hold the user details for the Dreamweaver Hotel staff.


                Below is the structure for the users table:































                Field Name




                Field Type




                Notes




                ID



                integer(8)



                Primary Key, Auto increment




                username



                varchar(20)



                Stores staff member's username




                password



                varchar(20)



                Stores staff member's password




                firstName



                varchar(30)



                Stores staff member's first name




                lastName



                varchar(30)



                Stores staff member's last name




                status



                varchar(10)



                Stores staff member's admin status




                The SQL Query below will create this table when executed:




                CREATE TABLE 'users' (
                'ID' INT(8) NOT NULL AUTO_INCREMENT PRIMARY KEY,
                'username' VARCHAR(20) NOT NULL,
                'password' VARCHAR(20) NOT NULL,
                'firstName' VARCHAR(30) NOT NULL,
                'lastName' VARCHAR(30) NOT NULL,
                'status' VARCHAR(10) NOT NULL
                ) ;


                You can create the table through the MySQL command line, or with your favorite MySQL Administration tool.






                Create Users Page - The Layout


                The first page we'll create is a page to add users to the users table, which we'll call create_users. php. It contains the following form fields:




                <form name="createuser" method="post" action="<?php echo($PHP_SELF) ?>">
                <input name="firstName" type="text" id="firstName" maxlength="30">
                <input name="lastName" type="text" id="lastName" maxlength="30">
                <input name="username" type="text" id="username" maxlength="20">
                <input name="password" type="password" id="password" maxlength="20">
                <input name="confirmpassword" type="password" id="confirmpassword"
                maxlength="20">
                <select name="status" id="status">
                <option value="Admin">Admin</option>
                <option value="Staff" selected>Staff</option>
                </select>
                <input type="submit" name="Submit" value="Create User">
                </form>


                Look at the action attribute for the <form> element above, and you'll see it's set to:




                <?php echo($PHP_SELF) ?>



                $PHP_SELF is one of the built-in PHP variables. When the code is run, it is replaced with the location of the current page. We use this because we want the form to submit the data back to the same page. If in future we change the location of this page, the code will still work with no changes necessary.


                Lay the form out in a similar manner to the form below, using the template for the Dreamweaver Hotel developed in Chapter 5:





                Save the page as create_users.php, and place it in a directory called admin, off the site root.


                We now need to add the PHP code, which will make the page dynamic.



                At first, we'll ignore the security checks so that we can use our create_users .php page to add our main admin record. We'll add security checks to it later.


                Our first task is to create our PHP include file (dreamweaver_hotel_admin.php) containing custom functions we are going to use to validate various aspects of the form data.


                These are all generic functions that can be used on any site, rather than being specially written for our Hotel Admin system. This is an area where the Snippets panel really becomes useful, as over time you can build a library of such PHP functions, and then just insert the snippets you're going to need into your include file.


                Using an include file containing generic functions keeps your page neater, and easier to read and debug. All specific functions, which are hard coded to do a specific job on that page only, can be stored in the page code, keeping them separate from the generic functions.






                The PHP Include File


                Our include file will be called dreamweaver_hotel_admin.php, and will be stored in a directory called include, which is in the site root.


                The include file contains many functions, each listed one after the other. We'll discuss each of these functions separately to avoid confusion.



                trim_data()




                function trimdata($formdata)
                {
                //Trim any leading or trailing spaces
                //$formdata = Form Data Array
                foreach ($formdata as $key=> $value)
                {
                $new_array[$key] = trim($value);
                }
                return $new_array;
                }


                This function is passed an array of form data. It works through this array, and trims any leading or trailing spaces from both the key to the array and the value stored at that key. The function returns the trimmed array.





                check_form()




                function check_form($formdata)
                {
                // Check all fields are filled in
                // $formdata = Form Data Array
                foreach ($formdata as $key => $value)
                {
                if (!isset($key) || $value == "" )
                return false;
                }
                return true;
                }



                When passed an array of the form data, this function works through each form field checking that the value in the array is not empty for every key in the array that has been set.


                If any fields are left blank, the function returns false, otherwise the function returns true.





                check_password_length()




                function check_password_length($formdata, $password, $minlen)
                {
                // Check that password is required length
                // $formdata = Form Data Array
                // $password = Name of password field
                // $minlen = Minimum number of password characters
                if (strlen($formdata[$password]) < $minlen)
                return false;
                else
                return true;
                }


                This function is passed an array containing the form data, the name of the password field, and the minimum number of password characters allowed. If the password is less than the minimum number of characters allowed, then the function returns false, otherwise it returns true.





                confirm_password()




                function confirm_password($formdata, $password1, $password2)
                {
                // Check that two passwords given match
                // $formdata = Form Data Array
                // $password1 = Name of first password field
                // $password2 = Name of second password field

                if ($formdata[$password1] === $formdata[$password2])
                return true;
                else
                return false;
                }


                This function is passed an array of form data, and the names of both the password, and the confirm password field. It then checks that both password fields are identical, and returns true if they are, and false if they are not.





                check_unique()




                function check_unique($formvalue, $db, $dbhost, $dbuser, $dbpassword, $table,
                $field)
                {
                // Checks a table in a database, to see if passed value already exists
                // $formvalue = Value you are checking to see whether it is unique
                // $db = mySQL Database Name
                // $dbhost = mySQL Server address, for example localhost
                // $dbuser = mySQL user name
                // $dbpassword = mySQL password
                // $table = mySQL Table to search
                // $field = mySQL Field to search
                $error = "";

                // Connect to the mySQL Server
                $mysql = mysql_connect($dbhost, $dbuser, $dbpassword);
                if(!$mysql)
                {
                $error = "Cannot connect to Database Host";
                return($error);
                }

                // Open the mySQL Database
                $mysqldb = mysql_select_db($db);
                if(!$mysqldb)
                {
                $error = "Cannot open Database";
                return($error) ;
                }

                // Query Table to see if $formvalue is unique
                $myquery = "SELECT * FROM $table WHERE $field = '$formvalue'";
                $result = mysql_query($myquery);
                if (!$result)
                {
                $error = "Cannot run Query";
                return($error);
                }

                // Get number of Records found, should be 0 if $formvalue is unique
                $unique = mysql_num_rows($result);
                if ($unique > 0)
                {
                $error = $formvalue. " already in use";
                return($error);
                }

                // Return true if $formvalue is unique
                return("true");
                }


                This is the last, and the longest, function in our include file. The purpose of the function is to take the form value that is passed to it and check it against a field in a table in the database, to see if the value already exists or not. For example, we'll be using this function to check whether an entered username already exists in the database.


                The function is passed the form value, the database name, the database host, the database username and password, and the field and table names you wish to check. The function then connects to the database, and opens the table with a WHERE clause that searches for the passed form value in the passed field. It then checks to see if any records are returned from the database.


                If there are no records returned, the function returns true, and the value will be unique when added to the database. If a record is returned, the value will not be unique, so the function returns an error message. If there are any errors connecting to the database, these are also returned by the function.


                You can see that any of the functions we've described above could be used in any site without changes. It's a good idea to make your functions generic, because you'll soon end up with a library of code, which will save you from having to waste time rewriting code in the future. A good idea would be to add the functions to the Snippets panel (as detailed earlier in the chapter), allowing you to quickly insert them into a PHP include file in the future.









                Create Users Page - Adding the Code


                We're now going to add the PHP code to the create users page (create_users.php) we started earlier, to make the page validate the form data and insert the new user into the users table.


                Switch to Code view, and scroll to the top of the page. The first line to add goes before the <HTML> tag - it includes our file dreamweaver_hotel_admin.php.




                <?php include ("../include/dreamweaver_hotel_admin.php"); ?>


                We'll now create three more pieces of PHP code, that are added after this include line.



                insert_data()


                First, we create a function that inserts our form data into the users table.




                <?php
                function insert_data($formdata)
                {
                // Insert Data into users table
                // $formdata = form array

                // set up database connection variables
                $error = "" ;
                $myhost = "localhost";
                $myuser = "php";
                $mypass = "password";
                $mydb = "php";

                // set up data to insert
                $firstName = $formdata['firstName'];
                $lastName = $formdata['lastName'];
                $username = $formdata['username'];
                $password = $formdata['password'];
                $status = $formdata['status'];

                // encrypt the password using the key "DWMXPHP"
                $password = crypt($password,"DWMXPHP");

                // connect to mySQL server
                $mysql = mysql_connect($myhost, $myuser, $mypass);
                if (!$mysql)
                {
                $error = "Cannot connect to mySQL server";
                return($error);
                }

                // Connect to Database
                $mysqldb = mysql_select_db($mydb, $mysql);

                if (!$mysqldb)
                {
                $error = "Cannot open database";
                return($error);
                }

                // Insert Data
                $myquery = "INSERT INTO users ( firstName, lastName, username, password, ";
                $myquery .= "status) VALUES ('$firstName', '$lastName', '$username', ";
                $myquery .= "'$password', '$status' ) ";
                $result = mysql_query($myquery, $mysql);
                if (!$result)
                {
                $error = "Cannot run Query";
                return $error;
                }

                // Return True if record written successfully
                return("true") ;
                }
                ?>


                This function takes the data from the form array that's passed to it, and puts the values into variables. It then encrypts the password, using the key "DWMXPHP", and this encrypted value is stored in the database. crypt () is a one-way hash function built into PHP, which means that once the data is encrypted, it can never be unencrypted again. To check against it, we encrypt the value to test with the same key, and if the two encrypted values match, we know the value is correct.


                Next, we connect to the MySQL database, and insert the user data into the users table. The query returns true if the record is inserted correctly, otherwise it returns a relevant error message.





                verify_data($formdata)


                This next function validates the user data entered in the form on the page. If it is validated successfully, it calls the insert_data () function described above.




                <?php
                function verify_data($formdata)
                {
                // This function uses the functions in the include file,
                // and uses them to validate various aspects of the form.
                // If validation fails, it returns $error, the appropriate error message
                // If validation suceeds, return true
                $error = "";
                $form_data = trim_data($formdata);
                $user = $form_data['username'];

                // check all form fields are filled in
                if (!check_form($form_data))
                {
                $error="All Form Fields must be filled in";
                return($error);
                }

                // check password and confirmation password match
                if (!confirm_password($form_data, 'password', 'confirmpassword'))
                {
                $error = "Password and Confirm Password do not match";
                return($error);
                }

                // check length of password
                if (!check_password_length($form_data, 'password', 5))
                {
                $error = "Password should be 5 characters or more";
                return($error);
                }

                // check that username is unique
                $check = check_unique($user, 'php', 'localhost' , 'php', '20012001', 'users',
                'username');
                if ($check != "true")
                {
                $error = "Username is already in use, select another";
                return($error);
                }

                // if validated successfully, insert data into table
                $insert_check = insert_data($formdata);

                // if error with insertion, return error
                if ($insert_check != "true")
                return($insert_check);

                // form validated and record inserted successfully
                return("");
                }
                ?>


                This function uses the validation functions defined in our include file to verify that all the fields are filled in, that the password and confirmation password match, that the length of the password is correct, and that the chosen username is unique.


                If all these validations are successful, the function calls the insert_data () function defined above, which inserts our user record into the users table. If everything is successful, the function returns nothing; otherwise the function returns the relevant error message.






                Using the Functions


                The last piece of code for this section is as follows:




                <?php
                // Main Code - Verifies the form data, and inserts into
                // the users table in the Database
                if($HTTP_POST_VARS["submit"]=="Create User"){
                $error = verify_data($HTTP_POST_VARS);
                if ($error == "")
                $success = "User inserted successfully";
                }
                ?>


                This code is run every time the page is loaded. First, it checks to see if the variable $Submit is equal to "Create User". If it is, the form data in the variable $HTTP_POST_VARS is passed to our verify_data() function, as defined above.


                The variable $Submit will only equal "Create User" if the form has been submitted - submit is the name of the submit button, and "Create User" is the label for the button. If you have changed either of these, you will need to change them in the code here as well.


                If the form has been submitted, it will run the verify_data () function, which if successful, will call the insert_data () function to insert the user details into the database.


                The values of two variables, $error and $success, are now set in such a way that one has a value and one is empty. If an error is returned by the verify_data() function, then $error has a value, and $success is empty. If no errors are returned, $error will be empty, and $success will be set to "User inserted successfully".


                We can place the two variables onto the page above the form using:




                <?php echo($error); ?>
                <?php echo($success); ?>


                When a user first visits the page, nothing will be displayed, since both variables are empty. However, once the form has been submitted and the page is redisplayed, one of the two messages will be shown - either an error message or the success message.





                Using the Page


                We're now going to use this page, to create an "Admin"-level user. Upload the page to your server (along with the include file), and then load the page in your web browser. Enter your name, a username, and password. Select Admin from the status dropdown, and click Submit.


                You should get a success message returned. If not, you will have to go back and check over your code. View the users table, and you should be able to see your new record. Look at the password field, and you will see it has been encrypted.


                At the moment, anybody can access the page and create a new user. Obviously, this isn't something we want, so we will return to this page later, and add some more code, so that only "Admin"-level users can use the page.









                The Login Page


                The login page is a simple page that contains a table with a username and password field.





                The details for the form are as follows:




                <form name="form1" method="post" action="<?php echo($PHP_SELF) ?>">
                <input name="username" type="text" id="username" maxlength="20">
                <input name="password" type="password" id="password" maxlength="20">
                <input type="submit" name="Submit" value="Login">
                </form>


                Note that, like the create_user. php page, this page submits back to itself using the $PHP_SELF variable.


                Layout the page as above using the template for the Dreamweaver Hotel site, and save the page as login.php in the admin folder.



                Next we'll add the code that checks the login details against the database. Change into Code view, and scroll to the top of the page. On the first line, before the <HTML> tag, we include the PHP file dreamweaver_hotel_admin.php, which we created earlier.




                <?php include("../include/dreamweaver_hotel_admin.php"); ?>


                Again, we'll create two more pieces of PHP code to be added below this include file line.



                check_login()


                We'll first add a function to check the user details entered against those stored in the table.




                <?php
                function check_login($formdata)
                {
                // This section queries the users table, and searches for
                // the username and password that were supplied. If the
                // user is not found, an error is returned. If the user
                // details are correct the users status is returned.

                // Set up MySQL Connection variables (change these to your own)
                $dbhost = "localhost";
                $dbuser = "username";
                $dbpassword = "password";
                $db = "databasename";

                // Get Form Data
                $form_data = trim_data($formdata);
                $user = $form_data['username'];
                $password = $form_data['password'];

                // Connect to the mySQL Server
                $mysql = mysql_connect($dbhost, $dbuser, $dbpassword);
                if(!$mysql)
                {
                $error = "Cannot connect to Database Host";
                return($error);
                }

                // Open the mySQL Database
                $mysqldb = mysql_select_db($db);
                if(!$mysqldb)
                {
                $error = "Cannot open Database";
                return($error);
                }

                // Query Database with username and encrypted password
                $myquery = "SELECT * FROM users WHERE username = '" . $user
                $myquery .= "' AND password = '" . crypt($password,"DWMXPHP") . "'";
                $result = mysql_query($myquery);
                if (!$result)
                {
                $error = "Cannot run Query";
                return($error);
                }

                // Check that we have a record returned
                $numRows = mysql_num_rows($result);
                if ($numRows < 1)
                {
                $error = "User name or password not recognised";
                return($error);
                }

                // Get user status from returned record
                $userRecord = mysql_fetch_array($result);
                $status = $userRecord["status"];
                return($status);
                }
                ?>


                The first section of this function sets up the database connection variables. Next, the form data array, which is passed to the function, is run though the trim_data() function we created earlier in the include file dreamweaver_hotel_admin.php.


                The function then connects to the MySQL server, and opens the database. It then queries the database with the following SQL statement:




                SELECT * FROM users WHERE username = ' " . $user . " ' AND
                password = ' " . crypt($password,"DWMXPHP") . " ' "


                Recall that when we create a user, we use the PHP crypt() function to encrypt the password with the key "DWMXPHP", and it was the encrypted password that we stored in the database.


                As we mentioned earlier, crypt() is a one-way hash function - once the original data is encrypted it is lost. This doesn't cause us a problem, because encrypting data with the same key will produce the same result. By encrypting the provided password with the key "DWMXPHP", if the two passwords are the same, the encrypted user password will match the encrypted password stored in the database.


                Next, we check the query has returned a record. If it hasn't, it means that the username or password are invalid, and we return this error.


                If a record has been found, then the username and password provided were correct, and the user's status (either "Admin" or "Staff") is returned.






                Using the Functions


                The main code for the page is shown below:




                <?php
                // This section is only run when the form has been submitted
                if($HTTP_POST_VARS['Submit']=="Login")
                {
                session_start();

                // Check whether the login details are correct, and put
                // the user status into a session variable.
                $statusCheck = check_login($HTTP_POST_VARS);
                if ($statusCheck == "Admin" || $statusCheck == "Staff")
                {
                session_register("statusCheck");
                header("Location: menu.php");
                }
                }
                ?>


                This code is only run if the form has been submitted, in which case the variable $submit has the value "Login".


                We then use the PHP function session_start() to begin a session, which we can use to store the user's status.


                We next run the check_login() function we created earlier in this page, and pass it the form values, using the PHP variable $HTTP_POST_VARS. This function returns the user status (Admin or Staff) if their username and password are correct; otherwise an error message is returned. We store the result of the function in the variable $statusCheck.


                Next, we check to see if $statusCheck contains "Admin" or "Staff", which means the login was successful. If so, we put the user's status into a session, which will allow their status to be saved and checked against as they change pages. Then we use the PHP header() function to redirect the user to the administration menu page, menu.php. Note, you can only use the PHP header() function if the header hasn't already been written by the browser. Keeping this code above the <HTML> tag ensures that the header() function can be used successfully, as the headers haven't been written yet.


                If the check_login() function returns an error, then the session won't be set, and the page continues to load as normal.


                You can now add the following code to your page, above the login form:




                <?php echo($statusCheck) ?>


                When the user first goes to the login page, this will show nothing since the $statusCheck variable will be empty. $statusCheck will only have a value once the user has submitted the login form, and the check_login() function has run. As this is not yet the case $statusCheck will contain the error message.









                The Menu Page


                The menu page is called menu.php. It is this page that users are redirected to once they have successfully logged in. For this example, it will contain three links, as shown in the screenshot below:








                • View Bookings


                  View Bookings should link to a page that will allow the user to view the various bookings that have been made. We will not actually be creating this page in this example.





                • Add New User (Manager Only)


                  This link will point to the create_user.php page we created earlier.





                • Log Out


                  This link takes the user back to the menu.php page, but with a query string parameter to tell the menu.php page that the user wants to log out. The href of the link is: menu.php?action=logout





                Lay out a page with such links using the Dreamweaver Hotel template. Save it as menu.php in the admin folder.



                The Menu PHP Code


                Switch into Code view, and go to the top of the menu.php code, above the <HTML> tag.


                The first segment of code we need to add is to start session support for the page. We then need to check the user has a session set, which can only happen if they have logged in correctly. If the user hasn't got a valid session, they are redirected back to the login page.




                <?php
                session_start();
                // If no session is set, redirect to login.php
                if (!session_is_registered("statusCheck"))
                header("Location: login.php");
                ?>


                This segment of code needs to be inserted at the top of every page you wish to protect with the login system. Because the session is checked, it means that people who know the name of the page cannot bypass the protection by pointing the browser manually to that page.


                We have one more segment of code to add to this page, and that is to log the user out. When the Log Out link is clicked, it sends the browser to the URL menu.php?action=logout. We modify the code block above, and add some code to handle the logout. Change the code above to read:




                <?php
                session_start();
                // If the user logs out, destroy their session
                if($HTTP_GET_VARS["action"]=="logout"){
                session_unregister("statusCheck");
                session_destroy();
                }
                // If no session is set, redirect to login.php
                if (!session_is_registered("statusCheck"))
                header("Location: login.php");
                ?>


                When the extra code we inserted above is run, it checks to see if the $action variable contains the value "logout", that is if the page is called through the Log Out link. It then deregisters and destroys the statusCheck session.


                The original code is then run, and because a session is no longer set the user is redirected to the login.php page - effectively, they are logged out.








                Create Users Page - Security


                We now need to go back to the create users page, create_users.php. Open the page, and switch into Code view.


                Earlier in this chapter, when we created this page, we didn't set any security, which meant that any user could log in and create a user. Obviously, we don't want this happening: we need to restrict ordinary staff (who have "Staff" status) from entering the page, but allow all managers (who have "Admin" status) to enter.



                We're going to add some code to check the status value in the session variable statusCheck, and redirect the user back to the menu page if they only have "Staff" status. The page will load normally if the user has "Admin" status.


                Go the top of the code, and you'll see the following line, which adds our extra functions stored in dreamweaver_hotel_admin.php to the code.




                <?php include("../include/dreamweaver_hotel_admin.php"); ?>


                Just after this line add the following block of code:




                <?php
                session_start();
                // Check the Users status, if not Admin level
                // redirect back to the menu.php page
                if ($HTTP_SESSION_VARS["statusCheck"] != "Admin")
                header("Location: menu.php");
                ?>


                This code can be placed on any page that needs protecting so that only "Admin" status users can use it. As the code uses the PHP header() function to redirect the user's browser, it is important that there is no "whitespace" (spaces or line breaks) sent to the browser before the code is executed. Make sure there are no spaces between your blocks of PHP code.


                We've now completed the login section, so upload it to your server and you can see the pages in action.


                Obviously, this is a very basic system, but it can be expanded on and used as a skeleton to make more complicated login systems, with more levels of authorization. This system can protect a large number of pages easily, simply by adding the relevant block of code to the top of the page, depending on which level of access is required.