Desenvolvimento de layouts com bootstrap

Aula 02 a 03 - Definindo um document root / URLs amigáveis
Esse projeto está sendo executado com o PHP 7.2.3. Minha versão é a 8.1.2.
Para iniciar vamos montar a estrutura inicial de nosso site. Nesse exemplo vamos criar uma pasta public que conterá os arquivos públicos de nosso sistema. Em um nível abaixo vamos criar um arquivo que conterá as instruções de servidor em PHP. O objetivo desse arquivo é ficar mantido em um local no servidor sem acesso para o usuário.
Podemos incluir esse arquivo no nosso arquivo principal de duas formas: com o include que em caso de erro retorna apenas um warning ou com o require que em caso de falha gera um Fatal Error. Como esse arquivo é vital para a aplicação o idel é que ele seja incluido com o require.
Uma outra dica é utilizar para o caminho do arquivo a constante __DIR__ concatenada com o caminho relativo do arquivo a ser incluido. Essa constante retorna o diretório completo onde está sendo executado o arquivo que está acessando o link.
Para rodar o PHP server (sem a necessidade do XAMPP) executamos o servidor do PHP com o comando abaixo executado na pasta do projeto:
$ php -S localhost:8000 -t public/
Onde:
-S addr:port - Run with built-in web server
-t docroot - Specify document root docroot for built-in web server.
Em seguida analisamos a variável global $_SERVER com o var_dump(). Ao analisar essa variável verificamos que ela tem alguns atributos que indicam o caminho da URL. São eles:
['REQUEST_URI'], ['PATH_INFO'] e ['PHP_SELF'].
Com o atributo ['PATH_INFO'] podemos verificar qual o parametro que está sendo passado para o servidor. Com isso podemos indicar o caminho dos arquivos (mesmo sem eles estarem na árvore de arquivos) de acordo com palavras amigáveis (sem o uso de extensões) deixando os arquivos do site também em um diretório fora da pasta public. Fizemos isso inicialmente de modo 'hard code'.
Abaixo como fazer a substituição dos arquivos pelo PATH_INFO:

$path = $_SERVER['PATH_INFO'] ?? '/';

if ($path == '/') {
require __DIR__ . '/site/routes.php';
} elseif ($path == '/contato') {
require __DIR__ . '/site/contato.php';
} else {
echo 'Página não encontrada';
}

Devemos verificar os PATHs do sistema e a estrutura de pastas públicas. O caminho pode variar também conforme o servidor que está sendo executado. Estou executando essas aulas com o XAMPP e com o PHP server (esse na porta localhost:8081).
Aula 04 - Separando URLs amigáveis do site e da administração
Aqui foi feita a limpeza dos 'echos' de teste e fizemos um loop para criar dois caminhos diferentes para as URLs. Uma para o site no path '/' e outro para a interface de administração no path '/admin'.
Aula 05 - URLs amigáveis dinâmicas
Para verificarmos o nome da URL de forma dinamica vamos criar uma função para processar esse valor de modo que não precisemos criar uma instrução para cada URL.
Utilizamos para isso uma regex. Inicialmente criamos uma expressão que recebe uma '/' E um ou mais caractere minusculo de 'a a z' (/^\/([a-z]+)$/).
PAra verificar essa regex utilizamos a função preg_match(regex, valor_a_verificar)
Entendido isso vamos criar uma regex dinamica recebendo o valor passado por parametro na função.
Como o caractere '/' não é lido como parte da regex devemos substitui-lo pelo caractere com 'escape' (\/). Substituimos isso no parametro recebido com a função str_replace().
Voltando a função preg_match() podemos incluir um terceiro parametro que retorna um array com o valor encontrado (caso positivo).
Agora podemos testar o URL e redirecioná-lo de acordo com o teste feito pela função. Nos parametros da função podemos complementar o filtro da regex de acordo com o nosso padrão. Foram feitos alguns testes:
Passa para a função '/admin/' e qualquer caractere entre 'a - z'
resolve('/admin/([a-z]*)')

Passa para a função '/admin/' e qualquer numero entre '0 - 9'
resolve('/admin/([0-9]*)')

Passa para a função '/admin/' e qualquer caractere
resolve('/admin/(.*)')

Passa para a função '/admin/', com ou sem a '/' e qualquer caractere
resolve('/admin/?(.*)')
Com essas expressões montadas podemos redirecionar para os arquivos routes a URL e processar o seu endereço complementar direto nesses arquivos.
Aula 06 a 07 - Criando sistema de template / Organizando funções
Agora vamos criar uma função chamada render() que receberá alguns parametros. Criamos também uma pasta de templates (os arquivos de templeta terão uma extensão .tpl.php apenas para identificação).
Os parametros da função serão o conteudo, o template e uma array de dados. O template contém o padrão da página e o conteudo o conteudo interno.
Essas páginas são organizadas dentro da pasta criada template.
Criada a função vamos executá-la a partir do arquivo de rotas que será responsável por enviar os parametros para a função de acordo com a página que será montada.
A função render() retorna o template da página principal e o valor do conteudo que é inserido nesse template.
Essa lógica funciona normalmente quando temos o sistema rodando diretamente no localhost. Quando o sistema está trabalhando em links para outro path não funciona corretamente pois as rotas apontam para a raiz do servidor. Para solucionar isso utilizei nos links a variável $_SERVER['SCRIPT_NAME'] para recuperar o caminho da raiz do projeto.
O próximo passo foi remover as funções do arquivo bootstrap.php e coloca-las em arquivos separados. Foi também criado o arquivo de conexão com o banco de dados. Note que a conexão com o DB não funcionou em todos os ambientes. No ambiente do WSL não foi possivel levantar o DB. No ambiente do XAMPP tudo funcionou corretamente. Não consegui receber as mensagens de erro do MYSQLI_REPORT_ERROR no servidor da Hostgator, porém não fiz testes o suficiente nessa plataforma.
Aula 08 a 10 - Manipulando erros / Configurações da aplicação / Retornando status code correto
Para fazer o tratamento de erros criamos um arquivo separado apenas com essa função. Esse arquivo deve ser o primeiro a ser incluido no arquivo de controle.
Nesse arquivo criamos uma função que recebe quatro parâmetros: numero do erro, texto do erro, arquivo do erro e linha do erro.
Em seguida utilizamos duas funções do PHP set_error_handler() e set_exception_handler() que recebem como parâmetro a função de tratamento de erros.
Na função de tratamento de erros criamos um switch para verificar as variáveis E_USER_ERROR, E_USER_WARNING e E_USER_NOTICE e em cada caso retornar o número do erro e a sua string.
Criamos também uma linha que recupera os dados com a linha de erro e o arquivo onde ele ocorreu.
Foi citado a utilização de um módulo do PHP chamado XDEBUG que não está ativo na minha versão (verificar no phpinfo()). Verificar sua utilização depois, mas criamos uma estrutura de verificação de erros que aparentemente funcionou.
Por último criamos o DB para apagar os erros.
Foi criado também uma arquivo de config para armazenar algumas constates para o sistema. Criamos uma constante de DEBUG com valor true que tem como objetivo desabilitar as mensagens de erro tratadas pelo error_handler para que elas não vazem no ambiente de produção. Outras contantes criadas são com os dados de conexão com o DB.
Como criamos um sistema para tratamento de erros, toda a requisição http acaba por retornar um status code 200 OK, porém não é o que está acontecendo realmente. Para isso devemos enviar o status code correto para as requisições http.
Para isso incluimos a função http_response_code() com o código 500 no arquivo de tratamento de erros e com o código 404 nos arquivos de rotas (para página não encontrada).
Aula 11 a 13 - Adicionando bootstrap 4 / Iniciando template do painel administrativo / Estilizando o template do painel administrativo
Instalamos os links para o Bootstrap. Utilizei a versão 4.5.3 instalada na pasta public para que o acesso seja possivel para os usuários. Atenção para os caminhos relativos que variam bastante de acordo com a plataforma e em razão das construções do PHP.
Não consegui definir um caminho relativo para os arquivos do Bootstrap pois não estou trabalhando com esse projeto na pasta raiz do servidor (localhost/). Em função disso optei por utilizar o link dinamico para os arquivos do Bootstrap.
Consegui solucionar o problema acima recuperando o caminho relativo através da variável $_SERVER['SCRIPT_NAME']. Essa variável indica o caminho inteiro do projeto, incluindo o arquivo de indice. Por isso utilizei a função substr_replace() para remover o nome do arquivo index.php do conteudo. Início da criação do html da página de administração em Bootstrap. Criação do header e do menu lateral.
Estilos em CSS adicionados. Utilização das classes FlexBox do Bootstrap.
Aula 14 a 17 - Definindo rotas de administração de páginas / Template de listagem de páginas / Template de formulário de cadastro de página / Inserindo um editor de texto
Nesta aula criamos os arquivos para a navegação do menu de administração (dentro dos templates) e definimos todas as rotas para esses arquivos através do arquivo admin/routes.php.
Nas aulas seguintes começamos a criar as páginas do módulo de administração. Primeiro fizemos o HTML/Bootstrap da página index que tem apenas uma tabela com botões (que futuramente receberá dados do DB).
A segunda página foi a create que possui um formulário com dois inputs (no segundo input foi utilizada uma opção do Bootstrap para fazer um caractere fixo anterior ao campo). No terceiro campo será inserido um editor de textos.
O editor de texto é uma API que pode ser obtida no site Trix. Dentro do GitHub do projeto buscamos a pasta dist e copiamos os arquivos CSS e JS da aplicação (dica: ao clicar no arquivo selecionar a opção RAW para ter acesso ao código completo e em seguida selecionar todo o conteudo com a tacla Ctrl + A e colar para o arquivo criado no projeto).
Com esses arquivos copiados no diretório do projeto, devemos inserir os dois arquivos (CSS e JS) através das tags correspondentes (link e script). Inseridos os arquivos criamos uma tag <trix-editor> e já temos nosso editor funcionando na página.
Para nosso exemplo, como desejamos armazenar os dados desse editor em um campo de DB, criamos um <input type="hidden"> que irá receber os dados desse campo:
<input type="hidden" name="body" id="pagesBody""> <trix-editor input="pagesBody"></trix-editor> Perceba que criamos uma propriedade input na tag <trix-editor> que tem o mesmo valor do id da tag <input type="hidden">. Essa tag por sua vez transfere os dados para o DB através da propriedade name.
Aula 18 a 20 - Visualização de detalhes de página / Formulário de edição de página / Criando funções para integrar com o banco de dados
Criação da página de visualização dos itens.
Criação da página de edição e ajuste dos títulos das páginas.
Agora transferimos a lógica de rotas para um arquivo separado. Desse modo a única rota processada pelo arquivo de rotas principal é a rota inicial e a rota para página não encontrada.
Criamos também um arquivo de funções para processar as requisições de cada página junto ao DB (de acordo com sua utilidade).
Essas funções serão executadas dentro da rotina de rota correnspondende a sua ação. Desse modo para cada rota teremos uma função anonima que conterá a rotina de requisição junto ao DB.
Ainda no arquivo de rotas, ao passarmos as funções devemos verificar os atributos necessários em cada caso. Para as requisições que apenas retornam dados basta executar a função. Já nas rotinas que enviam informações para o DB precisamos verificar o método de comunicação e passar os parâmetros em caso de requisição de registros específicos.
Para as funções de create e edit que devemos receber dados do tipo POST verificamos o método enviado pelo servidor através de im condicional if ($_SERVER['REQUEST_METHOD'] === POST).
Para as funções que renderizam os dados do banco na tela iremos receber esses dados e converter em um array com as informações que será passado como terceiro parâmetro na função render(). Esse array irá conter em cada um de seus índices os valores de cada campo dos registros recuperados.
Já para as funções que requerem como parametro o id do registro a ser manipulado vamos recuperar esse valor da variável $param que é um array gerado pela função preg_match() na função resolve_route().
Os valores que são isolados no array $param são os que são indicados entre paranteses na passagem do parametro (no caso o dígito da regex '/admin/pages/(\d+)'). Caso alteremos o parametro da seguinte forma: '(/admin/pages/)(\d+)' teremos três valores no array:

array(3) { [0]=> string(14) "/admin/pages/1" [1]=> string(13) "/admin/pages/" [2]=> string(1) "1" } Note que nesse segundo caso o valor do dígito fica no índice '2' do array.
Por fim o retorno da função resolve() que passa o valor de $params deve ser atribuida a uma variável (no caso $params também mas que é uma nova variável com novo escopo) para poder ser manipulada pelas funções.
Aula 21 - Exibindo notificações de ação concluida
Nesta aula vamos criar uma flash message que são mensagens que são persistidas em algum lugar e que podem ser exibidas e em seguida apagadas. No nosso exemplo vamos armazenar na SESSION.
Criamos um arquivo que executa uma função com parâmetros message e type.
Em seguida utilizamos uma condicional que verifica se existe mensagem, e caso positivo armazena um array na variável que criamos $_SESSION['flash']. Para a criação desse array utilizamos o método compact() que cria o array com o valor de índice igual a variável:
array[] = compact('message', type)

Essa instrução é o mesmo que:

array[] = [ 'message' => $message, 'type' => $type ];

Em seguida, caso não seja um envio de mensagem vamos processar a mensagem e apagá-la. Para isso iniciamos uma variável para verificar se há algum dado na $_SESSION e caso não exista inserir um array vazio.
Caso o valor da mensagem seja vazio vamos retornar, caso contrário vamos renderizar a mensagem através da função render(). Para isso vamos percorrer a variável que recebe os valores da variável da $_SESSION, renderizamos seu valor e o apagamos da sessão com o método unset().
Na função render() passamos como segundo parametro o valor ajax que será um template vazio, uma vez que não vamos utilizar nenhum template.
Criamos os arquivos para os dois parametro de template (ajax e flash). O primeiro só recebe um include $content para não mostrar nada e o segundo em princípio recebe um alert JS com os dados das mensagens. Executamos no final do template principal um script para executar a função flash()
Agora temos um problema. Nas rotas em que existem requisições do tipo POST o navegador renderiza a página na primeira execução. Em seguida quando ele recebe a requisição ele executa a condicional, chama a função especificada e redireciona para o location indicado. Entretanto ele não para a execução e executa novamente a função render(). Com isso ele renderiza a página antes de apresentar a mensagem do script. Para resolver isso incluimos um return em todos os redirecionamentos de modo que o sistema não faça novamente a renderização.
Por fim para a rota de delete não é necessário verificar o POST pois não existe essa requisição.
Aula 22 a 23 - Melhorando as notificações e incluindo confirmações de ação / Criando banco de dados
Para aprimorar as telas de notificação que nesse momento estão sendo executadas em um alert vamos utilizar a biblioteca PNotify. No curso utilizamos o PNotify na versão 3 mas atualmente está na versão 5. Para a versão 3 havia a possibilidade de criar os arquivos customizados. Atualmente a versão é instalável via npm. Baixei os arquivos (pnotify.css, pnotify.brighttheme.css, pnotify.js e pnotify.js.map) para poder executar a aplicação. Caso falte algum arquivo instalo posteriormente.
A estrutura das mensagens com o PNotify é:
new PNotify({
title:'Success!',
text:'<?php echo $data['message'] ?>',
type:'<?php echo $data['type'] ?>'
})

No nosso exemplo não utilizamos o parâmetro title e o parâmetro type deve ser em inglês pois ele define a cor de fundo da caixa de mensagem.
Criamos um Event Listener para evitar o comportamento padrão de click:
const confirmEl = document.querySelector('.confirm');
confirmEl.addEventListener('click', function(e) {
e.preventDefault();
}
A classe confirm foi incluida no botão remove para que não haja a remoção de um item sem antes passar por uma confirmação. Após isso criamos a rotina de confirmação utilizando a função confirm() do JS:
const confirmEl = document.querySelector('.confirm');
if (confirmEl) {
confirmEl.addEventListener('click', function(e) {
e.preventDefault();
if (confirm('Tem certeza que deseja fazer isso?')) {
window.location = e.target.getAttribute('href');
}
})
}
Criação do DB. Na aula foi utilizado o Workbench para criar os scripts. Eu criei manualmente portanto pode ser necessária alguma alteração.
Aula 24 a 26 - Listando registro / Cadastrando registro / Visualização de detalhes de um registro
Começamos aqui a criar as querys no DB para manipular os dados. Primeiramente precisamos indicar que a função anonima da opção que estamos trabalhando irá necessitar da variável $conn. Trazemos essa variável para o escopo local utilizando o método use(nome_da_variável).
O modo de recuperação dos dados utilizam uma sintaxe diferente da que estou acostumado. Tentei utiliar a mesma sintaxe que estou habituado mas tive alguns problemas. Quando fazemos a recuperação de dados utilizando o método mysqli_fetch_assoc temos como retorno um array. Tentei fazer a lógica dessa forma mas não consegui. Entretanto percebi que não é necessário fazer o fetch dos dados. Podemos passar diretamente os dados recebidos na query.
Já utilizando a sintaxe da aula fizemos a query da seguinte forma:
$result = $conn->query('SELECT * FROM pages');
return $result->fetch_all(MYSQLI_ASSOC);


Entretanto verifiquei que também não é necessário fazer o fetch pois o array passado para a função render() já é o suficiente para listar os dados.
Para a criação de páginas criamos uma função para recuperar os dados. Aqui utilizamos um método de recuperação de dados diferente.
A função recebe como parâmetro o caminho para a página inicial de inclusão de dados para casos de erro.
Os dados recebidos serão tratados com a função filter_input(INPUT_POST, 'name_do_input'). Essa é uma forma diferente da que utilizamos anteriormente (verificando a variável $_POST).
Recebidos os dados fazemos um teste para verificar se os campos obrigatórios estão preenchidos e caso negativo executamos a função flash() para enviar uma mensagem de erro para o PNotify e em seguida retornamos para a página anterior através do header com o valor recebido por parametro na função.
Caso as variáveis estejam corretas, retornamos seus valores em um array criado com o compact() (lembrando, o compact() cria um array com o nome da chave igual ao nome da variável).
Novamente na função de criação precisamos trazer a variável de conexão para o escopo local.
Criamos a query utilizando o prepared statement para proteger os inputs do SQL injection.
Dessa forma, para os campos digitáveis passamos como valor '?' e tratamos esses itens adiante.
A sintaxe de tratamento segue o estudado no curso anterior PHP com MySQL. - Atualização 2
$sql = 'INSERT INTO pages (title, url, body, created, updated) VALUES ( ?, ?, ?, NOW(), NOW())';
$stmt = $conn->prepare($sql);
$stmt->bind_param('sss', $data['title'], $data['url'], $data['body']);
flash('Criou registro com sucesso', 'success');
return $stmt->execute();

Detalhe: a lógica de verificação de campos nulos não funcionou para o campo url.
A atualização do view dos arquivos também utiliza o statement.
$sql = 'SELECT * FROM pages WHERE id=?';
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
return $result->fetch_assoc();

Após recuperarmos os dados no array page passamos os parametros de cada campo na página de view.
Aula 27 a 28 - Edição de registro / Remoção de registro
Para a página de edição, primeiro adicionamos a função utilizada para o view na route do edit, de modo que ele recupere os dados dos campos (a query é a mesma).
Passamos os parâmetros para os campos da página edit e agora devemos fazer a query para alterar os dados.
Para isso também vamos reaproveitar o statement da função de create alterando apenas a query:

$data = pages_get_data('/admin/pages/' . $id . '/edit');
print_r($data);
$sql = 'UPDATE pages SET title=?, url=?, body=?, updated=NOW() WHERE id=?';
$stmt = $conn->prepare($sql);
$stmt->bind_param('sssi', $data['title'], $data['url'], $data['body'], $id);
flash('Alterou registro com sucesso', 'success');
return $stmt->execute();

A última operação a fazer é a de remoção. Novamente utilizamos o statement como feito anteriormente apenas utilizando o id para seleção do DELETE:
$sql = 'DELETE FROM pages WHERE id=?';
$stmt = $conn->prepare($sql);
$stmt->bind_param('i', $id);
flash('Removeu registro com sucesso', 'success');
return $stmt->execute();
Aula 29 a 31 - Criando seção de usuários / Template de listagem de usuários / Finalizando os templates
Criação das rotas para as opções de usuários e das páginas de conteúdo para essas rotas. Seguimos o mesmo processo das páginas de administração.
Para o template da página de listagem de usuários utilizamos o mesmo padrão. O mesmo para as demais páginas de template.
Aula 32 a 35 - Listando dados do banco / Cadastro de usuário no banco / Remoção de usuários / Visualização de dados
Iniciamos a construção das querys com o DB e aplicação das funções nas routes. O método para essa construção foi o mesmo utilizado para as querys das páginas.
Nessa aula apareceu um erro para tratamento que não tinha aparecido até aqui, porém que eu já tinha recebido. Nesse tipo de erro o elemento que é retornado pela função set_exception_handler() é um objeto e portanto não pode ser tratado como diversas variáveis (por isso eu havia removido esse método de tratamento).
Para solucionar isso, inicialmente passamos um null como valor default para os parametros da função.
Em seguida verificamos se a variável de erro é um objeto com a função is_object(). Caso positivo devemos atribuir cada atributo do objeto para a variável correspondente.

if (is_object($errno)) {
$err = $errno;
$errno = $err->getCode();
$errstr = $err->getMessage();
$errfile = $err->getFile();
$errline = $err->getLine();
}

Para o módulo dde criação de usuários também utilizamos a mesma lógica. Na aula entretanto não foi feita a verificação do password na função auxiliar, mas sim na função de create. Para a criptografia do password utilizamos a função PHP password_hash($data['password'], PASSWORD_DEFAULT).
A remoção e visualização de usuários também manteve a mesma estrutura. Já para a edição devemos verificar como fazer a alteração da senha. Em um primeiro momento eu apenas coloquei os valores de senha criptografados na tela de edição, mas não acredito que será feito dessa forma.
Aula 36 - Edição de usuário
Nessa parte acompanhei as mudanças da aula. A senha não é inserida no formulário de edição. As variáveis de senha passam para as funções específicas de create e de edit. Passamos a fazer essas verificações em cada caso.
No caso da edição fizemos uma verificação se o campo de senha foi digitado. Caso positivo a query altera também o campo de senha, caso contrário só fazemos a alteração do campo de email.
Podemos melhorar isso pedindo para confirmar a senha antiga antes de altera-la
Aula 37 a 38 - Preparando o upload de imagens / Barra de progresso de upload
Na API do Trix podemos incluir imagens em nossa página arrastando-as para a janela de edição, porém nesse momento elas não são carregadas para o projeto. Para isso vamos monitorar um evento do próprio Trix:
document.addEventListener('trix-attachment-add')

Antes de continuar a entender a função do attachment vamos fazer um ajuste no jQuery importado junto com o Bootstrap. A versão importada é a versão slim e não tem as funções do AJAX. Para isso basta remover o termo slim no nome da importação e remover as chaves de verificação de integridade e de origem.
Ao verificar o evento vamos atribuir a uma variável o evento através do event.attachment. Esse evento traz diversas informações do arquivo arrastado.
Verificamos em seguida se existe arquivo no evento testando o atributo attachment.file. Caso não haja arquivo executamos um return e encerramos a função. Em seguida instanciamos um objeto com o construtor FormData(). Esse construtor compila os dados recebidos em pares chave valor. Podemos utilizar o método append para criar um par cuja chave será 'file' e o valor será recebido do atributo file do attachment.
Feito isso passamos o objeto criado em uma função AJAX. Na função AJAX devemos desativar o contentType e o processData para que os dados não sejam modificados.
A seguir o código completo (até esse momento). Precisarei estudar melhor essa execução.
document.addEventListener('trix-attachment-add', function() {
const attachment = event.attachment;
if (!attachment.file) {
return;
}
const form = new FormData();
form.append('file', attachment.file);
$.ajax({
url: '<?php echo BOOTSTRAP . 'index.php/admin/upload/image' ?>',
method: 'POST',
data: form,
contentType: false,
processData: false
}).done(function() {
console.log('Deu certo')
}).fail(function() {
console.log('Deu errado')
})
})
Em seguida foi feita a animação da barra de carregamento do arquivo. Para isso foi utilizado o parâmetro AJAX xhr. Esse parametro recebe uma função que monitora o evento progress e através de uma fórmula matemática. O valor (em porcentagem) do progress é passado para a função attachment.setUploadProgress() (esse é um método do event.attachment). Com isso temos a animação da barra de progresso (e sua conclusão).
$.ajax({
url: 'index.php/admin/upload/image',
method: 'POST',
data: form,
contentType: false,
processData: false,

xhr: function() {
const xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener('progress', function(e) {
let progress = e.loaded / e.total * 100;
attachment.setUploadProgress(progress);
})
return xhr;
}
}).done(function() {

...
Aula 39 - Finalizando o upload
Para a gravação do arquivo utilizamos o caminho no arquivo routes para criar a lógica para a gravação.
Primeiramente atribuimos a uma variável o arquivo buscando a variável $_FILES. Tratamos o erro caso não exista arquivo. Em seguida criamos um array com os tipos de arquivo permitidos e tratamos uma excessão caso o arquivo seja de tipo inválido.
Por fim fazemos a rotina de gravação do arquivo criando para ele um nome aleatorio.
Tive bastante problema para ajustar a tela de edição com o Trix. Na tag <input type='hidden'> passamos no atributo value os dados da página em HTML. Quando inserimos uma imagem os dados vem com várias "" o que estava dando conflito com as "" da propriedade, fechando o elemento em local errado. Alterei as "" do atributo value para '' e funcionou. Preciso rever essas aulas em outro momento para entender melhor o sistema de recuperação via AJAX - POST e gravação do arquivo pelo direcionamento de rotas.
Aula 40 a 43 - Finalizando o painel de administração / Preparando HTML do login / Criando login / Finalizando o login
Vamos fazer os retoques finais na página de administração.
Para adicionar a classe active para os itens do menu utilizamos uma condicional com a mesma condição do routes para inserir a classe quando resolvemos o path.
No mais apenas criamos alguns links para a página inicial e inserimos ícones para os links com o Font Awesome.
Nessa aula tabém foi levantado o problema com a opção edit das páginas após a inserção de imagens. Aqui foi identificado o problema no campo value do input. Minha solução foi utilizar o '' mas foi utilizado (algo que eu tentei mas não continuei pois a outra opção funcionou) o método htmlentities().

Começamos a criar o template de autenticação de usuários. Primeiramente criamos um botão para sair da sessão na barra de navegação da página de administração.
Em seguida iniciamos a criação do template da página de login. Esse template é uma cópia do template da página de administração sem o menu de navegação e sem os scripts. Já o centeudo é um form em Bootstrap.
No arquivo de routes principal adicionamos uma rota para o arquivo de routes de autenticação.
No arquivo routes de autenticação criamos o render para a página de login.
O próximo passo foi criar o arquivo db para inserir a rotina de tratamento do usuário junto ao DB. Para isso a rotina criada foi a seguinte:
$login = function () use ($conn) {
$email = filter_input(INPUT_POST, 'email');
$password = filter_input(INPUT_POST, 'password');

if ($email == '' or $password == '') {
return false;
}

$sql = 'SELECT * FROM users WHERE email=?';
$stmt = $conn->prepare($sql);
$stmt->bind_param('s', $email);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

if (!$user) {
return false;
}

if (password_verify($password, $user['password'])) {
unset($user['password']);
$_SESSION['auth'] = $user;
return true;
}
return false;
};

A lógica acima não tem muitas novidades. De importante o método password_verify() que é utilizado para fazer a decriptação da senha armazenada de acordo com a chave do PHP e o método unset() que limpa a senha da variável após a verificação.
Foi criada uma chave auth para a $_SESSION que será utilizada nas próximas aulas.
Agora faremos a proteção das páginas com o login.
Para isso criamos um arquivo de autenticação que terá as seguintes funções:
function getUser()
{
return $_SESSION['auth'] ?? null;
}

function auth_protection()
{
$user = getUser();

if (!$user and !resolve('/admin/auth.*')) {
header('location:' . BOOTSTRAP . 'index.php/admin/auth/login');
die();
}
}

function logout()
{
unset($_SESSION['auth']);
flash('Desconectado com sucesso', 'success');
header('location:' . BOOTSTRAP . 'index.php/admin/auth/login');
die();
}

Aqui temos uma função que verifica se existe valor de sessão em $_SESSION['auth'].
A outra função verifica se foi validado a sessão e se não estamos na página de autenticação e caso uma das opções seja verdadeira retorna para a página de autenticação.
Essa função é incluida no início do arquivo de rotas de admin de modo que se não houver sessão não podemos acessar as páginas.
O arquivo auth deve ser incluido no arquivo principal bootstrap.php Já a função logout() encerra a sessão e retorna para a tela de login.
Aula 44 a 45 - Template do site / Exibindo páginas do site
Inclusão dos templates do site (página home e contato). O html e css já estava pronto, apenas incluimos os valores nos arquivos de content de cada página e o css e a imagem na pasta public.
Feito os apontamentos dos links.
Para importar as páginas do DB vamos reaproveitar uma função do arquivo db do admin/pages. Para isso fizemos um require no arquivo de rotas.
Em seguida chamamos a função pages_all() e incluimos como parametro na função render o array de dados recuperados do DB.
Passamos os dados recuperados como itens na <li> do menu de navegação fazendo um foreach() nos dados recebidos pelo banco e recuperando os campos url e title. Esses dados passarão a ser os links para acessar as páginas.
No arquivo de rotas foi feito outro foreach() para percorrer o DB e resolver o caminho das páginas individualmente e renderiza-las de acordo com o url no arquivo page.
Uma observação aqui é que caso a url esteja em branco temos problemas para a indexação. Fiz um ajuste para verificar se há indicação de url e caso contrário para indexar o caminho pelo índice no DB.
Aula 46 a 49 - Disparando email / Sobre publicação do projeto / Enviando os arquivos para o host compartilhado / Conectando no banco de dados
Para o envio de emails pelo foormulário de contato, adicionamos a sua rota uma verificação de 'POST', recuperamos os valores do input e executamos a função mail() com os dados.
Essa função não funciona em servidores locais e portanto só foi criada mas não foi testada.
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$from = filter_input(INPUT_POST, 'from');
$subject = filter_input(INPUT_POST, 'subject');
$message = filter_input(INPUT_POST, 'message');
$headers = 'From: ' . $from . '\r\n' . 'Reply-To: ' . $from . '\r\n' . 'X-Mailer: PHP/' . phpversion();

if (mail('[email protected]', $subject, $message, $headers)) {
flash('Email enviado com sucesso', 'success');
} else {
flash('Algo deu errado e o email não foi enviado', 'error');
}
return header('location: ' . BOOTSTRAP . '/contato');
}

Detalhes sobre a criação de dominio e seleção de serviços de ISP para deploy do projeto.
Criação do DB no provedor de acesso com o phpMyAdmin, criação de um usuário no DB para acesso (nesse caso foi copiada a senha com a chave gerada pelo PHP para gravar no banco para passar pelo decriptador). Transferencia dos arquivos para a hospedagem via FTP.
Ajuste das constantes de conexão no banco de dados.
Aula 50 a 51 - Finalizando / Testando envio de email
Para manipular as pastas do servidor html podemos editar o arquivo .htaccess. Para isso devemos ter configurado no servidor Apache o mod_rewrite (LoadModule rewrite_module modules/mod_rewrite.so).
Em seguida criamos dois arquivos .htaccess: um no diretório principal da aplicação e outro no diretório public.
No primeiro:
RewriteEngine on
RewriteCond %{REQUEST_URI} !public/
RewriteRule (.*) /public/$1 [L]

Nesse arquivo habilitamos a reescrita das regras, informamos que a condição seja tudo (.*) que não estiver no caminho '/public/' (todos os arquivos fora de '/public' não sejam acessados) seja reescrito ($1) após /public/
No segundo arquivo:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) index.php/$1 [QSA,L]

Nesse arquivo as condições são quando não existir uma arquivo com o nome (!-f) ou um diretório (!-d) reescreva tudo (.*) após ($1) index.php/ e mantenha o nome da query solicitada [QSA].
Uma explicação sobre isso no Stackoverflow.
Com esses redirecionamentos tudo funciona, inclusive sem o arquivo index.php no caminho. Por fim em alguns casos a '/' no final da URL pode resultar em erro. Para remover isso foi feito um trim no path dentro do arquivo resolve-route.
if (strlen($path) > 1) {
$path = rtrim($path, '/');
}
Verificamos aqui se o $path for maior que 1 caractere (para preservar a primeira '/' ou seja a raiz) e removemos o ultimo caractere se for uma '/' (isso só é feito internamente, na URL a '/' continua aparecendo).
Por fim é feito o teste de envio de email pelo servidor.