Ergebnis 1 bis 19 von 19

Thema: Lagerbestand - Provisorische Lösung?

  1. #1
    Contao-Nutzer
    Registriert seit
    01.04.2015.
    Beiträge
    127

    Standard Lagerbestand - Provisorische Lösung?

    Hallo,

    bis es eine Core Lösung für Lagerbestände vielleicht irgendwann mal gibt, möchte ich versuchen es provisorisch zu lösen.

    Meine Idee wäre Folgende:

    - Textfeld Attribut für Lagerbestandeingabe
    - Prüfung im Produktlisten Template ob auf Lager, ansonsten als vergriffen Kennzeichnen.
    - Prüfung im Warenkorb ob genügend auf Lager, ansonsten Meldung

    - PostCheckout Hook
    - Lagerbestand-jeweils Anzahl der bestellten Produkte = neuer Lagerbestand der in der Tabelle gespeichert wird

    Ist das eine Lösung die Funktionieren kann?
    Wie speichere ich den Lagerbestand am Besten in der Tabelle?

    Vielen Dank im Voraus für Ratschläge und Tipps

    MfG
    Supahr

  2. #2
    Contao-Urgestein Avatar von zonky
    Registriert seit
    19.03.2010.
    Ort
    Berlin, Rdf
    Beiträge
    9.719
    User beschenken
    Wunschliste

    Standard

    als "Workaround" würde ich das auch per "Attribut + Hook" probieren - die Frage nach dem Speichern verstehe ich nicht... der Lagerbestand sollte doch ein Integer sein, sofern Du keine "halben Sachen" verkaufst ;-)

  3. #3
    Gesperrt
    Registriert seit
    07.05.2011.
    Beiträge
    1.199

    Standard

    Zur Bestandsaktualisierung bietet sich ja eigentlich ebenfalls jenes Attributfeld an, welches sonst.

    Prüfung im Warenkorb ob genügend auf Lager, ansonsten Meldung
    Dafür wäre der PostCheckout Hook freilich ungeeignet, denn dort ist der Besucher zu dem Zeitpunkt ja noch garnicht. Und Du müsstest im Warenkorb auch die Prüfung machen, wenn die Menge geändert wird.
    Dafür gehen dann wohl diese (aus Isotope 1.4.7, aber gibts hoffentlich auch noch in 2.3):
    addProductToCollection
    updateProductInCollection

    sofern Du keine "halben Sachen" verkaufst ;-)
    Kann man´s wissen. In manchen Shops kann man auch 1,5 kg von irgendwelchem Gedöns kaufen. :-)

    Edit:
    Ach da fällt mir grad noch ein, was garantiert denn eigentlich, ob die zuletzt im Warenkorb befindliche Menge immer noch verfügbar ist, wenn der Kunde vom Warenkorb in den Checkout wechselt? Während Kunde 1 noch den Einkauf fortsetzt, könnte inzwischen Kunde 2 ihm die begehrte Menge noch schnell vor der Nase weggeschnappt haben. Außerdem kann der Kunde die Seite verlassen und der Warenkorb ist bei späterer Rückkehr immer noch auf dem vorherigen Stand. Die Lagerprüfung im Warenkorb müsste daher nicht nur bei Produktablage, sondern auch bei jedem Seitenaufruf neu erfolgen und ansonsten auch nochmal bei Klick auf den "Zur Kasse"-Button oder im Kassenmodul. Das sind ne Menge Hooks, und falls es die garnicht alle gibt, müsstest Du fehlende erst registrieren. Puh, und nichtmal das reicht komplett. Auch im Checkout vergeht noch Zeit vom Aufruf des Kassenmoduls bis zum Klick auf den Bestellbutton. Letztlich würde ich mir deshalb wahrscheinlich das ganze Zeugs schenken, es erst beim Bestellklick bzw. bei Ausgabe der Bestellprüfungsansicht prüfen und bei Fehlmenge entweder gleich dort eine Korrekturmöglichkeit anbieten oder den Kunden eiskalt zur Korrektur in den Warenkorb zurückschicken. Bei letzterer Variante aber möglichst so, dass er die vorherigen Checkout-Step nicht erneut durchlaufen müsste, vor allem bei Gastbestellung die Adresse(n) nicht erneut eingeben.
    Geändert von soweit_ok (09.09.2015 um 15:44 Uhr)

  4. #4
    Gesperrt
    Registriert seit
    07.05.2011.
    Beiträge
    1.199

    Standard

    @tab

    Ich sah eben, Du bist auch oder wieder im Thread. Hab meinem letzten Posting eben noch einige wichtige Überlegungen hinzugefügt. Lade die Seite bitte neu, um es zu sehen. Deine Meinung wäre wichtig, Du weißt ja, ich erzähl auch manchmal Quatschkram und bin nach dem Arbeitstag schon etwas müde.

  5. #5
    Wandelndes Contao-Lexikon Avatar von tab
    Registriert seit
    22.10.2013.
    Beiträge
    10.078
    Contao-Projekt unterstützen

    Support Contao

    Standard

    Ich bin hier sicher nicht derjenige, der eine fundierte Meinung zu diesem Thema hat, weil ich mich mit Isotope überhaupt nicht auskenne und noch nie einen Shop realisiert oder gar betrieben habe. Da habe ich mich bisher vornehm rausgehalten. Deine Überlegungen erscheinen mir jedenfalls logisch, wäre halt mal interessant, von Shopbetreibern zu hören, wie sie bzw ihr Shop das machen.

    Grundsätzlich wird man wohl immer einen Kompromiss zwischen (mindestens) zwei Anforderungen schliessen müssen.
    1. Der (potenzielle) Kunde soll nicht genervt werden, indem man ihm zuerst sagt, der Artikel ist vorrätig - und wenn er dann zahlen will, dann sagt man ihm "Ätsch, verarscht, nicht mehr verfügbar."
    2. Artikel sollen nicht unnötig blockiert werden. Es soll möglichst vermieden werden, dass ein Kunde die letzten verfügbaren Stücke 1 Stunde lang blockiert, es sich am Ende anders überlegt und in der Zeit hat man 10 "ernsthafte" Interessenten vertröstet bzw. - eher realistisch - zum nächsten Shop geschickt, indem man ihnen gesagt hat, das der Artikel nicht verfügbar ist.

    Ein Kompromiss wird also im Wesentlichen darauf hinauslaufen, ab welchem Zeitpunkt man die Ware für einen bestimmten Kunden blockt und für wie lange. Vorstellbar wäre für mich als unbedarften Laien z.B., dass spätestens dann geblockt wird, wenn der Kunde "zur Kasse geht". Da sollte man von einer ernsthaften Kaufabsicht ausgehen. Weil er da aber natürlich im Prinzip immer noch einfach raus kann, würde ich diese "Reservierung" zeitlich beschränken wollen. Irgendwann klickt er dann hoffentlich auf den gesetzlich vorgeschriebenen "kostenpflichtig bestellen"-Button. Das ist m.E. der spätestmögliche Zeitpunkt um die Ware zu blocken, denn dann ist sie verkauft. Da sollte natürlich sicherheitshalber auch nochmal die Verfügbarkeit kontrolliert werden. Zwischendurch kann man natürlich auch nach jedem Schritt prüfen, um dem Kunden unnötige und vergebliche Eingaben möglichst zu ersparen. Trotzdem würde ich mich als Kunde schon ein wenig verarscht fühlen, wenn mir in dem Stadium noch die Ware auf "nicht verfügbar" gesetzt würde. Zumindest, wenn ich zügig meine Eingaben gemacht und nicht zwischendurch in der Kneipe ein Bierchen getrunken habe. Für wie lange die Produkte im Warenkorb dann reserviert werden, ist halt wieder ein Teil des zu schliessenden Kompromisses.

  6. #6
    Community-Moderator
    Wandelndes Contao-Lexikon
    Avatar von Spooky
    Registriert seit
    12.04.2012.
    Ort
    Scotland
    Beiträge
    34.114
    Partner-ID
    10107

    Standard

    Man muss bei einer einfachen Lagerbestandumsetzung bei einem Lagerbestand von <= 0 ja nicht unbedingt gleich blockieren, man kann ja im Produkttemplate und Warenkorb ja dann eine Meldung ausgeben, dass das Produkt vergriffen ist und es länger dauern kann, bis er wieder geliefert werden kann. Aber hängt halt auch von der Branche ab... wenn es lange dauert, bis dass so ein Produkt wieder verfügbar ist, naja.

  7. #7
    Wandelndes Contao-Lexikon Avatar von tab
    Registriert seit
    22.10.2013.
    Beiträge
    10.078
    Contao-Projekt unterstützen

    Support Contao

    Standard

    Mit blockieren meine ich ja nur, dass die im Warenkorb enthaltene Menge eben für andere Kunden nicht mehr als "verfügbar" bzw "auf Lager" gelistet werden soll. Wie man dann damit umgeht, wenn die gewünschte Menge für einen anderen dadurch (oder überhaupt) nicht mehr verfügbar ist, steht auf einem anderen Blatt. Wenn man schon weiss, dass da kurzfristig eine Lieferung eintrifft, wird man das sicher anders kommunizieren als wenn das das letzte Exemplar auf dem Planeten ist . Aber ist klar, meine Erfahrungen mit Online-Shops beschränken sich auf die Rolle des Käufers.

  8. #8

  9. #9
    Gesperrt
    Registriert seit
    07.05.2011.
    Beiträge
    1.199

    Standard

    zwei Paradigmen: optimistisch oder pessimistisch
    Hmmh, ich dachte auch daran, ob vielleicht ein Lock sinnvoll sein könnte und ggf. wann. Wenn überhaupt, dann wohl erst im Checkout nach dem Motto "Wer zuerst kommt, mahlt zuerst". Wozu die Verfügbarkeit bereits bei der Produktanzeige auf 0 setzen. Man stelle sich vor, Kunde A ist bereits im Checkout, dann klingelt das Telefon und er hält erstmal einen ausgiebigen Klönschnack. Aus welchem Grund sollte man dann den Kunden B, der nicht so trödelt, davon abhalten, sich das Produkt zu schnappen. Deshalb hielt ich letztlich die Prüfung ausschließlich an der Stelle für sinnvoll, wo der Käufer tatsächlich auf den Bestellbutton klickt. Dabei würde dann ja auch gleich die Lagermenge aktualisiert und bei 0 das Produkt entweder als derzeit nicht verfügbar angezeigt oder bis zur Bestandsauffüllung erstmal garnicht mehr.

    Ungeachtet der Softwareverarbeitungsmöglichkeiten durch die Kaufmannsbrille gesehen, bräuchte man klassisch eigentlich garnichts machen außer der Bestandsaktualisierung und dementsprechender Verfügbarkeitsanzeige. Denn man kann ja wie kaufmännisch eigentlich üblich, auch einen Mindestbestand in der Lagerhaltung festlegen, ab dem dann das Produkt nachbestellt oder nachgefertigt wird. Nur bei seltenen Liefer- oder Produktionsproblemen käme es so überhaupt zu dieser Situation und in diesen seltenen Fällen würde vielleicht auch eine Mailbenachrichtigung an den Kunden ausreichen, mit dem Angebot, noch zu warten oder zu stornieren. Ob die Implementierung der vollen und dann nicht mehr trivialen Funktionalität demnach nötig ist, muss halt der Shopbetreiber nach Best practise entscheiden. Gibt ja fast nix, was wir für Geld nicht machen, wenn der Auftraggeber es haben will.

    @Spooky
    Interessant, dass es neuerdings auch einen Warenbestand <0 und nicht nur =0 geben kann.
    In der Möbelbranche allerdings tatsächlich gang und gäbe. Dort ist allzuoft sogar die Lieferbarkeit kleiner Null, weil die Teile erst gefertigt werden, wenn genug Bestellungen zusammenkamen, anderenfalls nie. So fiel ich mal auf einen "stark preisreduzierten Restposten" einer Badmöbelserie rein bei einem der großen Anbieter wie Otto, Quelle etc.. Um nach der Bestellung erst nach dreimaliger Beschwerde Wochen später zu erfahren, die Möbel seien noch garnicht produziert, aber inzwischen eine ausreichende Zahl von Bestellungen eingegangen, damit der Hersteller "demnächst" die Produktion beginnt. Bis der Kram schließlich kam, verging fast ein halbes Jahr. Erstaunliche Restposten, von denen niemals der eigentliche Posten existierte.

  10. #10
    Community-Moderator
    Wandelndes Contao-Lexikon
    Avatar von Spooky
    Registriert seit
    12.04.2012.
    Ort
    Scotland
    Beiträge
    34.114
    Partner-ID
    10107

    Standard

    Zitat Zitat von soweit_ok Beitrag anzeigen
    @Spooky
    Interessant, dass es neuerdings auch einen Warenbestand <0 und nicht nur =0 geben kann.
    Alles < 0 entspricht quasi den Vorbestellungen.

  11. #11
    Contao-Urgestein Avatar von zonky
    Registriert seit
    19.03.2010.
    Ort
    Berlin, Rdf
    Beiträge
    9.719
    User beschenken
    Wunschliste

    Standard

    es gäbe noch eine einfache Lösung, die ohne Programmierung auskommt: genügend "Kram" zum Verkaufen haben ;-)

  12. #12
    Gesperrt
    Registriert seit
    07.05.2011.
    Beiträge
    1.199

    Standard

    Zitat Zitat von Spooky Beitrag anzeigen
    Alles < 0 entspricht quasi den Vorbestellungen.
    Stimmt, und die Idee hat sogar einen gewissen Charme. Würde dann aber auch noch die Fallscheidung benötigen, ob der Kunde bei Nichtlieferbarkeit vorbestellen will, also die Bestellung momentan nicht vorrätiger Artikel nicht generell unterbinden. Oder optional auf Kundenwunsch Mailbenachrichtigung, wenn wieder lieferbar. Viele Nebenaspekte zu dem Thema. Aber ich sah tatsächlich vereinzelt schon Shops mit all diesen Optionen.

  13. #13
    Gesperrt
    Registriert seit
    07.05.2011.
    Beiträge
    1.199

    Standard

    Zitat Zitat von zonky Beitrag anzeigen
    es gäbe noch eine einfache Lösung, die ohne Programmierung auskommt: genügend "Kram" zum Verkaufen haben ;-)
    Schrieb ich ja oben schon - die klassische kfm. Lagerlösung des Artikelmindestbestands. Sortimentsabhängig kann es dennoch vereinzelt zu Mindesbestandsunterschreitungen kommen.

    Da fällt mir auch grad noch ein, wie schaut´s denn eigentlich mit Produktvarianten aus? Wenn gelb nicht mehr verfügbar, dann einen Hinweis, welche anderen Farben aktuell noch lieferbar wären, und evtl. inkl. Änderungsoption der Variante an dieser Stelle? Eigentlich sind diese ganzen Aspekte für individuelle Anpassungen schon fast ein bisschen heavy. Vielleicht mal alles strukturiert zusammenstellen und Isotope Feature Request? Ich mein nicht das Thema "WaWi", denn warum dies für Isotope reichlich heftig wäre, wurde schon öfter diskutiert. Doch eine bloße Bestandsverarbeitung wäre auch mit allen genannten Optionen als Standard vermutlich überschaubar aufwändig.
    Geändert von soweit_ok (10.09.2015 um 13:27 Uhr)

  14. #14
    Contao-Nutzer
    Registriert seit
    01.04.2015.
    Beiträge
    127

    Standard

    Die Überlegungen sind schon richtig, man kann das ziemlich komplex machen.
    Allerdings geht es bei mir um einen sehr kleinen "Shop". Mehr Bestellsystem als Shop, da gibt es keine Produktvarianten, keine großen Lagerbestände, ... Zunächst möchte ich erstmal die Grundfunktion gesichert haben, dann mache ich mir um weitere Verbesserungen Gedanken.

    Das hier geht schon ziemlich in meine Richtung:
    https://community.contao.org/de/show...agerverwaltung

    Damit Funktioniert zumindest schonmal eine Grundfunktion einer Lagerverwaltung.

    Bisher hab ich folgendes gemacht:

    1. Attribut vom Typ Textfeld erstellt "bestand", mit welchem bei den Produkten dann die Bestandseingabe erfolgt.

    2. in die system/config/dcaconfig.php damit der Bestand übersichtlich im Backend angezeigt wird.
    PHP-Code:
    if(is_array($GLOBALS['TL_DCA']['tl_iso_product']['list']['label']['fields']))
    {
        if(!
    in_array('bestand',$GLOBALS['TL_DCA']['tl_iso_product']['list']['label']['fields']))
            
    $GLOBALS['TL_DCA']['tl_iso_product']['list']['label']['fields'][] = 'bestand'

    3. Produktlisten Template iso_list_default.html5
    PHP-Code:
    <?php foreach( $this->buttons as $name => $button ): ?>

    <?php if ($this->generateAttribute('bestand') < or $this->generateAttribute('verfuegbar')=="nein") : ?>
    <button type="button" class="btn btn-primary submit" disabled="disabled">Vergriffen</button>
    <?php else: ?>
    <input type="submit" class="btn btn-primary submit <?php echo $name?>" name="<?php echo $name?>" value="<?php echo $button['label']; ?>">
    <?php endif; ?>

    <?php endforeach; ?>
    4. Das in die isotope/library/Isotope/Module/Checkout.php über "public function generate()"
    PHP-Code:
        public function getStockUpdateInfo($arrItem){

            
    $arrStock = array();
            foreach(
    $arrItem as $a){
                
    $arrHelp = array(
                    
    'id'         => $a->product_id,
                    
    'quantity'    => $a->quantity
                
    );
                
    array_push($arrStock$arrHelp);
            }

            return 
    $arrStock;
        }


        public function 
    updateProductStock($arrItem){

            
    $arrItem $this->getStockUpdateInfo($arrItem);



            foreach(
    $arrItem as $a){
                
    $this->Database->prepare("UPDATE tl_iso_product
                                          SET bestand = bestand - ?
                                          WHERE id = ?"
    )->execute($a['quantity'], $a['id']);
            }


    5. Das in die isotope/library/Isotope/Module/Checkout.php an der entsprechenden Stelle
    PHP-Code:
    if ($objOrder->checkout() && $objOrder->complete()) {
    $this->updateProductStock($objOrder->getItems());

    \
    Controller::redirect(\Haste\Util\Url::addQueryString('uid=' $objOrder->uniqid$this->orderCompleteJumpTo));

    ------------------------------------------------------------------------------
    Das funktioniert soweit schonmal.
    Wie der Ersteller des anderen Threads würde ich das aber auch gerne Update sicher in ein eigenes Modul verpacken.
    Mit der genauen Verwendung der Hooks kenne ich mich allerdings nicht aus.

    Versucht hab ich:
    1. modules/isotope-checkout/config/config.php
    PHP-Code:
    <?php
    $GLOBALS
    ['TL_HOOKS']['postCheckout'][] = array('IsotopeCheckout''MyIsotopeCheckout');
    2. modules/isotope-checkout/classes/IsotopeCheckout.php
    PHP-Code:
    <?php
    class IsotopeCheckout extends Model {
        public function 
    MyIsotopeCheckout($this$arrItemIds$arrData)
        {

            public function 
    getStockUpdateInfo($arrItem){

                
    $arrStock = array();
                foreach(
    $arrItem as $a){
                    
    $arrHelp = array(
                        
    'id'         => $a->product_id,
                        
    'quantity'    => $a->quantity
                    
    );
                    
    array_push($arrStock$arrHelp);
                }

                return 
    $arrStock;
            }


            public function 
    updateProductStock($arrItem){

                
    $arrItem $this->getStockUpdateInfo($arrItem);



                foreach(
    $arrItem as $a){
                    
    $this->Database->prepare("UPDATE tl_iso_product
                                          SET bestand = bestand - ?
                                          WHERE id = ?"
    )->execute($a['quantity'], $a['id']);
                }

            }

        }
    }
    ?>
    3. /modules/isotope-checkout/config/autoload.php
    PHP-Code:
    <?php

    /**
     * Contao Open Source CMS
     *
     * Copyright (c) 2005-2015 Leo Feyer
     *
     * @license LGPL-3.0+
     */


    /**
     * Register the classes
     */
    ClassLoader::addClasses(array
    (
        
    // Classes
        
    'IsotopeCheckout' => 'system/modules/isotope-checkout/classes/IsotopeCheckout.php',
    ));
    Der Fehler wird ja innerhalb der MyIsotopeCheckout Funktion liegen.
    1. Wie füge ich den Code wo richtig ein?
    2. Wie debugge ich da am Besten? Wo wird z.B. ein print_r ausgegeben wenn ich den postCheckout Hook benutze? Auf der Bestellbestätigungsseite irgendwo?

    Danke für weitere Tipps
    Supahr
    Geändert von supahr (11.09.2015 um 15:13 Uhr)

  15. #15
    Contao-Nutzer
    Registriert seit
    01.04.2015.
    Beiträge
    127

    Standard

    Kann jemand bei dem Hook weiterhelfen?

    Gruß
    supahr

  16. #16
    Contao-Urgestein Avatar von zonky
    Registriert seit
    19.03.2010.
    Ort
    Berlin, Rdf
    Beiträge
    9.719
    User beschenken
    Wunschliste

    Standard

    die Hooks von Isotope wie die "Contao-Core-Hooks" einsetzen

    http://www.google.de/search?hl=de&bi...hVGPRoKHTo_C5c

  17. #17
    Contao-Nutzer
    Registriert seit
    01.04.2015.
    Beiträge
    127

    Standard

    Ich komme hier nicht weiter.

    Bisher habe ich das hier versucht:

    PHP-Code:
    Folgende Dateien angelegt
    modules/isotope-checkout/assets/.htaccess
    -------------------------------------------------
    <IfModule !mod_authz_core.c>
      Order allow,deny
      Allow from all
    </IfModule>
    <IfModule mod_authz_core.c>
      Require all granted
    </IfModule>

    modules/isotope-checkout/config/autoload.ini
    -------------------------------------------------
    ;;
    ; List modules which are required to be loaded beforehand
    ;;
    requires[] = "core"
    requires[] = "_autoload"
    requires[] = "isotope"
    requires[] = "tablelookupwizard"
    ;;
    ; Configure what you want the autoload creator to register
    ;;
    register_namespaces = false
    register_classes    = false
    register_templates  = false


    modules/isotope-checkout/config/autoload.php
    -------------------------------------------------
    <?php
    NamespaceClassLoader
    ::add('Isotope''system/modules/isotope-checkout/library');


    modules/isotope-checkout/config/config.php
    -------------------------------------------------
    <?
    php
    $GLOBALS
    ['TL_HOOKS']['postCheckout'][] = array('Isotope\IsotopeCheckout''updateProductStock');


    modules/isotope-checkout/library/Isotope/IsotopeCheckout.php
    -------------------------------------------------
    <?
    php

    namespace Isotope;

    use 
    Isotope\Interfaces\IsotopePrice;
    use 
    Isotope\Interfaces\IsotopeProductCollection;
    use 
    Isotope\Model\ProductCollection\Cart;
    use 
    Isotope\Model\ProductCollection\Order;
    use 
    Isotope\Model\IsotopeCheckout;


    class 
    IsotopeCheckout extends \Controller
    {

        
    /**
         * Current object instance (Singleton)
         * @var object
         */
        
    protected static $objInstance;

        
    /**
         * Prevent cloning of the object (Singleton)
         */
        
    final private function __clone() {}

        
    /**
         * Prevent direct instantiation (Singleton)
         */
        
    protected function __construct()
        {
            
    parent::__construct();

            
    // User object must be loaded from cart, e.g. for postsale handling
            
    if (Isotope::getCart()->member 0) {
                
    $this->User = \Database::getInstance()->prepare("SELECT * FROM tl_member WHERE id=?")->execute(Isotope::getCart()->member);
            }
        }

        
    /**
         * Instantiate the singleton if necessary and return it
         * @return object
         */
        
    public static function getInstance()
        {
            if (!
    is_object(static::$objInstance)) {
                static::
    $objInstance = new \Isotope\IsotopeCheckout();
            }

            return static::
    $objInstance;
        }

        public function 
    getStockUpdateInfo($arrItem){

                
    $arrStock = array();
                foreach(
    $arrItem as $a){
                    
    $arrHelp = array(
                        
    'id'         => $a->product_id,
                        
    'quantity'    => $a->quantity
                    
    );
                    
    array_push($arrStock$arrHelp);
                }

                return 
    $arrStock;
            }


        public function 
    updateProductStock($objOrder->getItems()){

                
    $arrItem $this->getStockUpdateInfo($arrItem);

                foreach(
    $arrItem as $a){
                    
    $this->Database->prepare("UPDATE tl_iso_product
                                          SET bestand = bestand - ?
                                          WHERE id = ?"
    )->execute($a['quantity'], $a['id']);
                    
    $this->checkStock($a['id']);
                }

            }

    }
    Damit möchte ich zunächst den Lösungsweg aus #14 Updatesicher machen. Es tut sich allerdings beim Bestand gar nichts.
    Ich bekomme auch keine Fehlermeldung. Wie kann man das debuggen?
    Der Fehler liegt ja vermutlich hier: "updateProductStock($objOrder->getItems())"
    Woher bekomme ich die korrekten "$objOrder->getItems()"?

    Wenn das Funktioniert möchte ich noch die Bestandmenge beim betätigen des Zahlungspflichtig bestellen Buttons mit den Order Item gegenchecken und falls der Bestand nicht ausreicht die Bestellung zurück weisen.
    Ist hierfür der preCheckout Hook der richtige?
    Wie kann ich dann innerhalb der Hook Klassen auf den Warenkorb und die Datenbank zugreifen?

    Vielen Dank für Tipps und Ratschläge
    Supahr

  18. #18
    Contao-Urgestein Avatar von zonky
    Registriert seit
    19.03.2010.
    Ort
    Berlin, Rdf
    Beiträge
    9.719
    User beschenken
    Wunschliste

    Standard

    Zitat Zitat von supahr Beitrag anzeigen
    Wie kann man das debuggen?
    gibt es sicher mehrere Möglichkeiten... die "Profis" würden sicher einen "echten" Debugger über eine IDE verwenden - probier mal ein \System::log(..) einzubauen oder kill den ganzen Vorgang an der entsprechenden Stelle mit die(...)

  19. #19
    Contao-Nutzer
    Registriert seit
    01.04.2015.
    Beiträge
    127

    Standard

    das mit dem loggen werde ich probieren, in der Zwischenzeit hab ich an der Hardcoded in der Checkout.php gearbeitet, da das Arbeiten mit Hooks und Modulen für mich immer noch ein Buch mit Sieben Siegeln ist.

    Hier ist die komplette Datei, mit FUTURE sind alle Änderungen der Datei gekennzeichnet die in ein Modul ausgegliedert werden sollen.
    Damit ist es jetzt gerade nach den Schritten 1-3 aus Post #14 möglich die Bestellung abzubrechen wenn ein Produkt vom Bestand her zu gering ist.

    In der langconfig.php habe ich noch folgendes für den stockFailed Case als Error Nachricht hinzugefügt:
    PHP-Code:
    $GLOBALS['TL_LANG']['ERR']['stockFailed'] = 'Eines der von Ihnen gewählten Produkte ist derzeit vergriffen...'
    Optimierungsbedarf sehe ich hier zunächst an diesen Stellen:
    • Update sicher alles in ein Modul mit Hooks auslagern
    • der stock-failed Case Leitet auf das Kassenformular zurück und zeigt die Fehlermeldung mit den Zurück und Vor Buttons, hier sollte ein Link zurück zum Shop angezeigt werden. Wie macht man das (stylt die Seite) am Besten aus dem stock-failed Case heraus?


    /system/modules/isotope/library/Isotope/Module/Checkout.php
    PHP-Code:
    <?php

    /**
     * Isotope eCommerce for Contao Open Source CMS
     *
     * Copyright (C) 2009-2014 terminal42 gmbh & Isotope eCommerce Workgroup
     *
     * @package    Isotope
     * @link       http://isotopeecommerce.org
     * @license    http://opensource.org/licenses/lgpl-3.0.html
     */

    namespace Isotope\Module;

    use 
    Haste\Generator\RowClass;
    use 
    Isotope\Interfaces\IsotopeCheckoutStep;
    use 
    Isotope\Interfaces\IsotopeProductCollection;
    use 
    Isotope\Isotope;
    use 
    Isotope\Model\ProductCollection\Order;


    /**
     * Class ModuleIsotopeCheckout
     * Front end module Isotope "checkout".
     */
    class Checkout extends Module
    {

        
    /**
         * Template
         * @var string
         */
        
    protected $strTemplate 'mod_iso_checkout';

        
    /**
         * Do not continue to next step
         * @var boolean
         */
        
    public $doNotSubmit false;

        
    /**
         * Disable caching of the frontend page if this module is in use.
         * @var boolean
         */
        
    protected $blnDisableCache true;

        
    /**
         * Current step
         * @var string
         */
        
    protected $strCurrentStep;

        
    /**
         * Form ID
         * @var string
         */
        
    protected $strFormId 'iso_mod_checkout';









        
    // FUTURE: Bestand ausgliedern

        //-------------------------------------------------------------------------------------------
        // Note: getStockUpdateInfo, used by updateProductStock und checkStock
        
    public function getStockUpdateInfo($arrItem){

            
    $arrStock = array();
            foreach(
    $arrItem as $a){
                
    $arrHelp = array(
                    
    'id'         => $a->product_id,
                    
    'quantity'    => $a->quantity
                
    );
                
    array_push($arrStock$arrHelp);
            }

            return 
    $arrStock;
        }
        
    // Note: updateProductStock, neuen Stock in Datenbank speichern Zugriff per $this->updateProductStock($objOrder->getItems());
        
    public function updateProductStock($arrItem){

            
    $arrItem $this->getStockUpdateInfo($arrItem);

            foreach(
    $arrItem as $a){
                
    $this->Database->prepare("UPDATE tl_iso_product
                                          SET bestand = bestand - ?
                                          WHERE id = ?"
    )->execute($a['quantity'], $a['id']);
            }
        }
        
    // Note: checkStock, Zugriff per $this->checkStock($objOrder->getItems()); FALSE wenn irgend ein Produkt Bestand = 0 --> Case stock-failed
        
    public function checkStock($arrItem){
            
    $arrItem $this->getStockUpdateInfo($arrItem);

            foreach(
    $arrItem as $a){
                
    $helper $this->getStock($a['id']);
            }
            return 
    $helper;
        }
        
    // Note: Stock von Datenbank holen, FALSE wenn Bestand = 0
        
    public function getStock($id){
            
    $stock $this->Database->prepare("SELECT sku, name, bestand FROM tl_iso_product WHERE id = ?")->execute($id)->fetchAllAssoc();

            if(
    $stock[0]['bestand'] == 0){
                return 
    false;
            }
        }

        
    //-------------------------------------------------------------------------------------------










        /**
         * Display a wildcard in the back end
         *
         * @return string
         */
        
    public function generate()
        {
            if (
    TL_MODE == 'BE') {
                
    $objTemplate = new \BackendTemplate('be_wildcard');

                
    $objTemplate->wildcard '### ISOTOPE CHECKOUT ###';
                
    $objTemplate->title    $this->headline;
                
    $objTemplate->id       $this->id;
                
    $objTemplate->link     $this->name;
                
    $objTemplate->href     'contao/main.php?do=themes&amp;table=tl_module&amp;act=edit&amp;id=' $this->id;

                return 
    $objTemplate->parse();
            }

            
    $this->strCurrentStep = \Haste\Input\Input::getAutoItem('step');

            if (
    $this->strCurrentStep == '') {
                
    $this->redirectToNextStep();
            }

            return 
    parent::generate();
        }

        
    /**
         * Returns the current form ID
         *
         * @return string
         */
        
    public function getFormId()
        {
            return 
    $this->strFormId;
        }

        
    /**
         * Generate module
         */
        
    protected function compile()
        {
            
    $arrBuffer = array();

            
    // Default template settings. Must be set at beginning so they can be overwritten later (eg. trough callback)
            
    $this->Template->action        ampersand(\Environment::get('request'), ENCODE_AMPERSANDS);
            
    $this->Template->formId        $this->strFormId;
            
    $this->Template->formSubmit    $this->strFormId;
            
    $this->Template->enctype       'application/x-www-form-urlencoded';
            
    $this->Template->previousLabel specialchars($GLOBALS['TL_LANG']['MSC']['previousStep']);
            
    $this->Template->nextLabel     specialchars($GLOBALS['TL_LANG']['MSC']['nextStep']);
            
    $this->Template->nextClass     'next';
            
    $this->Template->showPrevious  true;
            
    $this->Template->showNext      true;
            
    $this->Template->showForm      true;
            
    $this->Template->steps         = array();

            
    // These steps are handled internally by the checkout module and are not in the config array
            
    switch ($this->strCurrentStep) {

                
    // Complete order after successful payment
                // At this stage, we do no longer use the client's cart but the order through UID in URL
                
    case 'complete':
                    
    /** @var Order $objOrder */
                    
    if (($objOrder Order::findOneBy('uniqid', (string) \Input::get('uid'))) === null) {
                        if (
    Isotope::getCart()->isEmpty()) {
                            
    /** @type \PageError404 $objHandler */
                            
    $objHandler = new $GLOBALS['TL_PTY']['error_404']();
                            
    $objHandler->generate((int) $GLOBALS['objPage']->id);
                            exit;
                        } else {
                            static::
    redirectToStep('failed');
                        }
                    }

                    
    // Order already completed (see #1441)
                    
    if ($objOrder->checkout_complete) {
                        \
    Controller::redirect(\Haste\Util\Url::addQueryString('uid=' $objOrder->uniqid$this->orderCompleteJumpTo));
                    }

                    
    $strBuffer $objOrder->hasPayment() ? $objOrder->getPaymentMethod()->processPayment($objOrder$this) : true;

                    
    // true means the payment is successful and order should be completed
                    
    if ($strBuffer === true) {
                        
    // If checkout is successful, complete order and redirect to confirmation page
                        
    if ($objOrder->checkout() && $objOrder->complete()) {






                            
    //FUTURE: Ausgliedern, Bestand Aktualisierung
                            //-------------------------------------------------------------------------------------------
                            //NOTE: Checkout Complete, Update Stock
                            
    $this->updateProductStock($objOrder->getItems());
                            
    //-------------------------------------------------------------------------------------------








                            
    \Controller::redirect(\Haste\Util\Url::addQueryString('uid=' $objOrder->uniqid$this->orderCompleteJumpTo));
                        }

                        
    // Checkout failed, show error message
                        
    static::redirectToStep('failed');
                    }

                    
    // False means payment has failed
                    
    elseif ($strBuffer === false) {
                        static::
    redirectToStep('failed');
                    }

                    
    // Otherwise we assume a string that shows a message to customer
                    
    else {
                        
    $this->Template->showNext     false;
                        
    $this->Template->showPrevious false;
                        
    $arrBuffer                    = array(array('html' => $strBuffer'class' => $this->strCurrentStep));
                    }
                    break;

                
    // Process order and initiate payment method if necessary
                
    case 'process':

                    
    // canCheckout will override the template and show a message
                    
    if (!$this->canCheckout()) {
                        return;
                    }

                    
    $arrSteps $this->getSteps();

                    
    // Make sure all steps have passed successfully
                    
    foreach ($arrSteps as $step => $arrModules) {
                        
    /** @type IsotopeCheckoutStep $objModule */
                        
    foreach ($arrModules as $objModule) {
                            
    $objModule->generate();

                            if (
    $objModule->hasError()) {
                                static::
    redirectToStep($step);
                            }
                        }
                    }

                    
    $objOrder Isotope::getCart()->getDraftOrder();
                    
    $objOrder->checkout_info        $this->getCheckoutInfo($arrSteps);
                    
    $objOrder->nc_notification      $this->nc_notification;
                    
    $objOrder->iso_addToAddressbook $this->iso_addToAddressbook;
                    
    $objOrder->email_data           $this->getNotificationTokensFromSteps($arrSteps$objOrder);





                    
                    
                    
                    

                    
    //FUTURE: Ausgliedern, Bestand vor Bestellungsabschluss überprüfen
                    //-------------------------------------------------------------------------------------------
                    
    if ($this->checkStock($objOrder->getItems()) === false) {
                        \
    System::log('Callback ' $callback[0] . '::' $callback[1] . '() cancelled checkout for Order ID ' $this->id__METHOD__TL_ERROR);
                        static::
    redirectToStep('stock-failed');
                    }
                    
    //-------------------------------------------------------------------------------------------


                    
                    
                    
                    



                    // !HOOK: pre-process checkout
                    
    if (isset($GLOBALS['ISO_HOOKS']['preCheckout']) && is_array($GLOBALS['ISO_HOOKS']['preCheckout'])) {
                        foreach (
    $GLOBALS['ISO_HOOKS']['preCheckout'] as $callback) {
                            
    $objCallback = \System::importStatic($callback[0]);
                            if (
    $objCallback->$callback[1]($objOrder$this) === false) {
                                \
    System::log('Callback ' $callback[0] . '::' $callback[1] . '() cancelled checkout for Order ID ' $this->id__METHOD__TL_ERROR);
                                static::
    redirectToStep('failed');
                            }
                        }
                    }

                    
    $objOrder->lock();

                    
    $strBuffer $objOrder->hasPayment() ? $objOrder->getPaymentMethod()->checkoutForm($objOrder$this) : false;

                    if (
    $strBuffer === false) {
                        static::
    redirectToStep('complete'$objOrder);
                    }

                    
    $this->Template->showForm false;
                    
    $this->doNotSubmit        true;
                    
    $arrBuffer                = array(array('html' => $strBuffer'class' => $this->strCurrentStep));
                    break;







                
    // FUTURE: Ausgliedern stock-failed Case
                //-------------------------------------------------------------------------------------------
                // NOTE: Stock Failed Case
                
    case 'stock-failed':
                    
    $this->Template->mtype   'error';
                    
    $this->Template->message strlen(\Input::get('reason')) ? \Input::get('reason') : $GLOBALS['TL_LANG']['ERR']['stockFailed'];
                    
    $this->strCurrentStep    'review';
                    break;
                 
    //-------------------------------------------------------------------------------------------






                // Checkout/payment has failed, show the review page again with an error message
                /** @noinspection PhpMissingBreakStatementInspection */
                
    case 'failed':
                    
    $this->Template->mtype   'error';
                    
    $this->Template->message strlen(\Input::get('reason')) ? \Input::get('reason') : $GLOBALS['TL_LANG']['ERR']['orderFailed'];
                    
    $this->strCurrentStep    'review';
                    
    // no break

                
    default:

                    
    // canCheckout will override the template and show a message
                    
    if (!$this->canCheckout()) {
                        return;
                    }

                    
    $arrBuffer $this->generateSteps($this->getSteps());
                    break;
            }

            
    RowClass::withKey('class')->addFirstLast()->applyTo($arrBuffer);

            
    $this->Template->fields $arrBuffer;
        }

        
    /**
         * Run through all steps until we find the current one or one reports failure
         *
         * @param array $arrSteps
         *
         * @return array
         */
        
    protected function generateSteps(array $arrSteps)
        {
            
    $arrBuffer = array();
            
    $intCurrentStep 0;
            
    $intTotalSteps  count($arrSteps);

            if (!isset(
    $arrSteps[$this->strCurrentStep])) {
                
    $this->redirectToNextStep();
            }

            
    /**
             * Run trough all steps until we find the current one or one reports failure
             * @type string                $step
             * @type IsotopeCheckoutStep[] $arrModules
             */
            
    foreach ($arrSteps as $step => $arrModules) {
                
    $this->strFormId            'iso_mod_checkout_' $step;
                
    $this->Template->formId     $this->strFormId;
                
    $this->Template->formSubmit $this->strFormId;

                
    $intCurrentStep += 1;
                
    $arrBuffer = array();

                foreach (
    $arrModules as $objModule) {

                    
    $arrBuffer[] = array(
                        
    'class' => standardize($step) . ' ' $objModule->getStepClass(),
                        
    'html'  => $objModule->generate()
                    );

                    if (
    $objModule->hasError()) {
                        
    $this->doNotSubmit true;
                    }

                    
    // the user wanted to proceed but the current step is not completed yet
                    
    if ($this->doNotSubmit && $step != $this->strCurrentStep) {
                        static::
    redirectToStep($step);
                    }
                }

                if (
    $step == $this->strCurrentStep) {
                    global 
    $objPage;
                    
    $objPage->pageTitle sprintf($GLOBALS['TL_LANG']['MSC']['checkoutStep'], $intCurrentStep$intTotalSteps, ($GLOBALS['TL_LANG']['MSC']['checkout_' $step] ?: $step)) . ($objPage->pageTitle ?: $objPage->title);
                    break;
                }
            }

            
    $arrStepKeys array_keys($arrSteps);

            
    $this->Template->steps      $this->generateStepNavigation($arrStepKeys);
            
    $this->Template->activeStep $GLOBALS['TL_LANG']['MSC']['activeStep'];

            
    // Hide back buttons it this is the first step
            
    if (array_search($this->strCurrentStep$arrStepKeys) === 0) {
                
    $this->Template->showPrevious false;
            } 
    // Show "confirm order" button if this is the last step
            
    elseif (array_search($this->strCurrentStep$arrStepKeys) === (count($arrStepKeys) - 1)) {
                
    $this->Template->nextClass 'confirm';
                
    $this->Template->nextLabel specialchars($GLOBALS['TL_LANG']['MSC']['confirmOrder']);
            }

            
    // User pressed "back" button
            
    if (strlen(\Input::post('previousStep'))) {
                
    $this->redirectToPreviousStep();
            } 
    // Valid input data, redirect to next step
            
    elseif (\Input::post('FORM_SUBMIT') == $this->strFormId && !$this->doNotSubmit) {
                
    $this->redirectToNextStep();
            }

            return 
    $arrBuffer;
        }

        
    /**
         * Redirect visitor to the next step in ISO_CHECKOUTSTEP
         */
        
    protected function redirectToNextStep()
        {
            
    $arrSteps array_keys($this->getSteps());
            
    $intKey   array_search($this->strCurrentStep$arrSteps);

            if (
    false === $intKey) {
                if (
    $this->iso_forward_review) {
                    static::
    redirectToStep('review');
                }

                
    $intKey = -1;
            } 
    // redirect to step "process" if the next step is the last one
            
    elseif (($intKey 1) == count($arrSteps)) {
                static::
    redirectToStep('process');
            }

            static::
    redirectToStep($arrSteps[$intKey 1]);
        }

        
    /**
         * Redirect visitor to the previous step in ISO_CHECKOUTSTEP
         */
        
    protected function redirectToPreviousStep()
        {
            
    $arrSteps array_keys($this->getSteps());
            
    $intKey   array_search($this->strCurrentStep$arrSteps);

            if (
    false === $intKey || === $intKey) {
                
    $intKey 1;
            }

            static::
    redirectToStep($arrSteps[($intKey 1)]);
        }

        
    /**
         * Return the checkout information as array
         *
         * @param array $arrSteps
         *
         * @return array
         */
        
    public function getCheckoutInfo(array $arrSteps null)
        {
            if (
    null === $arrSteps) {
                
    $arrSteps $this->getSteps();
            }

            
    $arrCheckoutInfo = array();

            
    // Run trough all steps to collect checkout information
            /** @type IsotopeCheckoutStep[] $arrModules */
            
    foreach ($arrSteps as $arrModules) {
                foreach (
    $arrModules as $objModule) {

                    
    $arrInfo $objModule->review();

                    if (!empty(
    $arrInfo) && is_array($arrInfo)) {
                        
    $arrCheckoutInfo += $arrInfo;
                    }
                }
            }

            
    RowClass::withKey('class')->addFirstLast()->applyTo($arrCheckoutInfo);

            return 
    $arrCheckoutInfo;
        }

        
    /**
         * Retrieve the array of notification data for parsing simple tokens
         *
         * @param array                    $arrSteps
         * @param IsotopeProductCollection $objOrder
         *
         * @return array
         */
        
    protected function getNotificationTokensFromSteps(array $arrStepsIsotopeProductCollection $objOrder)
        {
            
    $arrTokens = array();

            
    // Run trough all steps to collect checkout information
            
    foreach ($arrSteps as $arrModules) {

                
    /** @type IsotopeCheckoutStep $objModule */
                
    foreach ($arrModules as $objModule) {
                    
    $arrTokens array_merge($arrTokens$objModule->getNotificationTokens($objOrder));
                }
            }

            return 
    $arrTokens;
        }

        
    /**
         * Check if the checkout can be executed
         *
         * @return bool
         */
        
    protected function canCheckout()
        {
            
    // Redirect to login page if not logged in
            
    if ($this->iso_checkout_method == 'member' && FE_USER_LOGGED_IN !== true) {

                
    /** @type \PageModel $objJump */
                
    $objJump = \PageModel::findPublishedById($this->iso_login_jumpTo);

                if (
    null === $objJump) {
                    
    $this->Template          = new \Isotope\Template('mod_message');
                    
    $this->Template->type    'error';
                    
    $this->Template->message $GLOBALS['TL_LANG']['ERR']['isoLoginRequired'];

                    return 
    false;
                }

                
    $objJump->loadDetails();
                \
    Controller::redirect($objJump->getFrontendUrl(null$objJump->language));

            } elseif (
    $this->iso_checkout_method == 'guest' && FE_USER_LOGGED_IN === true) {
                
    $this->Template          = new \Isotope\Template('mod_message');
                
    $this->Template->type    'error';
                
    $this->Template->message $GLOBALS['TL_LANG']['ERR']['checkoutNotAllowed'];

                return 
    false;
            }

            
    // Return error message if cart is empty
            
    if (Isotope::getCart()->isEmpty()) {
                
    $this->Template          = new \Isotope\Template('mod_message');
                
    $this->Template->type    'empty';
                
    $this->Template->message $GLOBALS['TL_LANG']['MSC']['noItemsInCart'];

                return 
    false;
            }

            
    // Insufficient cart subtotal
            
    if (Isotope::getCart()->hasErrors()) {
                if (
    $this->iso_cart_jumpTo 0) {

                    
    /** @type \PageModel $objJump */
                    
    $objJump = \PageModel::findPublishedById($this->iso_cart_jumpTo);

                    if (
    null !== $objJump) {
                        
    $objJump->loadDetails();
                        \
    Controller::redirect($objJump->getFrontendUrl(null$objJump->language));
                    }
                }

                
    $this->Template          = new \Isotope\Template('mod_message');
                
    $this->Template->type    'error';
                
    $this->Template->message implode("</p>\n<p class=\"error message\">"Isotope::getCart()->getErrors());

                return 
    false;
            }

            return 
    true;
        }

        
    /**
         * Return array of instantiated checkout step modules
         *
         * @return array
         */
        
    protected function getSteps()
        {
            
    $arrSteps = array();

            foreach (
    $GLOBALS['ISO_CHECKOUTSTEP'] as $strStep => $arrModules) {
                foreach (
    $arrModules as $strClass) {

                    
    $objModule = new $strClass($this);

                    if (!
    $objModule instanceof IsotopeCheckoutStep) {
                        throw new \
    RuntimeException("$strClass has to implement Isotope\Interfaces\IsotopeCheckoutStep");
                    }

                    if (
    $objModule->isAvailable()) {
                        
    $arrSteps[$strStep][] = $objModule;
                    }
                }
            }

            return 
    $arrSteps;
        }

        
    /**
         * Generate checkout step navigation
         *
         * @param array $arrStepKeys
         *
         * @return array
         */
        
    protected function generateStepNavigation(array $arrStepKeys)
        {
            
    $arrItems  = array();
            
    $blnPassed true;

            foreach (
    $arrStepKeys as $step) {

                
    $blnActive false;
                
    $href      '';
                
    $class     standardize($step);

                if (
    $this->strCurrentStep == $step) {
                    
    $blnPassed false;
                    
    $blnActive true;
                    
    $class .= ' active';
                } elseif (
    $blnPassed) {
                    
    $href = static::generateUrlForStep($step);
                    
    $class .= ' passed';
                }

                
    $arrItems[] = array
                (
                    
    'isActive' => $blnActive,
                    
    'class'    => $class,
                    
    'link'     => ($GLOBALS['TL_LANG']['MSC']['checkout_' $step] ? : $step),
                    
    'href'     => $href,
                    
    'title'    => specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['checkboutStepBack'], ($GLOBALS['TL_LANG']['MSC']['checkout_' $step] ? : $step))),
                );
            }

            
    // Add first/last classes
            
    RowClass::withKey('class')->addFirstLast()->applyTo($arrItems);

            return 
    $arrItems;
        }

        
    /**
         * Redirect to given checkout step
         *
         * @param string                   $strStep
         * @param IsotopeProductCollection $objCollection
         */
        
    public static function redirectToStep($strStepIsotopeProductCollection $objCollection null)
        {
            \
    Controller::redirect(static::generateUrlForStep($strStep$objCollection));
        }

        
    /**
         * Generate frontend URL for current page including the given checkout step
         *
         * @param string                   $strStep
         * @param IsotopeProductCollection $objCollection
         * @param \PageModel               $objTarget
         *
         * @return string
         */
        
    public static function generateUrlForStep($strStepIsotopeProductCollection $objCollection null, \PageModel $objTarget null)
        {
            if (
    null === $objTarget) {
                global 
    $objPage;
                
    $objTarget $objPage;
            }

            if (!
    $GLOBALS['TL_CONFIG']['useAutoItem'] || !in_array('step'$GLOBALS['TL_AUTO_ITEM'])) {
                
    $strStep 'step/' $strStep;
            }

            
    $strUrl = \Controller::generateFrontendUrl($objTarget->row(), '/' $strStep$objTarget->language);

            if (
    null !== $objCollection) {
                
    $strUrl = \Haste\Util\Url::addQueryString('uid=' $objCollection->uniqid$strUrl);
            }

            return 
    $strUrl;
        }
    }
    Für Verbesserungsvorschläge bis ich sehr Dankbar
    supahr
    Geändert von supahr (01.10.2015 um 07:51 Uhr)

Aktive Benutzer

Aktive Benutzer

Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1)

Lesezeichen

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •