PHP CRUD Generator - Dokumentation

PHPCG ist ein CRUD-Generator, der in reinem PHP entwickelt wurde, um Ihr komplettes Bootstrap-Administrationspanel mit einer visuellen Benutzeroberfläche zu erstellen.

PHPCG eignet sich sowohl für Nicht-Programmierer als auch für fortgeschrittene PHP-Programmierer, die Zugang zu sauberem und gut organisiertem Code haben, so dass sie sein ganzes Potenzial ausschöpfen können.

PHPCG ist in der Lage, Ihre Datenbank zu analysieren und Tabellen, Felder und jede Art von Beziehung auf intelligente Weise zu extrahieren.

Die CRUD-Engine basiert auf PHP PDO und bietet volle Unterstützung für die folgenden relationalen Datenbankmanagementsysteme (RDBMS):

  • MySQL

    MySQL
  • MariaDB

    MariaDB
  • Oracle

    Oracle
  • Feuervogel

    Firebird
  • PostgreSQL

    PostgreSQL

Sie können dann die analysierten Daten aus der Datenbank verwenden, um Ihr Benutzerverwaltungspanel zu erstellen:

  • Paginierte Listen (CRUD READ)
    • Datensatzfilterung, auch mit internen/externen relationalen Tabellen
    • Sortieren
    • Erweiterte Vor-Ort-Bearbeitung (Text, Auswahl, Boolesche Verknüpfung, Datum und Uhrzeit,...)
    • verschachtelte Tabellen
    • externe Datentabellen (externe Beziehungen)
    • Export in die Formate Excel/CSV/PDF/PRINT
  • Formulare erstellen und aktualisieren (CRUD CREATE/UPDATE)
    • Formulare, die mit PHP Form Builder erstellt wurden, der für seine Robustheit und Zuverlässigkeit bekannt ist
    • Alle Arten von Feldern
    • Automatische Integration der besten jQuery-Plugins zur Verbesserung der Benutzerfreundlichkeit (benutzerdefinierte Auswahl & Radio, Rich-Text-Editor, Dateiuploader, Picker, ...)
    • Dropdown-Listen mit intelligentem und anpassbarem Inhalt
    • Hochladen von Dateien/Bildern mit Zuschneiden/Größenänderung und anpassbarer Miniaturbilderstellung
    • automatische Validierung je nach Art der anpassbaren Daten
    • effizientes und übersichtliches Layout - Möglichkeit, Felder in 2 und 3 Spalten zu gruppieren
  • Formulare löschen (CRUD DELETE)
    • Kaskadierendes Löschen von abhängigen Datensätzen
    • Anzeige der Anzahl der abhängigen Datensätze, die gelöscht werden

Schnelle Tipps

Hier ist ein kurzes Präsentationsvideo, das zeigt, wie PHP CRUD Generator von A bis Z funktioniert.

Um Ihre relationale Datenbank zu erstellen, empfehlen wir Ihnen einen Blick auf:

Anforderungen

Server Anforderungen

  • PHP 7.4+ mit aktiviertem PDO-Treiber, mb_string und curl-Erweiterungen.
    Die PHP curl-Erweiterung muss berechtigt sein, Anfragen an den Lizenzierungsserver zu senden.
  • Apache-Server mit aktiviertem Rewrite-Modul
    ODER
    NGINX | Microsoft IIS Server mit Rewrite-Regeln in Ihrer Konfigurationsdatei
  • Eine MySQL, MariaDB, Oracle, Firebird oder PostgreSQL Datenbank
  • Die Zeichensätze von PHP und der Datenbank müssen auf "utf8" oder Variationen von utf8 (utf8_general_ci, utf8mb4, ...) eingestellt sein.

Struktur der Datenbank

Benennung von Tabellen und Feldern

Ihre Tabellen und Felder müssen diese Standards einhalten:

  • Tabellen- und Feldnamen dürfen nur die folgenden Zeichen enthalten:
    • Klein-/Großbuchstaben
    • Unterstriche
    • Ziffern
  • Ein Tabellenname oder ein Feldname darf nicht mit einer Zahl beginnen.
  • Die Feldtypen müssen mit den Datentypen, die sie enthalten, korrekt eingestellt sein.

Weitere Informationen finden Sie auf der Seite Datenbankanforderungen und bewährte Verfahren.

Struktur des Pakets

Legende:

*
Erforderlich auf Ihrem Produktionsserver

Schnellstart

Über lokale & Produktionsabläufe

Der Ordner "generator" wird nur benötigt, um Ihr Bootstrap-Adminpanel zu erstellen und zu bearbeiten.


  • Wenn Sie einen lokalen Server + einen Remote-Server ("Produktionsserver") verwenden:
    Sie können entweder:
    • den lokalen Generator verwenden, um Ihr Admin-Panel zu erstellen, und dann die Ordner "admin" und "conf" auf Ihren entfernten Server hochladen.
    • das Admin-Panel direkt von Ihrem Produktionsserver aus erstellen.
    Die 2. Möglichkeit ist die empfohlene Methode.
  • Wenn Sie keinen lokalen Server verwenden:
    Laden Sie alle erforderlichen Ordner hoch und erstellen Sie Ihr CRUD-Admin-Dashboard direkt vom Remote-Generator aus.

Weitere Informationen finden Sie in der Anleitung zu lokalen/entfernten Servern.


Installationsvorgang Öffnen Sie das Tutorial

  1. 1

    Laden Sie die erforderlichen* Ordner auf Ihren Server hoch, wie im Abschnitt"Paketstruktur" beschrieben.

    Wenn Sie PHP CRUD Generator in einem Unterordner installieren

    (mit anderen Worten, wenn sich die Ordner admin, class, conf, generator, ... nicht im Stammverzeichnis Ihres Projekts befinden),
    müssen Sie admin/.htaccess bearbeiten, um 404-Fehler zu vermeiden:

    1. Öffnen Sie /admin/.htaccess in Ihrem Code-Editor
    2. fügen Sie Ihren Unterordner in die RewriteRule ein.
      Zum Beispiel: RewriteRule . /ihr-ordner/admin/index.php [QSA,L]
  2. 2Öffnen Sie das Installationsprogramm - install/index.php - in Ihrem Browser.
    Wenn Sie einen lokalen Server + einen Remote-Server verwenden, müssen Sie das Installationsprogramm auf beiden ausführen.
    Weitere Informationen finden Sie im Abschnitt Installation/Registrierung

    Sie müssen Ihre Datenbankverbindungseinstellungen für localhost oder Produktionsserver + wichtige allgemeine Informationen eingeben.
  3. 3 Jetzt ist alles bereit, um Ihr Admin-Panel mit dem CRUD-Generator zu erstellen.
    Öffnen Sie den Generator - generator/generator.php - in Ihrem Browser
    .

Installation/Registrierung

Wenn Sie einen lokalen Server + einen Remote-Server verwenden, müssen Sie das Installationsprogramm auf beiden ausführen.

Was macht das Installationsprogramm?

Das Installationsprogramm:

  • Prüft die Kompatibilität Ihres Servers (PHP-Version, verfügbare Module, weiße Rechte, ...)
  • Testen & registrieren Sie Ihre Datenbankverbindungsnachweise
  • Prüft & registriert Ihre Lizenz
  • Erzeugt eine MySQL-Tabelle mit Ihren Lizenzeinstellungen

Wenn Sie einen Fehler feststellen

Wie bei jedem PHP-Programm können bei der Installation oder beim ersten Start Probleme auftreten.
Das bedeutet nicht, dass das Programm kaputt ist, sondern dass Sie Ihren Server richtig konfigurieren müssen, um es verwenden zu können.

Wenn es passieren sollte:

  1. Prüfen Sie zunächst, ob Ihr Server die Anforderungen erfüllt (PHP-Version, Rewriting aktiviert, ...)
  2. Alle häufigen Fehler und deren Behebung werden im Hilfe-Center erklärt.

Wenn Sie noch Hilfe benötigen, lesen Sie bitte diese Hinweise und kontaktieren Sie uns.

Wenn Sie nach Lösungen für bestimmte Fragen suchen:

Auf der Seite Tutorials finden Sie viele Schritt-für-Schritt-Anleitungen in schriftlicher Form oder als Video, die zum Beispiel die häufigsten Situationen beantworten:

Konfiguration

CORE-Konfiguration

Ändern Sie hier nichts, wenn Sie nicht wissen, was Sie tun.

USER-Konfiguration (Allgemeine Einstellungen)

Diese Datei enthält einige globale Einstellungen, die angepasst werden können

Um diese Einstellungen zu ändern, öffnen Sie den Generator in Ihrem Browser und klicken Sie auf die Registerkarte Konfiguration.

Projekt

Name der Website
Der Name Ihres Projekts, der in der Kopfzeile des Admin-Dashboards angezeigt wird.
Verwaltungslogo
Ihr Projektlogo wird in der Kopfzeile des Admin-Dashboards angezeigt.

Sicherheit

Sperren Sie den Generator
Ermöglicht es, den Zugriff auf den Generator zu sperren/freizugeben.
Wenn der Generator gesperrt ist, ist der Zugang durch eine Identifikationsseite geschützt. Sie müssen Ihre E-Mail-Adresse und Ihren Kaufcode eingeben, um Zugang zu erhalten.

Fehlersuche

Anzeige der Datenbankfehler
Wählen Sie "Ja", um die Details anzuzeigen, wenn bei einer Datenbankabfrage ein Fehler auftritt.
Simulieren und debuggen
Wenn diese Option aktiviert ist, werden alle Abfragen zum Einfügen/Aktualisieren/Löschen simuliert (NICHT ausgeführt), und die Details aller Datenbankabfragen werden auf dem Bildschirm angezeigt.

Überwachung von PHP-Fehlern

Aktivieren Sie die PHP ERRORS Überwachung
wählen Sie "Ja", um die Überwachung von PHP-Fehlern zu aktivieren und jedes Mal eine E-Mail zu erhalten, wenn ein Fehler im Admin-Panel auftritt.
FEHLERSTAND
Es sind 3 Fehlerstufen verfügbar: NOTICE + WARNING + FATAL|UNBEKANNTER FEHLER, WARNING + FATAL|UNBEKANNTER FEHLER, FATAL|UNBEKANNTER FEHLER.
E-Mail
Die E-Mail-Adresse(n), an die die Fehler gesendet werden sollen.

Stile

Bootstrap Thema
Wählen Sie in der Dropdown-Liste eines der vielen verfügbaren Themen aus.
Navbar-Stil
Stil des oberen Navigationsmenüs des Admin-Dashboards.
Stil der Seitenleiste
Stil der Seitenleiste des Admin-Dashboards.
CSS-Klasse der gefilterten Spalten
Im Admin-Dashboard werden die gefilterten Spalten mit einem bestimmten Stil Ihrer Wahl markiert. Hier können Sie eine Bootstrap-CSS-Klasse eingeben und die Deckkraft anpassen. Zum Beispiel: bg-danger bg-opacity-10
Datum & Uhrzeit Picker Stil
Stil der Datums- und Zeitauswahl ("Standard" oder "Material Design").
Standard-Schaltflächenklasse
Bootstrap CSS-Klasse für die sekundären Schaltflächen des Admin-Panels. Zum Beispiel: text-bg-light
Standardhintergrund für Tabellenüberschriften
Bootstrap CSS-Klasse für die Tabellenüberschriften des Admin-Panels. Zum Beispiel: text-bg-dark

Benutzeroberfläche (UI)

Position der Admin ACTION-Schaltflächen
Wählen Sie, ob Sie die Schaltflächen zur Datensatzbearbeitung (Anzeigen/Bearbeiten/Löschen) links oder rechts in den administrativen READ-Listen anzeigen möchten.
Aktivieren Sie die Möglichkeit, Stile über das Admin-Dashboard zu ändern
Wenn diese Funktion aktiviert ist, kann jeder Benutzer sein eigenes Design und die Farben der Navigationsleiste wählen. Ihre Einstellungen werden im Browser gespeichert und haben keine Auswirkungen auf die anderen Benutzer.
Filter einschalten
Wenn ein Benutzer des Admin-Panels einen Filter setzt, kann der Filter sofort oder nach einem Klick auf die Schaltfläche "Filter" angewendet werden, ganz wie Sie möchten.
Inaktive Kategorien in der Seitenleiste einklappen
Wählen Sie, ob sich die Admin-Seitenleiste wie ein Akkordeon-Menü verhalten soll oder ob mehrere Kategorien gleichzeitig ausgeklappt bleiben können.
Anzeige von Datentabellen
Wählen Sie, ob Sie die Tabellendaten im sichtbaren Teil der Seite mit einer internen Bildlaufleiste oder in einer Tabelle mit unbegrenzter Höhe unter Verwendung der Browser-Bildlaufleiste anzeigen möchten.
Suchergebnisse anzeigen
Zeigen Sie die Suchergebnisse alle auf einer Seite an, oder verwenden Sie paginierte Ergebnisse.
Passwortbeschränkung für neue Benutzerkonten
Wählen Sie die Mindestsicherheitsstufe für Admin-Benutzerpasswörter.

Spracheinstellungen

Sprache
Die Hauptsprache des Admin-Dashboards.
Datum/Uhrzeit-Übersetzung für Admin-Listen
Legt die PHP Locale::setDefault fest, um PHP-Daten automatisch zu übersetzen.
Zeitzone
Ihre Zeitzone
Datum & Zeitauswahl Sprache
Die verfügbaren Sprachen befinden sich in class/phpformbuilder/plugins/pickadate/lib/compressed/translations/
Live-Validierungssprache (JavaScript) für Formulare
Die verfügbaren Sprachen befinden sich in class/phpformbuilder/plugins/formvalidation/js/locales
Serverseitige (PHP) Formularvalidierungssprache
Lesen Sie die Dokumentation des PHP Form Builders hier: https://www.phpformbuilder.pro/documentation/class-doc.php#php-validation-multilanguage

CRUD-Generator

Schützen Sie den Zugriff auf den Generator mit einer Anmeldeseite

Um den Zugriff auf den Generator zu schützen:

  1. Öffnen Sie den Generator - generator/generator.php - in Ihrem Browser
  2. Öffnen Sie die Registerkarte Konfiguration
  3. Setzen Sie den Generator sperren auf Ja
  4. Erledigt - wenn Sie generator/generator.php in Ihrem Browser öffnen. Sie werden auf die Anmeldeseite weitergeleitet.
    Geben Sie Ihre Registrierungs-E-Mail und Ihren Kaufcode ein, um sich anzumelden.

Hauptbedienfeld

Wenn Sie den CRUD-Generator öffnen, erhalten Sie Zugriff auf alle wichtigen Funktionen der Anwendung.

Wählen Sie die Tabelle, die Sie konfigurieren möchten, und klicken Sie dann auf eine der drei verfügbaren Schaltflächen, um Ihre Admin-Leseliste zu erstellen oder zu bearbeiten, Formulare zu erstellen/aktualisieren oder Formulare für die gewählte Tabelle zu löschen.

Die Hauptseite der Anwendung ermöglicht auch den Zugriff auf die Installation des Authentifizierungsmoduls, das Tool zum Vergleichen/Zusammenführen von Dateien, die Organisation der Admin-Navigationsleiste und die globale Konfiguration.

PHP CRUD Generator Main screen

Listen lesen

Auf dieser Registerkarte können Sie alles einstellen, was Sie zum Erstellen der Leseliste für die ausgewählte Tabelle benötigen.
Weitere Informationen finden Sie in diesem Tutorial, in dem Schritt für Schritt gezeigt wird, wie Sie die Leselisten für Ihr Admin-Dashboard erstellen.

PHP CRUD Generator Read Lists

Filter auflisten

Add Filter

Klicken Sie auf die Schaltfläche"Filter hinzufügen" im Formular Leselisten-Generator, um einen neuen Filter hinzuzufügen.

Dadurch wird ein neuer Filter zu Ihrer Liste hinzugefügt.

PHPCG bietet Ihnen zwei Arten von Filtern:

Einfache Filter

Sie müssen nur das zu filternde Feld in der Dropdown-Liste auswählen.

Erweiterte Filter

Erweiterte Filter sind nützlich:

  • wenn Sie zwei oder mehr Werte in der Admin-Dropdown-Liste anzeigen möchten.
    Zum Beispiel werden der Vor- und der Nachname angezeigt, und der gefilterte Wert ist die ID.
  • wenn Sie Werte aus externen Beziehungen filtern möchten.

Um erweiterte Filter zu verwenden, müssen Sie Abfrageparameter mit Joins eingeben.

Es gibt eine Hilfe-Schaltfläche, die Sie bei der Erstellung Ihrer Abfrage unterstützt, sowie eine Vorschau-Schaltfläche, mit der Sie die generierte Dropdown-Liste ansehen und ihre Gültigkeit überprüfen können.

Hier finden Sie eine Anleitung, die Ihnen zeigt, wie Sie mit Filtern umgehen.

Für die Erstellung Ihrer Anfragen empfehlen wir die hervorragende Software FlySpeed SQL Query.


Formulare löschen

Erstellen Sie immer zuerst die Listenansicht und dann die Formulare.

PHPCG Delete Forms

Ausführliche Erklärungen finden Sie in der AnleitungFormulare löschen.


Bootstrap Verwaltungskonsole

Admin Panel Thema & Farben

Sie können das Bootstrap-Thema und die Farben der Navigationsleiste auf der Registerkarte Konfiguration des CRUD-Generators auswählen.

Jeder Benutzer des Admin-Dashboards kann dann individuell, vom Admin und nach seinen Vorlieben, sein bevorzugtes Thema und seine Farben auswählen, die nur in seinem eigenen Browser angewendet werden (als Cookies gespeichert).

Bootstrap-Themen & CSS

Wenn Sie ein Entwickler sind und Gulp + SASS verwenden, sind die Quelldateien verfügbar, sofort einsatzbereit und gut organisiert:

SASS-Benutzer: alle SASS-Quellen sind verfügbar

Gulp-Benutzer: Laden Sie den PHP CRUD Generator Gulp auf Github herunter, um Ihre SCSS-Quelldateien zu bearbeiten und zu kompilieren

Bitte lesen Sie diese Anleitung über die Anpassung des Admin-Dashboards.

Zugang, Schutz und Anmeldung

Solange Sie das Authentifizierungsmodul (Login) nicht installiert und aktiviert haben, ist der Zugang zum Adminbereich öffentlich.

Um das Admin-Panel zu öffnen, öffnen Sie /admin/home

Wenn Sie eine 404-Fehlermeldung erhalten, finden Sie die Ursache und die Lösung im Hilfe-Center.

Wenn Sie versuchen, sich unter /admin/login anzumelden, wird dies logischerweise fehlschlagen, da das Authentifizierungsmodul noch nicht installiert ist.

Das Authentifizierungsmodul muss am Ende des Prozesses installiert werden, wenn Sie alle Ihre READ-Listen und Formulare mit dem Generator erstellt haben.

Modul zur Benutzerauthentifizierung für Administratoren

Installation des Moduls für die Admin-Benutzerauthentifizierung

Mit dem Installationsprogramm für das Benutzerauthentifizierungsmodul können Sie die Zugriffsrechte für die Admin-Elemente konfigurieren.
Es sollte daher als letztes installiert werden, nachdem Sie alle Elemente des CRUD erstellt haben.

Öffnen Sie den Generator in Ihrem Browser und klicken Sie dann auf die Registerkarte"Authentifizierungsmodul".

Das Installationsprogramm erstellt die Tabellen users und users_profiles

Sie müssen auch die Daten des Hauptadministrators eingeben.

Der Hauptadministrator kann dann Benutzer und Profile im Administrationsbereich verwalten.

phpcg authentication module installer

Installieren/Aktualisieren Sie das Modul für die Admin-Benutzerauthentifizierung neu

Wenn Sie nach der Installation des Benutzerauthentifizierungsmoduls einige Tabellen zu Ihrem Verwaltungsbereich hinzufügen, müssen Sie die Tabelle users_profiles aktualisieren.

Zu diesem Zweck haben Sie zwei Möglichkeiten:

1 - Neuinstallation mit dem automatischen Installationsprogramm

  • Deaktivieren Sie das Modul vom Generator
    Disable the module from the generator
  • Deinstallieren Sie das Modul vom Generator
    Uninstall the module from the generator
  • Löschen Sie admin/secure/install/install.lock von Ihrem Server
  • Löschen Sie die Tabelle"users" und die Tabelle"users_profiles" aus Ihrer Datenbank.
  • Starten Sie das Installationsprogramm erneut und folgen Sie dem Prozess

2 - Aktualisierung mit einer einfachen SQL-Abfrage

Wenn Sie das Authentifizierungsmodul installieren/neu installieren, wird die Tabelle users_profiles, die vom PHP CRUD Generator zur Verwaltung der Benutzerrechte verwendet wird, geändert. Für jede Tabelle in Ihrer Datenbank, die im Admin-Panel verwendet wird, werden vier Felder erstellt:

  • read_table
  • update_table
  • create_delete_table
  • constraint_query_table

Anstatt das automatische Installationsprogramm zu verwenden, können Sie die vier Felder mit einer einfachen MySQL-Abfrage hinzufügen.
Das Ergebnis ist genau dasselbe wie bei der Verwendung des Installationsprogramms.

Kopieren Sie die folgende Abfrage in Ihre Datenbankverwaltungsoberfläche (z.B. phpmyadmin), um die vier Felder zur Tabelle users_profiles hinzuzufügen.

Ersetzen Sie users_profiles durch den Namen Ihrer Tabelle users_profiles, die ein Präfix haben kann.

Ersetzen Sie _table durch den Namen der Tabelle, die Sie dem Authentifizierungsmodul hinzufügen möchten.

ALTER TABLE `users_profiles`
    ADD `r_table` BOOLEAN NOT NULL DEFAULT TRUE AFTER `profile_name`,
    ADD `u_table` BOOLEAN NOT NULL DEFAULT TRUE AFTER `r_table`,
    ADD `cd_table` BOOLEAN NOT NULL DEFAULT TRUE AFTER `u_table`,
    ADD `cq_table` VARCHAR(255) NULL DEFAULT '' AFTER `cd_table`;

Struktur

Bitte besuchen Sie die folgende Seite, um die detaillierte Anwendungslogik und die Struktur des Verwaltungsordners zu sehen.

Hauptnavigation

Die Navigationsleiste ist in Kategorien und Elemente unterteilt.

Jedes Element steht für eine Tabelle in der Datenbank und ermöglicht den Zugriff auf die Seite der MySQL-Datentabelle (READ List).

Benutzer können Kategorien erstellen, bearbeiten, löschen, per Drag-and-Drop organisieren und Elemente auf die gleiche Weise ordnen.

Auf der Benutzeroberfläche können Sie auch ein Symbol für jedes Element auswählen.

Der Zugriff auf die Verwaltungsschnittstelle für die Navbar erfolgt über den CRUD Generator, Schaltfläche"Navbar organisieren".

Organize the Bootstrap Dashboard Navbar

Die Informationen werden in einer einfachen JSON-Datei gespeichert: php-crud-generator/admin/crud-data/nav-data.json

Diese Datei kann manuell bearbeitet werden. Es ist nicht notwendig, die Schnittstelle zu benutzen.

Alles wird hier in der Anleitung für die Admin-Navbar ausführlich erklärt.

Bootstrap Dashboard Code Beispiele

Die Programmierer werden es wahrscheinlich zu schätzen wissen, wenn sie eine Vorschau auf die Codes der von PHPCG generierten Dateien des Admin-Panels erhalten.

Hier sind einige Codebeispiele:

Tabelle READ Liste - PHP Objekt

Dies ist die Haupt-PHP-Klasse, die die Datensätze aus der Datenbank abruft und alle Werte aufbaut

<?php

namespace crud;

use common\Utils;
use phpformbuilder\database\DB;
use phpformbuilder\database\Pagination;
use secure\Secure;

class Actor extends Elements
{

    // item name passed in url
    public $item;

    // item name displayed
    public $item_label;

    // associative array : field => field displayed name
    public $fields;

    // external relations
    public $external_tables_count = 1;
    public $external_fields_count;
    public $external_rows_count;
    public $external_tables_labels = array('Film');
    public $external_add_btn = array();
    public $external_fields = array();

    // primary key passed to create|edit|delete
    public $primary_keys; // primary keys fieldnames

    // CREATE rights
    public $can_create = false;

    public $pks = array(); // primary key values for each row
    public $pk_concat_values = array(); // concatenated values of primary key(s) for each row
    public $pk_url_params = array(); // primary key(s) sent to the edit/delete forms URL for each row
    public $update_record_authorized = array();
    public $actor_id = array();
    public $first_name = array();
    public $last_name = array();
    public $last_update = array();

    public $active_filtered_fields = array();
    public $debug_content = '';
    public $export_data_button;
    public $filters_form;
    public $is_single_view = false;
    public $item_url;
    public $join_query = '';
    public $main_pdo_settings = array();
    public $pagination_html;

    // Array of primary fieldnames => values to select a single record for view
    public $params;

    public $records_count;
    public $select_number_per_page;
    public $sorting;

    public function __construct($element, $params = array())
    {
        $this->table         = $element->table;
        $this->item          = $element->item;
        $this->item_label    = $element->item_label;
        $this->primary_keys  = $element->primary_keys;
        $this->select_data   = $element->select_data;
        $this->fields        = $element->fields;

        $table = $this->table;

        $this->params = $params;

        if (!empty($params)) {
            $this->is_single_view = true;
        }

        $json = file_get_contents(ADMIN_DIR . 'crud-data/' . $this->item . '-filter-data.json');
        $filters_array = json_decode($json, true);
        $this->item_url = $_SERVER['REQUEST_URI'];

        // connect to the database
        $db = new Pagination(DEBUG);
        $db->setDebugMode('register');


        $columns = 'actor.actor_id, actor.first_name, actor.last_name, actor.last_update';
        $where = array();

        // restricted rights query
        if (Secure::canReadRestricted($table)) {
            $where = array_merge($where, Secure::getRestrictionQuery($table));
        }

        if (!isset($_SESSION['npp'])) {
            $_SESSION['npp'] = 20;
        }

        // filters
        $filters = new ElementsFilters($table, $filters_array, $this->join_query);
        $this->active_filtered_fields = $filters->getActiveFilteredFields();
        $where_filters = $filters->getWhere();
        $where = array_merge($where, $where_filters);

        // search
        $where_search = array();
        if (isset($_POST['search_field']) && isset($_POST['search_string'])) {
            $searchVals = explode(' + ', $_POST['search_string']);
            $search_string = $searchVals[0];
            $_SESSION['rp_search_field'][$table] = $_POST['search_field'];
            $_SESSION['rp_search_string'][$table] = $search_string;
            if (sizeof($searchVals) > 1) {
                $_SESSION['rp_search_string_2'][$table] = $searchVals[1];
            } else {
                unset($_SESSION['rp_search_string_2'][$table]);
            }
        }

        if (isset($_SESSION['rp_search_string'][$table]) && !empty($_SESSION['rp_search_string'][$table])) {
            $sf = $_SESSION['rp_search_field'][$table];
            $search_field = $table . '.' . $sf;
            $search_field2 = '';
            $search_string_sqlvalue = $db->safe('%' . $_SESSION['rp_search_string'][$table] . '%');
            if (isset($_SESSION['rp_search_string_2'][$table])) {
                $search_string_2_sqlvalue = $db->safe('%' . $_SESSION['rp_search_string_2'][$table] . '%');
            }
            if (file_exists(ADMIN_DIR . 'crud-data/' . $this->item . '-select-data.json')) {
                $json = file_get_contents(ADMIN_DIR . 'crud-data/' . $this->item . '-select-data.json');
                $selects_array = json_decode($json, true);
                if (isset($selects_array[$sf]) && $selects_array[$sf]['from'] == 'from_table') {
                    $search_field = $selects_array[$sf]['from_table'] . '.' . $selects_array[$sf]['from_field_1'];
                    if (!empty($selects_array[$sf]['from_field_2'])) {
                        $search_field2 = $selects_array[$sf]['from_table'] . '.' . $selects_array[$sf]['from_field_2'];
                    }
                }
            }
            $where_search[] = 'LOWER(' . $search_field . ') LIKE LOWER(' . $search_string_sqlvalue . ')';
            if (!empty($search_field2) && isset($search_string_2_sqlvalue) && ($search_string_2_sqlvalue != "'%%'")) {
                $where_search[] = 'LOWER(' . $search_field2 . ') LIKE LOWER(' . $search_string_2_sqlvalue . ')';
            }
            $where = array_merge($where, $where_search);
        }

        $this->filters_form = $filters->returnForm($this->item_url);

        // Get join queries from active filters
        $active_filters_join_queries = $filters->buildElementJoinQuery();

        if (isset($_POST['search_field'])) {
            $pagination_url = str_replace(ADMIN_URL . 'search/', ADMIN_URL, $_SERVER['REQUEST_URI']);
        } else {
            $pagination_url = $_SERVER['REQUEST_URI'];
        }
        if (isset($_POST['npp']) && is_numeric($_POST['npp'])) {
            $_SESSION['npp'] = $_POST['npp'];
        }
        if ($this->is_single_view) {
            // if single record view
            $active_filters_join_queries = $filters->buildElementJoinQuery();
            $pagination_url = '';
            // replace 'fieldname' with 'table.fieldname' to avoid ambigous query
            $where_params = array_combine(
                array_map(function ($k) {
                    return $this->table . '.' . $k;
                }, array_keys($this->params)),
                $this->params
            );
            $where = array_merge($where, $where_params);
        }

        // order query
        $this->sorting = ElementsUtilities::getSorting($table, 'last_name', 'ASC');

        $npp = $_SESSION['npp'];
        if (!empty($where_search) && PAGINE_SEARCH_RESULTS === false) {
            $npp = 1000000;
        }

        if (empty($where)) {
            $where = null;
        }

        // $this->main_pdo_settings are the PDO settings without the pagination LIMIT.
        $this->main_pdo_settings = array(
            'function' => 'select',
            'from'    => 'actor' . $active_filters_join_queries,
            'values'   => $columns,
            'where'    => $where,
            'extras'   => array('order_by' => $this->sorting),
            'debug'    => DEBUG_DB_QUERIES
        );

        $this->pagination_html = $db->pagine($this->main_pdo_settings, $npp, 'p', $pagination_url, 5, true, '/', '');

        if (DEBUG_DB_QUERIES) {
            $this->debug_content .= '<p class="debug-title text-bg-info">"' . $this->table . '" queries</p>' . $db->getDebugContent();
        }

        $update_authorized = false;
        if (Secure::canUpdate($this->table)) {
            // user can update ALL the records
            $update_authorized = true;
        }

        $this->records_count = $db->rowCount();
        if (!empty($this->records_count)) {
            while ($row = $db->fetch()) {
                $primary_keys_array = array(
                    'actor_id' => $row->actor_id
                );
                $this->pks[] = $primary_keys_array;
                $pk_concatenated_values = $row->actor_id;
                $this->pk_concat_values[] = $pk_concatenated_values;
                $this->update_record_authorized[$pk_concatenated_values] = $update_authorized;
                $this->pk_url_params[] = http_build_query($primary_keys_array, '', '/');
                $this->actor_id[] = $row->actor_id;
                $this->first_name[] = $row->first_name;
                $this->last_name[] = $row->last_name;
                $this->last_update[] = $row->last_update;
            }
        }

        // Autocomplete doesn't need the followings settings
        if (!isset($_POST['is_autocomplete'])) {
            if (!$this->is_single_view) {
                // CREATE/DELETE rights
                if (Secure::canCreate($table) || Secure::canCreateRestricted($table)) {
                    $this->can_create = true;
                }

                // restricted UPDATE rights
                if (Secure::canUpdateRestricted($table)) {
                    $where = array_merge(
                        Secure::getRestrictionQuery($table),
                        $where_filters,
                        $where_search
                    );

                    $pdo_settings = array(
                        'function' => 'select',
                        'from'    => 'actor' . $active_filters_join_queries,
                        'values'   => $columns,
                        'where'    => $where,
                        'extras'   => array('order_by' => $this->sorting),
                        'debug'    => DEBUG_DB_QUERIES
                    );

                    // get authorized update primary keys
                    $db->pagine($pdo_settings, $npp, 'p', $pagination_url, 5, true, '/', '');
                    if (DEBUG_DB_QUERIES) {
                        $this->debug_content .= '<p class="debug-title text-bg-info">"' . $this->table . '" - get authorized update primary keys</p>' . $db->getDebugContent();
                    }
                    $records_count = $db->rowCount();
                    if (!empty($records_count)) {
                        while ($row = $db->fetch()) {
                            $this->update_record_authorized[$row->actor_id] = true;
                        }
                    }
                }
            }

            /* external relations */

            for ($i = 0; $i < count($this->pks); $i++) {
                $this->external_rows_count[$i] = array();
                $this->external_fields[$i] = array();
                $this->external_add_btn[$i] = array();

                // actor => film_actor => film
                $from = 'actor INNER JOIN film_actor ON film_actor.actor_id=actor.actor_id INNER JOIN film ON film_actor.film_id=film.film_id';
                $values = 'film_actor.actor_id AS film_actor_actor_id, film_actor.film_id AS film_actor_film_id, film.title, film.release_year, film.film_id AS target_table_pk_0';
                $where = array();
                foreach ($this->pks[$i] as $key => $value) {
                    $where[] = 'actor.' . $key . ' = ' . $value;
                }
                $db->select($from, $values, $where, array('order_by' => $this->sorting), DEBUG_DB_QUERIES);
                if (DEBUG_DB_QUERIES) {
                    if ($i === 0) {
                        $this->debug_content .= '<p class="debug-title text-bg-info">"film" queries <small>(External relation)</small></p>' . $db->getDebugContent();
                    } else {
                        $this->debug_content .= $db->getDebugContent();
                    }
                }
                $records_count = $db->rowCount();
                $this->external_rows_count[$i][] = $records_count;
                $ext_fields = array(
                    'table' => 'film',
                    'table_label' => 'Film',
                    'uniqid' => 'f-' . uniqid(),
                    'fields' => array(
                        'title' => array(),
                        'release_year' => array()
                    ),
                    'fieldnames' => array(
                        'title' => 'title',
                        'release_year' => 'release_year'
                    )
                );

                // get user custom fieldnames
                $ext_fieldnames = ElementsUtilities::getFieldNames($ext_fields['table']);
                if ($ext_fieldnames !== false) {
                    foreach ($ext_fields['fieldnames'] as $key => $value) {
                        if (isset($ext_fieldnames[$key])) {
                            $ext_fields['fieldnames'][$key] = $ext_fieldnames[$key];
                        }
                    }
                }

                if (!$this->is_single_view) {
                    // add button
                    $add_btn = '';
                    if (Secure::canCreate('film_actor')) {
                        if (!empty($records_count)) {
                            // add button for nested table
                            $add_btn = '<div class="d-flex flex-row-reverse mb-2">';
                            $add_btn .= ' <a href="' . ADMIN_URL . 'filmactor/create?actor_id=' . $this->pks[$i]['actor_id'] . '" class="btn btn-xs btn-primary" data-bs-title="Add new" data-bs-toggle="tooltip"><span class="fas fa-plus-circle prepend"></span>Add new Film</a>';
                            $add_btn .= '</div>';
                        } else {
                            // add button for empty cell
                            $add_btn = '<div class="d-flex justify-content-center">';
                            $add_btn .= ' <a href="' . ADMIN_URL . 'filmactor/create?actor_id=' . $this->pks[$i]['actor_id'] . '" class="btn btn-xs btn-outline-secondary" data-bs-title="Add new" data-bs-toggle="tooltip"><span class="fas fa-plus-circle prepend"></span>Add new</a>';
                            $add_btn .= '</div>';
                        }
                    }
                    $this->external_add_btn[$i][] = $add_btn;
                }

                if (!empty($records_count)) {
                    while ($row = $db->fetch()) {
                        $json = false;
                        if (!is_null($row->title)) {
                            $test_if_json = json_decode($row->title);
                            if (json_last_error() == JSON_ERROR_NONE && is_array($test_if_json)) {
                                $json = $test_if_json;
                            }
                        }
                        if ($json) {
                            $ext_fields['fields']['title'][] = implode(', ', $json);
                        } else {
                            $ext_fields['fields']['title'][] = $row->title;
                        }
                        $json = false;
                        if (!is_null($row->release_year)) {
                            $test_if_json = json_decode($row->release_year);
                            if (json_last_error() == JSON_ERROR_NONE && is_array($test_if_json)) {
                                $json = $test_if_json;
                            }
                        }
                        if ($json) {
                            $ext_fields['fields']['release_year'][] = implode(', ', $json);
                        } else {
                            $ext_fields['fields']['release_year'][] = $row->release_year;
                        }
                        if (!$this->is_single_view) {
                            // edit/delete buttons
                            if (Secure::canUpdate('film_actor') || Secure::canCreate('film_actor')) {
                                $action_btns = '<div class="btn-group">';
                                $relation_table_pk_columns = array(
                                    'actor_id' => $row->film_actor_actor_id,
                                    'film_id' => $row->film_actor_film_id
                                );
                                $url_params = http_build_query($relation_table_pk_columns, '', '/');
                                if (Secure::canUpdate('film_actor')) {
                                    $action_btns .= '<a href="' . ADMIN_URL . 'filmactor/edit/' . $url_params . '" class="btn btn-xs btn-warning" data-bs-title="' . addslashes(EDIT) . '" rel="noindex" data-bs-toggle="tooltip"><span class="fas fa-pencil-alt"></span></a>';
                                }
                                if (Secure::canCreate('film_actor')) {
                                    $action_btns .= '<a href="' . ADMIN_URL . 'filmactor/delete/' . $url_params . '" class="btn btn-xs btn-danger" data-bs-title="' . addslashes(DELETE_CONST) . '" rel="noindex" data-bs-toggle="tooltip"><span class="fas fa-times-circle"></span></a>';
                                }
                                $action_btns .= '</div>';
                                $ext_fields['fieldnames']['action'] = ACTION_CONST;
                                $ext_fields['fields']['action'][] = $action_btns;
                            } // end if
                        } // end if !$this->is_single_view
                    } // end while
                } // end if
                $this->external_fields[$i][] = $ext_fields;
            } // end for
            $this->external_fields_count = count($this->external_fields);
        } // end if

        if (!$this->is_single_view) {
            // Export data button
            $this->export_data_button = ElementsUtilities::exportDataButtons($table, $this->main_pdo_settings);

            // number/page
            $numbers_array = array(5, 10, 20, 50, 100, 200, 10000);
            $this->select_number_per_page = ElementsUtilities::selectNumberPerPage($numbers_array, $_SESSION['npp'], $this->item_url);
        }
    }
}

                        

Tabelle READ-Liste - TWIG-Vorlage

Sobald das PHP-Objekt erstellt wurde, wird die Ansicht mit Hilfe einer sauberen TWIG-Vorlage erstellt:

    <div class="card {{ constant('DEFAULT_CARD_CLASS') }} me-4">
            <div class="card-header d-lg-flex flex-wrap justify-content-between {{ constant('DEFAULT_CARD_HEADING_CLASS') }}">
                {% if object.records_count > 0 %}

                <div class="d-flex ms-auto order-lg-2">
                    {{ object.select_number_per_page|raw }}
                </div>

                <hr class="w-100 d-lg-none">

                {% endif %}
                <div class="d-flex order-lg-0 mb-3 mb-sm-0">
                    {% if object.can_create == true %}
                    <a href="{{ constant('ADMIN_URL') }}{{ object.item }}/create" class="btn btn-sm me-1 btn-primary d-flex align-items-center legitRipple"><i class="{{ constant('ICON_PLUS') }} position-left"></i>{{ constant('ADD_NEW') }}</a>
                    {% endif %}
                    {% if object.records_count > 0 %}
                    {{ object.export_data_button|raw }}
                    {% endif %}
                </div>

                <div class="order-lg-1 mx-lg-auto">
                    <form name="rp-search-form" id="rp-search-form" action="" class="form-inline justify-content-center">
                        <div class="form-group">
                            <div class="input-group">
                                <div id="rp-search-field" class="dropdown input-group-prepend">
                                    <a class="dropdown-toggle pl-4 pr-3 rounded-left border-left border-top border-bottom" id="search-dropdown-link" data-bs-toggle="dropdown" aria-haspopup="true"
                                            aria-expanded="false"></a>
                                    <div class="dropdown-menu" aria-labelledby="search-dropdown-link">
                                        {% for field_name, field_display_name in object.fields %}
                                        {% set active = '' %}
                                        {% if field_name == attribute(session.rp_search_field, object.table) %}
                                        {% set active = ' active' %}
                                        {% endif %}
                                        <a class="dropdown-item{{ active }}" href="#" data-value="{{ field_name }}">{{ field_display_name }}</a>
                                        {% endfor %}
                                    </div>
                                </div>
                                {% set search_value = '' %}
                                {% if attribute(session.rp_search_string, object.table) is defined %}
                                {% set search_value = attribute(session.rp_search_string, object.table) %}
                                {% endif %}
                                <input id="rp-search" name="rp-search" type="text" value="{{ search_value }}" placeholder="{{ constant('SEARCH') }}" class="form-control flex-grow-1">
                                <div class="input-group-append">
                                    <button id="rp-search-submit" class="btn btn-secondary ladda-button" data-style="zoom-in" type="submit"><span class="ladda-label"><i class="{{ constant('ICON_SEARCH') }}"></i></span></button>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>

            </div>

            {# Partial block list - rendered alone on the research results #}
            {% block object_list %}

            <div id="{{ object.item }}-list">

            {% if object.records_count > 0 %}

                <div class="table-responsive">
                    <table class="table table-striped table-condensed table-data">
                        <thead>
                            <tr class="{{ constant('DEFAULT_TABLE_HEADING_BACKGROUND') }}">
                                {% if constant('ADMIN_ACTION_BUTTONS_POSITION') == 'left' %}
                                <th>{{ constant('ACTION_CONST') }}</th>
                                {% endif %}

                                <th class="sorting">{{ object.fields.actor_id }}<a href="#" class="sorting-up" data-field="actor_id" data-direction="ASC"><i class="{{ constant('ICON_ARROW_UP') }}"></i></a><a href="#" class="sorting-down" data-field="actor_id" data-direction="DESC"><i class="{{ constant('ICON_ARROW_DOWN') }}"></i></a></th>
                                <th class="sorting">{{ object.fields.first_name }}<a href="#" class="sorting-up" data-field="first_name" data-direction="ASC"><i class="{{ constant('ICON_ARROW_UP') }}"></i></a><a href="#" class="sorting-down" data-field="first_name" data-direction="DESC"><i class="{{ constant('ICON_ARROW_DOWN') }}"></i></a></th>
                                <th class="sorting">{{ object.fields.last_name }}<a href="#" class="sorting-up" data-field="last_name" data-direction="ASC"><i class="{{ constant('ICON_ARROW_UP') }}"></i></a><a href="#" class="sorting-down" data-field="last_name" data-direction="DESC"><i class="{{ constant('ICON_ARROW_DOWN') }}"></i></a></th>
                                <th class="sorting">{{ object.fields.last_update }}<a href="#" class="sorting-up" data-field="last_update" data-direction="ASC"><i class="{{ constant('ICON_ARROW_UP') }}"></i></a><a href="#" class="sorting-down" data-field="last_update" data-direction="DESC"><i class="{{ constant('ICON_ARROW_DOWN') }}"></i></a></th>
                                <th>film</th>
                                <th>{{ constant('DISPLAY') }}</th>
                            {% if constant('ADMIN_ACTION_BUTTONS_POSITION') == 'right' %}
                                <th>{{ constant('ACTION_CONST') }}</th>
                            {% endif %}
                            </tr>
                        </thead>
                        <tbody>
                        {% for i in range(0, object.records_count - 1) %}
                            <tr>
                                {% if constant('ADMIN_ACTION_BUTTONS_POSITION') == 'left' %}
                                <td class="has-btn-group no-ellipsis">
                                    <div class="btn-group">
                                        {% if object.pk[loop.index0] in object.authorized_update_pk %}
                                        <a href="{{ constant('ADMIN_URL') }}{{ object.item }}/edit/{{ object.pk[loop.index0] }}" class="btn btn-sm btn-warning legitRipple" data-tooltip="{{ constant('EDIT') }}" data-delay="500"><span class="{{ constant('ICON_EDIT') }} icon-md"></span></a>
                                        {% endif %}
                                        {% if object.can_create == true %}
                                        <a href="{{ constant('ADMIN_URL') }}{{ object.item }}/delete/{{ object.pk[loop.index0] }}" class="btn btn-sm btn-danger legitRipple" data-tooltip="{{ constant('DELETE_CONST') }}" data-delay="500"><span class="{{ constant('ICON_DELETE') }} icon-md"></span></a>
                                        {% endif %}
                                    </div>
                                </td>
                                {% endif %}
                            <td>{{ object.actor_id[ loop.index0 ] }}</td>
                            <td>{{ object.first_name[ loop.index0 ] }}</td>
                            <td>
                            {% if object.pk[loop.index0] in object.authorized_update_pk %}
                            <span class="jedit-text tip" data-field="last_name" data-delay="500" title="{{ constant('CLICK_TO_EDIT') }}" id="actor-last_name-actor_id-{{ object.pk[ loop.index0 ] }}">{{ object.last_name[ loop.index0 ] }}</span>
                            {% else %}
                                {{ object.last_name[ loop.index0 ] }}
                            {% endif %}
                    </td>
                            <td>{{ toDate(object.last_update[ loop.index0 ], 'dd MMMM yyyy H:m a')|raw }}</td>
                                {% if object.external_tables_count > 0 %}
                                {% for j in range(0, object.external_tables_count - 1) %}
                                <td class="no-ellipsis">
                                    {% if object.external_rows_count[i][j] > 0 %}
                                    <h6 class="card-title text-center text-nowrap mb-2"><span class="badge bg-gray-300 position-left">{{ object.external_rows_count[i][j] }}</span><a class="dropdown-toggle" data-bs-toggle="collapse" href="#{{ object.external_fields[i][j]['uniqid'] }}" role="button" aria-expanded="false" aria-controls="{{ object.external_fields[i][j]['uniqid'] }}"><small class="text-muted nowrap">{{ constant('SHOW') }} / {{ constant('HIDE') }}</small></a></h6>
                                    <div class="collapse" id="{{ object.external_fields[i][j]['uniqid'] }}">
                                    {{ object.external_add_btn[i][j]|raw }}
                                        <table class="table table-striped table-condensed">
                                            <thead class=" {{ constant('DEFAULT_TABLE_HEADING_BACKGROUND') }}">
                                                <tr>
                                                    {% for field, value in object.external_fields[i][j].fieldnames %}
                                                    <th>{{ value }}</th>
                                                    {% endfor %}
                                                </tr>
                                            </thead>
                                            <tbody>

                                                {# Loop records #}

                                                {% for k in range(0, object.external_rows_count[i][j] - 1) %}
                                                <tr>

                                                    {# Loop fields #}

                                                    {% for field, value in object.external_fields[i][j].fields %}
                                                    <td>{{ object.external_fields[i][j].fields[field][k]|raw }}</td>
                                                    {% endfor %}
                                                </tr>
                                                {% endfor %}
                                            </tbody>
                                        </table>
                                    </div>
                                    {% else %}
                                    {{ object.external_add_btn[i][j]|raw }}
                                    {% endif %}
                                </td>
                                {% endfor %}
                                {% endif %}
                                <td><a href="{{ constant('BASE_URL') }}" data-delay="500" data-tooltip="{{ constant('OPEN_URL') }}" target="_blank"><span class="{{ constant('ICON_NEW_TAB') }} text-center"></span></a></td>
                                {% if constant('ADMIN_ACTION_BUTTONS_POSITION') == 'right' %}
                                <td class="has-btn-group no-ellipsis">
                                    <div class="btn-group">
                                        {% if object.pk[loop.index0] in object.authorized_update_pk %}
                                        <a href="{{ constant('ADMIN_URL') }}{{ object.item }}/edit/{{ object.pk[loop.index0] }}" class="btn btn-sm btn-warning legitRipple" data-tooltip="{{ constant('EDIT') }}" data-delay="500"><span class="{{ constant('ICON_EDIT') }} icon-md"></span></a>
                                        {% endif %}
                                        {% if object.can_create == true %}
                                        <a href="{{ constant('ADMIN_URL') }}{{ object.item }}/delete/{{ object.pk[loop.index0] }}" class="btn btn-sm btn-danger legitRipple" data-tooltip="{{ constant('DELETE_CONST') }}" data-delay="500"><span class="{{ constant('ICON_DELETE') }} icon-md"></span></a>
                                    {% endif %}
                                    </div>
                                </td>
                                {% endif %}
                            </tr>
                            {% endfor %}
                        </tbody>
                    </table>
                </div> <!-- END table-responsive -->

                {% else %}
                <div class="card-body">
                    <p class="text-semibold">
                        {{ alert(constant('NO_RECORD_FOUND'), 'alert-info has-icon')|raw }}
                    </p>
                </div>
                {% endif %}

                <div class="card-footer  {{ constant('DEFAULT_CARD_FOOTER_CLASS') }} p-4 mt-5">
                    {{ object.pagination_html|raw }}
                </div>
            </div> <!-- END {{ object.item }}-list -->

            {% endblock object_list %}
            {# END Partial block - rendered alone on the research results #}

        </div> <!-- END card -->

                        

Formular für Tabellenaktualisierung - PHP-Formular

Das Formular, das erzeugt wird, um Datensätze aus der angegebenen Tabelle zu bearbeiten.

Das Formular wurde mit PHP Form Builder erstellt

Alle Operationen werden in der gleichen Datei durchgeführt:

  • Geschützt durch das Modul zur Benutzerauthentifizierung und Rechteverwaltung
  • Datensätze zum Ausfüllen des Formulars abrufen
  • Erstellen und Anzeigen des Formulars, einschließlich aller Plugins (Dropdowns, Picker, Uploader, ...)
  • PHP-Validierung
  • Aktualisieren Sie die Datenbankeinträge oder zeigen Sie die Fehler an, wenn falsche Werte eingegeben wurden.
<?php
use phpformbuilder\Form;
use phpformbuilder\Validator\Validator;
use phpformbuilder\database\DB;
use common\Utils;
use secure\Secure;

include_once ADMIN_DIR . 'secure/class/secure/Secure.php';

$debug_content = '';

/* =============================================
    validation if posted
============================================= */

if ($_SERVER["REQUEST_METHOD"] == "POST" && Form::testToken('form-edit-actor') === true) {
    $validator = Form::validate('form-edit-actor', FORMVALIDATION_PHP_LANG);
    $validator->required()->validate('first_name');
    $validator->required()->validate('last_name');
    $validator->required()->validate('last_update');
    if (isset($_POST['last_update_submit'])) {
        $validator->date()->validate('last_update_submit');
    } else {
        $validator->date()->validate('last_update');
    }

    // check for errors
    if ($validator->hasErrors()) {
        $_SESSION['errors']['form-edit-actor'] = $validator->getAllErrors();
    } else {
        require_once CLASS_DIR . 'phpformbuilder/database/db-connect.php';
        require_once CLASS_DIR . 'phpformbuilder/database/DB.php';
        $db = new DB(DEBUG);
        $db->setDebugMode('register');
        $values = array();
        $values['first_name'] = $_POST['first_name'];
        $values['last_name'] = $_POST['last_name'];
        $values['last_update'] = $_POST['last_update'];
        $where = $_SESSION['actor_editable_primary_keys'];

        // begin transaction
        $db->transactionBegin();

        try {
            // update actor
            if (DEMO !== true && !$db->update('actor', $values, $where, DEBUG_DB_QUERIES)) {
                $error = $db->error();
                throw new \Exception($error);
            } else {
                // get records from film_actor
                $film_actor_current_records   = array();

                // Array with film.film_id
                $film_actor_records_to_add    = array();

                // Array with film_actor.actor_id
                $film_actor_records_to_delete = array();

                $from = 'film_actor';
                $columns = array('film_id');
                $where = array('actor_id' => $_SESSION['actor_editable_primary_keys']['actor.actor_id']);

                $db->select($from, $columns, $where, array(), DEBUG_DB_QUERIES);

                $db_count = $db->rowCount();
                if (!empty($db_count)) {
                    while ($row = $db->fetch()) {
                        $film_actor_current_records[] = $row->film_id;
                    }
                }

                foreach ($_POST['ext_film'] as $film_value) {
                    if (!in_array($film_value, $film_actor_current_records)) {
                        $film_actor_records_to_add[] = $film_value;
                    }
                }

                foreach ($film_actor_current_records as $film_value) {
                    if (!in_array($film_value, $_POST['ext_film'])) {
                        $film_actor_records_to_delete[] = $film_value;
                    }
                }

                // insert records in film_actor
                foreach ($film_actor_records_to_add as $value) {
                    $values = array();
                    $values['actor_id'] = $_SESSION['actor_editable_primary_keys']['actor.actor_id'];
                    $values['film_id'] = $value;
                    if (DEMO !== true && $db->insert('film_actor', $values, DEBUG_DB_QUERIES) === false) {
                        $error = $db->error();
                        throw new \Exception($error);
                    }
                }

                // delete records from film_actor
                foreach ($film_actor_records_to_delete as $film_id_value) {
                    $where = array();
                    $where['actor_id'] = $_SESSION['actor_editable_primary_keys']['actor.actor_id'];
                    $where['film_id'] = $film_id_value;
                    if (DEMO !== true && !$db->delete('film_actor', $where, DEBUG_DB_QUERIES)) {
                        $error = $db->error();
                        throw new \Exception($error);
                    }
                }

                // ALL OK
                if (!DEBUG_DB_QUERIES) {
                    $db->transactionCommit();

                    $_SESSION['msg'] = Utils::alert(UPDATE_SUCCESS_MESSAGE, 'alert-success has-icon');

                    // reset form values
                    Form::clear('form-edit-actor');

                    // redirect to list page
                    if (isset($_SESSION['active_list_url'])) {
                        header('Location:' . $_SESSION['active_list_url']);
                    } else {
                        header('Location:' . ADMIN_URL . 'actor');
                    }

                    // if we don't exit here, $_SESSION['msg'] will be unset
                    exit();
                } else {
                    $debug_content .= $db->getDebugContent();
                    $db->transactionRollback();

                    $_SESSION['msg'] = Utils::alert(UPDATE_SUCCESS_MESSAGE . '<br>(' . DEBUG_DB_QUERIES_ENABLED . ')', 'alert-success has-icon');
                }
            }
        } catch (\Exception $e) {
            $db->transactionRollback();
            $msg_content = DB_ERROR;
            if (DEBUG) {
                $msg_content .= '<br>' . $e->getMessage() . '<br>' . $db->getLastSql();
            }
            $_SESSION['msg'] = Utils::alert($msg_content, 'alert-danger has-icon');
        }
    } // END else
} // END if POST

// register editable primary keys, which are NOT posted and will be the query update filter
// $params come from data-forms.php
// replace 'fieldname' with 'table.fieldname' to avoid ambigous query
$where_params = array_combine(
    array_map(function ($k) {
        return 'actor.' . $k;
    }, array_keys($params)),
    $params
);
$_SESSION['actor_editable_primary_keys'] = $where_params;

if (!isset($_SESSION['errors']['form-edit-actor']) || empty($_SESSION['errors']['form-edit-actor'])) { // If no error registered
    $from = 'actor';
    $columns = '*';

    $where = $_SESSION['actor_editable_primary_keys'];

    // if restricted rights
    if (ADMIN_LOCKED === true && Secure::canUpdateRestricted('actor')) {
        $where = array_merge($where, Secure::getRestrictionQuery('actor'));
    }

    $db = new DB(DEBUG);
    $db->setDebugMode('register');

    $db->select($from, $columns, $where, array(), DEBUG_DB_QUERIES);
    if ($db->rowCount() < 1) {
        if (DEBUG) {
            exit($db->getLastSql() . ' : No Record Found');
        } else {
            exit('No Record Found');
        }
    }
    if (DEBUG_DB_QUERIES) {
        $debug_content .= $db->getDebugContent();
    }
    $row = $db->fetch();
    $_SESSION['form-edit-actor']['actor_id'] = $row->actor_id;
    $_SESSION['form-edit-actor']['first_name'] = $row->first_name;
    $_SESSION['form-edit-actor']['last_name'] = $row->last_name;
    $_SESSION['form-edit-actor']['last_update'] = date('Y-m-d H:i');
}

$_SESSION['form-edit-actor']['ext_film'] = array();

$from = 'film_actor';
$columns = array('film_id');
$where = array('actor_id' => $_SESSION['actor_editable_primary_keys']['actor.actor_id']);

$db = new DB();
$db->select($from, $columns, $where, array(), DEBUG_DB_QUERIES);

if (DEBUG_DB_QUERIES) {
    $debug_content .= $db->getDebugContent();
}

$db_count = $db->rowCount();
if (!empty($db_count)) {
    while ($row = $db->fetch()) {
        $_SESSION['form-edit-actor']['ext_film'][] = $row->film_id;
    }
}

// $params come from data-forms.php
$pk_url_params = http_build_query($params, '', '/');

$form = new Form('form-edit-actor', 'horizontal', 'novalidate');
$form->setAction(ADMIN_URL . 'actor/edit/' . $pk_url_params);
$form->startFieldset();

// actor_id --

$form->setCols(2, 10);
$form->addInput('hidden', 'actor_id', '');

// first_name --

$form->setCols(2, 10);
$form->addInput('text', 'first_name', '', 'First Name', 'required');

// last_name --
$form->addInput('text', 'last_name', '', 'Last Name', 'required');

// last_update --
$form->addInput('hidden', 'last_update', date('Y-m-d H:i'));

// external relation: actor => film_actor => film;
$from = 'film';
$columns = 'title, film_id';
$where = false;
$extras = array(
    'select_distinct' => true
);

$db = new DB();
$db->select($from, $columns, $where, $extras, DEBUG_DB_QUERIES);

if (DEBUG_DB_QUERIES) {
    $debug_content .= $db->getDebugContent();
}

$db_count = $db->rowCount();
if (!empty($db_count)) {
    $values = array();
    $display_values = array();
    while ($row = $db->fetch()) {
        $values[] = $row->film_id;
        $display_values[] = $row->title;
    }
    for ($i=0; $i < $db_count; $i++) {
        $form->addOption('ext_film[]', $values[$i], $display_values[$i]);
    }
    $form->addSelect('ext_film[]', 'Film', 'data-slimselect=true, multiple, data-close-on-select=false');
}
$form->addBtn('button', 'cancel', 0, '<i class="' . ICON_BACK . ' prepend"></i>' . CANCEL, 'class=btn btn-warning, data-ladda-button=true, data-style=zoom-in, onclick=history.go(-1)', 'btn-group');
$form->addBtn('submit', 'submit-btn', 1, SUBMIT . '<i class="' . ICON_CHECKMARK . ' append"></i>', 'class=btn btn-success, data-ladda-button=true, data-style=zoom-in', 'btn-group');
$form->setCols(0, 12);
$form->centerContent();
$form->printBtnGroup('btn-group');
$form->endFieldset();
$form->addPlugin('pretty-checkbox', '#form-edit-actor');
$form->addPlugin('formvalidation', '#form-edit-actor', 'default', array('language' => FORMVALIDATION_JAVASCRIPT_LANG));

                        

Anpassungen für fortgeschrittene Benutzer

Wenn sich Ihre Datenbankstruktur ändert, ist PHPCG in der Lage, die Daten wiederherzustellen und ermöglicht Ihnen, die entsprechenden CRUD-Seiten neu zu generieren.

Bei der Erstellung der Seiten des Administrationspanels erstellt PHPCRUD automatisch eine Sicherungskopie der vorherigen Version.

Mit dem in den Generator integrierten Dateivergleichstool können Sie Ihre aktuelle Version und die vorherige Version nebeneinander vergleichen und zusammenführen, indem Sie die Codeblöcke auswählen, die beibehalten werden sollen.

Anpassungen der Verwaltung können so bei Versions-/Strukturänderungen beibehalten werden.

Anweisungen aktualisieren

Die Updates erfolgen automatisch.

Wenn eine neue Version veröffentlicht wird, sehen Sie in der Datei /generator/generator.php die Meldung "New PHP CRUD GENERATOR version is available" (Neue PHP CRUD GENERATOR Version ist verfügbar) und müssen nur noch auf die Schaltfläche "Installieren" klicken.

Ihre Versionsnummer finden Sie in /conf/conf.php (VERSION)


Upgrade von 1.x auf 2.x

Die Version 2 ist ein großes Update. Es ist daher nicht möglich, ein Upgrade von Version 1 durchzuführen.

Die Lösung besteht also darin, die Version 2 zu installieren und dann Ihr Admin-Dashboard von dort aus neu zu konfigurieren.

Sprachen/Übersetzung (I18n)

Der PHP CRUD Generator und das generierte Bootstrap-Administrationspanel sind beide vollständig mehrsprachig.

Zur Übersetzung in Ihre eigene Sprache:

  1. Duplizieren Sie admin/i18n/de.php und benennen Sie sie in Ihre eigene Sprache um.
  2. Erstellen Sie die Übersetzungen in der Datei, die Sie erstellt haben(admin/i18n/[Ihre-Sprache].php).
  3. Öffnen Sie conf/user-conf.php und ersetzen Sie define('LANG', 'en'); mit dem Dateinamen, den Sie zuvor verwendet haben.
  4. Prüfen Sie class/phpformbuilder/plugins/select2/dist/js/i18n/[Ihre-Sprache].js und erstellen Sie sie, falls sie nicht existiert.
  5. Sie können uns gerne Ihre Übersetzung schicken. Sie wird für andere Benutzer nützlich sein.

PHP Form Builder

PHP Form Builder ist im Paket enthalten, und Sie können ihn ohne Einschränkung auf derselben Domain wie Ihr CRUD verwenden.

Das bedeutet, dass Sie jedes beliebige Formular auf Ihrer Website/Projekt erstellen und die integrierten Plugins und Funktionen nutzen können.

Um den PHP Form Builder in Ihrem Projekt zu verwenden, erstellen Sie eine PHP-Datei, in die Sie ein Formular einfügen möchten, oder öffnen Sie eine vorhandene PHP-Datei und fügen Sie diesen Code ganz am Anfang ein:

use phpformbuilder\Form;

session_start();
include_once rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR) . '/conf/conf.php';
include_once CLASS_DIR . 'phpformbuilder/Form.php';

Dann können Sie Ihre Formulare erstellen. Die Dokumentation finden Sie auf der offiziellen Website: https://www.phpformbuilder.pro

Drag & Drop Form Builder & Vorlagen

Der Drag & Drop Form Builder und die Formularvorlagen sind nicht im Paket des PHP CRUD Generators enthalten. Dies ist keine Einschränkung, sondern liegt einfach daran, dass sie online verfügbar sind und die meisten Benutzer sie nicht benötigen, wodurch sich das Gewicht des PHPCG-Pakets verringert.

Wenn Sie sie herunterladen möchten, finden Sie sie hier:

Laden Sie das Drag-and-Drop-Tool und die Formularvorlagen des PHP Form Builders
herunter (zip - ~ 2.33Mo)

Die ZIP-Datei enthält zwei Verzeichnisse: drag-n-drop-form-builder und templates

Drag & Drop Form Builder

  • Legen Sie das Verzeichnis drag-n-drop-form-builder innerhalb des Ordners class ab, direkt neben dem Ordner phpformbuilder.
    Sie können dann /class/drag-n-drop-form-builder/index.html in Ihrem Browser öffnen und mit der Erstellung Ihrer Formulare beginnen.
  • Wenn Sie die vom Drag&Drop-Tool generierten Formularcodes erhalten, vergessen Sie nicht, den CRUD-Generator-spezifischen Code immer manuell hinzuzufügen (wie oben erklärt):
    use phpformbuilder\Form;
    
    session_start();
    include_once rtrim($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR) . '/conf/conf.php';
    include_once CLASS_DIR . 'phpformbuilder/Form.php';

Vorlagen

  • Legen Sie das Verzeichnis drag-n-drop-form-builder innerhalb des Ordners class ab, direkt neben dem Ordner phpformbuilder.
    Sie können dann /class/drag-n-drop-form-builder/index.html in Ihrem Browser öffnen und mit der Erstellung Ihrer Formulare beginnen.
  • Das Verzeichnis für die Vorlagen kann sich an einem beliebigen Ort auf Ihrem Server befinden, egal in welchem Ordner es sich befindet, die Vorlagen funktionieren problemlos.

Quellen & Credits

Vielen Dank an die Autoren für ihre großartige Arbeit

Changelog

Schließen Sie nach jeder Aktualisierung Ihren Browser und öffnen Sie ihn erneut, um PHP SESSION zu löschen.

Version 2.3.5 (12/2023)


    Improvements:
        - improve Spanish translations
    Bug Fix:
        - fix "|raw" apprearing in the list's tables headers"
        - fix quotes escaping in the forms help texts
    

Version 2.3.4 (11/2023)


    Bug Fix:
        - fix a weird error with the error_log function call in DB class
        - fix error with the Spanish translation
    

Version 2.3.3 (11/2023)


    New Features:
        - Add PHP Errors Monitoring with email sending. Available in the generator's configuration tab.
    Improvements:
        - lint code with SonarLint + PHP 8.2, remove all php deprecated and warning messages
    Bug Fix:
        - fix a bug with "update" queries using the same fieldname in both values and where clause
    

Version 2.3.2 (10/2023)


    Improvements:
        - add SQL group_by clause in DB->select() '$extras' argument
        - ability to use HTML code for field titles
    Bug Fix:
        - the custom fieldnames are now properly displayed in the single records views
        - fix image field addons display in the generator forms
        - fix admin infinite loading bug in Firefox due to an internal Pace loader bug (https://github.com/CodeByZach/pace/issues/510)
    

Version 2.3.1 (04/2023)


    Bug Fix:
        - fix the file comparison tab not showing the tool
        - fix the broken date pickers translations
    

Version 2.3 (03/2023)


    Improvements:
        - fix wrong links & images in the documentation
        - database insert now returns the last insert id if supported by the db driver
    Bug Fix:
        - fix plugins_path in class/Form.php (PHP Form Builder) when the plugins folder URL is set with $form->setPluginsUrl()
        - repair the database getLastInsertId() function
        - remove PHP warning with Secure class when passing NULL to the constraint query
        - fix floating values that were converted to int with input[type="number"]
        - fix query error with empty filters
        - fix a warning in admin forms with some special restriction queries
        - fix error when sorting fields from relational tables
        - fix Deprecated error in class.fileuploader.php
    

Version 2.2 (02/2023)


    Improvements:
        - remove the search box in the Edit in place's boolean selects
    Bug Fix:
        - fix non-working Generator lock
        - fix error on cascade delete - please regenerate your delete forms if they use cascade deletion to get them working properly.
    

Version 2.1 (01/2023)


    Improvements:
        - authentication module installer will keep the multiselect opened when choosing the admin tables
        - update the wordcharcount plugin to set the default maxWords to -1 (= infinity)
    Bug Fix:
        - fix a wrong query in create/update forms on external tables
        - fix tinymce theme in class/phpformbuilder/plugins-config-custom/tinymce.xml
    

Version 2.0.1 (01/2023)


    Improvements:
        - enable of the auto-update system for the package's version 2
    Bug Fix:
        - fix filters inappropriate error message when the filter data contains null values
    

Version 2.0 (01/2023)


    New Features:
        - switch from Mysqli to PDO with prepared queries
        - Oracle database support
        - PostgreSQL database support
        - Firebird database support
        - upgrade to Bootstrap 5
        - upgrade Bootswatch themes
        - add the SCSS source files to the package
        - rewrite the color palette and make use of the Bootstrap 5 color contrast utilities (.text-bg-xxx)
        - replace PHP Form Builder by the latest version
        - add 'Display the database errors' and 'Simulate and debug' options to the generator's general settings for easy database debugging.
        - add an option to the generator's general settings to display the data tables in the viewport with or without a vertical scrollbar.
        - highly improve the debugging system, which now can show all the queries (including Ajax requests), PDO parameters and performances using the 'Simulate and debug' option.
        - add the ability to set a custom CSS class for the filtered columns in the admin READ
    Improvements:
        - auto-highlight filtered columns in the admin READ lists and remove the "Column number" setting in the generator "advanced filters" settings
        - the generator's advanced filters test tool now analyzes the queries and display an explicit error message when a field are missing from the query.
        - optimisation of the CRUD Generator forms and tools for more efficient loading
        - complete redesign of the generator and admin dashboard User Interface (UI)
    

Version 1.29 (08/2022)


    Improvements:
        - add an error message with a link to the documentation when a user opens a READ lists in the admin with the authentication module enabled and the table not registered inside it.
    Bug Fix:
        - fix an error with external fields multiple checkboxes
    

Version 1.28 (07/2022)


    New Features:
        - Display of values from secondary relationships.
        E.g: address.city_id -> city.country_id -> country.name
        PHPCG now can show the country name straight from the address READ list and forms.
        - Add capability to load the select options dynamically in Ajax from the database in the admin forms
    Improvements:
        - add 'json' in database fields types
        - update and improve the file comparison tool
        - test Apache FollowSymLinks + admin 404 errors in the installer and return the appropriate help messages
    

Version 1.27 (06/2022)


    Improvements:
        - the installer will now show a clear message if the database connection is successful but no table is found, instead of showing a connection failure message.
    Bug Fix:
        - fix a missing translation in Italian
        - fix a missing parenthesis in generator/generator-templates/form-edit-template.php
    

Version 1.26 (05/2022)


    Bug Fix:
        - fix a missing parenthesis in generator/generator-templates/form-create-template.php
    

Version 1.25 (05/2022)


    Improvements:
        - PHPCG will now preselect the current values in READ lists live-edit's select dropdowns
        - the Live edit in admin READ lists will now show the values of the relational table instead after editing
        - fix PHP warning with mysqli_free_result, boolean and null values & PHP 8.1
        - add a loading indicator when loading Live Edit forms from the READ lists
    Bug Fix:
        - set mysqli_report to MYSQLI_REPORT_OFF during the installation to make sure that mysqli_query will not throw a warning when a query fails (which is normal when we test if a table exist for instance, the query returns false);
        - fix all the errors in the generator with PHP8 and the count() function when some fields have been deleted from the database
        - fix broken queries due to a regex that didn't include table/field names with a number in admin/class/crud/ElementFilters
        - fix PHP Warning with Ajax filters in the admin dashboard
        - escape JSON values in Live edit select dropdowns
    

Version 1.24 (02/2022)


    Improvements:
        - add a warning to the installer if the install folder is not at the root of the project
        - add the same warning to the Quick Start Guide
    

Version 1.23 (11/2021)


    Improvements:
        - update the TWIG engine to 3.3.4
        - add a "Select" type in the READ list generator to prevent the CREATE/UPDATE form from reverting to "text" when rebuilding the READ list
        - add a message in the installer to warn users if their url has uppercase characters (uppercase are not valid and cause problems).
    Bug Fix:
        - replace a php arrow function call in ElementsFilters.php for php <7.4 compatibility
    

Version 1.22 (10/2021)


    Improvements:
        - load the Codecanyon package images in documentation/index.html from local assets instead of Cloudinary CDN, which was not authorized for external hostnames
    Bug Fix:
        - fix a sql error caused by Secure users rights (Secure::getRestrictionQuery() returning a single space instead of an empty value)
        - the Date Range filters now remove the NULL values to detect the minimum / maximum available dates for filter
        - fix filter error with invalid JSON values in database in latest MySQL versions
        - fix missing images in the documentation on users localhost.
        - fix the admin search engine for relational fields with 2 values
    

Version 1.21 (08/2021)


    Bug Fix:
        - the PHP integer validator will now accept null values
        - In the generator - custom validation: removing a validator will now work as expected instead of always removing the last validation rule.
    

Version 1.20 (07/2021)


    New Features:
        - Add a new option in the generator that allows to choose the target (intermediate or final table) of the add/edit/delete buttons for relational tables
    Bug Fix:
        - fix error in Material Datepicker months (error coming from the original plugin)
        - fix missing ACTION header in the admin lists when the action buttons are on the right and bulk check is disabled
    

Version 1.19 (04/2021)


    Improvements:
        - update the admin css to align the nested tables vertically on top
    Bug Fix:
        - fix broken boolean filters in the admin panel
        - fix php warnings in the generator with validation auto + INT fields + MySQL v8
        - restore the "add new" button for nested tables (external relations) in the admin dashboard READ lists
        - fix paginated search results urls in the admin dashboard
        - fix wrong links from READ lists nested tables to their edit/delete form if the nested table name contains underscores
    

Version 1.18 (03/2021)


    New Features:
        - PHPCG now accepts PHP 8
    Improvements:
        - choice of the name of the license table during the installation process
        - improve php version checking during the installation process
        - update the Tinymce responsive filemanager plugin to the latest version - only in the Codecanyon package, not in the auto-update to avoid breaking customized code from users.
        - improve code standards for PHP 8 compatibility
    Bug Fix:
        - fix issues with boolean values storage in the generator
        - remove PHP warning with Ajax filters loading
        - remove PHP warnings in the generator about missing relations
        - fix weird field types returned by MySQL, e.g., "smallint unsigned" instead of "smallint", which is the official valid field type
    

Version 1.17 (03/2021)


        - quickly unreleased because of unexpected bugs
    

Version 1.16 (02/2021)


    New Features:
    Improvements:
    Bug Fix:
        - fix wrong behavior of the admin delete forms when no option or "no" is selected due to a previous update
        - solve assets urls issues in the main index.html and the documentation
    

Version 1.15 (12/2020)


    New Features:
        - add a new 'html' field type in the generator to show the HTML content in the admin lists instead of HTML code when the fields contain HTML
        - auto enable textarea + tinyMce in the generator for HTML fields
    Improvements:
        - add $mail->Sender in Form.php for PHPMailer to improve email deliverability
        - edit the Fileuploader PHP image upload script to crop the images AFTER resizing
        - (the original behavior that center-crops the original image is still available in the file code comments)
        - add a default empty value in the admin forms select, radio & checkbox when the field is not required
    Bug Fix:
        - validator now validates integers with leading zeros (PHP :: Bug #43372)
        - fix wrong ajax POST url in the admin search with paginated results
        - fix textarea custom heights in CREATE forms
    

Version 1.14 (11/2020)


    New Features:
        - add field height option for textarea in the generator
    Improvements:
        - Accept NULL date / time instead of registering the default '1970-01-01 00:00' timestamp in database
        - sanitize directory separator in class/Form.php to avoid wrong plugins url detection on server with inconsistent $_SERVER['SCRIPT_NAME'] and $_SERVER['SCRIPT_FILENAME'] values
        - show tinymce and word char count in the generator only for textarea
    Bug Fix:
        - remove php warning when posting a delete form without choosing yes/no
    

Version 1.13.3 (11/2020)


    Improvements:
        - improve scrolling behavior in admin nested tables show / hide
    Bug Fix:
        - fix non-working nested tables show / hide due to the new OverlayScrollbars plugin
    

Version 1.13.2 (11/2020)


    Bug Fix:
        - replace the deprecated admin table scrollbar plugin broken by the latest jQuery with the great new OverlayScrollbars plugin
    

Version 1.13.1 (11/2020)


    New Features:
        - new tutorial to customize the admin Home page: https://www.phpcrudgenerator.com/tutorials/how-to-customize-the-bootstrap-admin-homepage
        - add documentation to update the Authentication Module with a simple SQL query instead of reinstalling from scratch:
        https://www.phpcrudgenerator.com/documentation/index#admin-user-authentication-module
    Bug Fix:
        - fix a bug in the General Settings Form due to the previous update
    

Version 1.13 (11/2020)


    Bug Fix:
        - update jQuery to 3.5.1 due to a recent browser bug that prevented the generator forms to submit (nothing happened after clicking the submit button)
    

Version 1.12 (11/2020)


    Bug Fix:
        - prefill the generator create/update form properly with TinyMce and character counter options & values
        - remove php warning when installing the authentication module
        - move the generator scripts to the <head> part to avoid jQuery not loaded error in some special circumstances
        - change the target table in READ lists nested tables EDIT buttons to the end relationnal table instead of intermediate
        - edit class/Utils/isValidTimeStamp function to return true with number entry as well as string
    

Version 1.11 (09/2020)


    Improvements:
        - trim $url in CrudTwigExtension::ifRemoteFileExists($url) - vendor/twig/twig/src/Extension/CrudTwigExtension.php
        - replace "url" property in object classes with "item_url" to avoid conflicts with database fields named "url"
    Bug Fix:
        - fix php Notice when building single record READ lists
        - add empty default value in create  / update forms for fields that get their values from a table when no record exist
        - fix the filtered columns overlay colored by colorColumns in the admin READ lists
        - fix wrong default dates / times in UPDATE forms with the pickadate & material date/time pickers hidden fields
        - fix date value with date pickers when a form is posted with errors
        - fix the index of the colored columns in READ lists when some filters are active with bulk delete enabled and admin action buttons are on the left
    

Version 1.10 (06/2020)


    Improvements:
        - add "open url button" link to the documentation in the generator
        - better date & time formats management with the material datepicker plugin
        (rebuild your create/update forms if you want to benefit from these changes)
    Bug Fix:
        - fix date and time custom formats with translations in the create / edit forms
        - add missing session_start() in ajax bulk delete forms
    

Version 1.9 (06/2020)


    New Features:
        - add Bulk Delete capabilities to the admin dashboard's data lists
        - add date range picker filter to the generator filters options + the admin dashboard's data lists
        - add "Default field for search" option to the generator
    Improvements:
        - improve the generator design consistency
        - improve root path detection for servers with inconsistent directory separators
        - collapse admin inactive sidebar categories on categorie click
    Bug Fix:
        - fix Ajax filter results when the result options use 2 field names
    

Version 1.8 (05/2020)


    Improvements:
        - add timezone to the generator general settings
        - update PHP Form Builder to the latest version (4.4)
        - upgrade Twig to Twig 3.0 and others vendor libraries for PHP 7.4.x compatibility
        - add php DOM extension test in the installer's server capabilities tests
        - add a clear error message with a link to the help center on root path detection failure
        - add a loading indicator to the auto-updater
    Bug Fix:
        - fix wrong urls in admin forms when moving the admin files from localhost to the production server
        - fix the missing relational values in the exported data
        - fix admin login failure after reinstalling the authentication module with changing the user table name
        - replace the double quotes with single quotes in the generator delete form template main query
    

Version 1.7.7 (04/2020)


    Bug Fix:
        - fix stupid ROOT path error with subfolder installations due to the previous update
    

Version 1.7.6 (04/2020)


    Improvements:
        - add a server test file in the install folder to debug paths & urls
        - auto-apply ORDER BY changes from the generator to the admin panel without clearing PHP session
        - update ElementFilters to allow simple quotes in advanced filters
    Bug Fix:
        - fix ROOT path with server alias
    

Version 1.7.5 (04/2020)


    New Features:
        - add an "Ajax loading" option in the generator READ Lists filters (default: false)
            Hint: Enable Ajax loading on all the tables that contain a lot of records
            This new option allows to load the filters options on demand and will GREATLY improve the loading speed
        - add ORDER BY in the generator READ List main settings
        - add website search to https://www.phpcrudgenerator.com documentation, tutorials & help center
        - add default skin loader for each Bootstrap admin theme CSS in the general settings form
        - add the item name in the admin header h1
        - add a footer template for admin READ lists (admin/templates/footer.html)
    Improvements:
        - cleaner generator design
        - add instructions to solve 404 errors on some servers (lightspeed) in the help center + admin/.htaccess
        - various minor optimizations
    Bug Fix:
        - fix Tinymce's Responsive file manager url
        - edit the cUrl test file in install/
    

Version 1.7.4 (12/2019)


    New Features:
        - new setting available to choose to show search results in all on the same page or in a paginated list
        IMPORTANT: regenerate your READ lists from the generator if you want the paginated search results
                    or your paginated results will lead to 404 NOT FOUND
    Bug Fix:
        - fix nested table records in READ lists with only the primary key displayed
        - fix the "add new" button link (previously to 404) in READ lists nested tables with page > 1
        - the generator delete form now sets the correct stored options for external tables records
    

Version 1.7.3 (12/2019)


    New Features:
        - add an "Advanced" section in the tutorials with a new "Date and Time formats management logic tutorial
    Bug Fix:
        - fix wrong date / time formats in admin READ lists for servers without PHP intl extension in some random cases depending on the chosen format
        - fix date / time format dropdown helpers in the generator
    

Version 1.7.2 (11/2019)


    Improvements:
        - add Czech translation
        - improve documentation
    Bug Fix:
        - fix PHP warnings with forms & array values
        - fix error in general settings form when no logo is registered
        - fix filters query with number values & MySQL 5.7+
        - fix error in the Italian translation
        - fix PHP warning caused by primary keys aliases in the admin READ lists external relations
    

Version 1.7.1 (08/2019)


    Bug Fix:
        - Fix the Admin Dropdown Search field cross-browser compatibility
        (rebuild your lists to apply)
    

Version 1.7 (08/2019)


    New Features:
        - New live search with Ajax Autocomplete for Bootstrap Admin Panel READ lists
        (rebuild your lists to apply)
    Improvements:
        - Update Material Pickers for compatibility
    

Version 1.6.1 (07/2019)


    New Features:
        - Admin filters now can deal with JSON array values (select multiple, checkboxes)
        - New PHP CRUD Generator Tutorials channel on Youtube
    Improvements:
        - Array values from database now displayed as comma-separated values instead of raw JSON
        - improve the online Documentation & Tutorials
    Bug Fix:
        - Rewrite code to limit users rights to their own records
        (rebuild your lists / forms to apply)
    

Version 1.6 (06/2019)


    New Features:
        - 20+ new Bootstrap themes are now available
        - Choose your preferred Bootstrap theme from the General Settings form
        - Customize all the main layout Bootstrap CSS classes from the General Settings form
        - Compile the SASS files with Gulp using the new PHP CRUD Generator Gulp Github repository
        - New tutorial for Admin Theming & CSS: https://www.phpcrudgenerator.com/tutorials/how-to-customize-the-bootstrap-admin-panel-css
    Bug Fix:
        - Great, no known bug!
    

Version 1.5.6 (06/2019)


    Improvements:
        - The General Settings form in the generator now allows to change the Bootstrap admin main body class
    Bug Fix:
        - the installer was broken by the previous changes. Solved now.
        - Edit in place is no more available in Admin READ lists for users with insufficient rights
        - the broken "enable/disable" authentication module in the Generator now works again
    

Version 1.5.5 (06/2019)


    New Features:
        - The date & Time pickers languages can now be defined in the General Settings form
        - You can now choose the style of the Bootstrap admin date & Time pickers
        (default | Material Design)
        - New Italian translation - Many thanks to Alberto
    

Version 1.5.4 (06/2019)


    New Features:
        - New General Settings form available in the Generator
        - The action buttons of the Bootstrap Admin panel can now be on the left or right of the table
        - The filters of the Bootstrap Admin panel can now be triggered automatically when selected
        - You can change the site title and admin logo using the General Settings form
        - You can change the admin language using the General Settings form
        - You can change the admin skin using the General Settings form
    Improvements:
        - Show custom table names in Admin READ lists nested tables
    Bug Fix:
        - The Validation button in the Generator should now never overlap the forms
    

Version 1.5.3 (06/2019)


    New Features:
        - Action buttons in the admin panel can now be displayed in the
        1st column of the admin READ lists
    Improvements:
        - Responsive & others in admin CSS
    Bug Fix:
        - datepicker plugin
        - files & images upload
        - tooltips
        (these bugs were due to the previous update with latest PHP Form Builder)
    

Version 1.5.2 (05/2019)


    Bug Fix:
        - fix sorting buttons in admin panel READ lists
    

Version 1.5.1 (05/2019)


    Bug Fix:
        - fix export to excel/csv in admin panel
    

Version 1.5 (05/2019)


    New Features:
        - replace PHP Form Builder with the latest version 4.2.1
    Improvements:
        - Admin Panel Fast Loading optimization with the new LoadJS features
        - PHP CRUD Fast Loading optimization with the new LoadJS features
        - rewrite queries for admin restricted users rights
        - upgrade Bootstrap to the latest version 4.3.1
        - minor various others improvements
    

Version 1.4.9 (05/2019)


    New Features:
        - add new Export features (print - current view - all records) in admin READ lists
    

Version 1.4.8 (02/2019)


    New Features:
    Improvements:
        - add (very) strong protection for fileuploader plugin uploads
    Bug Fix:
        - remove some PHP warnings
        - solved admin sidebar duplicate items issue
    

Version 1.4.7 (02/2019)


    Bug Fix:
        - fix navbar issue with empty icons
    

Version 1.4.6 (02/2019)


    New Features:
        - New "array" field type in generator for checkboxes & select multiple values
        will show JSON decoded values in the READ lists
    Improvements:
        - better admin navbar content management ("Organize Navbar")
        - improve array values management in the generator
    Bug Fix:
        - fix non-working select multiple with "set" & "enum" field types
        - fix changelog url in auto-update success message
    

Version 1.4.5 (02/2019)


    New Features:
        - License system now accepts domain with multiple extensions
        e.g., domain.com, domain.eu, domain.co.uk are all valid with the same license.
        - New button in the Generator to reload fresh database structure
        (When you add or remove tables)
    Improvements:
    Bug Fix:
        - admin filters now accept zero values
        - fix queries in admin lists on external tables with direct relation (no intermediate table)
    

Version 1.4.4 (02/2019)


    New Features:
        - external records from relational tables can now be managed
        from the READ LISTS & the CREATE/UPDATE forms (!)
        - add self-referential foreign keys management
        - tables can now be removed/re-enabled from the admin navbar
        - add Spanish admin translation (Thanks to Sergio)
    Improvements:
        - export buttons (csv/xls[x]) now export the exact filtered list items
        - align single fields on the left in admin panels

    Bug Fix:
        - remove phone validation in auth. module installer
        - logout from generator/generator.php now does its job as intended
        - upgrade PHPMailer to latest 6.0.6 to fix PHP 7.3 warnings
    

Version 1.4.3 (12/2018)


    Bug Fix:
        - fix inverted label & value in form CREATE/EDIT templates
        - protect relation tables SELECT queries in form CREATE/EDIT templates
    

Version 1.4.2 (10/2018)


    New Features:
        - new "Add New" button in admin READ lists on external nested tables even if no record
    Improvements:
        - ADMIN panel: register URL query parameters in $_GET (Altorouter ROUTES doesn't deal with these).
        - the ADMIN ADD & UPDATE forms now redirect to the correct list if we come from a nested table (external relation)
        - move date_default_timezone_set from conf/conf.php to conf/user-conf.php
        - add Help & instructions for Microsoft IIS & NGINX servers
    Bug Fix:
        - "Add New" button in admin READ lists now always targets the right CREATE form
        even if there's several external nested tables in the list.
        - Fix several warnings & minor issues
    

Version 1.4.1 (10/2018)


    New Features:
        - add "Add New", "Edit" & "Delete" buttons in READ Lists nested tables for external tables records
    Improvements:
        - add compatibility for date & time without PHP intl extension
    Bug Fix:
        - definitely fix the Apache mod_security error on the install process with some misconfigured servers
    

Version 1.4 (10/2018)

Achtung! Wenn Ihre administrativen READ-Listen Datums- oder Datetime-Felder enthalten, öffnen Sie die entsprechenden Vorlagen in /admin/templates, suchen Sie die Funktionen toDate(...) und ersetzen Sie das PHP-Datumsformat durch das entsprechende ICU-Datumsformat.

neue Online PHP CRUD Tutorials


    New Features:
        - PHPCG includes now the complete latest PHP Form Builder version with all its features & plugins.
        - Add the online knowledge base with numerous tutorials & videos
    Improvements:
        - improve date & time translations management - https://www.phpcrudgenerator.com/tutorials/how-to-translate-dates-times-in-admin-panel
        - add full date & time translation in admin lists & forms
        - change admin form action from absolute url to root relative url
        - add install/curl-test.php to help with CURL debbuging
    Bug Fix:
        - the generator now retrieves the correct stored values to be displayed in READ lists for the external fields
        - get the correct time value in admin edit forms with datetime fields
        - solve plugins URL detection with paths containing uppercase letters
    

Version 1.3.2 (08/2018)


    Improvements:
        - dates edit in place now get the current field value
        - image now crop from the center
    Bug Fix:
        - fix missing fields in update forms due to previous update error
        - fix admin lists bug with fields having uppercase characters
        - fix admin edit in place with dates & uppercase table name
    

Version 1.3.1 (08/2018)


    Improvements:
    Bug Fix:
        - fix Generator form create profiles
    

Version 1.3 (08/2018)


    Notes:
            - After this update you may have to reinstall the user authentication module from the Generator page.
    Improvements:
        - update server-side validation functions to accept empty values,
                except for the validators whose internal logic make values required.
                Details available here: https://www.phpformbuilder.pro/documentation/class-doc.php#php-validation-methods
        - the User Authentication Module now keeps the users & users profiles tables and records when uninstalling.
        - the User Authentication Module can now be reinstalled even if the users & users profiles table exist
        - improve user profiles management and rights limitations
        - the users rights changes now take effect without clearing the session
        - the admin sidebar doesn't show empty categories anymore
        - the only required fields in users table are now name, firstname, profile ID, email, pass & active
        (takes effect on new User Authentication Module installs only)
        - add simulate property to Generator.php to simulate when we reset a table structure from generator
        - remove several warnings & improve various feedback messages
    Bug Fix:
        - solve problem with updates & SSL errors on misconfigured servers
    

Version 1.2.4 (07/2018)


    Notes:
            - After this update you may have to reinstall the user authentication module from the Generator page.
    Improvements:
            - set default empty value for passwords in UPDATE FORMS
    Bug Fix:
            - solve CREATE/UPDATE forms generation with custom validation
            - solve READ LISTS generation with advanced filders
            - solve image path in admin when the field thumbs are not enabled
            - remove password validation in UPDATE FORMS if posted value is empty
            - correct select values count in generator CREATE/UPDATE forms with custom values
            - solve error 500 when adding new users
    

Version 1.2.3 (07/2018)


    New Features:
            - add an uninstallation process
            - add a login module for the generator on the production server
            - primary key management in admin forms
    Improvements:
            - remove the "select database" form in generator & auto select the correct database
            - add warnings for non-standard tables & field names (hyphenated)
            - improve password fields management in CREATE/UPDATE forms:
            better password encryption with Secure class
            password are now automatically optional on update forms with an helper text: "Leave blank to keep the current password"
            - turn fileuploader debug on for CREATE/UPDATE forms
            - improve documentation
            - improve auto-validation detection according to forms & database field types
    Bug Fix:
            - revert Twig template engine to version 1.35.4 to preserve PHP < 7.0 compatibility
            - regenerate css & js combined plugin files for CREATE/UPDATE forms when the forms are edited with the generator
            - fix generator which failed to validate when custom validators were selected while generating the CREATE/UPDATE forms
            - fix password encryption when changes are made in CREATE/UPDATE users table
    

Version 1.2.2 (07/2018)


    Improvements:
            - add user-conf file to avoid breaking user custom settings with updates
            - move the install folder outside the generator folder.
            - improve the updater script
            - improve url & path management
    Bug Fix:
            - fix server issues in some special configurations
    

Version 1.2.1 (07/2018)

Warnung: Wenn Ihr Authentifizierungsmodul nicht aktiviert ist, öffnen Sie nach der Aktualisierung php-crud-generator/conf/admin-lock.php und setzen Sie ADMIN_LOCKED auf false.


    Improvements:
            - move ADMIN_LOCKED and ADMIN_LOGO to separate files for easier updates
    Bug Fix:
            - fix several minor bugs
    

Version 1.2 (06/2018)


    Bug Fix:
            - fix authentication module installation (wrong users filters)
    

Version 1.1 (06/2018)


    New Features:
            - add File uploader to Generator + Admin panel
            - add version check & auto-updater
    Improvements:
            - update dependencies & move to vendor with Composer
            - improve ROOT path analysis
    Bug Fix:
            - correct date & time validation
            - correct value/display inversion with live-edit custom select
    

Version 1.0 (06/2018)


    First Release