TUTORIAL Construyendo un CMS en una tarde con PHP y MySQL

La creación de un sistema de administración de contenido puede parecer una tarea desalentadora para el desarrollador de PHP novato. Sin embargo, no tiene por qué ser tan difícil. En este tutorial, le mostraré cómo crear un CMS básico, pero completamente funcional, desde cero en solo unas pocas horas. ¡Sí, se puede hacer!

A lo largo del camino, aprenderá cómo crear bases de datos y tablas MySQL; cómo trabajar con objetos PHP, constantes, incluye, sesiones y otras características; cómo separar la lógica de negocios de la presentación; ¡Cómo hacer que tu código PHP sea más seguro, y mucho más!

Antes de comenzar, consulte el producto terminado haciendo clic en el enlace Ver demostración de arriba. (Por razones de seguridad, esta demostración es de solo lectura, por lo que no puede agregar, cambiar o eliminar artículos). También puede hacer clic en el enlace Descargar código arriba para descargar el código PHP completo para el CMS, para que pueda ejecutarlo en su propio servidor.

Para este tutorial, deberá tener instalado el servidor web Apache con PHP, así como el servidor de base de datos MySQL en su computadora. La configuración de todo esto está fuera del alcance del tutorial, pero una forma realmente fácil de hacerlo es simplemente instalar XAMPP en su computadora.

La lista de características

Nuestro primer trabajo es resolver exactamente lo que queremos que haga nuestro CMS. El CMS tendrá las siguientes características:

Interfaz:

  • La página de inicio, enumerando los 5 artículos más recientes.
  • La página de listado de artículos, listando todos los artículos.
  • La página “ver artículo”, que permite a los visitantes ver un solo artículo.

Extremo posterior

  • Admin inicio / cierre de sesión
  • Listar todos los artículos
  • Añadir un nuevo articulo
  • Editar un artículo existente
  • Eliminar un artículo existente

Cada artículo tendrá un título, resumen y fecha de publicación asociados.

Planificándolo

Estos son los pasos que debemos seguir para crear nuestro CMS:

Esta página contiene todo el código para el CMS, listo para copiar y pegar en sus propios archivos. Si no desea crear los archivos usted mismo, simplemente descargue el archivo zip terminado , que contiene todos los archivos de código y las carpetas.

Listo? ¡Toma una taza de té y comencemos a programar!

Paso 1: Crea la base de datos

Lo primero que debemos hacer es crear una base de datos MySQL para almacenar nuestro contenido. Puede hacerlo de la siguiente manera:

  1. Ejecute el mysqlprograma cliente
    Abra una ventana de terminal e ingrese lo siguiente:mysql -u username -pLuego ingrese su contraseña de MySQL cuando se le solicite.
  1. usernameDebe ser un usuario que tenga permiso para crear bases de datos. Si está trabajando en un servidor de desarrollo, como su propia computadora, entonces puede usar al rootusuario para esto, para evitar tener que crear un nuevo usuario.
  • Crear la base de datos
    En el mysql>indicador, escriba:create database cms;Luego presione Enter.
  • Salga del mysqlprograma cliente
    En el mysql>indicador, escriba:exitLuego presione Enter.

¡Eso es! Ahora ha creado una nueva base de datos vacía, en la que puede colocar las tablas y el contenido de su base de datos.

Algunas configuraciones de servidor web le permiten crear bases de datos a través de una herramienta basada en web como cPanel o Plesk (de hecho, a veces esta es la única forma de crear bases de datos MySQL). Si no está seguro de qué hacer en su servidor, solicite ayuda a su equipo de soporte técnico.

Paso 2: Crear la articlestabla de base de datos

Nuestra sencilla CMS tiene sólo una tabla de base de datos: articles. Esto, como podría imaginar, contiene todos los artículos del sistema.

Vamos a crear el esquema para la tabla. El esquema de una tabla describe los tipos de datos que la tabla puede contener, así como otra información sobre la tabla.

Crea un archivo de texto llamado en tables.sqlalgún lugar de tu disco duro. Agregue el siguiente código al archivo

DROP TABLE IF EXISTS articles;
CREATE TABLE articles
(
  id              smallint unsigned NOT NULL auto_increment,
  publicationDate date NOT NULL,                              # When the article was published
  title           varchar(255) NOT NULL,                      # Full title of the article
  summary         text NOT NULL,                              # A short summary of the article
  content         mediumtext NOT NULL,                        # The HTML content of the article

  PRIMARY KEY     (id)
);

El código anterior define el esquema para la articlestabla. Está escrito en SQL, el lenguaje utilizado para crear y manipular bases de datos en MySQL (y en la mayoría de los otros sistemas de bases de datos).

Vamos a romper un poco el código anterior:

  1. Crear la articlestabla
    DROP TABLE IF EXISTS articleselimina cualquier articlestabla existente (y datos, ¡cuidado!) si ya existe. Hacemos esto porque no podemos definir una tabla con el mismo nombre que una tabla existente.CREATE TABLE articles ( )crea la nueva articlestabla. Las cosas dentro de los paréntesis definen la estructura de los datos dentro de la tabla, que se explica a continuación …
  2. Dale a cada artículo una identificación única
    Ahora estamos listos para definir nuestra estructura de tabla. Una tabla consta de varios campos (también denominados columnas ). Cada campo contiene un tipo específico de información sobre cada artículo.

Primero, creamos un idcampo. Este tiene un smallint unsignedtipo de datos (entero pequeño sin signo), lo que significa que puede contener números enteros de 0 a 65,535. Esto permite que nuestro CMS tenga hasta 65,535 artículos. También especificamos el NOT NULLatributo, lo que significa que el campo no puede estar vacío (nulo), lo que nos facilita la vida. También agregamos el auto_incrementatributo, que le dice a MySQL que asigne un valor nuevo y único al idcampo de un artículo cuando se crea el registro del artículo. Así que el primer artículo tendrá un id1, el segundo tendrá un id2 y así sucesivamente. Usaremos este valor único como un identificador para referirnos al artículo que queremos mostrar o editar en el CMS.

Agregar el publicationDatecampo
La siguiente línea crea el publicationDatecampo, que almacena la fecha en que se publicó cada artículo. Este campo tiene un tipo de datos date, lo que significa que puede almacenar valores de fecha.

Agregue el titlecampo
A continuación creamos el titlecampo para contener el título de cada artículo. Tiene un tipo de datos de varchar(255), lo que significa que puede almacenar una cadena de hasta 255 caracteres

  1. Agregue los campos summaryycontent
    Los últimos 2 campos summarycontentmantenga un breve resumen del artículo y el contenido HTML del artículo, respectivamente. el resumen tiene un texttipo de datos (que puede contener hasta 65,535 caracteres) y contenttiene un mediumtexttipo de datos (que puede contener hasta 16,777,215 caracteres).
  2. Añadir la clave principal
    La última línea dentro de la CREATE TABLEdeclaración define una clave para la tabla. Una clave también se denomina índice , y en términos simples hace que sea más rápido encontrar datos en la tabla, a expensas de un espacio de almacenamiento adicional.Hacemos el idcampo a PRIMARY KEY. Cada mesa solo puede tener una sola PRIMARY KEY; esta es la clave que identifica de forma única cada registro en la tabla. Además, al agregar esta clave, MySQL puede recuperar un artículo basado en su ID muy rápidamente.

Ahora que hemos creado nuestro esquema de tabla, debemos cargarlo en MySQL para crear la propia tabla. La forma más sencilla de hacerlo es abrir una ventana de terminal y cambiar a la carpeta que contiene su tables.sqlarchivo, luego ejecute este comando:

mysql -u username -p cms < tables.sql

… ¿dónde usernameestá tu nombre de usuario MySQL. cmses el nombre de la base de datos que creó en el Paso 1.

Introduzca su contraseña cuando se le solicite. MySQL luego carga y ejecuta el código en su tables.sqlarchivo, creando la articlestabla dentro de la cmsbase de datos.También puede usar una herramienta de administración basada en la web como phpMyAdmin para ejecutar su tables.sqlcódigo y crear la tabla. phpMyAdmin viene preinstalado con la mayoría de las cuentas de alojamiento web.

Paso 3: Hacer un archivo de configuración

Ahora que ha creado su base de datos, está listo para comenzar a escribir su código PHP. Comencemos por crear un archivo de configuración para almacenar varias configuraciones útiles para nuestro CMS. Este archivo será utilizado por todos los archivos de script en nuestro CMS.

Primero, cree una cmscarpeta en algún lugar del sitio web local de su computadora, para guardar todos los archivos relacionados con el CMS. Si está ejecutando XAMPP, entonces el sitio web local estará en una htdocscarpeta dentro de su carpeta XAMPP. O, si lo prefiere, puede crear un nuevo sitio web solo para su CMS y colocar todos los archivos en la carpeta de documentos raíz de ese nuevo sitio web.

Dentro de la cmscarpeta, cree un archivo llamado config.phpcon el siguiente código:

<? php
ini_set ("display_errors", true);
date_default_timezone_set ("Australia / Sydney"); // http://www.php.net/manual/en/timezones.php
define ("DB_DSN", "mysql: host = localhost; dbname = cms");
define ("DB_USERNAME", "username");
define ("DB_PASSWORD", "contraseña");
define ("CLASS_PATH", "classes");
define ("TEMPLATE_PATH", "templates");
define ("HOMEPAGE_NUM_ARTICLES", 5);
define ("ADMIN_USERNAME", "admin");
define ("ADMIN_PASSWORD", "mypass");
require (CLASS_PATH. "/Article.php");

función handleException ($ excepción) {
  echo "Lo siento, ocurrió un problema. Inténtalo más tarde.";
  error_log ($ exception-> getMessage ());
}

set_exception_handler ('handleException');
?>

Vamos a desglosar este archivo:

  1. Mostrar errores en el navegador
    La ini_set()línea hace que los mensajes de error se muestren en el navegador. Esto es bueno para la depuración, pero debe configurarse falseen un sitio en vivo ya que puede ser un riesgo de seguridad.
  2. Establezca la zona horaria
    Como nuestro CMS utilizará la date()función de PHP , debemos indicar a PHP la zona horaria de nuestro servidor (de lo contrario, PHP genera un mensaje de advertencia). El mío está configurado para "Australia/Sydney": cambiar este valor a su zona horaria local .
  3. Configure los detalles de acceso a la base de datos
    A continuación, definimos una constante DB_DSN, que le indica a PHP dónde encontrar nuestra base de datos MySQL. Asegúrese de que el dbnameparámetro coincida con el nombre de su base de datos de CMS ( cmsen este caso). También almacenamos el nombre de usuario y la contraseña de MySQL que se utilizan para acceder a la base de datos de CMS en las constantes DB_USERNAMEDB_PASSWORD. Establezca estos valores en su nombre de usuario y contraseña de MySQL.
  4. Establecer las rutas
    Establecemos 2 nombres de ruta en nuestro archivo de configuración:,CLASS_PATHque es la ruta a los archivos de clase, y TEMPLATE_PATH, que es donde nuestro script debe buscar los archivos de plantilla HTML. Ambas rutas son relativas a nuestra cmscarpeta de nivel superior .
  1. Establecer el número de artículos para mostrar en la página de inicio
    HOMEPAGE_NUM_ARTICLES controla el número máximo de titulares de artículos para mostrar en la página de inicio del sitio. Hemos establecido esto en 5 inicialmente, pero si desea más o menos artículos, simplemente cambie este valor.
  2. Establezca el nombre de usuario y la contraseña del administrador
    Las constantes ADMIN_USERNAMEADMIN_PASSWORDcontienen los detalles de inicio de sesión del usuario administrador de CMS. De nuevo, querrás cambiarlos a tus propios valores.
  3. Incluya la Articleclase
    Dado Articleque todos los scripts de nuestra aplicación necesitan el archivo de clase, que crearemos a continuación, lo incluimos aquí.
  4. Crear un controlador de excepciones
    Finalmente, definimos handleException(), una función simple para manejar cualquier excepción de PHP que pueda surgir a medida que se ejecuta nuestro código. La función muestra un mensaje de error genérico y registra el mensaje de excepción real en el registro de errores del servidor web. En particular, esta función mejora la seguridad al manejar las excepciones de PDO que de otra manera podrían mostrar el nombre de usuario y la contraseña de la base de datos en la página. Una vez que hemos definido handleException(), lo establecemos como el controlador de excepciones llamando a la set_exception_handler()función de PHP .Este controlador de excepciones es un atajo rápido y sucio para mantener el tutorial lo más simple posible. La forma “adecuada” de manejar las excepciones es envolver todas las llamadas de PDO dentro Article.phpde try ... catchbloques .

Nota de seguridad

En un entorno de servidor en vivo sería una buena idea ubicarlo en un config.phplugar fuera de la raíz de documentos de su sitio web, ya que contiene nombres de usuario y contraseñas. Si bien generalmente no es posible leer el código fuente de un script PHP a través del navegador, a veces sucede si el servidor web está mal configurado.

También puede usar hash()para hacer un hash desde su contraseña de administrador y almacenar el hash en config.phplugar de la contraseña de texto simple. Luego, al momento de iniciar sesión, puede hash()ingresar la contraseña y ver si coincide con el hash config.php.

Paso 4: construir la Articleclase

Ya estás listo para construir la Articleclase PHP. Esta es la única clase en nuestro CMS, y se encarga del almacenamiento esencial de los artículos en la base de datos, así como la recuperación de artículos de la base de datos. Una vez que hemos creado esta clase, será muy fácil para nuestros otros scripts de CMS crear, actualizar, recuperar y eliminar artículos.

Dentro de su cmscarpeta, cree una classescarpeta. Dentro de esa classescarpeta, cree un nuevo archivo llamado Article.phpy coloque el siguiente código en él:

<? php

/**
 * Clase para manejar artículos.
 */

clase de artículo
{

  // Propiedades

  /**
  * @var int El ID del artículo de la base de datos
  */
  public $ id = null;

  /**
  * @var int Cuando el artículo fue publicado
  */
  public $ publishDate = null;

  /**
  * @var cadena Título completo del artículo
  */
  public $ title = null;

  /**
  * @var cadena Un breve resumen del artículo
  */
  public $ summary = null;

  /**
  * @var string El contenido HTML del artículo.
  */
  public $ content = null;


  /**
  * Establece las propiedades del objeto usando los valores en la matriz suministrada
  *
  * @param assoc Los valores de propiedad
  */

  función pública __construct ($ data = array ()) {
    if (isset ($ data ['id'])) $ this-> id = (int) $ data ['id'];
    if (isset ($ data ['publishDate'])) $ this-> publishDate = (int) $ data ['publishDate'];
    if (isset ($ data ['title'])) $ this-> title = preg_replace ("/[^.,-_'"@?!:$ a-zA-Z0-9 ()] /", " ", $ datos ['título']);
    if (isset ($ data ['summary'])) $ this-> summary = preg_replace ("/[^.,-_'"@?!:$ a-zA-Z0-9 ()] /", " ", $ datos ['resumen']);
    if (isset ($ data ['content'])) $ this-> content = $ data ['content'];
  }


  /**
  * Establece las propiedades del objeto utilizando los valores de publicación de formulario de edición en la matriz suministrada
  *
  * @param assoc Los valores de post de formulario
  */

  función pública storeFormValues ​​($ params) {

    // Almacenar todos los parámetros
    $ esto -> __ construir ($ params);

    // Analizar y almacenar la fecha de publicación.
    if (isset ($ params ['publishDate'])) {
      $ publishDate = explode ('-', $ params ['publishDate']);

      if (count ($ publishDate) == 3) {
        list ( $y, $m, $d ) = $publicationDate;
        $ this-> publishDate = mktime (0, 0, 0, $ m, $ d, $ y);
      }
    }
  }


  /**
  * Devuelve un objeto de artículo que coincide con el ID del artículo dado
  *
  * @param int El ID del artículo
  * @return Article | false El objeto del artículo, o false si no se encontró el registro o hubo un problema
  */

  función estática pública getById ($ id) {
    $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD);
    $ sql = "SELECT *, UNIX_TIMESTAMP (publishDate) AS publishDate FROM articles WHERE id =: id";
    $ st = $ conn-> prepare ($ sql);
    $ st-> bindValue (": id", $ id, PDO :: PARAM_INT);
    $ st-> execute ();
    $ row = $ st-> fetch ();
    $ conn = null;
    if ($ row) devolver nuevo artículo ($ row);
  }


  /**
  * Devuelve todos (o un rango de) objetos de artículo en el DB
  *
  * @param int Opcional El número de filas a devolver (predeterminado = todas)
  * @return Array | false Una matriz de dos elementos: resultados => matriz, una lista de objetos del artículo; TotalRows => Número total de artículos
  */

  función estática pública getList ($ numRows = 1000000) {
    $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD);
    $ sql = "SELECCIONE SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP (publishDate) AS editionDate FROM articles
            ORDEN POR publicaciónFECHA LÍMITE DESC: numRows ";

    $ st = $ conn-> prepare ($ sql);
    $ st-> bindValue (": numRows", $ numRows, PDO :: PARAM_INT);
    $ st-> execute ();
    $ list = array ();

    while ($ row = $ st-> fetch ()) {
      $ articulo = nuevo articulo ($ fila);
      $ list [] = $ articulo;
    }

    // Ahora obtenga el número total de artículos que coincidieron con los criterios
    $ sql = "SELECT FOUND_ROWS () AS totalRows";
    $ totalRows = $ conn-> query ($ sql) -> fetch ();
    $ conn = null;
    return (array ("results" => $ list, "totalRows" => $ totalRows [0]));
  }


  /**
  * Inserta el objeto Artículo actual en la base de datos y establece su propiedad de ID.
  */

  función pública insert () {

    // ¿El objeto Article ya tiene una ID?
    if (! is_null ($ this-> id)) trigger_error ("Article :: insert (): Intente insertar un objeto Article que ya tiene su propiedad ID establecida (a $ this-> id).", E_USER_ERROR);

    // Insertar el artículo
    $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD);
    $ sql = "INSERTAR EN artículos (fecha de publicación, título, resumen, contenido) VALORES (DE_UNIXTIME (: publicación Fecha),: título,: resumen,: contenido)";
    $ st = $ conn-> prepare ($ sql);
    $ st-> bindValue (": publishDate", $ this-> publishDate, PDO :: PARAM_INT);
    $ st-> bindValue (": title", $ this-> title, DOP :: PARAM_STR);
    $ st-> bindValue (": summary", $ this-> summary, DOP :: PARAM_STR);
    $ st-> bindValue (": content", $ this-> content, DOP :: PARAM_STR);
    $ st-> execute ();
    $ this-> id = $ conn-> lastInsertId ();
    $ conn = null;
  }


  /**
  * Actualiza el objeto Artículo actual en la base de datos.
  */

  actualización de la función pública () {

    // ¿Tiene el objeto Article una identificación?
    if (is_null ($ this-> id)) trigger_error ("Article :: update (): Intente actualizar un objeto Article que no tiene su propiedad ID establecida"., E_USER_ERROR);
   
    // Actualizar el artículo
    $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD);
    $ sql = "ACTUALIZAR artículos SET editionDate = FROM_UNIXTIME (: publishDate), title =: title, summary =: summary, content =: content WHERE id =: id";
    $ st = $ conn-> prepare ($ sql);
    $ st-> bindValue (": publishDate", $ this-> publishDate, PDO :: PARAM_INT);
    $ st-> bindValue (": title", $ this-> title, DOP :: PARAM_STR);
    $ st-> bindValue (": summary", $ this-> summary, DOP :: PARAM_STR);
    $ st-> bindValue (": content", $ this-> content, DOP :: PARAM_STR);
    $ st-> bindValue (": id", $ this-> id, PDO :: PARAM_INT);
    $ st-> execute ();
    $ conn = null;
  }


  /**
  * Borra el objeto Artículo actual de la base de datos.
  */

  función pública eliminar () {

    // ¿Tiene el objeto Article una identificación?
    if (is_null ($ this-> id)) trigger_error ("Article :: delete (): Intento de eliminar un objeto Article que no tiene su propiedad ID establecida"., E_USER_ERROR);

    // Eliminar el artículo
    $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD);
    $ st = $ conn-> prepare ("ELIMINAR DE LOS Artículos DONDE id =: id LIMIT 1");
    $ st-> bindValue (": id", $ this-> id, PDO :: PARAM_INT);
    $ st-> execute ();
    $ conn = null;
  }

}

?>

Este archivo es bastante largo, pero es bastante simple cuando lo desglosas. Echemos un vistazo a cada sección del código:

1. La definición de la clase y las propiedades.

Primero, comenzamos a definir nuestra Articleclase con el código:

clase de artículo
{ 

Todo después de estas líneas de código, hasta la llave de cierre al final del archivo, contiene el código que conforma la Articleclase.

Después de iniciar nuestra definición de clase, declaramos las propiedades de la clase: $id$publicationDatey así sucesivamente. Cada Articleobjeto que creamos almacenará los datos de sus artículos en estas propiedades. Puede ver que los nombres de propiedades reflejan los nombres de los campos en nuestra articlestabla de base de datos.Técnicamente, este tipo de clase, que contiene propiedades que se asignan directamente a los campos correspondientes de la base de datos, así como los métodos para almacenar y recuperar registros de la base de datos, sigue un patrón de diseño orientado a objetos conocido como registro activo .

2. The constructor

A continuación creamos los métodos de clase . Estas son funciones que están vinculadas a la clase, así como a los objetos creados a partir de la clase. Nuestro código principal puede llamar a estos métodos para manipular los datos en los Articleobjetos.

El primer método,, __construct()es el constructor . Este es un método especial al que el motor de PHP llama automáticamente cada vez Articleque se crea un nuevo objeto. Nuestro constructor toma una $datamatriz opcional que contiene los datos para colocarlos en las propiedades del nuevo objeto. Luego rellenamos esas propiedades dentro del cuerpo del constructor. Esto nos brinda una forma práctica de crear y rellenar un objeto de una sola vez.$this->propertyNamesignifica: “La propiedad de este objeto que tiene el nombre“ $propertyName“.

Notará que el método filtra los datos antes de almacenarlos en las propiedades. Las propiedades idpublicationDatese convierten en enteros utilizando (int), ya que estos valores siempre deben ser enteros. Los titlesummaryse filtran usando una expresión regular para permitir solo un cierto rango de caracteres. Es una buena práctica de seguridad filtrar datos en entradas de este tipo, permitiendo solo valores y caracteres aceptables.

contentSin embargo, no filtramos la propiedad. ¿Por qué? Bueno, el administrador probablemente querrá usar una amplia gama de caracteres, así como el marcado HTML, en el contenido del artículo. Si restringiéramos el rango de caracteres permitidos en el contenido, limitaríamos la utilidad del CMS para el administrador.

Normalmente, esto podría ser una laguna de seguridad, ya que un usuario podría insertar JavaScript malicioso y otras cosas desagradables en el contenido. Sin embargo, dado que presumiblemente confiamos en nuestro administrador del sitio, que es la única persona autorizada para crear el contenido, este es un compromiso aceptable en este caso. Si estaba tratando con contenido generado por usuarios, como comentarios o publicaciones en foros, querría tener más cuidado y solo permitir el uso de HTML “seguro”. Una herramienta realmente excelente para esto es el Purificador de HTML , que analiza a fondo la entrada de HTML y elimina todo el código potencialmente malicioso.La seguridad de PHP es un tema importante y está fuera del alcance de este tutorial. Si desea obtener más información, comience con la excelente publicación de Terry Chay, Salida de escape con filtro de entrada: Principio de seguridad y práctica . También vea las entradas de Wikipedia sobre manejo seguro de entrada / salida , XSS , CSRF , inyección SQL y fijación de sesión .

3. storeFormValues()

Nuestro siguiente método,, storeFormValues()es similar al constructor en que almacena una matriz de datos suministrada en las propiedades del objeto. La principal diferencia es que storeFormValues()puede manejar los datos en el formato que se envía a través de nuestros formularios Nuevo artículo y Editar artículo (que crearemos más adelante). En particular, puede manejar las fechas de publicación en el formato YYYY-MM-DD, convirtiendo la fecha en el formato de marca de tiempo UNIX adecuado para almacenar en el objeto.Las marcas de tiempo de UNIX son valores enteros que representan el número de segundos entre la medianoche del 1 de enero de 1970 y la fecha / hora en cuestión. Por lo general, me gusta manejar fechas y horas como marcas de tiempo UNIX en PHP, ya que son fáciles de almacenar y manipular.

El propósito de este método es simplemente facilitar que nuestros scripts de administración almacenen los datos enviados por los formularios. Todo lo que tienen que hacer es llamar storeFormValues(), pasando la matriz de datos de formulario.Todos los miembros (es decir, las propiedades y los métodos) de nuestra Articleclase tienen la publicpalabra clave antes de sus nombres, lo que significa que están disponibles para codificar fuera de la clase. También puede crear privatemiembros (que solo pueden ser utilizados por la propia clase) y protectedmiembros (que pueden ser utilizados por la clase y cualquiera de sus subclases). No te preocupes, estaré cubriendo todo esto en un tutorial posterior.

4. getById()

Ahora llegamos a los métodos que realmente acceden a la base de datos MySQL. El primero de estos, getById()acepta un argumento de ID de artículo ( $id), luego recupera el registro del artículo con ese ID de la articlestabla y lo almacena en un nuevo Articleobjeto.

Generalmente, cuando llama a un método, primero crea o recupera un objeto, luego llama al método en ese objeto. Sin embargo, dado que este método devuelve un nuevo Articleobjeto, sería útil si el código de llamada pudiera llamar al método directamente, y no a través de un objeto existente. De lo contrario, tendríamos que crear un nuevo objeto ficticio cada vez que quisiéramos llamar al método y recuperar un artículo.

Para permitir que nuestro método sea llamado sin necesidad de un objeto, agregamos la staticpalabra clave a la definición del método. Esto permite llamar al método directamente sin especificar un objeto:

  función estática pública getById ($ id) {

El método en sí mismo utiliza PDO para conectarse a la base de datos, recuperar el registro del artículo utilizando una SELECTdeclaración SQL y almacenar los datos del artículo en un nuevo Articleobjeto, que luego se devuelve al código de llamada.PDO (Objetos de datos de PHP): es una biblioteca orientada a objetos integrada en PHP que facilita a los scripts de PHP comunicarse con bases de datos.

Vamos a romper este método:

  1. Conectarse a la base de datos $ conn = nuevo PDO (DB_DSN, DB_USERNAME, DB_PASSWORD); Esto establece una conexión con la base de datos MySQL utilizando los detalles de inicio de sesión del config.phparchivo y almacena el identificador de conexión resultante $conn. Este código lo utiliza el código restante en el método para comunicarse con la base de datos.
  2. Recuperar el registro del artículo. $ sql = “SELECT *, UNIX_TIMESTAMP (publishDate) AS publishDate FROM articles WHERE id =: id”; $ st = $ conn-> prepare ($ sql); $ st-> bindValue (“: id”, $ id, PDO :: PARAM_INT); $ st-> execute (); $ row = $ st-> fetch (); Nuestra SELECTdeclaración recupera todos los campos ( *) del registro en la articlestabla que coincide con el idcampo dado . También recupera el publicationDatecampo en formato de marca de hora UNIX en lugar del formato de fecha de MySQL predeterminado, por lo que podemos almacenarlo fácilmente en nuestro objeto.En lugar de colocar nuestro $idparámetro directamente dentro de la SELECTcadena, lo que puede ser un riesgo de seguridad, en lugar de eso lo usamos :id. Esto se conoce como un marcador de posición . En un minuto, llamaremos un método de DOP para vincular nuestro $idvalor a este marcador de posición.Una vez que hemos almacenado nuestra SELECTdeclaración en una cadena, preparamos la declaración llamando $conn->prepare(), almacenando el identificador resultante en una $stvariable.Las declaraciones preparadas son una característica de la mayoría de las bases de datos; Permiten que las llamadas a su base de datos sean más rápidas y seguras.Ahora vinculamos el valor de nuestra $idvariable, es decir, el ID del artículo que queremos recuperar, a nuestro :idmarcador de posición llamando al bindValue()método. Pasamos el nombre del marcador de posición; el valor para enlazarlo; y el tipo de datos del valor (entero en este caso) para que PDO sepa cómo escapar correctamente del valor.Por último, llamamos execute()para ejecutar la consulta, luego usamos fetch()para recuperar el registro resultante como una matriz asociativa de nombres de campo y valores de campo correspondientes, que almacenamos en la $rowvariable.
  3. Cierra la conexion $ conn = null; Como ya no necesitamos nuestra conexión, la cerramos asignándola nulla la $connvariable. Es una buena idea cerrar las conexiones de la base de datos lo antes posible para liberar memoria en el servidor.
  4. Devuelve el nuevo Articleobjeto if ($ row) devolver nuevo artículo ($ row); } Lo último que debe hacer nuestro método es crear un nuevo Articleobjeto que almacene el registro devuelto por la base de datos y devolver este objeto al código de llamada. Primero verifica que el valor devuelto de la fetch()llamada, $rowde hecho, contiene datos. Si lo hace, entonces crea un nuevo Articleobjeto, pasando $rowcomo lo hace. Recuerde que esto llama a nuestro constructor que creamos anteriormente, que rellena el objeto con los datos contenidos en la $rowmatriz. Luego devolvemos este nuevo objeto, y nuestro trabajo aquí está hecho.

5. getList()

Nuestro siguiente método getList()es similar en muchos aspectos a getById(). La principal diferencia, como puede imaginar, es que puede recuperar muchos artículos a la vez, en lugar de solo 1 artículo. Se utiliza cuando necesitamos mostrar una lista de artículos al usuario o administrador.

getList() acepta un argumento opcional:$numRowsEl número máximo de artículos a recuperar. Tenemos este valor predeterminado en 1,000,000 (es decir, efectivamente todos los artículos). Este parámetro nos permite mostrar, digamos, solo los primeros 5 artículos en la página de inicio del sitio.

Gran parte del código de este método es similar al getById(). Veamos algunas líneas de interés:

    $ sql = "SELECCIONE SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP (publishDate) COMO publishDate DE los artículos 
PEDIDO POR publishDate DESC LIMIT: numRows";

Nuestra consulta es un poco más compleja que la última vez. Primero, note que no hay WHEREcláusula esta vez; esto se debe a que queremos recuperar todos los artículos, en lugar de un artículo que coincida con una ID específica.

También hemos agregado una  LIMITcláusula, pasando el $numRowsparámetro (como un marcador de posición), de modo que opcionalmente podamos limitar el número de registros devueltos.

Finalmente, el valor especial de MySQL SQL_CALC_FOUND_ROWSle dice a MySQL que devuelva el número real de registros devueltos; esta información es útil para mostrarla al usuario, así como para otras cosas como la paginación de resultados.

    $ list = array ();

    while ($ row = $ st-> fetch ()) {
      $ articulo = nuevo articulo ($ fila);
      $ list [] = $ articulo;
    }

Dado que estamos devolviendo varias filas, creamos una matriz,, $listpara contener los Articleobjetos correspondientes . Luego usamos un whilebucle para recuperar la siguiente fila a través de fetch(), crear un nuevo Articleobjeto, almacenar los valores de fila en el objeto y agregar el objeto a la $listmatriz. Cuando no hay más filas, fetch()vuelve falsey el bucle sale.

    // Ahora obtenga el número total de artículos que coincidieron con los criterios
    $ sql = "SELECT FOUND_ROWS () AS totalRows";
    $ totalRows = $ conn-> query ($ sql) -> fetch ();
    $ conn = null;
    return (array ("results" => $ list, "totalRows" => $ totalRows [0]));

Finalmente, ejecutamos otra consulta que usa la MySQL FOUND_ROWS()función para obtener el número de filas devueltas calculadas por nuestro SQL_CALC_FOUND_ROWScomando anterior . Esta vez usamos el query()método PDO , que nos permite ejecutar rápidamente una consulta si no hay ningún marcador de posición para vincular. Llamamos fetch()al controlador de la declaración resultante para recuperar la fila de resultados, luego devolvemos tanto la lista de Articleobjetos ( $list) como el recuento total de filas como una matriz asociativa.

6. insert()

Los métodos restantes en nuestra Articleclase tratan de agregar, cambiar y eliminar registros de artículos en la base de datos.

insert()agrega un nuevo registro de artículo a la articlestabla, utilizando los valores almacenados en el Articleobjeto actual :

  • Primero, el método se asegura de que el objeto aún no tenga su $idpropiedad establecida. Si tiene un ID, entonces el artículo probablemente ya existe en la base de datos, por lo que no debemos intentar insertarlo nuevamente.
  • Luego, el método ejecuta una INSERTconsulta SQL para insertar el registro en la articlestabla, utilizando marcadores de posición para pasar los valores de propiedad a la base de datos. Tenga en cuenta el uso de la FROM_UNIXTIME()función MySQL para convertir la fecha de publicación del formato de marca de hora UNIX nuevamente al formato MySQL.
  • Después de ejecutar la consulta, el método recupera el nuevo ID del registro de artículo mediante la lastInsertId()función PDO y lo almacena en la $idpropiedad del objeto para futuras referencias. Recuerde que configuramos el campo de la articlestabla idcomo un auto_incrementcampo, de modo que MySQL genere una nueva ID única para cada nuevo registro de artículo.

Observe que usamos PDO::PARAM_INTcuando vinculamos valores enteros con marcadores de posición y PDO::PARAM_STRcuando vinculamos valores de cadena. Esto es para que la DOP pueda escapar de los valores adecuadamente.

7. update()

Este método es similar a insert(), excepto que actualiza un registro de artículo existente en la base de datos en lugar de crear un nuevo registro.

Primero verifica que el objeto tenga una ID, ya que no puede actualizar un registro sin conocer su ID. Luego usa la UPDATEinstrucción SQL para actualizar los campos del registro. Observe que pasamos la ID del objeto a la UPDATEdeclaración para que sepa qué registro actualizar.

5. delete()

El delete()método es bastante autoexplicativo. Utiliza la DELETEdeclaración SQL para eliminar el artículo almacenado en el objeto de la articlestabla, utilizando la $idpropiedad del objeto para identificar el registro en la tabla. Por razones de seguridad, agregamos información LIMIT 1a la consulta para asegurarnos de que solo se pueda eliminar un registro de 1 artículo a la vez.

Paso 5: Escribir el index.phpguión de front-end

Ahora hemos creado nuestra Articleclase, que hace el trabajo pesado para nuestro CMS. Ahora que está fuera del camino, el resto del código es bastante simple!

Primero, creemos index.php, el script que controla la visualización de las páginas frontales del sitio. Guarde este archivo en la cmscarpeta que creó anteriormente, al comienzo del Paso 4:

<? php

requiere ("config.php");
$ action = isset ($ _GET ['action'])? $ _GET ['action']: "";

interruptor ($ acción) {
  caso 'archivo':
    archivo();
    descanso;
  caso 'viewArticle':
    viewArticle ();
    descanso;
  defecto:
    página principal();
}


archivo de funciones () {
  $ resultados = matriz ();
  $ data = Artículo :: getList ();
  $ resultados ['artículos'] = $ datos ['resultados'];
  $ resultados ['totalRows'] = $ datos ['totalRows'];
  $ results ['pageTitle'] = "Archivo de artículos | Noticias de widgets";
  require (TEMPLATE_PATH. "/archive.php");
}

función viewArticle () {
  if (! isset ($ _ GET ["articleId"]) ||! $ _ GET ["articleId"]) {
    página principal();
    regreso;
  }

  $ resultados = matriz ();
  $ resultados ['artículo'] = Artículo :: getById ((int) $ _ GET ["articleId"]);
  $ results ['pageTitle'] = $ results ['article'] -> title. "| Widget Noticias";
  require (TEMPLATE_PATH. "/viewArticle.php");
}

función de página de inicio () {
  $ resultados = matriz ();
  $ data = Article :: getList (HOMEPAGE_NUM_ARTICLES);
  $ resultados ['artículos'] = $ datos ['resultados'];
  $ resultados ['totalRows'] = $ datos ['totalRows'];
  $ results ['pageTitle'] = "Widget News";
  require (TEMPLATE_PATH. "/homepage.php");
}

?>

Vamos a desglosar este script:

  1. Incluir el archivo de configuración
    La primera línea de código incluye el config.phparchivo que creamos anteriormente, para que todos los ajustes de configuración estén disponibles para el script. Utilizamos en require()lugar de include()require()genera un error si no se puede encontrar el archivo.
  2. Agarrar el actionparametro
    Almacenamos el $_GET['action']parámetro en una variable llamada $action, de modo que podamos usar el valor más adelante en el script. Antes de hacer esto, verificamos que el $_GET['action']valor exista utilizando isset(). Si no lo hace, configuramos la $actionvariable correspondiente a una cadena vacía ( "").Es una buena práctica de programación verificar que los valores proporcionados por el usuario, como los parámetros de cadena de consulta, los valores de publicación de formularios y las cookies, realmente existan antes de intentar usarlos. No solo limita los agujeros de seguridad, sino que también evita que el motor PHP genere avisos de “índice no definido” a medida que se ejecuta el script.
  3. Decida qué acción realizar
    El switchbloque mira el actionparámetro en la URL para determinar qué acción realizar (mostrar el archivo o ver un artículo). Si no hay ningún actionparámetro en la URL, la secuencia de comandos muestra la página de inicio del sitio.
  4. archive()
    Esta función muestra una lista de todos los artículos en la base de datos. Lo hace llamando al getList()método de la Articleclase que creamos anteriormente. Luego, la función almacena los resultados, junto con el título de la página, en una $resultsmatriz asociativa para que la plantilla pueda mostrarlos en la página. Finalmente, incluye el archivo de plantilla para mostrar la página. (Vamos a crear las plantillas en un momento.)
  5. viewArticle()
    Esta función muestra una sola página de artículo. Recupera el ID del artículo para mostrar desde el articleIdparámetro de URL, luego llama al método de la Articleclase getById()para recuperar el objeto del artículo, que almacena en la $resultsmatriz para que la utilice la plantilla. (Si no articleIdse suministró o no se pudo encontrar el artículo, la función simplemente muestra la página de inicio).Observe que usamos (int)para convertir el valor del articleIDparámetro de consulta en un entero. Esta es una buena medida de seguridad, ya que evita que se pase a nuestro código cualquier cosa que no sean enteros.
  6. homepage()
    Nuestra última función homepage(),, muestra la página de inicio del sitio que contiene una lista de hasta HOMEPAGE_NUM_ARTICLESartículos (5 de forma predeterminada). Es muy similar a la archive()función, excepto que pasa HOMEPAGE_NUM_ARTICLESal getList()método para limitar el número de artículos devueltos.

Paso 6: Escribe el admin.phpscript de back-end

Nuestro script de administración es un poco más complejo index.php, ya que se ocupa de todas las funciones de administración para el CMS. La estructura básica, sin embargo, es similar al index.php.

Guarde este archivo admin.phpen la misma carpeta que su index.phpscript:

<? php

requiere ("config.php");
session_start ();
$ action = isset ($ _GET ['action'])? $ _GET ['action']: "";
$ username = isset ($ _SESSION ['username'])? $ _SESSION ['username']: "";

if ($ action! = "login" && $ action! = "logout" &&! $ username) {
  iniciar sesión();
  salida;
}

interruptor ($ acción) {
  caso 'login':
    iniciar sesión();
    descanso;
  caso 'logout':
    cerrar sesión();
    descanso;
  caso 'newArticle':
    articulo nuevo();
    descanso;
  case 'editArticle':
    editArticle();
    descanso;
  caso 'deleteArticle':
    deleteArticle ();
    descanso;
  defecto:
    listArticles();
}


función login () {

  $ resultados = matriz ();
  $ results ['pageTitle'] = "Admin Login | Widget News";

  if (isset ($ _POST ['login'])) {

    // El usuario ha publicado el formulario de inicio de sesión: intentar iniciar sesión en el usuario

    if ($ _POST ['username'] == ADMIN_USERNAME && $ _POST ['password'] == ADMIN_PASSWORD) {

      // Iniciar sesión correctamente: crear una sesión y redirigir a la página de inicio de administración
      $ _SESSION ['username'] = ADMIN_USERNAME;
      encabezado ("Ubicación: admin.php");

    } else {

      // Error de inicio de sesión: mostrar un mensaje de error al usuario
      $ results ['errorMessage'] = "Nombre de usuario o contraseña incorrectos. Inténtelo de nuevo.";
      require (TEMPLATE_PATH. "/admin/loginForm.php");
    }

  } else {

    // El usuario aún no ha publicado el formulario de inicio de sesión: muestra el formulario
    require (TEMPLATE_PATH. "/admin/loginForm.php");
  }

}


función logout () {
  unset ($ _SESSION ['username']);
  encabezado ("Ubicación: admin.php");
}


función newArticle () {

  $ resultados = matriz ();
  $ results ['pageTitle'] = "Nuevo artículo";
  $ results ['formAction'] = "newArticle";

  if (isset ($ _POST ['saveChanges'])) {

    // El usuario ha publicado el formulario de edición del artículo: guardar el nuevo artículo
    $ artículo = nuevo artículo;
    $ artículo-> storeFormValues ​​($ _POST);
    $ artículo-> insertar ();
    encabezado ("Ubicación: admin.php? status = changesSaved");

  } elseif (isset ($ _POST ['cancel'])) {

    // El usuario ha cancelado sus ediciones: volver a la lista de artículos
    encabezado ("Ubicación: admin.php");
  } else {

    // El usuario aún no ha publicado el formulario de edición de artículo: muestra el formulario
    $ resultados ['artículo'] = nuevo artículo;
    require (TEMPLATE_PATH. "/admin/editArticle.php");
  }

}


function editArticle() {

  $ resultados = matriz ();
  $ results ['pageTitle'] = "Editar artículo";
  $ results ['formAction'] = "editArticle";

  if (isset ($ _POST ['saveChanges'])) {

    // El usuario ha publicado el formulario de edición del artículo: guardar los cambios del artículo

    if (! $ article = Article :: getById ((int) $ _ POST ['articleId'])) {
      header ("Location: admin.php? error = articleNotFound");
      regreso;
    }

    $ artículo-> storeFormValues ​​($ _POST);
    $ artículo-> actualizar ();
    encabezado ("Ubicación: admin.php? status = changesSaved");

  } elseif (isset ($ _POST ['cancel'])) {

    // El usuario ha cancelado sus ediciones: volver a la lista de artículos
    encabezado ("Ubicación: admin.php");
  } else {

    // El usuario aún no ha publicado el formulario de edición de artículo: muestra el formulario
    $ resultados ['artículo'] = Artículo :: getById ((int) $ _ GET ['articleId']);
    require (TEMPLATE_PATH. "/admin/editArticle.php");
  }

}


función deleteArticle () {

  if (! $ article = Article :: getById ((int) $ _ GET ['articleId'])) {
    header ("Location: admin.php? error = articleNotFound");
    regreso;
  }

  $ artículo-> eliminar ();
  header ("Location: admin.php? status = articleDeleted");
}


function listArticles() {
  $ resultados = matriz ();
  $ data = Artículo :: getList ();
  $ resultados ['artículos'] = $ datos ['resultados'];
  $ resultados ['totalRows'] = $ datos ['totalRows'];
  $ results ['pageTitle'] = "Todos los artículos";

  if (isset ($ _GET ['error'])) {
    if ($ _GET ['error'] == "articleNotFound") $ results ['errorMessage'] = "Error: Artículo no encontrado.";
  }

  if (isset ($ _GET ['status'])) {
    if ($ _GET ['status'] == "changesSaved") $ results ['statusMessage'] = "Sus cambios se han guardado.";
    if ($ _GET ['status'] == "articleDeleted") $ results ['statusMessage'] = "Artículo eliminado.";
  }

  require (TEMPLATE_PATH. "/admin/listArticles.php");
}

?>

Veamos algunas secciones interesantes de este script:

  1. Iniciar una sesión de usuario
    Hacia la parte superior del guión que llamamos session_start(). Esta función de PHP inicia una nueva sesión para el usuario, que podemos utilizar para rastrear si el usuario ha iniciado sesión o no. (Si ya existe una sesión para este usuario, PHP la recoge automáticamente y la utiliza).Debido a que las sesiones necesitan cookies para funcionar, y las cookies se envían al navegador antes del contenido, debe llamar session_start()desde la parte superior de la secuencia de comandos, antes de que se genere contenido.
  2. Agarre el actionparámetro y la usernamevariable de sesión
    A continuación, almacenamos el $_GET['action']parámetro en una variable llamada $action, y la $_SESSION['username']variable de sesión $username, para que podamos usar estos valores más adelante en el script. Antes de hacer esto, comprobamos que estos valores existen utilizando isset(). Si un valor no existe, entonces configuramos la variable correspondiente a una cadena vacía ( "").
  3. Compruebe que el usuario haya iniciado sesión
    El usuario no debería tener permitido hacer nada a menos que haya iniciado sesión como administrador. Entonces, lo siguiente que hacemos es inspeccionar $usernamepara ver si la sesión contenía un valor para la usernameclave, que usamos para indicar que el usuario ha iniciado sesión. Si $usernameel valor está vacío, y el usuario aún no está intentando iniciar sesión o fuera – entonces mostramos la página de inicio de sesión y salimos inmediatamente.
  4. Decidir qué acción realizar
    El switchbloque funciona de manera muy parecida a la que se encuentra en index.php: llama a la función apropiada según el valor del actionparámetro de URL. La acción predeterminada es mostrar la lista de artículos en el CMS.
  5. login()
    Esto se llama cuando el usuario necesita iniciar sesión o está en proceso de iniciar sesión.Si el usuario ha enviado el formulario de inicio de sesión, que verificamos buscando el loginparámetro del formulario, la función verifica el nombre de usuario y la contraseña ingresados ​​con los valores de configuración ADMIN_USERNAMEADMIN_PASSWORD. Si coinciden, la usernameclave de la sesión se configura con el nombre de usuario del administrador, al iniciar sesión efectivamente, y luego redirigimos el navegador al admin.phpscript, que luego muestra la lista de artículos. Si el nombre de usuario y la contraseña no coinciden, se vuelve a mostrar el formulario de inicio de sesión con un mensaje de error.Si el usuario aún no ha enviado el formulario de inicio de sesión, la función simplemente muestra el formulario.
  6. logout()
    Esta función se llama cuando el usuario elige cerrar sesión. Simplemente elimina la usernameclave de sesión y redirige a admin.php.
  7. newArticle()Esta función le permite al usuario crear un nuevo artículo. Si el usuario acaba de publicar el formulario de “nuevo artículo”, la función crea un nuevo Articleobjeto, almacena los datos del formulario en el objeto llamando storeFormValues(), inserta el artículo en la base de datos llamando insert()y redirige de nuevo a la lista de artículos, mostrando un ” Mensaje de estado “Cambios guardados”.Si el usuario aún no ha publicado el formulario de “nuevo artículo”, la función crea un nuevo Articleobjeto vacío sin valores, y luego utiliza la editArticle.phpplantilla para mostrar el formulario de edición de artículo utilizando este Articleobjeto vacío .
  8. editArticle()Esta función es similar a newArticle(), excepto que le permite al usuario editar un artículo existente. Cuando el usuario guarda sus cambios, la función recupera el artículo existente utilizando getById(), almacena los nuevos valores en el Articleobjeto y luego guarda el objeto modificado llamando update(). (Si el artículo no se encuentra en la base de datos, la función muestra un error).Cuando se muestra el formulario de edición de artículo, la función utiliza nuevamente el getById()método para cargar los valores de campo del artículo actual en el formulario para editar.Observe que la secuencia de comandos utiliza la misma plantilla ( editArticle.php) tanto para crear artículos nuevos como para editar artículos existentes. Esto significa que solo necesitamos crear un único formulario HTML. El formActionparámetro se utiliza para determinar si el usuario está agregando o editando un artículo.
  9. deleteArticle()
    Si el usuario ha elegido eliminar un artículo, esta función primero recupera el artículo que se va a eliminar (mostrando un error si el artículo no se pudo encontrar en la base de datos), luego llama al delete()método del artículo para eliminar el artículo de la base de datos. Luego, redirige a la página de la lista de artículos, mostrando un mensaje de estado de “artículo eliminado”.
  10. listArticles()
    La última función en admin.phpmuestra una lista de todos los artículos en el CMS para editar. La función utiliza el método de la Articleclase getList()para recuperar todos los artículos, luego usa la listArticles.phpplantilla para mostrar la lista. A lo largo del camino, también verifica los parámetros de consulta de URL errorstatuspara ver si es necesario mostrar algún error o mensaje de estado en la página. Si es así, crea el mensaje necesario y lo pasa a la plantilla para su visualización.

Paso 7: Crea las plantillas front-end

Ahora hemos creado todo el código PHP para la funcionalidad de nuestro CMS. El siguiente paso es crear las plantillas HTML para las páginas front-end y admin.

Primero, las plantillas front-end.

1. Los archivos incluidos.

Crea una carpeta llamada templatesdentro de tu cmscarpeta. Ahora crea una carpeta llamada includedentro de la templatescarpeta. En esta carpeta, vamos a colocar el encabezado y el pie de página que es común a cada página del sitio, para evitar tener que colocarlo dentro de cada archivo de plantilla.

Cree un nuevo archivo llamado header.phpdentro de su includecarpeta, con el siguiente código:

<! DOCTYPE html>
<html lang = "en">
  <head>
    <title> <? php echo htmlspecialchars ($ results ['pageTitle'])?> </title>
    <link rel = "hoja de estilo" type = "text / css" href = "style.css" />
  </head>
  <body>
    <div id = "container">

      <a href="."> <img id = "logo" src = "images / logo.jpg" alt = "Widget News" /> </a>

Como puede ver, este código simplemente muestra el marcado para iniciar la página HTML. Utiliza la $results['pageTitle']variable pasada desde el script principal ( index.phpadmin.php) para establecer el titleelemento, y también enlaza a una hoja de estilo style.css(crearemos esto en un momento).Observe que hemos pasado el valor de a $results['pageTitle']través de la función PHP htmlspecialchars(). Esta función codifica los caracteres HTML especiales, como por ejemplo <>&, en sus equivalentes de entidades HTML ( &lt;&gt;&amp;). Junto con la entrada de filtrado, que hicimos cuando escribimos el Articleconstructor en el Paso 4, la codificación de la salida es un buen hábito de seguridad en el que entrar. Codificaremos la mayoría de los datos en nuestras plantillas de esta manera.

A continuación, crea un archivo llamado footer.phpen la misma carpeta:

      <div id = "footer">
        Widget Noticias y copia; 2011. Todos los derechos reservados. <a href="admin.php"> Administrador del sitio </a>
      </div>

    </div>
  </body>
</html>

Este marcado termina cada página HTML en el sistema.

2. homepage.php

Ahora vuelva a la templatescarpeta y cree un homepage.phparchivo de plantilla allí, con el siguiente código:

<? php incluye "templates / include / header.php"?>

      <ul id = "titulares">

<? php foreach ($ resultados ['artículos'] como $ artículo) {?>

        <li>
          <h2>
            <span class = "pubDate"> <? php echo date ('j F', $ article-> publishDate)?> </span> <a href = ".? action = viewArticle & amp; articleId = <? php echo $ article -> id?> "> <? php echo htmlspecialchars ($ article-> title)?> </a>
          </h2>
          <p class = "summary"> <? php echo htmlspecialchars ($ article-> summary)?> </p>
        </li>

<? php}?>

      </ul>

      <p> <a href="./?action=archive"> Archivo de artículos </a> </p>

<? php incluye "templates / include / footer.php"?>

Esta plantilla muestra los títulos de los artículos en la página de inicio como una lista sin ordenar. Recorre la matriz de Articleobjetos almacenados $results['articles']y muestra la fecha de publicación, el título y el resumen de cada artículo. El título está vinculado de nuevo a '.'index.php), pasando action=viewArticle, así como la ID del artículo, en la URL. Esto permite al visitante leer un artículo haciendo clic en su título.

La plantilla también incluye un enlace al archivo de artículos ( "./?action=archive").Tenga en cuenta que esta plantilla, así como las siguientes, usan la includedeclaración de PHP para incluir los archivos de encabezado y pie de página en la página.

3. archive.php

Ahora crea un archive.phparchivo de plantilla en tu templatescarpeta:

<? php incluye "templates / include / header.php"?>

      <h1> Archivo de artículos </h1>

      <ul id = "headlines" class = "archive">

<? php foreach ($ resultados ['artículos'] como $ artículo) {?>

        <li>
          <h2>
            <span class = "pubDate"> <? php echo date ('j F Y', $ article-> publishDate)?> </span> <a href = ".? action = viewArticle & amp; articleId = <? php echo $ article-> id?> "> <? php echo htmlspecialchars ($ article-> title)?> </a>
          </h2>
          <p class = "summary"> <? php echo htmlspecialchars ($ article-> summary)?> </p>
        </li>

<? php}?>

      </ul>

      <p> <? php echo $ results ['totalRows']?> article <? php echo ($ results ['totalRows']! = 1)? 's': ''?> en total. </p>

      <p> <a href="./"> Volver a la página de inicio </a> </p>

<? php incluye "templates / include / footer.php"?>

Esta plantilla muestra el archivo de todos los artículos en el CMS. Como se puede ver, es casi idéntico al homepage.php. Agrega una archiveclase de CSS a la lista desordenada para que podamos diseñar los elementos de la lista de manera un poco diferente a la página de inicio, y también agrega el año a las fechas de publicación del artículo (ya que el archivo puede tener una antigüedad de algunos años).

La página también incluye un recuento total de los artículos en la base de datos, recuperados a través de $results['totalRows']. Finalmente, en lugar del enlace de archivo en la parte inferior de la página, incluye un enlace “Volver a la página de inicio”.

4. viewArticle.php

La última plantilla de front-end muestra un artículo para el usuario. Cree un archivo llamado viewArticle.phpen su templatescarpeta y agregue el siguiente marcado:

<? php incluye "templates / include / header.php"?>

      <h1 style = "width: 75%;"> <? php echo htmlspecialchars ($ results ['article'] -> title)?> </h1>
      <div style = "width: 75%; font-style: italic;"> <? php echo htmlspecialchars ($ results ['article'] -> summary)?> </div>
      <div style = "width: 75%;"> <? php echo $ results ['article'] -> content?> </div>
      <p class = "pubDate"> Publicado en <? php echo date ('j F Y', $ resultados ['artículo'] -> fecha de publicación)?> </p>

      <p> <a href="./"> Volver a la página de inicio </a> </p>

<? php incluye "templates / include / footer.php"?>

Esta plantilla es muy sencilla. Muestra el título, resumen y contenido del artículo seleccionado, así como su fecha de publicación y un enlace para volver a la página de inicio.Usted puede haber notado que no hemos pasado $results['article']->contenta través htmlspecialchars(). Como se explicó cuando creamos el Articleconstructor en el Paso 4, el administrador probablemente querrá usar el marcado HTML, como las <p>etiquetas, en el contenido del artículo. Si codificamos el contenido, las <p>etiquetas aparecerán en la página como <p>, en lugar de crear párrafos.

Paso 8: Crea las plantillas de back-end

Ahora que hemos creado las plantillas para la parte delantera del sitio, es hora de crear las 3 plantillas de administración.

1. loginForm.php

Primero, crea otra carpeta llamada admindentro de tu templatescarpeta. Dentro de la admincarpeta, crear la primera de las 3 plantillas, loginForm.php:

<? php incluye "templates / include / header.php"?>

      <form action = "admin.php? action = login" method = "post" style = "width: 50%;">
        <input type = "hidden" name = "login" value = "true" />

<? php if (isset ($ resultados ['errorMessage'])) {?>
        <div class = "errorMessage"> <? php echo $ results ['errorMessage']?> </div>
<? php}?>

        <ul>

          <li>
            <label for = "username"> Nombre de usuario </label>
            <input type = "text" name = "username" id = "username" placeholder = "Tu nombre de usuario de administrador" requiere autofocus maxlength = "20" />
          </li>

          <li>
            <label for = "password"> Password </label>
            <input type = "password" name = "password" id = "password" placeholder = "Tu contraseña de administrador" requerido maxlength = "20" />
          </li>

        </ul>

        <div class = "buttons">
          <input type = "submit" name = "login" value = "Login" />
        </div>

      </form>

<? php incluye "templates / include / footer.php"?>

Esta página contiene el formulario de inicio de sesión de administrador, que publica de nuevo en admin.php?action=login. Incluye un campo oculto login, que nuestra login()función del Paso 6 utiliza para verificar si el formulario se ha publicado. El formulario también contiene un área para mostrar cualquier mensaje de error (como un nombre de usuario o contraseña incorrectos), así como los campos de nombre de usuario y contraseña y el botón “Iniciar sesión”.Hemos utilizado algunas características de formulario HTML 5 como placeholderrequiredautofocusdateen nuestras formas de administración. Esto hace que los formularios sean más fáciles de usar y también nos evita tener que verificar los campos requeridos en nuestro código PHP. Dado que no todos los navegadores admiten actualmente estas funciones de formulario HTML5, es probable que desee utilizar los recursos alternativos de JavaScript y / o PHP para verificar los campos requeridos en un sistema de producción.

2. listArticles.php

Ahora crea la segunda plantilla de administrador en tu admincarpeta. Este se llama listArticles.php:

<? php incluye "templates / include / header.php"?>

      <div id = "adminHeader">
        <h2> Widget News Admin </h2>
        <p> Has iniciado sesión como <b> <? php echo htmlspecialchars ($ _SESSION ['username'])?> </b>. <a href="admin.php?action=logout"?> Cerrar sesión </a> </p>
      </div>

      <h1> Todos los artículos </h1>

<? php if (isset ($ resultados ['errorMessage'])) {?>
        <div class = "errorMessage"> <? php echo $ results ['errorMessage']?> </div>
<? php}?>


<? php if (isset ($ results ['statusMessage'])) {?>
        <div class = "statusMessage"> <? php echo $ results ['statusMessage']?> </div>
<? php}?>

      <table>
        <tr>
          <th> Fecha de publicación </th>
          <th> Artículo </th>
        </tr>

<? php foreach ($ resultados ['artículos'] como $ artículo) {?>

        <tr onclick = "location = 'admin.php? action = editArticle & amp; articleId = <? php echo $ article-> id?>'">
          <td> <? php echo date ('j M Y', $ article-> publishDate)?> </td>
          <td>
            <? php echo $ article-> title?>
          </td>
        </tr>

<? php}?>

      </table>

      <p> <? php echo $ results ['totalRows']?> article <? php echo ($ results ['totalRows']! = 1)? 's': ''?> en total. </p>

      <p> <a href="admin.php?action=newArticle"> Agregar un nuevo artículo </a> </p>

<? php incluye "templates / include / footer.php"?>

Esta plantilla muestra la lista de artículos para que el administrador los edite. Después de mostrar cualquier error o mensaje de estado, recorre la matriz de Articleobjetos almacenados $results['articles'], mostrando la fecha de publicación de cada artículo y el título en una fila de la tabla. También agrega un onclickevento de JavaScript a la fila de la tabla de cada artículo, para que el administrador pueda hacer clic en un artículo para editarlo.

La plantilla también incluye el número total de artículos, así como un enlace para permitir que el administrador agregue un nuevo artículo.

3. editArticle.php

Ahora guarda la plantilla final editArticle.php, en tu admincarpeta:

<? php incluye "templates / include / header.php"?>

      <div id = "adminHeader">
        <h2> Widget News Admin </h2>
        <p> Has iniciado sesión como <b> <? php echo htmlspecialchars ($ _SESSION ['username'])?> </b>. <a href="admin.php?action=logout"?> Cerrar sesión </a> </p>
      </div>

      <h1> <? php echo $ results ['pageTitle']?> </h1>

      <form action = "admin.php? action = <? php echo $ results ['formAction']?>" method = "post">
        <input type = "hidden" name = "articleId" value = "<? php echo $ results ['article'] -> id?>" />

<? php if (isset ($ resultados ['errorMessage'])) {?>
        <div class = "errorMessage"> <? php echo $ results ['errorMessage']?> </div>
<? php}?>

        <ul>

          <li>
            <label for = "title"> Título del artículo </label>
            <input type = "text" name = "title" id = "title" placeholder = "Nombre del artículo" requerido autofocus maxlength = "255" value = "<? php echo htmlspecialchars ($ results ['article'] -> título)?> "/>
          </li>

          <li>
            <label for = "summary"> Resumen del artículo </label>
            <textarea name = "summary" id = "summary" placeholder = "Breve descripción del artículo" required maxlength = "1000" style = "height: 5em;"> <? php echo htmlspecialchars ($ results ['article'] - > resumen)?> </textarea>
          </li>

          <li>
            <label for = "content"> Contenido del artículo </label>
            <textarea name = "content" id = "content" placeholder = "El contenido HTML del artículo" required maxlength = "100000" style = "height: 30em;"> <? php echo htmlspecialchars ($ results ['article'] -> contenido)?> </textarea>
          </li>

          <li>
            <label for = "publishDate"> Fecha de publicación </label>
            <input type = "date" name = "publishDate" id = "publishDate" placeholder = "YYYY-MM-DD" requerido maxlength = "10" value = "<? php echo $ results ['article'] -> publishDate? fecha ("Ymd", $ resultados ['artículo'] -> fecha de publicación): ""?> "/>
          </li>


        </ul>

        <div class = "buttons">
          <input type = "submit" name = "saveChanges" value = "Save Changes" />
          <input type = "submit" formnovalidate name = "cancel" value = "Cancel" />
        </div>

      </form>

<? php if ($ resultados ['artículo'] -> id) {?>
      <p> <a href="admin.php?action=deleteArticle&articleId=<php echo $results['article'◆-> id?> "onclick =" return confirm ('Delete this Article?') "> Eliminar este artículo </a> </p>
<? php}?>

<? php incluye "templates / include / footer.php"?>

Este formulario de edición se utiliza tanto para crear nuevos artículos como para editar artículos existentes. Publica a cualquiera admin.php?action=newArticleadmin.php?action=editArticle, dependiendo del valor pasado en la $results['formAction']variable. También contiene un campo oculto,, articleIdpara rastrear el ID del artículo que se está editando (si existe).

El formulario también incluye un área para mensajes de error, así como campos para el título del artículo, el resumen, el contenido y la fecha de publicación. Finalmente, hay 2 botones para guardar y cancelar cambios, y un enlace para permitir que el administrador elimine el artículo editado actualmente.Como de costumbre, pasamos todos los datos htmlspecialchars()antes de enviarlos en el marcado. No solo es un buen hábito de seguridad, sino que también garantiza que nuestros valores de campo de formulario se escapen correctamente. Por ejemplo, si el titlevalor del campo contenía una comilla doble ( ") que no se escapó, el título se truncaría, ya que las comillas dobles se usan para delimitar el valor del campo en el marcado.Tenga en cuenta el uso del formnovalidateatributo HTML5 en el botón “Cancelar”. Este atributo práctico le indica al navegador que no valide el formulario si el usuario presiona “Cancelar”.

Paso 9: Crea la hoja de estilo y la imagen del logo.

Básicamente, nuestra aplicación de CMS ya está lista, pero para que se vea un poco mejor tanto para nuestros visitantes como para el administrador del sitio, crearemos un archivo CSS para controlar el aspecto del sitio. Guarde este archivo como style.cssen su cmscarpeta:

/ * Estilo del cuerpo y contenedor exterior * /

cuerpo {
  margen: 0;
  color: #333;
  color de fondo: # 00a0b0;
  Familia tipográfica: "Trebuchet MS", Arial, Helvetica, sans-serif;
  altura de la línea: 1.5em;
}

#envase {
  ancho: 960px;
  fondo: #fff;
  margen: 20px auto;
  relleno: 20px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  radio del borde: 5px;
}


/ * El logo y pie de página * /

#logo {
  bloqueo de pantalla;
  ancho: 300px;
  relleno: 0 660px 20px 0;
  frontera: ninguna;
  borde inferior: 1px sólido # 00a0b0;
  margen inferior: 40px;
}

# pie de página {
  borde superior: 1px sólido # 00a0b0;
  margen superior: 40px;
  relleno: 20px 0 0 0;
  tamaño de letra: .8em;
}


/ * Encabezados * /

h1 {
  color: #eb6841;
  margen inferior: 30px;
  altura de la línea: 1.2em;
}

h2, h2 a {
  color: #edc951;
}

h2 a {
  texto-decoración: ninguno;
}


/ * Titulares de artículos * /

# encabezados {
  estilo de lista: ninguno;
  relleno-izquierda: 0;
  ancho: 75%;
}

#headlines li {
  margen inferior: 2em;
}

.pubDate {
  tamaño de letra: .8em;
  color: #eb6841;
  transformar texto: mayúsculas;
}

#headlines .pubDate {
  pantalla: bloque en línea;
  ancho: 100px;
  tamaño de fuente: .5em;
  alineación vertical: medio;
}

# headlines.archive .pubDate {
  ancho: 130px;
}

.resumen {
  relleno-izquierda: 100px;
}

# headlines.archive .summary {
  relleno-izquierda: 130px;
}


/ * "Has iniciado sesión ..." encabezado en las páginas de administrador * /

#adminHeader {
  ancho: 940px;
  relleno: 0 10px;
  borde inferior: 1px sólido # 00a0b0;
  margen: -30px 0 40px 0;
  tamaño de letra: 0.8em;
}


/ * Estilo de la forma con un fondo de color, junto con esquinas curvas y una sombra paralela * /

forma {
  margen: 20px auto;
  relleno: 40px 20px;
  desbordamiento: auto;
  fondo: # fff4cf;
  borde: 1px sólido # 666;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;  
  radio del borde: 5px;
  -moz-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  cuadro de sombra: 0 0 .5em rgba (0, 0, 0, .8);
}


/ * Da a los elementos del formulario un margen consistente, relleno y altura de línea * /

forma ul {
  estilo de lista: ninguno;
  margen: 0;
  relleno: 0;
}

forma ul li {
  margen: .9em 0 0 0;
  relleno: 0;
}

forma * {
  línea altura: 1em;
}


/ * Las etiquetas de campo * /

etiqueta {
  bloqueo de pantalla;
  flotador izquierdo;
  claro: izquierda;
  text-align: right;
  ancho: 15%;
  relleno: .4em 0 0 0;
  margen: .15em .5em 0 0;
}


/* Los campos */

input, select, textarea {
  bloqueo de pantalla;
  margen: 0;
  relleno: .4em;
  ancho: 80%;
}

input, textarea, .date {
  borde: 2px sólido # 666;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;    
  radio del borde: 5px;
  fondo: #fff;
}

entrada {
  tamaño de letra: .9em;
}

seleccione {
  relleno: 0;
  margen inferior: 2.5em;
  posición: relativa;
  arriba: .7em;
}

área de texto
  Familia tipográfica: "Trebuchet MS", Arial, Helvetica, sans-serif;
  tamaño de letra: .9em;
  altura: 5em;
  altura de la línea: 1.5em;
}

textarea # contenido {
  font-family: "Courier New", servicio de mensajería, arreglado;
}
  

/ * Coloque un borde alrededor de los campos enfocados * /

forma *: enfoque {
  frontera: 2px sólido # 7c412b;
  esquema: ninguno;
}


/ * Mostrar los campos correctamente rellenados con un fondo verde * /

entrada: válido, área de texto: válido {
  fondo: #efe;
}


/ * Botones de envío * /

botones
  text-align: center;
  margen: 40px 0 0 0;
}

entrada [type = "submit"] {
  pantalla: en línea;
  margen: 0 20px;
  ancho: 12em;
  relleno: 10px;
  frontera: 2px sólido # 7c412b;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;  
  radio del borde: 5px;
  -moz-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  cuadro de sombra: 0 0 .5em rgba (0, 0, 0, .8);
  color: #fff;
  fondo: # ef7d50;
  font-weight: negrita;
  -webkit-apariencia: ninguno;
}

input [type = "submit"]: hover, input [type = "submit"]: active {
  cursor: pointer;
  fondo: #fff;
  color: #ef7d50;
}

entrada [type = "submit"]: active {
  fondo: #eee;
  -moz-box-shadow: 0 0 .5em rgba (0, 0, 0, .8) inserto;
  -webkit-box-shadow: 0 0 .5em rgba (0, 0, 0, .8) inserto;
  cuadro de sombra: 0 0 .5em rgba (0, 0, 0, .8) inserción;
}


/* Mesas */

mesa {
  ancho: 100%;
  colapso de la frontera: colapso;
}

Tr, th, td
  relleno: 10px;
  margen: 0;
  text-align: left;
}

mesa, th
  borde: 1px sólido # 00a0b0;
}

th {
  borde izquierdo: ninguno;
  borde derecho: ninguno;
  fondo: # ef7d50;
  color: #fff;
  cursor: por defecto;
}

tr: nth-child (impar) {
  fondo: # fff4cf;
}

tr: nth-niño (par) {
  fondo: #fff;
}

tr: hover {
  fondo: #ddd;
  cursor: pointer;
}


/ * Casillas de estado y error * /

.statusMessage, .errorMessage {
  tamaño de letra: .8em;
  relleno: .5em;
  margen: 2em 0;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  radio del borde: 5px; 
  -moz-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
  -box-shadow: 0 0 .5em rgba (0, 0, 0, .8);
}

.mensaje de estado {
  color de fondo: # 2b2;
  borde: 1px sólido # 080;
  color: #fff;
}

.mensaje de error {
  color de fondo: # f22;
  borde: 1px sólido # 800;
  color: #fff;
}

No entraré en los detalles del CSS, ¡ya que este tutorial es sobre PHP y MySQL! Basta con decir que estiliza cosas como el diseño de la página, colores, fuentes, formularios, tablas, etc.

Por último, pero no menos importante, nuestro sitio necesita un logotipo. Aquí hay uno que preparé anteriormente: guárdelo en una imagescarpeta dentro de su cmscarpeta, llamándolo logo.jpg(o enrolle su propio logotipo)

¡Todo listo!

Hemos terminado nuestro CMS! Para probarlo, abra un navegador y apúntelo a la URL base de su CMS (por ejemplo, http://localhost/cms/). Haga clic en el enlace em> Site Admin en el pie de página, inicie sesión y agregue algunos artículos. Luego intente buscarlos en la parte delantera (haga clic en el logotipo para volver a la página de inicio).

¡No olvides que también puedes probar la demo en mi servidor!

En este tutorial, ha construido un sistema básico de gestión de contenido desde cero, utilizando PHP y MySQL. Ha aprendido sobre MySQL, tablas, tipos de campos, PDO, programación orientada a objetos, plantillas, seguridad, sesiones y mucho más.

Si bien este CMS es bastante básico, es de esperar que le haya brindado un punto de partida para crear sus propios sitios web basados ​​en CMS. Algunas características que quizás desee agregar incluyen:

Espero que hayas disfrutado de este tutorial y te haya resultado útil. ¡Feliz codificación!