Jump to content

mac_gyver

Staff Alumni
  • Posts

    5,385
  • Joined

  • Days Won

    173

Posts posted by mac_gyver

  1. your database design must enforce uniqueness, it is the last step in the process. when there are multiple concurrent instances of your script running, they can all query to find that the value does not exist and attempt to insert it. the first one will win this timing race and succeed. all the following ones must fail with an error. to do this, the column you are trying to match must be defined as a unique index. you would then just simply attempt to insert the data and in the error handling, which should be using exceptions, you would test if the error number is for a unique index violation (1062), and setup a message letting the user know that the value is already in use. for all other error numbers, just rethrow the exception and let php handle it.

  2. you are using a post method form, followed by an unnecessary redirect, to select which record to edit and then a get method form for updating the data. this is backwards. you should use get inputs to determine what will be displayed on a page and a post method form when performing an action on the server, such as updating the data. also, you can and should do all of this on one page to avoid repetition of code.

    the code for any page should be laid out in this general order -

    1. initialization
    2. post method form processing
    3. get method business logic - get/produce data needed to display the page
    4. html document

    when you display the existing records, the edit button should be a get method link with the id as part of the link. when you click one of those links, the resulting code that gets executed would query to get the  existing row of data to populate the form field values, but only if the update form has never been submitted. if the update form has been submitted, you would not execute this query. the way to accomplish this 'interlocking' of the data being edited is to use an internal 'working' array variable to hold this data, then use elements in this array variable through out the rest of the code. inside the post method form processing logic, you would store a trimmed copy of the $_POST form data in this variable. at the point of querying for the initial data, if this variable is empty, you would execute the query.

    here are some issues with and things that will simplify the posted code -

    1. your login system should only store the user id in the session variable upon successful login, then query on each page request to get any other user information, such as the user's name, permissions.
    2. your code should check if the current logged in user is an admin before allowing access to the edit logic.
    3. when conditional 'fail' code is much shorter than the 'success' code, if you invert the condition being tested and put the fail code first, it results in clearer, cleaner code. also, since the fail code is a redirect in this case, which must have an exit/die statement to stop php code execution, you can eliminate the else {} part of the conditional test since the redirect/exit/die will stop the execution for a non-logged in user.
    4. don't copy variables to other variables for nothing. this is just a waste of typing and introduces errors.
    5. don't use multiple names for the same piece of data. whatever the actual meaning of the data is, use that name throughout the code. one such example is the staff_id value being called 'data' and at another point it is a name value.
    6. since you will be switching to use a post method form for the update operation, after you detect if a post method form has been submitted, all the form fields (except for unchecked checkbox/radio fields) will be set. there will be no need for a massive list of isset() statements.
    7. you should put the database connection code in a separate .php file, then require it when needed.
    8. you should not unconditionally echo database errors onto the web page, which will only help hackers when they internationally trigger errors. instead, use exceptions for database statement errors an in most cases let php catch and handle the exception. the exception to this rule is when inserting/updating user submitted data that can result in duplicate or out of range values, which is something that you are doing. in this case, your code should catch the exception, test if the error number is for something that your code is designed to handle, and setup a message letting the user know what was wrong with the data that they submitted. for all other error numbers, just re-throw the exception and let php handle it.
    9. the logout operation should use a post method form.
    10. any function definitions should either be in the initialization section of code or be in their own .php files that get required in the initialization section of code.
    11. your application should not use the root user without any password. instead, create a specific database user with a password with only the  permissions that it needs for you application.
    12. the updateRecord function should only have two call-time parameters. an array of the input data and the database connection.
    13. the updateRecord should not contain any application specific html markup. this should be handled in the calling code. the function should only return a true or false value to the calling code.
    14. don't put external, unknown, dynamic values directly into sql query statements. you must protect against sql special characters in data values from being able to break the sql syntax, which is how sql injection is accomplished. the fool-proof way of doing this is to use prepared queries. since the mysqli extension's prepared query interface is overly complicated and inconsistent, this would be a good time to switch to the more modern and simple PDO database extension.
    15. the updateRecord function should not close the database connection. it is not the responsibility of this function to do this, only to update the recorded.
    16. the update form should populate the form field values and preselect the option that matches the initial existing data being edited, then populate/preselect using the submitted form data, as described above.
    17. any dynamic value that you output on a web page should have htmlentities() applied to it to help prevent cross site scripting.
    18. the value attribute for the select/option 1st prompt option should be an empty string.
    19. since you are putting the <label></label> tags around the form field they belong with, you don't need the for='...' and matching id='...' attributes.

    the post method form processing code should -

    1. detect if a post method form was submitted.
    2. keep the form data as a set in an array variable.
    3. trim all the input data as at once. after you do item #2 on this list, you can do this with one php statement.
    4. validate all the inputs, storing validation errors in an array using the field name as the array index.
    5. after the end of all the validation logic, if there are no errors, use the form data.
    6. after using the form data, if there are no errors, redirect to the exact same url of the current page to cause a get request for the page.
    7. if you want to display a one-time success message, store it in a session variable, then test, display, and clear the session variable at the appropriate location in the html document.
    8. if there are errors at step #5 or #6 on this list, the code would continue on to display the html document, where you would test for and display any errors, and redisplay the form, repopulating the field values/selected option choices with the values that are in the 'working' array variable holding the submitted form data.
  3. so, now you have to maintain two almost identical pages, where every change you make to the poll output, must be repeated in both pages. that's the wrong direction to move toward when doing programming. you want to reduced the amount of work you have to do to create and maintain a web site, not increase it.

    what's wrong with adding a simple conditional test where the two links appear at in the html document, so that they are only output if the current user is an admin? Keep It Simple (KISS.)

    edit: i also recommend that you convert your mysqli based code to use PDO. it is very simple to do so and actually eliminates a bunch of lines of code. you will also be able to directly fetch the result from the query into an appropriately named array variable, such as $user_data, so that you don't need to worry about naming a bunch of variables to keep from overwriting other variables that may already exist.

  4. now that we know a bit more about what you are doing, a multi-page form, collecting data that eventually gets used for some purpose, now would be a good time to switch to using a data-driven design, to eliminate all the repetitive code for the data collection, rather than to spend time fixing each page of it.

  5. i was able to make the code repopulate the fields/select-option, when navigating around, using 3 lines of code and one conditional test, added to the get method business logic section. i won't post the code i came up with because it is probably not how your application is determining which step/page it is on. you could also just merge the successful $post data into the $_SESSION['step'] array inside the post method form processing code, then when the page is visited without any $post data, copy the whole $_SESSION['step'] array back into $post (which i just tested and it works as well.)

    if you want help with anything your application is or is not doing, you must post enough of the code so that someone here can reproduce what it is doing.

  6. for the activity you have shown in this thread and using the previously given programming practices about how to organize, cleanup, and code your form processing and form,  a lot of this typing of code goes away, you would end up with the following -

    <?php
    /*
    put any error relating settings in the php.ini on your system
    you may have a need to store the result from some step in session variable(s), but by unconditionally storing each piece of post data, you are doubling the amount of code needed. only store the end result. Keep It Simple (KISS.)
    to dynamically generate a select/option list, you would not use discrete variables for each value and write out logic for each option. you would instead have an array of the values, then loop to dynamically build the options.
    in html5, an empty action='' attribute is not valid. to get a form to submit to the same page it is on, leave the action attribute completely out of the form tag.
    you should validate your resulting web pages at validator.w3.org
    */
    
    // initialization
    session_start();
    
    $post = []; // an array to hold a trimmed working copy of the form data
    $errors = []; // an array to hold user/validation errors
    
    // post method form processing
    if($_SERVER['REQUEST_METHOD'] === 'POST')
    {
    	// trim all the data at once
    	$post = array_map('trim',$_POST); // if any of the fields are arrays, use a recursive trim call-back function here instead of php's trim function
    
    	// validate inputs
    	if($post['owner'] === '')
    	{
    		$errors['owner'] = 'The owner is required.';
    	}
    	if($post['renter'] === '')
    	{
    		$errors['renter'] = 'The renter is required.';
    	}
    	if($post['state'] === '')
    	{
    		$errors['state'] = 'The state is required.';
    	}
    	
    	// add validation for other inputs here...
    
    	
    	// if no errors, use the form data
    	if(empty($errors))
    	{
    		// if this is a step in a multi-step process, store the now validated data in a specific session variable
    		$_SESSION['step'][1] = $post;
    	}
    
    	// if no errors, success
    	if(empty($errors))
    	{
    		// if you want to display a one-time success message, store it in a session variable here,
    		// then test, display, and clear that session variable at the appropriate point in the html document
    		$_SESSION['success_message'] = 'Some success message for this step in the process';
    		// redirect to the exact same url of the current page to cause a get request for the page
    		die(header("Refresh:0"));
    	}
    }
    
    // get method business logic - get/produce data needed to display the page
    // query to get the states in the order that you want them
    
    // fake some values
    $states = [];
    $states[]="Maine";
    $states[]="Texas";
    
    // html document
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
    	<title>Form processing/form example</title>
    </head>
    <body>
    
    <?php
    // display and clear any success message
    if(isset($_SESSION['success_message']))
    {
    	echo "<p>{$_SESSION['success_message']}</p>";
    	unset($_SESSION['success_message']);
    }
    ?>
    
    <?php
    // display any errors
    if(!empty($errors))
    {
    	echo "<p>".implode('<br>',$errors)."</p>";
    }
    ?>
    
    <form method="post">
    <label>Owner: <input type="text" style="width:255px;" name="owner" value="<?=htmlentities($post['owner']??'',ENT_QUOTES)?>"></label><br>
    <label>Renter: <input type="text" style="width:255px;" name="renter" value="<?=htmlentities($post['renter']??'',ENT_QUOTES)?>"></label><br>
    <label>State: <select name="state">
    <option value="">select state</option>
    <?php
    foreach($states as $state)
    {
    	$sel = isset($post['state']) && $post['state'] === $state ? 'selected' : '';
    	echo "<option value='$state'$sel>$state</option>";
    }
    ?>
    </select></label><br>
    <input type="submit">
    </form>
      
    <?php
    // note: if you have the need for a 'clear' session function, add that as a separeate post method form, with a hidden field with a value to indicate that action
    // then test for that action value in the form processing code to clear the session data
    ?>
    
    <?php
    // display the content of the session
    if(!empty($_SESSION))
    {
    	echo '<pre>'; print_r($_SESSION); echo '</pre>';
    }
    ?>
    
    </body>
    </html>

    and as already mentioned, if you have more than 2-3 form fields, you should use a data-driven design, where you have a data structure (database table,  array) that defines the expected form fields (for each step), validation rules, and processing for each field, then dynamically validate and process the form data, rather than to write out bespoke logic for each field.

  7. the following is a 'tricky' example of INSERTing data that satisfies a maximum count of rows -

    	$query = "INSERT INTO team_members (team_id, staff_id, stafftype)
    	SELECT -- the following values being SELECTed are the actual data values to insert
    	?,?,? FROM DUAL -- dual is an allowed dummy table name to satisfy the FROM ... WHERE syntax
    	WHERE (SELECT COUNT(*) FROM team_members WHERE team_id = ? AND stafftype='leader') < 1 -- insert the data if the WHERE (subquery count) < 1 is TRUE";
    	$stmt = $pdo->prepare($query);
    	$stmt->execute([$team_id, $staff_id, $stafftype, $team_id]);
    	
    	if($stmt->rowcount())
    	{
    		echo "A leader row was inserted for team_id: $team_id, staff_id: $staff_id<br>";
    		} else {
    		echo "A leader already exists for team_id: $team_id<br>";
    	}

    this example was to insert a maximum of one 'leader' row per team id. you would change it to insert a maximum of two rows per datetime appointment slot.

    because this uses a single query to both get a count of the number of existing rows and insert a new row, it will work correctly for multiple concurrent instances of your script.

    • Like 1
  8. code for any page should be laid out in this general order -

    1. initialization.
    2. post method form processing.
    3. get method business logic - get/produce data needed to display the page.
    4. html document.

    the post method form processing should -

    1. detect if a post method form has been submitted before referencing any of the form data.
    2. keep the form data as a set in a php array variable, then operate on elements in this array variable throughout the rest of the code.
    3. trim all the input data, mainly so that you can detect if it consists of all white-space characters.
    4. validate inputs, storing validation errors in an array using the field name as the array index.
    5. after the end of the validation logic, if there are no errors, use the form data.
    6. after using the form data, if there are no errors, perform a redirect to the exact same url of the current page to cause a get request for that page.
    7. any redirect needs an exit/die statement after it to stop code execution.
    8. to display a one-time success message, store it in a session variable, then test, display, and clear the session variable at the appropriate location in the html document.
    9. if there are errors at step #5 or #6 on this list, the code would continue on to display the html document, where you would display any errors and redisplay the form, populating the form field values with any existing data.
    10. since there won't be any existing data values the first time the form is displayed, you need to address this at the point of using the values in the form. php's null coalescing operator ?? is a good choice to use here.
    11. any external, dynamic, unknown value output in a html context should have htmlentities() applied to it to help prevent cross site scripting.

    once you have detected that a post method form has been submitted, except for unchecked checkbox/radio fields, all form fields will be set and won't produce php errors. for checkbox/radio fields, you need to use isset() statements to test if they are set before referencing them in the form processing code. since the posted code isn't detecting if a post method form has been submitted at all before referencing the form data and isn't doing anything for item #10 on this list, you are getting unnecessary php errors.

    btw - if you have more than 2-3 form fields, you should use a data-driven design, where you have a data structure (database table,  array) that defines the expected form fields, validation rules, and processing for each field, then dynamically validate and process the form data, rather than to write out bespoke logic for each field.

  9. DOM ids should start with a letter (though browsers will probably let you use ones that start/are just a number.) the parameter in the function call is a string and must be surrounded by quotes. the onclick='...' attribute must also be quoted. the two quote types must be different. the id attribute in the <div ...> must also be set to be the same value as the parameter in the function call.

    i recommend that you slow down and reread the linked to example.

  10. the msyqli extension does have a fetch_all() function/method, which they finally fixed so that it doesn't depend on the mysqlnd driver, but it is still messed up in that the default fetch mode is numeric, which is different from the default fetch mode for every other general fetch - both/assoc/numeric statement, because the nimrods that programmed this didn't understand that the fetch mode applies to the rows of data, not the all-array holding those rows.

  11. 7 minutes ago, ChenXiu said:

    like this?

    yes.

    7 minutes ago, ChenXiu said:

    Or is there a better ("more efficient") mySQL style code to use?

    if you were using the PDO database extension, it has a fetch mode that will directly produce an array of the SELECTed column values.

  12. i would use array_chunk() to break the starting array into chunks of 5 elements, with the last chunk containing the remainder. you can then simply use a foreach(){} loop to loop over the chunks.

    rather than concatenating to build the query string, just implode each chunk array.

  13. the main point of using a prepared query, e.g. with place-holders in the sql statement where the data values are to be acted upon, then supply the actual data values when the query is executed, is to prevent any sql special characters in a data value from being able to break the sql query syntax, which is how sql injection is accomplished, by separating the parsing of the sql query syntax from the evaluation of the data values. a secondary point is they provide a performance gain (~5%) in the rare cases when you execute the same query within one instance of your script with different data values, since the sql query statement is only sent to the database server once, where it is parsed and its execution is planned only once.

  14. the simplest, straightforward solution would be to query on each page request to get the current user's status. even though you are not performing any of the actions i listed, you are forcing the user to become logged out.

    in the current code you should have login check logic on each protected page, that starts the session and tests if $_SESSION['unique_id'] is or is not set. you would change it to the following to cause a status value of "Offline now" to log the user out and redirect to the logintool.php page -

    <?php
    // this sets the lifetime to zero and the rest of the values to null
    session_set_cookie_params(0);
    session_start();
    
    // login check code
    if(!isset($_SESSION['unique_id']))
    {
    	// if not logged in, go elsewhere
    	header("location: https://www.peredy1.com/adminchat/logintool.php");
    	die;
    }
    
    // at this point there's a logged in user, get the user's current status
    // note: use 'require' for things your code must have for it to work
    require "config.php";
    
    $sql = "SELECT status from users WHERE unique_id = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param('i', $_SESSION['unique_id']);
    $stmt->execute();
    $stmt->bind_result($user_status);
    $stmt->fetch();
    
    if($user_status === "Offline now")
    {
    	// force a log-out
    	// note: you should only unset() $_SESSION['unique_id'] since a session can contain other pieces of data
    	session_unset();
    	session_destroy();
    	header("location: https://www.peredy1.com/adminchat/logintool.php");
    	die;
    }
    
    // at this point the user is logged in and can access the rest of the code on the protected page

     

  15. the code wasn't designed to let you do this, because it isn't querying on each page request to get the user's current status, which would let the login check code force a log-out based on a value stored in the database table.

    you can either rewrite the code to do this, or you could delete all the session data files, which would also log-out the admin performing this action, unless you want to scan through the session data files to find the one for the admin and not delete it too.

    btw - not only should the logout be a post method form, but using mysqli_real_escape_string() on a value that isn't being used in a string context in a query doesn't provide any sql injection protection, which still can occur for trusted users if cross site scripting is possible, since any value submitted to your code can be changed by cross site scripting to be anything and cannot be trusted. the only fool-proof way of preventing sql injection is to use a prepared query.

  16. in general, functions should accept input data as call-time parameter(s), so that they can be reused with different input values.

    start by dynamically building the id attribute, incorporating the $row['id'] value (note: you can put php variables directly into an overall double-quoted string without all the extra quotes and concatenation dots) -

    id='c_{$row['id']}'

    dynamically build the function call with the same value as its call-time parameter -

    <button onclick=\"myFunction('c_{$row['id']}')\">Copy</button>

    modify the function definition to use the call-time parameter as the id of the element to reference -

    function myFunction(id) {
      // Get the text field
      var copyText = document.getElementById(id);
    
    .. the rest of the function code is the same

     

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.