Sfoglia il codice sorgente

Implement form viewer to controll form access
- Implement admin area to enable/disable forms

Lucas de Souza 3 anni fa
parent
commit
97b894fdda

+ 168 - 0
admin/index.php

@@ -0,0 +1,168 @@
+<?php
+    session_start();
+    if (!isset($_SESSION['auth'])) {
+        header('Location: login.php');
+        exit;
+    }
+    if (isset($_GET['logout'])) {
+        unset($_SESSION['auth']);
+        session_destroy();
+        header('Location: login.php');
+        exit;
+    }
+?>
+
+<html>
+<head>
+    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
+    <title>LInE Quest</title>
+    <style>
+        body {
+            font-family: Arial;
+        }
+        table {
+            border-collapse: collapse;
+            width: 100%;
+            margin-bottom: 1em;
+        }
+
+        table td, table th {
+            border: 1px solid #ddd;
+            padding: 8px;
+        }
+
+        table tr:nth-child(even){background-color: #f2f2f2;}
+
+        table tr:hover {background-color: #ddd;}
+
+        table tr:hover td {border: 1px solid white;}
+
+        table th {
+            padding-top: 12px;
+            padding-bottom: 12px;
+            text-align: left;
+            background-color: #4CAF50;
+            color: white;
+        }
+
+        .pagination {
+            display: inline-block;
+            margin-top: 1em;
+        }
+
+        .pagination a {
+            color: black;
+            float: left;
+            padding: 8px 16px;
+            text-decoration: none;
+            border: 1px solid #ddd;
+        }
+
+        .pagination a:hover:not(.active) {background-color: #ddd;}
+
+        .pagination a:first-child {
+            border-top-left-radius: 5px;
+            border-bottom-left-radius: 5px;
+        }
+
+        .pagination a:last-child {
+            border-top-right-radius: 5px;
+            border-bottom-right-radius: 5px;
+        }
+
+        .disabled {
+
+            pointer-events: none;
+            cursor: default;
+            text-decoration: none;
+            color: #918a8a !important;;
+            background-color: #f0efef;
+        }
+
+        .export {
+            float: right;
+        }
+
+        .total {
+            float: left;
+        }
+        .button {
+            border: none;
+            color: white;
+            background-color: #555555;
+            padding: 15px 32px;
+            text-align: center;
+            text-decoration: none;
+            display: inline-block;
+            font-size: 16px;
+        }
+    </style>
+</head>
+<body>
+    <?php
+        print "<p><a class='button' href='?logout'>Sair</a></p>";
+    ?>
+    <table>
+    <?php
+        require_once('../controller/admin.php');
+        require_once('../controller/validator.php');
+        require_once('../controller/util.php');
+        if (isset($_GET['check'])) {
+            try {
+                $hash = Validator::str($_GET['check']);
+                switchQuestionnaireActive($hash);
+            } catch (Exception $e) {
+            }
+        }
+        if (isset($_GET['pageno'])) {
+            $pageno = Validator::int($_GET['pageno']);
+        } else {
+            $pageno = 1;
+        }
+
+        $no_of_records_per_page = 10;
+        $offset = ($pageno-1) * $no_of_records_per_page;
+
+        $total_rows = getQuestionnairesTotal();
+        $total_pages = ceil($total_rows / $no_of_records_per_page);
+
+        $res_data = paginateQuestionnaire($offset, $no_of_records_per_page);
+
+        $all_fields = ['Título','Visualizar','Email','Ativar?'];
+
+        print '<tr>';
+        foreach($all_fields as $field) {
+            print "<th>$field</th>";
+        }
+        print '</tr>';
+
+        foreach($res_data as $form) {
+            $id = $form['view_hash'];
+            $url = generateURI("/forms/viewer.php?id=$id");
+            $active = boolval($form['active']) ? 'checked' : '';
+            $email = $form['email'];
+            print '<tr>';
+            print '<td>'.$form['title'].'</td>';
+            print "<td><a target='_blank' href='$url'>link</a></td>";
+            print "<td><a href='mailto:$email'>$email</a></td>";
+            print "<td><a href='?pageno=$pageno&check=$id'><input type='checkbox' $active/></a></td>";
+            print '</tr>';
+        }
+
+    ?>
+    </table>
+    <div class="total">
+        Total de questionários: <b><?= $total_rows ?></b>
+    </div>
+    <center>
+        <div class="pagination">
+            <a class="<?php if($pageno <= 1){ echo 'disabled'; } ?>" href="?pageno=1">Primeira</a>
+
+            <a class="<?php if($pageno <= 1){ echo 'disabled'; } ?>" href="<?php if($pageno <= 1){ echo '#'; } else { echo "?pageno=".($pageno - 1); } ?>">Anterior</a>
+
+            <a class="<?php if($pageno >= $total_pages){ echo 'disabled'; } ?>" href="<?php if($pageno >= $total_pages){ echo '#'; } else { echo "?pageno=".($pageno + 1); } ?>">Próxima</a>
+            <a class="<?php if($pageno >= $total_pages){ echo 'disabled'; } ?>" href="?pageno=<?php echo $total_pages; ?>">Última</a>
+        </div>
+    </center>
+</body>
+</html>

+ 32 - 0
admin/login.php

@@ -0,0 +1,32 @@
+<?php
+require_once('../templates/templates.php');
+$template = getTemplate('admin_login.html');
+$context = ['errors' => '','post_url' => '../admin/login.php'];
+
+function errorMessage () {
+    global $context;
+    $errorAlert = getTemplate('error_alert.html');
+    $context['errors'] = parseTemplate($errorAlert, ['message'=>'Error. Certifique-se que os dados informados estão corretos']);
+}
+if (!empty($_POST)) {
+    require_once('../controller/forms.php');
+    require_once('../config/linequest.php');
+    require_once('../controller/validator.php');
+
+    global $CFG;
+    Validator::check(['senha'], $_POST);
+    $password = Validator::str($_POST['senha']);
+    try {
+        $valid = strcmp($password, $CFG->viewpass) === 0;
+        if ($valid) {
+            session_start();
+            $_SESSION['auth'] = md5($password.time());
+            header('Location: index.php');
+            exit;
+        }
+        errorMessage();
+    } catch (Exception $e) {
+        errorMessage();
+    }
+}
+echo parseTemplate($template, $context);

+ 4 - 4
app/create_form.php

@@ -60,8 +60,8 @@ function proccessRequest ($data) {
         $form['id'] = $keys[1];
         $html = generateFormHTML($form);
         storeNewForm($form, $keys, $formSource);
-        $path = saveHTML($form['title'], $keys[1], $html);
-        $link = generateURI('/forms/'.$path);
+        $id = $keys[1];
+        $link = generateURI("/forms/viewer.php?id=$id");
         $context = ['link'=> htmlspecialchars($link), 'secret'=>$keys[0], 'title'=>$form['title']];
         $success = getTemplate('created_successful.html');
         echo parseTemplate($success, $context);
@@ -73,12 +73,12 @@ function proccessRequest ($data) {
 }
 
 
-function saveHTML ($title, $folder,$html) {
+/*function saveHTML ($title, $folder,$html) {
     $date = new DateTime();
     $cleanTitle = cleanTitle($title);
     $datePart = $date->format("Y-m-j");
     $filePath = "$dateart-$cleanTitle-$folder.html";
     file_put_contents('../forms/'.$filePath, $html);
     return $filePath;
-}
+}*/
 

+ 2 - 2
app/post.php

@@ -66,8 +66,8 @@
 
         $data['form'] = $info['title'].'-'.$data['form'];
 
-        // $data["ip"] = get_user_ip();
-        // $data["timestamp"] = time();
+        $data["ip"] = get_user_ip();
+        $data["timestamp"] = time();
 
         try {
             storeUserSubmission(json_encode($data, JSON_UNESCAPED_UNICODE), $info['qid'], $data['uuid']);

+ 90 - 0
controller/admin.php

@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Este arquivo é parte do software linequest
+ * Ambiente de questionários para a coleta de dados
+ *
+ * Laboratório de Informática na Educação - LInE
+ * https://www.usp.br/line/
+ *
+ * Utilize os atributos definidos abaixo para
+ * configurar o ambiente de questionários.
+ *
+ * @author Lucas Calion
+ * @author Igor Félix
+ */
+
+ require_once ('../config/linequest.php');
+
+ function connect () {
+    global $CFG, $DB;
+    $DB  = new mysqli($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname);
+    if ($DB->connect_error) {
+       die("Connection failed: " . $DB->connect_error);
+    }
+    $DB->set_charset("utf8");
+ }
+
+ function switchQuestionnaireActive ($hash) {
+    global $DB; connect();
+
+    $sql = "UPDATE questionnaire SET active = not active WHERE view_hash = ?";
+
+    if (!($stmt = $DB->prepare($sql))) {
+        die("Prepare failed: (" . $DB->errno . ") " . $DB->error);
+    }
+
+    $stmt->bind_param('s', $hash);
+    $stmt->execute();
+    $stmt->close();
+    $DB->close();
+    return;
+ }
+
+ function getQuestionnairesTotal () {
+    global $DB; connect();
+
+    $sql = "SELECT count(id) FROM questionnaire";
+
+    if (!($stmt = $DB->prepare($sql))) {
+        die("Prepare failed: (" . $DB->errno . ") " . $DB->error);
+    }
+
+    $stmt->execute();
+    $stmt->bind_result($count);
+    $stmt->fetch();
+    $stmt->free_result();
+    $stmt->close();
+    $DB->close();
+    return $count;
+ }
+
+ function paginateQuestionnaire ($page, $no_of_records_per_page) {
+    global $DB; connect();
+
+    $sql = "SELECT title, email, view_hash, active FROM questionnaire LIMIT ?, ?";
+
+    if (!($stmt = $DB->prepare($sql))) {
+        die("Prepare failed: (" . $DB->errno . ") " . $DB->error);
+    }
+
+    if (!$stmt->bind_param("ii", $page,$no_of_records_per_page)) {
+       die("Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error);
+    }
+
+    if (!$stmt->execute()) {
+       die("Execute failed: (" . $stmt->errno . ") " . $stmt->error);
+    }
+
+    $stmt->store_result();
+    $stmt->bind_result($title, $email, $hash, $active);
+    $all_data = array();
+    while ($stmt->fetch()) {
+       $all_data[] = ['title' => $title, 'email' => $email, 'view_hash' => $hash, 'active' => $active];
+    }
+    $stmt->free_result();
+    $stmt->close();
+    $DB->close();
+
+    return $all_data;
+ }

+ 7 - 3
controller/formparser.php

@@ -119,6 +119,7 @@ class Parser {
       try {
          while (!$this->reader->eof()) {
             $this->skipBlanklines();
+            if($this->reader->eof()) break;
             $char = $this->reader->getChar();
             if ($char === '#') {
                // skip comment
@@ -222,12 +223,15 @@ class Parser {
       $this->reader->consume();
 
       $this->reader->consumeWhile(' ');
-      $question->text = trim($this->reader->consumeUntil("\n"));
+      $question->text = trim($this->reader->consumeUntil("\n", false, true));
       if (strlen($question->text) === 0) {
          throw new Exception('Error na linha '.($this->line).'. Questões devem ter um texto associado!', 800);
       }
-      $this->reader->consume();
-      $this->line++;
+
+      if (!$this->reader->eof()) {
+         $this->reader->consume();
+         $this->line++;
+      }
 
       // echo $question->type;
       // echo '<br/>';

+ 7 - 4
controller/forms.php

@@ -50,10 +50,10 @@
     return -1;
  }
 
- function getQuestionaireInfo ($hash) {
+ function getQuestionaireInfo ($hash, $admin = false) {
     global $DB; connect();
 
-    $sql = "SELECT id,title FROM questionnaire WHERE view_hash = ?";
+    $sql = "SELECT id,title,description,source,active FROM questionnaire WHERE view_hash = ?";
 
     if (!($stmt = $DB->prepare($sql))) {
         die("Prepare failed: (" . $DB->errno . ") " . $DB->error);
@@ -62,12 +62,15 @@
     $stmt->bind_param('s', $hash);
     $stmt->execute();
     $stmt->store_result();
-    $stmt->bind_result($qid, $title);
+    $stmt->bind_result($qid, $title,$description, $source, $active);
     if ($stmt->fetch()) {
        $stmt->free_result();
        $stmt->close();
        $DB->close();
-       return ['qid' => $qid, 'title' => $title];
+       if($active === 0 && !$admin) {
+          return null;
+       }
+       return ['qid' => $qid, 'title' => $title, 'description' => $description, 'source' => $source, 'active' => $active];
     }
     throw new Exception("User attempted to submit data to a non-existent questionnaire.");
  }

+ 32 - 0
forms/viewer.php

@@ -0,0 +1,32 @@
+<?php
+session_start();
+
+$admin = isset($_SESSION['auth']);
+
+require_once('../templates/templates.php');
+require_once('../controller/forms.php');
+require_once('../controller/validator.php');
+require_once('../controller/formparser.php');
+require_once('../controller/generateform.php');
+
+Validator::check(['id'],$_GET);
+
+$view_hash = Validator::str($_GET['id']);
+
+$info = getQuestionaireInfo($view_hash,$admin);
+if (is_null($info)) {
+    $template = getTemplate('no_form.html');
+    echo parseTemplate($template,[]);
+    exit;
+}
+
+$form = ['title'=>$info['title'], 'description' => $info['description']];
+$reader = new SourceReader($info['source']);
+$parser = new Parser($reader);
+$form['questions'] = $parser->parse();
+if (boolval($info['active'])) {
+    $form['id'] = $view_hash;
+} else {
+    $form['id'] = "null";
+}
+echo generateFormHTML($form);

+ 6 - 1
guia.html

@@ -60,6 +60,11 @@
  B. Segunda alternativa da questão 3
  C. Terceira alternativa da questão 3
 
+ # Esse é um comentário que é representado por # no ínicio da linha, eles são sempre ignorados pelo processador.
+ # :: representa uma quebra de página, use-o para criar páginas no seu questionário!
+ ::
+
+
  4. [I] O I cria um campo simples de texto ao invés de uma área para texto
 
  5. [S] Uma questão do tipo S é uma questão de multipla escolha que apresenta as alternativas de forma simplificada
@@ -67,7 +72,6 @@
  B. Segunda alternativa da questão 5
  C. Terceira alternativa da questão 5
 
- # Esse é um comentário que é representado por # no ínicio da linha, eles são sempre ignorados pelo processador.
 </code>
 </pre>
   <p>Observações</p>
@@ -81,6 +85,7 @@
     </li>
     <li>Um *(asterísco) antes ou depois do tipo da questão indica que ela é obrigatória</li>
     <li>As alternativas das questões do tipo multipla escolha <b>DEVEM</b> ser uma sequência de letras maiúsculas de A à Z, seguidas <b>obrigatoriamente</b> de um .(ponto).</li>
+    <li>Usar quebra de página (::) de forma consecutiva não cria páginas em branco!</li>
   </ul>
 
     <script src="assets/js/jquery-3.3.1.slim.min.js"></script>

+ 62 - 0
templates/admin_login.html

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="pt-br">
+  <head>
+    <!-- Meta tags Obrigatórias -->
+    <meta charset="utf-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1, shrink-to-fit=no"
+    />
+
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="../assets/css/bootstrap.min.css" />
+
+    <title>LInE Quest</title>
+
+    <style>
+      body {
+        background-color: #e9ecef;
+      }
+      .page-header {
+        margin-top: 1em;
+        border-top: 1px solid rgb(155, 155, 155);
+        padding-top: 0.5em;
+      }
+      .badge {
+        font-size: 100%;
+      }
+      .form-div {
+        margin: 2rem;
+        width: 60vw;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="jumbotron" style="padding-bottom: 0;">
+      <p style="float: right; color: #333; font-weight: 300;">
+        Free Education, Private Data (FEPD)
+      </p>
+      <img src="../assets/img/logo.png" width="250px" />
+      <p style="font-size: 21px; color: #333; font-weight: 300;">
+        Plataforma para questionários
+      </p>
+      <hr/>
+    </div>
+    <div class="form-div">
+      <h3 class="">Acessar área administrativa</h3>
+      <form action="{post_url}" method="POST">
+        <div class="form-group">
+          <label for="senha">Senha</label>
+          <input
+            id="senha"
+            name="senha"
+            type="password"
+            class="form-control"
+          />
+        </div>
+        <button class="btn btn-primary" type="submit">Enviar</button>
+      </form>
+    </div>
+    {errors}
+  </body>
+</html>

+ 1 - 0
templates/created_successful.html

@@ -64,6 +64,7 @@
           exibido!</span
         >
       </p>
+      <p>Seu questionário ainda não está ativo, para ativá-lo você deve enviar nos <a href="mailto:leo@ime.usp.br, igormf@ime.usp.br, lucasmens@ime.usp.br">enviar um email</a></p>
     </div>
     <script src="../assets/js/closealert.js"></script>
   </body>

+ 39 - 0
templates/no_form.html

@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html lang="pt-BR">
+  <head>
+    <title>LInE Quest</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta name="author" content="LInE" />
+    <link rel="stylesheet" href="../assets/foundation.min.css">
+    <style>
+      .rotulo {
+        font-weight: bold;
+        background-color: #e4f1fa;
+        margin: 2px;
+      }
+      .required::before {
+        content: '* ';
+        color: red;
+      }
+      .condicional {
+        visibility: hidden;
+      }
+      .page {
+        display: none;
+      }
+      #btn_submit {
+        display: none;
+      }
+      legend {
+        font-weight: bold;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="callout alert">
+        <p class="paragrafo lead text-justify">
+        Questionário não existe ou não está disponível no momento!
+        </p>
+    </div>
+   </body>
+</html>

+ 4 - 2
templates/thanks.html

@@ -4,7 +4,7 @@
     <title>{title}</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
     <meta name="author" content="LInE" />
-    <link rel="stylesheet" href="/assets/foundation.min.css">
+    <link rel="stylesheet" href="../assets/foundation.min.css">
     <style>
       .rotulo {
         font-weight: bold;
@@ -33,8 +33,10 @@
     <div class="callout">
         <p class="paragrafo lead text-justify">
         Suas respostas foram gravadas com sucesso!<br/>
-        {thanks}
         </p>
     </div>
+    <div class="callout primary">
+        <p>{thanks}</p>
+    </div>
    </body>
 </html>