Удаление доступа к голосованию для определенного объекта в зависимости от IP-адреса, сохраненного в базе данных

Я попытался сделать несколько постов об этой проблеме, но решил собрать все в этом финальном, чтобы, надеюсь, как-то ее решить.

Я создаю сайт, на котором пользователи могут голосовать по вопросам из базы данных. Логин отсутствует, поэтому, чтобы убедиться, что каждый может проголосовать только один раз за вопрос, я использую их IP вместе с идентификатором вопроса.

Во-первых, я получаю идентификатор и IP-адрес и сохраняю их, убедившись, что они являются целыми числами:

if(isset($_GET['id']))
          {

           //Get IP address

           //Test if it is a shared client
           if (!empty($_SERVER['HTTP_CLIENT_IP'])){
            $ip=$_SERVER['HTTP_CLIENT_IP'];

           //Is it a proxy address
           }elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
            $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
           }else{
            $ip=$_SERVER['REMOTE_ADDR'];
           }

           //Save id and IP address as variables
           $id = $_GET['id'];
           $ip_long = ip2long($ip);

Затем я проверяю, проголосовал ли уже пользователь, используя две переменные. Вот где я ожидаю, что проблема возникает. я получаю:

Примечание: попытка получить свойство не-объекта

из строки 116, а именно: $row_cnt = $result->num_rows.

Кроме того, var_dump ($result) возвращает bool(false), а var_dump ($row_cnt) возвращает Null. Добавление кавычек вокруг двух переменных в запросе, $ip_long и $id, устраняет проблему на локальном хосте, но не на моем сервере.

Локальный var_dump($result) с кавычками вокруг переменных возвращает следующее:

object(mysqli_result)#2 (5) { ["current_field"]=> int(0) ["field_count"]=> int(1) ["length"]=> NULL ["num_rows"]=> int(1 ) ["тип"]=> целое(0) }

Я хотел бы добавить 1 к QuestionVotes для конкретного вопроса, а затем удалить возможность голосовать по тому же вопросу для определенного IP-адреса.

//Save id and IP address as variables
           $id = $_GET['id'];
           $ip_long = ip2long($ip);

           ///Check to see if user already voted
           $stmt = $conn->prepare("SELECT * FROM User_Votes where UserID = ? and QuestionID = ?");
           mysqli_stmt_bind_param($stmt, 'ss', $ip_long, $id);
           $stmt->execute();
           $result = $stmt->get_result();
            if($result->num_rows){
                //The user has already voted
                echo "Already voted";
            }else{
                //Add IP Address and ID to the User_Votes table
                $stmt = $conn->prepare("INSERT INTO User_Votes (UserID, QuestionID) VALUES (?, ?)");
                mysqli_stmt_bind_param($stmt, 'ss', $ip_long, $id);
                $stmt->execute();
                $stmt = $conn->prepare("UPDATE Question SET QuestionVotes = QuestionVotes + 1 where QuestionID = ?");
                mysqli_stmt_bind_param($stmt, 's', $id);
                $stmt->execute();
            }

       }

И, наконец, вот код, который я использую для создания html-полей, содержащих информацию о вопросе базы данных, добавляю кнопку голосования, которая отображает текущие голоса, и добавляю то, что используется как QuestionID, к URL-адресу:

// Build 4 question boxes from database Question table, including voting button
      $stmt = $conn->prepare("SELECT * FROM question ORDER BY QuestionVotes DESC LIMIT 4");
      $stmt->execute();

      $result = $stmt->get_result();
      if ($result->num_rows > 0) {
           // output data of each row
           while($row = $result->fetch_assoc()) {
               //$row["QuestionID"] to add id to url
               echo "<div class=\"col-md-3\"><h2>". $row["QuestionHeader"]. "</h2><p>". $row["QuestionText"]. "</p><p><a href=\"index.php?id=". $row["QuestionID"]. "\" class=\"btn btn-success\"> " . $row["QuestionVotes"] . "</a></p></div>";

           }
      }
      else
      {
        echo "0 results";
      }

Мои таблицы выглядят следующим образом:

Вопрос: QuestionID(int11)(pk), QuestionHeader(varchar(20)), QuestionText(text), QuestionVotes(int(5))
User_Votes: UserID (без знака, целое (39)), QuestionID (целое (11))


person Nenn    schedule 09.12.2015    source источник
comment
Предупреждение о внедрении SQL Никогда не используйте непроверенные данные, поступающие из браузера (например, $id = $_GET['id'];, за которым следует $conn->query("SELECT ... QuestionID = $id");). Если я отправлю 1; DELETE * FROM question в качестве значения для id, вы не будете счастливы.   -  person jcaron    schedule 09.12.2015
comment
Ваш запрос возвращает false, что означает ошибку. Проверьте значение $result перед его использованием. Если false, запишите ошибку ($conn->error).   -  person jcaron    schedule 09.12.2015
comment
Ах, да, я знаю, что мой код уязвим для SQL-инъекций, но нельзя ли это исправить с помощью подготовленных операторов? И var_dump на $result возвращает 'bool (false)'. Однако я очень новичок в этом, поэтому я не совсем уверен, где вы хотите, чтобы я использовал «($ conn-› error)»? Не могли бы вы направить меня? Кроме того, большое спасибо за ваши комментарии, эта проблема ставит меня в тупик.   -  person Nenn    schedule 09.12.2015
comment
Зарегистрируйте это, чтобы вы знали, почему $result ложно. И я уже знаю, что $result было ложным, я говорил вам проверить это в своем коде и использовать $result только в том случае, если оно не ложно.   -  person jcaron    schedule 09.12.2015


Ответы (3)


Есть несколько вещей, которые я хотел бы отметить. Во-первых, ваша ошибка:

Я получаю «Уведомление: попытка получить свойство, не являющееся объектом» из строки 116, которая выглядит следующим образом: $row_cnt = $result->num_rows;.

Когда вы вызываете mysqli->query() с запросом на выборку, который не находит результатов, возвращаемый объект является не объектом, а false.

Во-вторых, вместо COUNT(*) просто используйте *.

Итак, чтобы сохранить свою логику, вы должны сделать что-то вроде этого:

//Check to see if user already voted
$result = $conn->query("SELECT * FROM User_Votes where UserID = '$ip_long' and QuestionID = '$id'");

if ($result === false) { 
    //Add IP Address and ID to the User_Votes table
    $result = $conn->query("INSERT INTO `User_Votes` (`UserID`, `QuestionID`) VALUES ('$ip_long', '$id')");
}elseif($result && $result->num_rows) { 
    //The user has already voted
    echo "Already voted";
}

Отредактировано:

//Check to see if user already voted
$result = $conn->query("SELECT * FROM User_Votes where UserID = '$ip_long' and QuestionID = '$id'");

if($result->num_rows){
    //The user has already voted
    echo "Already voted";
}else{
    //Add IP Address and ID to the User_Votes table
    $result = $conn->query("INSERT INTO User_Votes (UserID, QuestionID) VALUES ('$ip_long', '$id')");
}

Повторно отредактировано:

Вы должны позвонить $stmt->store_result() после $stmt->execute(). И ваш $stmt->get_result() здесь не нужен, потому что вы не используете выбранные данные.

Часть комментария из документации:

Если вы не используете mysqli_stmt_store_result() и сразу вызываете эту функцию после выполнения подготовленного оператора, эта функция обычно возвращает 0, так как она не может узнать, сколько строк находится в результирующем наборе, так как результирующий набор не сохраняется в памяти. пока что.

Итак, ваш код должен быть таким:

if(isset($_GET['id']) && !empty($_GET['id'])){
    $id = $_GET['id'];
    $ip_long = ip2long($ip);

    //Check to see if user already voted
    $stmt = $conn->prepare("SELECT * FROM User_Votes where UserID = ? and QuestionID = ?");
    $stmt->bind_param('ss', $ip_long, $id);
    $stmt->execute();
    $stmt->store_result();
    if($stmt->num_rows){
        //The user has already voted
        echo "Already voted";
    }else{
        //Add IP Address and ID to the User_Votes table
        $stmt = $conn->prepare("INSERT INTO User_Votes (UserID, QuestionID) VALUES (?, ?)");
        $stmt->bind_param('ss', $ip_long, $id);
        $stmt->execute();
        $stmt = $conn->prepare("UPDATE Question SET QuestionVotes = QuestionVotes + 1 where QuestionID = ?");
        $stmt->bind_param('s', $id);
        $stmt->execute();
    }
}

Примечание. Не смешивайте процедурный и объектно-ориентированный стиль mysqli.

person Rajdeep Paul    schedule 09.12.2015
comment
Спасибо за ваш ответ, однако проблема в том, что несмотря ни на что, без кавычек вокруг $ip_long и $ip запрос каждый раз возвращает false и никогда не возвращает true. Таким образом, ваше изменение может быть правильным, но все еще есть проблема, связанная с запросом или чем-то подобным. - person Nenn; 09.12.2015
comment
@ user2304993 Я обновил запросы. Попробуйте с указанными $id и $ip_long. - person Rajdeep Paul; 09.12.2015
comment
Хм, я только что попробовал это, и теперь я получаю «Уже проголосовали», независимо от того, что я делаю, даже после изменения if на === true, потому что запрос не возвращает ни false, ни true. - person Nenn; 09.12.2015
comment
@user2304993 user2304993 Проблема определенно связана с пунктом if и вокруг него. - person Rajdeep Paul; 09.12.2015
comment
Я думаю, ты прав. Я очень думаю, что запрос не возвращает то, о чем я его прошу, и я не могу исправить его, чтобы правильно проверить, проголосовал ли кто-то. - person Nenn; 09.12.2015
comment
@user2304993 user2304993 Сначала проверьте, установлено ли ваше соединение. Используйте для этого $conn->connect_errno. Здесь документация - person Rajdeep Paul; 09.12.2015
comment
Я сделал это сейчас, и кажется, что проблем с подключением или ошибок нет. - person Nenn; 09.12.2015
comment
@ user2304993 Я обновил свой ответ. См. раздел отредактированный моего ответа. Теперь он должен работать нормально. - person Rajdeep Paul; 09.12.2015
comment
Вау, вы звучите так уверенно, и вы так правы, что решили проблему локально, я, к сожалению, не могу протестировать сервер раньше завтрашнего дня. Но скажи мне, что именно было исправлено? Я не уверен, что понимаю. Кроме того, большое спасибо за ваши полезные ответы! - person Nenn; 09.12.2015
comment
@user2304993 user2304993 На самом деле мое первоначальное предположение было неверным. Независимо от того, находит он строку или нет, он всегда возвращает объект. Я только что проверил, содержит ли свойство $result->num_rows этого объекта какое-либо значение или нет. Если в запросе нет ни одной строки, то $result->num_rows будет равно 0, в противном случае он будет содержать количество возвращенных строк. И да, если это работает на вашем удаленном сервере, отметьте его как принято. :) - person Rajdeep Paul; 09.12.2015
comment
Ах, я боюсь, что мы снова в этом. Наконец-то я могу проверить, и это не работает. Я обновил свой сценарий подготовленными утверждениями и показал его в своем вопросе, чтобы вы могли видеть. Когда я нажимаю кнопку "голосовать", вся часть базы данных исчезает, как это может быть? - person Nenn; 10.12.2015
comment
@ user2304993 Я обновил свой ответ. См. раздел отредактировано моего ответа. - person Rajdeep Paul; 10.12.2015
comment
Большое спасибо за ваше редактирование и все ваше время. Я многому учусь благодаря этому, и это приносит удовольствие. При использовании этого кода на локальном хосте он работает, но как только я загружаю и проверяю код в Интернете, нажатие одной из кнопок приводит к удалению всего из базы данных. Вот ссылка, если поможет: linettemmd.dk/skodfri Также не смейтесь, анимация и все такое все еще в стадии разработки, хех. - person Nenn; 10.12.2015
comment
@user2304993 user2304993 Пожалуйста, объясните, нажатие одной из кнопок приводит к удалению всего из базы данных. Какую ошибку вы получаете? - person Rajdeep Paul; 10.12.2015
comment
Нет, вот что меня смущает. Прошу прощения за мое расплывчатое объяснение. Если вы перейдете по ссылке и нажмете одну из сгенерированных запросом зеленых кнопок с цифрами, эта часть веб-сайта исчезнет, ​​но без каких-либо ошибок. - person Nenn; 10.12.2015
comment
@user2304993 user2304993 Убедитесь, что ваши учетные данные базы данных на вашем сервере верны. Проверьте журнал ошибок вашего сервера. - person Rajdeep Paul; 10.12.2015
comment
Он подключается нормально, и я не могу найти журнал ошибок, но я проверил таблицы, и они возвращаются нормально. Может быть, это потому, что мой сервер PHP версии 5.6? - person Nenn; 10.12.2015
comment
@user2304993 user2304993 Не могли бы вы включить следующий код в начало ваших скриптов: error_reporting(E_ALL|E_STRICT); ini_set('display_errors', true); - person Rajdeep Paul; 10.12.2015
comment
Я сделал это, замечательный маленький код! Вот что он возвращает: «Неустранимая ошибка: вызов функции-члена bind_param() для логического значения в /customers/6/0/0/linettemmd.dk/httpd.www/skodfri/index.php в строке 108» о следующем код: '$stmt = $conn-›prepare(SELECT * FROM User_Votes, где UserID = ? и QuestionID = ?); $stmt-›bind_param('ss', $ip_long, $id);' - person Nenn; 10.12.2015
comment
@user2304993 user2304993 Ваш запрос не соответствует prepare(), поэтому он возвращает false. Убедитесь, что все правильно, имя таблицы, имена столбцов и т. д. - person Rajdeep Paul; 10.12.2015
comment
@user2304993 user2304993 Хорошо, если ваш код будет заключен в блок if. Я обновил свой ответ. См. раздел отредактировано моего ответа. - person Rajdeep Paul; 10.12.2015
comment
Код выглядит гораздо более структурированным. Но он по-прежнему, к сожалению, возвращает ту же фатальную ошибку, по какой-то причине вместо строки возвращается логическое значение. Является ли запрос ошибочным по какой-либо причине? - person Nenn; 10.12.2015
comment
Ах, теперь это работает! Я был дурак, вы были полностью правы давно, мои имена в базе данных были преобразованы в нижний регистр, изменение сделало работу! Хотя мой 'if($stmt-›num_rows){' сейчас не работает и, кажется, не проверяет строку. - person Nenn; 10.12.2015
comment
@ user2304993 Итак, с какой проблемой вы сейчас столкнулись? - person Rajdeep Paul; 10.12.2015
comment
Моя новая и гораздо меньшая проблема заключается в том, что «if($stmt-›num_rows)» несмотря ни на что возвращает «0» вместо «1», когда пользователь уже находится в базе данных? - person Nenn; 10.12.2015
comment
@user2304993 user2304993 Вы использовали $stmt->store_result();, как я предложил в моем разделе отредактировано? - person Rajdeep Paul; 10.12.2015
comment
Да, код такой: '$stmt-›store_result(); if($stmt-›num_rows){ //Пользователь уже проголосовал echo Уже проголосовал; эхо ($stmt-›num_rows); }else{' И каждый раз возвращает 0. - person Nenn; 10.12.2015
comment
О, наконец-то это работает, это потрясающе! Большое спасибо за всю вашу помощь, я не могу отблагодарить вас достаточно! Теперь мне просто нужно заставить его использовать kool-swap для Ajax, чтобы получить новые результаты, ну, я повеселюсь с этим. Я, конечно, отмечу ваш ответ как ответ! Еще раз спасибо! - person Nenn; 10.12.2015
comment
@ user2304993 Добро пожаловать! Приветствую командную работу. :) - person Rajdeep Paul; 10.12.2015
comment
Очень мило с твоей стороны называть это командной работой, но ты проделал здесь большую часть работы, и без нее я не знаю, что бы я делал! Но спасибо, что многому меня научили. Ваше здоровье! - person Nenn; 10.12.2015

Вы должны проверить имя вашей таблицы.

Вы используете это в одном из запросов User_Votes и это user_votes в другом. Это может работать на вашем сервере разработки, если он работает под управлением Windows, которая нечувствительна к регистру, но Linux, который, вероятно, работает на вашем производственном сервере, чувствителен к регистру.

Проверьте этот вопрос для получения дополнительной информации: Являются ли имена таблиц в MySQL чувствительными к регистру?

Также обратите внимание, что из приведенного выше кода ваше приложение выглядит небезопасным для SQL-инъекций. Вы должны привести переменные к int или к тому, что вы ожидаете от них.

person Vojtech Kane    schedule 09.12.2015
comment
Информация передается в базу данных правильно, поэтому я боюсь, что проблема не в чувствительности к регистру. Но я рад, что вы обратили мое внимание на эту ошибку, я немедленно ее изменю. - person Nenn; 09.12.2015
comment
Как узнать, что информация передается в базу данных правильно? - person jcaron; 09.12.2015

Ваш оператор вставки использует одинарные кавычки для заключения ваших переменных. Это должны быть двойные кавычки, чтобы PHP интерпретировал ваши переменные как значения, а не буквальную строку.

Похоже, это основная причина происходящего. Удалось ли вам убедиться, что все правильно записывается в таблицы вашей базы данных, прежде чем загружать их для работы? Затем убедитесь, что ваш оператор select правильно извлекает данные и какую форму они принимают?

И комментарий jcaran правильный ... необходимо будет рассмотреть некоторую проверку переменных, которые вы захватили.

person David Soo    schedule 09.12.2015
comment
Все правильно отправляется в базу, да. Так что, хотя это хорошая информация, она, к сожалению, не является причиной проблемы. - person Nenn; 09.12.2015