From 3453461b19b01e206381105c4bf9f71470b29720 Mon Sep 17 00:00:00 2001 From: Konrad Rosenbaum Date: Mon, 12 Feb 2018 00:06:34 +0100 Subject: [PATCH] draft for seatplans Change-Id: I8d2ec513094d434441d45f0cc8c223fd25d75ffa --- Makefile | 1 + commonlib/misc/config.h | 6 + commonlib/widgets/seatplanview.cpp | 307 +++++++++++++ commonlib/widgets/seatplanview.h | 109 +++++ commonlib/widgets/widgets.pri | 6 +- doc/index.html | 9 +- doc/seatplans.txt | 92 +++- doc/template.html | 5 +- doc/web/config.html | 21 + doc/web/flow.html | 197 +++++++++ doc/web/flow.odg | Bin 0 -> 30911 bytes doc/web/flow.svg | 825 +++++++++++++++++++++++++++++++++++ doc/web/variables.html | 172 ++++++++ iface/wext/seatplanobj2.h | 4 +- magicsmoke.aurora | 2 +- pack | 2 +- seatplangui/seatplangui.pro | 17 + seatplangui/spgui.cpp | 121 +++++ seatplangui/spgui.h | 39 ++ src/dialogs/eventedit.cpp | 4 +- src/dialogs/pricecatdlg.cpp | 1 + taurus | 2 +- tests/docker/qtenv/Dockerfile | 2 +- tzone | 2 +- wob/classes/seatplan.wolf | 35 ++- wob/magicsmoke.wolf | 90 ++-- www/inc/classes/basevars.php | 51 +-- www/inc/rendering/cart_listing.php | 42 -- www/inc/rendering/event_listing.php | 15 - www/inc/wext/customer.php | 20 +- www/index.php | 9 +- www/template/en/login.html | 23 +- 32 files changed, 2019 insertions(+), 212 deletions(-) create mode 100644 commonlib/misc/config.h create mode 100644 commonlib/widgets/seatplanview.cpp create mode 100644 commonlib/widgets/seatplanview.h create mode 100644 doc/web/config.html create mode 100644 doc/web/flow.html create mode 100644 doc/web/flow.odg create mode 100644 doc/web/flow.svg create mode 100644 doc/web/variables.html create mode 100644 seatplangui/seatplangui.pro create mode 100644 seatplangui/spgui.cpp create mode 100644 seatplangui/spgui.h diff --git a/Makefile b/Makefile index 949b933..b11bea6 100644 --- a/Makefile +++ b/Makefile @@ -142,6 +142,7 @@ sdoc: $(MAKE) -C pack doc cd tzone && $(DOXYGEN) Doxyfile cd taurus && ./build-src-doc.sh + cd twig/doc && touch contents.rst && sphinx-build -C . ../../doc/twig lrelease: cd src && $(LREL) smoke.pro diff --git a/commonlib/misc/config.h b/commonlib/misc/config.h new file mode 100644 index 0000000..fd9bd8c --- /dev/null +++ b/commonlib/misc/config.h @@ -0,0 +1,6 @@ +/*this is an autogenerated config.h for libqrencode as used by qrcode.* */ +#define STATIC_IN_RELEASE static +#define VERSION "4.0.0" +#define MAJOR_VERSION 4 +#define MINOR_VERSION 0 +#define MICRO_VERSION 0 diff --git a/commonlib/widgets/seatplanview.cpp b/commonlib/widgets/seatplanview.cpp new file mode 100644 index 0000000..6b1b301 --- /dev/null +++ b/commonlib/widgets/seatplanview.cpp @@ -0,0 +1,307 @@ +// +// C++ Implementation: seatplan viewer +// +// Description: +// +// +// Author: Konrad Rosenbaum , (C) 2017 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#include "seatplanview.h" + +#include +#include +#include +#include + +MSeatPlanView::MSeatPlanView(const MOSeatPlan&p){resetPlan(p);} + + +static inline QRect parseGeo(const QString s) +{ + QStringList g=s.trimmed().split(' '); + if(g.size()!=4)return QRect(); + QRect r(g[0].toInt(), g[1].toInt(), g[2].toInt(), g[3].toInt()); + return r; +} + +enum ColorRole {Background=0,TkBackground,WBackground,UBackground,Foreground,TkForeground,WForeground,UForeground,MaxColor=UForeground}; + +static inline QVector getColors(const MOSeatPlan&plan,const MOSeatPlanGroup&group,const MOSeatPlanRow&row) +{ + auto getcol=[](Nullableps,Nullablegs,Nullablers,QColor fb)->QColor{ + if(!rs.isNull() && !rs.data().trimmed().isEmpty())return QColor(rs); + if(!gs.isNull() && !gs.data().trimmed().isEmpty())return QColor(gs); + if(!ps.isNull() && !ps.data().trimmed().isEmpty())return QColor(ps); + return fb; + }; + QVector pcolor((int)MaxColor+1); + pcolor[Foreground]=getcol(plan.fgcolor(),group.fgcolor(),row.fgcolor(),QColor(Qt::black)); + pcolor[Background]=getcol(plan.bgcolor(),group.bgcolor(),row.bgcolor(),QColor(Qt::white)); + pcolor[TkForeground]=getcol(plan.tkfgcolor(),group.tkfgcolor(),row.tkfgcolor(),pcolor[Foreground]); + pcolor[TkBackground]=getcol(plan.tkcolor(),group.tkcolor(),row.tkcolor(),QColor(Qt::darkGray)); + pcolor[WForeground]=getcol(plan.wfgcolor(),group.wfgcolor(),row.wfgcolor(),pcolor[Foreground]); + pcolor[WBackground]=getcol(plan.wcolor(),group.wcolor(),row.wcolor(),QColor(Qt::darkGreen)); + pcolor[UForeground]=getcol(plan.ufgcolor(),group.ufgcolor(),row.ufgcolor(),pcolor[Foreground]); + pcolor[UBackground]=getcol(plan.ucolor(),group.ucolor(),row.ucolor(),QColor(Qt::gray)); + return pcolor; +} + +void MSeatPlanView::resetPlan(const MOSeatPlan&p) +{ + mplan=p; + + //parse plan for seats + mrows.clear(); + const QRect mr=QRect(QPoint(0,0),mplan.qSize()); + for(const auto&grp:mplan.Group()){ + QRect gr=parseGeo(grp.geo()); + if(!gr.isValid())gr=mr; + qDebug()<<"scanning group"<contains(':')) + qDebug()<<"WARNING: group"<contains(':')) + qDebug()<<"WARNING: row"<0) + qDebug()<<"WARNING: row"< elements! Ignoring explicit seats. Please fix the plan!"; + const int cap=row.capacity().data(0); + const int first=row.first().data(1); + QSize ssz=QSize(rr.width()/cap-1,rr.height()); + if(ssz.width()>ssz.height())ssz.setWidth(ssz.height()); + qDebug()<<"generating"<1?cap-1:1) + ssz.width(); + for(int i=0;i0) + mrows< rows -> seats + p.setPen(QColor(Qt::blue)); + p.setBrush(QColor(Qt::yellow)); + for(const auto&row:mrows) + for(const auto&seat:row.seats){ +// qDebug()<<"painting seat"<pos().x(); + int y=event->pos().y(); + x-=mclip.x(); + y-=mclip.y(); + x/=mscale; + y/=mscale; + //find matching seat + for(auto&row:mrows) + for(auto&seat:row.seats) + if(seat.contains(QPoint(x,y))){ + qDebug()<<"found seat"<pos(); + event->setAccepted(true); + //button press... + if((event->button()&Qt::LeftButton)==Qt::LeftButton){ + if(seat.state==Free){ + seat.state=Wanted; + emit seatSelected(SelectedSeat(row.gid,row.gname,row.id,row.name,seat.id)); + emit selectionChanged(); + }else if(seat.state==Wanted){ + seat.state=Free; + emit seatDeselected(SelectedSeat(row.gid,row.gname,row.id,row.name,seat.id)); + emit selectionChanged(); + } + //TODO: handle multiple available price categories? + } + if(mrightIsBlock && (event->button()&Qt::RightButton)==Qt::RightButton){if(seat.state==Blocked)seat.state=Free;else seat.state=Blocked;} + if(mrightIsBlock && (event->button()&Qt::MiddleButton)==Qt::MiddleButton){if(seat.state==Unavailable)seat.state=Free;else seat.state=Unavailable;} + //done + update(); + return; + } +} +void MSeatPlanView::mouseReleaseEvent(QMouseEvent *event) +{ + QWidget::mouseReleaseEvent(event); +} + + +void MSeatPlanView::resizeEvent(QResizeEvent *event) +{ + //calculate scaled plan size + QSize ps=mplan.qSize(); + if(ps.width()>0 && ps.height()>0){ + QSize ws=event?event->size():size(); + double f=(double)ws.width()/ps.width(); + if((f*ps.height())>ws.height())f=(double)ws.height()/ps.height(); + mscale=f; + mclip.setX((ws.width()-f*ps.width())/2); + mclip.setY((ws.height()-f*ps.height())/2); + mclip.setWidth(f*ps.width()); + mclip.setHeight(f*ps.height()); +// qDebug()<<"widget size"< MSeatPlanView::selection()const +{ + QListresult; + for(const auto&row:mrows) + for(const auto&seat:row.seats) + if(seat.state==Blocked) + result<, (C) 2017 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef SMOKE_SEATPLANVIEW_H +#define SMOKE_SEATPLANVIEW_H + +#include +#include + +#include + +#include "MOSeatPlan" + +#include "commonexport.h" + +class MAGICSMOKE_COMMON_EXPORT MSeatPlanView:public QWidget +{ + Q_OBJECT +public: + MSeatPlanView(const MOSeatPlan&); + + void resetPlan(const MOSeatPlan&); + void setBlocked(const QStringList&); + void setWanted(const QStringList&); + QStringList wanted()const; + + ///used by test gui only: if true then right-click blocks or unblocks instead of "wanting", middle click sets to Unavailable + void setRightIsBlock(bool b){mrightIsBlock=b;} + ///if true it is not possible to select seats + void setReadOnly(bool b){mreadOnly=b;} + + MOSeatPlan plan()const{return mplan;} + + class SelectedSeat{ + QString mgroupId,mrowId,mgroupName,mrowName; + int mseatId=0; + friend class MSeatPlanView; + SelectedSeat(QString gid,QString gn,QString rid,QString rn,int sid):mgroupId(gid),mrowId(rid),mgroupName(gn),mrowName(rn),mseatId(sid){} + public: + SelectedSeat()=default; + SelectedSeat(const SelectedSeat&)=default; + + SelectedSeat& operator=(const SelectedSeat&)=default; + bool operator==(const SelectedSeat&s)const{return mgroupId==s.mgroupId && mrowId==s.mrowId && mgroupName==s.mgroupName && mrowName==s.mrowName && mseatId==s.mseatId;} + + QString groupId()const{return mgroupId;} + QString groupName()const{return mgroupName;} + QString rowId()const{return mrowId;} + QString rowName()const{return mrowName;} + int seatId()const{return mseatId;} + + QString fullId()const{return QString("%1:%2:%3").arg(mgroupId).arg(mrowId).arg(mseatId);} + }; + QListselection()const; +signals: + void seatSelected(SelectedSeat); + void seatDeselected(SelectedSeat); + void selectionChanged(); + +private slots: + void checkSelection(SelectedSeat); + +protected: + void paintEvent(QPaintEvent*)override; + void mousePressEvent(QMouseEvent *event)override; + void mouseReleaseEvent(QMouseEvent *event)override; + void resizeEvent(QResizeEvent *event)override; + + +private: + MOSeatPlan mplan; + double mscale=0.0; + QRect mclip; + bool mrightIsBlock=false,mreadOnly=false; + enum SeatState {Free,Blocked,Wanted,Unavailable}; + struct Seat { + Seat(){} + Seat(int i,QTransform f,QSize s):id(i),position(f),size(s){} + Seat(const Seat&)=default; + Seat& operator=(const Seat&)=default; + + bool contains(QPoint); + + int id=0; + SeatState state=Free; + QTransform position; + QSize size; + }; + struct Row { + Row(QString i,QString n,QString gi,QString gn):id(i),name(n),gid(gi),gname(gn){if(name.isEmpty())name=id;if(gname.isEmpty())gname=gid;} + Row(const Row&)=default; + Row& operator=(const Row&)=default; + QString id,name,gid,gname; + QList seats; + QVector colors; + }; + QList mrows; +}; + +#endif diff --git a/commonlib/widgets/widgets.pri b/commonlib/widgets/widgets.pri index 40f1ed2..1063557 100644 --- a/commonlib/widgets/widgets.pri +++ b/commonlib/widgets/widgets.pri @@ -3,13 +3,15 @@ HEADERS += \ widgets/listview.h \ widgets/treeview.h \ widgets/barcodeline.h \ - widgets/eventview.h + widgets/eventview.h \ + widgets/seatplanview.h SOURCES += \ widgets/centbox.cpp \ widgets/listview.cpp \ widgets/treeview.cpp \ widgets/barcodeline.cpp \ - widgets/eventview.cpp + widgets/eventview.cpp \ + widgets/seatplanview.cpp INCLUDEPATH += ./widgets diff --git a/doc/index.html b/doc/index.html index eff0da3..3527240 100644 --- a/doc/index.html +++ b/doc/index.html @@ -8,7 +8,7 @@

Copyright

-© Konrad Rosenbaum, 2007-2016
+© Konrad Rosenbaum, 2007-2017
© Peter Keller, 2007/8

MagicSmoke is licensed under GPL (Client), AGPL (Server), @@ -60,6 +60,11 @@ This software comes with no warranty at all. You use it at your own risk.

  • Building and Installing MagicSmoke from Source
  • Installing pre-packaged MagicSmoke
  • Creating a Server Instance
  • +
  • Web-Client Configuration:
  • Configuring the Client
  • Templates: - \ No newline at end of file + diff --git a/doc/seatplans.txt b/doc/seatplans.txt index 13c7ac1..540fc75 100644 --- a/doc/seatplans.txt +++ b/doc/seatplans.txt @@ -1,12 +1,12 @@ Seat-Plans =========== -Specification Version (YYYYnn): 201601 +Specification Version (YYYYnn): 201801 Server Side Minimum: --------------------- - + @@ -65,7 +65,7 @@ one of the following values: To number seats by row add Row-elements to the Group tag: - + @@ -87,8 +87,11 @@ match the sum of row capacities. defines a single row of seats inside this group id - a free form ID for the row, must only contain letters and digits, may be empty, but no spaces, the default is empty - capacity - number of seats in this row - first - a number if counting does not start at 1 + capacity - number of seats in this row, the seats are auto-generated left-to-right + first - number of the first seat if counting does not start at 1 + + defines a single seats inside a row, only used if capacity is missing from Row, + can be used to give rows unusual geometries If two rows have identical IDs then the seat numbers in those rows must be unique. E.g. above: in the Expensive group above both rows use the empty ID, one of them @@ -122,14 +125,23 @@ Graphical Seat Plans --------------------- This part is never evaluated by the server, only by displays: + GUI: rotation of the row rectangle - + - - + + + - + + + + + + + + + @@ -139,17 +151,26 @@ This part is never evaluated by the server, only by displays: New Attributes: - SeatPlan/size - relative canvas size, the actual canvas will be scaled to fit the display + SeatPlan/size - relative canvas size, the actual canvas will be scaled to fit the display; if size is missing all graphical elements are ignored! geo - coordinates "x y w h" - top left corner X and Y (canvas top left is 0,0), width and height Group/geo - geometry relative to the canvas Row/geo - geometry relative to the Group - bgcolor - (Plan/Group/Row) background or fill color - fgcolor - (Plan/Group/Row) foreground or text color + Row/closegap - hint to the gap closing algorithm (see below) + Seat/geo - geometry relative to the Row + bgcolor - (Plan/Group/Row) background or fill color for seats + fgcolor - (Plan/Group/Row) foreground or line & text color for seats tkcolor - (Plan/Group/Row) fill color for seats that are taken (on Plan/Group it sets the default) - linecolor - (Group/Row) the color of the outline; defaults to bgcolor - linewidth - (Group/Row) the width of the outline; defaults to 1 pixel - Group/angle - rotate the group (value in degrees) - Row/curve - row curvature, see below + tkfgcolor - (Plan/Group/Row) foreground or line & text color for seats when tkcolor is used, fallback is fgcolor + wcolor - (Plan/Group/Row) fill color for seats that are wanted by the user (on Plan/Group it sets the default) + wfgcolor - (Plan/Group/Row) foreground or line & text color for seats when wcolor is used, fallback is fgcolor + angle - (Group/Row/Seat) rotate the group (value in degrees) + name - (Group/Row) human readable name for the group or row + + Note: within each specific Row element the use of the capacity attribute and Seat elements are exclusive. Only + one or the other is permitted. Use of both leads to undefined behavior. + + Note: Groups and Rows are not painted themselves, they are organizational elements. If you want to trace the outline + of a group or row you need to do this with additional Background elements (see below). Colors: all web colors can be used by name, numeric values can be specified in hex either as "#rgb" or "#rrggbb". @@ -172,28 +193,53 @@ Curvature of Rows: a series of points at which to align seats... ? TODO: define trapezes(?), curved rows, etc. +The closegap algorithm: this algorithm makes sure there are no unsightly gaps between guests. +Per default this algorithm is off (value of zero "0"). If active then the closegap attribute +contains the maximum number of empty seats in a gap that will be closed for the sale to +continue. The algorithm always works on rows by moving seats wanted by the customer closer to already +occupied seats or closer to the side of the row. The algorithm choses the closest occupied sear or isle. + +For example (O=empty, #=already sold, +=wanted by customer), assuming closegap=1: +OOOOO+OO -> sale will be allowed as is +OOOOOO+O -> will move the customer to the right +O+OOOOOO -> will move the customer to the left +##O++OOO -> will move the customer left +##OO++OO -> will be allowed +##OOOO++ -> will be allowed +OO+O+OOO -> will move seats together, closing gap +OO+OO+OO -> will be allowed +OO+#+OOO -> will be allowed (gap is not empty) +assuming closegap=2: +OOOOO+OO -> will be moved right +OOOO+OOO -> will be allowed +OO+OO+OO -> gap will be closed towards center +OO+##+OO -> will be allowed (gap not empty) + Additional Graphics -------------------- -The background of the seat plan can be defined in an additional element: +The background of the seat plan can be defined in aDas Stuhlhaus Dresden +4,0 (6) · Möbelgeschäft +Dresden · 0351 5637610 +Geöffnet bis 18:00n additional element: - + - + - + - + base64-encoded data @@ -205,7 +251,7 @@ Attributes: angle - rotation in degree Background:text/fontsize - font size in relative points of the seat plan, automatically scaled Background:text/content - textual content - Background:image/image - link to an Image tag + Background:image/content - link to an Image tag Image/Data - contains the actual image data base64 encoded Image/name - link name of the image, usually derived from the original file name diff --git a/doc/template.html b/doc/template.html index d9f9358..91f578d 100644 --- a/doc/template.html +++ b/doc/template.html @@ -7,7 +7,7 @@ The web user interface is constructed with templates with just a few dynamic values filled in. Those templates are found in the template/* directories. These templates are normal HTML or text with some special constructs to fill in the blanks.

    -Please see the Twig template engine documentation for syntax details. +Please see the Twig template engine documentation for syntax details.

    Template Files

    @@ -34,5 +34,6 @@ Allowing the web server write access to a directory opens a potential security h
  • comment out the "setAdminPassCode" line in config.php
  • + - \ No newline at end of file + diff --git a/doc/web/config.html b/doc/web/config.html new file mode 100644 index 0000000..1131aeb --- /dev/null +++ b/doc/web/config.html @@ -0,0 +1,21 @@ + + +Web Configuration + + +

    Web Configuration

    + +

    This chapter explains how to configure the web interface of MagicSmoke.

    + +

    Main Configuration: config.php

    + + +

    Language Configuration: template/*/format.cfg

    + +template/format.cfg +template/LANG/format.cfg +template/LANG/lang.po + + + + diff --git a/doc/web/flow.html b/doc/web/flow.html new file mode 100644 index 0000000..3da39d7 --- /dev/null +++ b/doc/web/flow.html @@ -0,0 +1,197 @@ + + +Web Function Flow + + +

    Web Function Flow

    + +

    This chapter deals with the functionality implemented in the Web Interface for customers and how it uses web templates. Each template is then documented in the Template Reference.

    + +

    The following diagram shows the flow of HTTP-calls and display pages in MagicSmoke:

    +

    + +

    Each box refers to one call to index.php?mode=label with the label of the box being the value of the mode parameter. The following conventions are used: +

      +
    • dark green circle: starting point. This is called if/when no mode parameter is present. It behaves as if mode was set to the configured starting mode.
        +
      • (*) At the moment the system always starts at mode=tickets, this will be configurable in later versions.
      • +
    • +
    • light blue box: This is a starting mode, i.e. it can be linked from the main layout and will display fine without any other parameters or session state.
        +
      • (-) the shop mode is currently not implemented and redirects to the starting point instead.
      • +
    • +
    • light green box: this mode needs session state to display correctly.
    • +
    • yellow box: this mode is transitory, i.e. it redirects immediately to the next green box, unless there is an error.
    • +
    • dark green box: this mode can be called from the main layout, but redirects immediately.
    • +
    • red box: an admin mode, this can only be called by manipulating the URL bar and needs to be configured to work.
    • +
    • dark blue box: called externally with a code - e.g. from an email.
    • +
    • dark grey box: to be implemented...
    • +
    • black arrow: web call - either by click or redirection.
    • +
    • green arrow: internal redirection, no round trip through the browser.
    • +

    + +

    Modes/Functions

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ModeTemplatesDescription
    addcoupon-...
    cartcart.html...
    changeDeliveryAddress*.html...
    changeInvoiceAddress*.html...
    checkoutcheckout.html...
    compile-...
    customerAddressEdit**.html...
    customerAddresses**.html...
    customerData**.html...
    customerLogin*cart.html...
    customerLoginOrder-...
    customerRegistrationOrder-...
    customerReset**...
    customerResetLoginresetlogin.html
    reseterror.html
    ...
    customerResetOrder-...
    eventDetailseventdetails.html...
    eventOrder-...
    indexindex.html...
    mycart-...
    orderDetail**.html...
    orderDownload**...
    orderList**.html...
    orderLogin*.html...
    payvoucher**...
    placeorder*.html...
    removeItem-...
    setlanguage-...
    shop**.html...
    ticketstickets.html...
    vouchersvouchers.html...
    voucherOrder-...
    + + + diff --git a/doc/web/flow.odg b/doc/web/flow.odg new file mode 100644 index 0000000000000000000000000000000000000000..367977baa2eaa4f2cb15db75c34121a43cb65753 GIT binary patch literal 30911 zcmV*5Ky<%QO9KQH000O805>o^OP>QS#47**04o3h00;m80Bvb)WpsIPWnpk|Y-wX* zbZKvHFLrKZE^lFTX>%@baAj^}Z)0_BWo~pXXL4b1XlY|}P)h>@6aWAS001{IJWHEU zzFN3Q006j1000;O002~Ib!}p9VQFl0FLY>iZDMX=X>2ZVZfA*5PDc$28VUda01Zh< zL{b0%0RR910ILWB00006yw#`x07!pHL_t(|ob9~{cwAR`H~!vr_C@=?%a$eE@)|p~ z6S5H}gh0X;N@-F8v?Ndd+zqW=iT2UB7`7!z@u>-$HrtE_%ny& z=aEM?$es?x+@6@7#yVM^`tw*4WKV}VW&p-&ilRLE3@|^LU>(bIeBP+YBj>m*D$ucG$A0RD9ju!k31HS+%N#8 zey|*>+qYl!kq>{YsIXAiHI5e;wT6@7BSS|vmv2@KWnL7F8Ada;lVd0M)b25nDeyv0 zt~q;T`qbvDgR%Yd%GwCw`0>09z1-VWzAC{t|>`5w3yE1rRPCo*5pg+av$S zr$$o>SvG_4_ySBeSe^%#A+lnCOe;3?6;4r@VFHIR_5b3T-#_#(Z_%_#QQ&^{?B6%n zUi-+0zrVhrhV5_S|L`X=FL^FX+T=g>rBiYQ661ISGY&`T4=Y;M^~`ov*0w;T$U-QO z4TAMLd}cx56N;c}gaHsy%&5fUh?17#33di{$sAMO$fhiT!v^$_JoPU-WW@lpS+SYn z227D>1|~lL?|&r=lRl4N7)a##v;F%%{ml>k=jVTpi2xG>4`kw}#K>iGltsaYVc-qj zFh>G09m@@R7J(CMq>X>Q{u3i(N=%De?x6A_mt7W^rKgLy~*Or4=n2r_I?{JkiO zfk5DQPyFt-y|@4DxnGa=_4wU_GHK{>;6$)Hx#L~Ge*TqX*KcVzGA90M95tn8@OYl( zUOGMcuTP$H*hKt;iHS&*2)T0A&mHZ+bVpsONHtJfmFHt`UXN)Lh^E2A4C7cg4Urd~ z9zFgh&raS4rV4=h(4f?>;hzDH1GoV;1BB=Sc)K5q8{m8#l98{!rDNi7Wr=fOaM0s& z#1cYf@lP8nhBcK729c*2;t>$-WY|~)@Pio(!-kCj3Jgs^Z5`-IeEZ;bbD=0YH}oFN z06<*87rD+nmJnGn0G_%8K{#;Wz~tm)Au(-r_6qU$cDud1yZbYr`E)X>O-K`%ypofc z9w9ac&Qg%@)F|i)B{prqX&ePni1@JP!8h}cG{|j&i;C6tQ0LB2e07NZ+GHhpZ%Tui>J`8?eOFi(6Sk-i{a=|sB3|?Cit%(Kr99hKLmaVlRDi00a)wC zx2AQTI2s?Z2SdZPwRLj`CiA$$n4b!?W5*860MjJ+wr$(q{N^`f4te6mm;UotpY;_1 zC-dG46RR#tT=)Cx+nYB5nZlV!W&Cbnf0= z9y!4Lyg-c9m@dn{N!zTvuB&^m+J5Uz?|E|nw~A$6ltIyqun8Z({h_L2GI3_|%xoNW z?D#Q(=bt)y-rwi+xx_?5sc3T=y2^_-tiQW!;Sa5J z83|i0357d)_TE_X1wnL+4r3BOy8=*57>-h7fCFa<423{&6AeqJ19$Xbu2h1N`s$K4 zcHi@RIeA=u%#Q(>;O6<1bCA0Ec;+e*1gu}fmp<@Vfv0lc%iq_{*xJgg-+aS+-*(gO z2}O0-JDI~Fo>E~b(%3{wZQy`;K}KcYqrB7(H^op6NEFS3Cs4~rp;_)OtZ~-oA@^Tzjgn%%QFhv|3c-_K#Z&{n=H3%0Ai?PwzuqHy4 zC9Zk-UP!_cR$Zvih$9YAfgNs_4To(WE}RU9?3E7Q<&hoeU;}q9$UM-4ZEmD3kd>(_h*pyIXP_h^b!8 z6Hj8#LR$XJL6RID!9=DN9LNS_e>N%|IDKI}3+iQGL`3(`+q=sbIg13RqO&X}fP}u# zfJ-{58(Jzx#BqjcU}iWzeB7jQS-aid8|#H2Ou&R?1W$uS+kbj8@Z5AsQdxPxMR-jK@UkL&0;nU9?~0xz7kx{ORz3jto4UZkh!i`_|@S znre2zB%R8Mz#_urh62H{&`FYp#$z5M{u#U>%b+T$(_#bKW&=e5!vIMFL#Mle?sGQQ zmDQNY;L=#mqF`ufXxp}JD_0KSXDdn4=;&yFe?Mk`%cL&Oh8#-(>UA+Y$2(O+W#7!< z`(`>Hr2VRh)^Qq!@tr^QOenm>ldK%TA_B9J<~#(Gl3hiHfk_k#f$(JXzP;wP8!HTx zWL(a;5}o6P{?YHQt@(&U7NT*(;UE^ZnuOkdsH+D&I2JmQ031CAt?f|afq_9NDh9iq z0|T8KZ5fz2TwUTcGpc!V_@HtHc2zc;t+=>&sjp?>U}S^sz_GD0SlKEN^&XxJp2Pnd z3mfO!lXAJLs-2ykrKP2cqIf)>KmPGgzy0lR?z{hQj6^&!JQ7JxYC05@R>p$EN;0Nt z$R_h3xp_UVYDUr8Jw3fWb?#_1X;}1+hjlm{r%s)E;DNvSzz06J?Rt_z`jf}`xMo&V zapgrYHVVhOVE5H<<~S%kRFpzpE&TX9u&xcxbisHC*0pf!+Kj5Yz{wLQm9PhRv8k~U z=3fSIWsu=Co42X6e2|%IeC5x6Sro^Hf(DyF5_0D4GXZS5f*?xau|C=6h);%;$e3;z zu7V1m$3ArWWmid~$Lop&dz7TYagLc!3=0sq+r4GW7N6Iv8^~V>m9?hRfz%|iHp

    l3KzNDA-hW+l38vencb9 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 ZZ+KN+>K*j&-B8{>Y;O`oJNw_?{uik@;?4j7 literal 0 HcmV?d00001 diff --git a/doc/web/flow.svg b/doc/web/flow.svg new file mode 100644 index 0000000..736cf06 --- /dev/null +++ b/doc/web/flow.svg @@ -0,0 +1,825 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + init + + + + + + + + + tickets (*) + + + + + + + + + + + + + + + index + + + + + + + + + vouchers + + + + + + + + + shop + + + + + + + + + + + + + + + + + + + + + + + + + + + setlanguage + + + + + + + + + + + + + + + eventDetails + + + + + + + + + eventOrder + + + + + + + + + placeOrder + + + + + + + + + mycart + + + + + + + + + addcoupon + + + + + + + + + removeItem + + + + + + + + + cart + + + + + + + + + voucherOrder + + + + + + + + + checkout + + + + + + + + + orderLogin + + + + + + + + + customerLoginOrder + + + + + + + + + customerRegistrationOrder + + + + + + + + + customerResetOrder + + + + + + + + + changeDeliveyAddress + + + + + + + + + changeInvoiceAddress + + + + + + + + + compile + + + + + + + + + customerResetLogin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + customerData + + + + + + + + + customerAddresses + + + + + + + + + customerAddressEdit + + + + + + + + + orderList + + + + + + + + + orderDetail + + + + + + + + + payVoucher + + + + + + + + + customerLogin + + + + + + + + + customerReset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + orderDownload + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/web/variables.html b/doc/web/variables.html new file mode 100644 index 0000000..559586d --- /dev/null +++ b/doc/web/variables.html @@ -0,0 +1,172 @@ + + +Web Template Variable Reference + + +

      Web Template Variable Reference

      + +

      This chapter contains a reference for all templates and their available variables.

      + +

      Global Variables

      + +

      The following variables are available to all templates:

      + +/** \page templates Templates +\section tpl_base Base Variables + +There are some variables available for all templates.

      + +\param script.* variables contain URLs for different modes of the web site:
      + + + + + + + + + + + + + + +
      script.rootroot URL for the index.php script
      script.thisthe URL of the currently called script mode
      script.indexURL of the list index
      script.eventDetailsURL for event detail pages, append the event ID to it to complete it
      script.eventOrderURL for ordering tickets for a specific event, arguments are expected as POST or GET parameters
      script.vouchersURL for listing vouchers
      script.voucherOrderURL for ordering vouchers, arguments are expected as POST or GET parameters
      script.removeItemURL for removing tickets/vouchers/items from the cart, arguments are expected as POST or GET parameters
      script.shopURL for listing shop items
      script.cartURL of the cart, the cart must exist when calling it, otherwise a cookie error will be displayed
      script.mycartURL of the cart that transparently creates the cart if it does not exist yet
      script.checkoutURL to check out the cart
      script.setlanguageURL for setting the language cookie, add the language code to it to complete it

      +\param inputnames.* variables contain names for specific form input elements:
      + + + + + +
      inputnames.amountTicketsamount of tickets to be ordered
      inputnames.eventcontains the event ID
      inputnames.modecontains the display mode
      inputnames.cartidID of the cart of the current customer, usually the cart cookie is used instead

      +\param cartcookie variable contains the name of the cookie that contains the cart ID.

      +\param lang variable is an object of type LanguageManager - it represents translations done for the language the user has chosen.

      + +\param sessionid contains the ID of the current session as it is stored in a cookie and the session table. + +\param user.* contains basic data about the web user. + + + + + +
      user.namethe (family) name of the user
      user.firstnamethe first name of the user
      user.titlethe title of the user
      user.emailthe e-mail and login name of the user
      + +\section tpl_error error.html + +This template is used whenever an error occurs during processing. + +\param ErrorText the text to be shown for an error +\param ErrorTrace a full ASCII version of an exception trace. This is only filled if the $WebShowErrors option is set to true in the config.php file. +**/ + + +

      Global Web Layouts

      + +

      layout.html

      + +

      error.html

      + + + +

      Web Layouts

      + +

      cart.html

      + +

      The cart.html template is used to render the current cart of the customer.

      + +\param cart an object of the web type WebCart (PHP: WOWebCart) that represents the entire cart of the user. +\param shipping and array of web objects of type Shipping (PHP: WOShipping) which represent all available shipping types that customers can chose from. +\param forceshipping a boolean that contains whether customers must chose a shipping type. + +\subsection tpl_cartext Cart Extensions + +Some of the sub-objects of cart have extended attributes in addition to those generated by Wob: + +\param cart.isempty true if the cart is empty +\param cart.totalsum the complete sum of all prices in the cart +\param cart.tickets[...].eventprice.*: +\param *.amountInputField name of the input field of a form that should contain the amount of tickets for ordering, see the example on how to use it +\param *.categoryIdWeb part of the URL query that represents this category, used for removing tickets from the cart, see the example on how to use it + +\section tpl_voucher Voucher Variables + +The voucher.html template is used to render the voucher selection form. + +\param voucherprices contains an array of valid voucher prices. + +

      carterror.html

      + +\section tpl_carterror Cart Error Variables + +The carterror.html template is used to when the cart expired or the customers browser lost the cookie. + +Just the \ref tpl_base Base Variables +are available. +**/ + +

      changeaddress.html

      + +

      checkout.html

      + +The checkout.html template is used to render the current cart of the customer. + +\param cart an object of the web type WebCart (PHP: WOWebCart) that represents the entire cart of the user. +*/ + +

      eventdetails.html

      + +The eventdetails.html template is used to render a single event and to offer the user to buy tickets for it. + +\param event the event being rendered; the event is of web object type Event (PHP: WOEvent) +*/ + +

      index.html

      + +The index.html template is used to render the list of events available to customers. + +\param events an array of the events available, starting at the current time, ordered by time; the events are of web object type Event (PHP: WOEvent) + + +

      login.html

      + +The login.html template is used to render the customer login and registration page. + +\param script.customerLogin the page to call for logging in +\param script.customerRegistration the page to call for registering a new customer +\param customer_name the name (mail) of the customer (or an empty string if unknown) + + +

      loginerror.html

      + + /** \page templates Templates + \section tpl_logerr Login Error + + The loginerror.html template is used to render customer login and registering errors. + + \param errorType the type of error: "login" - the login failed (wrong mail or password), "exist" - an account with the same mail already exists, "mismatch" - the new passwords do no match, "create" for unspecified errors during creation of the new account (e.g. missing or invalid parameters) + \param backUrl the URL to call back to the login page + */ + +

      placeorder.html

      + +

      reseterror.html

      + +

      resetlogin.html

      + +

      tickets.html

      + +

      vouchers.html

      + + + + +

      Mail Layouts

      + +

      ordermail.txt

      + +

      resetlogin.txt

      + + + + diff --git a/iface/wext/seatplanobj2.h b/iface/wext/seatplanobj2.h index ababa62..3ba457f 100644 --- a/iface/wext/seatplanobj2.h +++ b/iface/wext/seatplanobj2.h @@ -14,13 +14,15 @@ #define MAGICSMOKE_MOSEATPLANOBJ2_H #include "MOSeatPlanAbstract" +#include class MSIFACE_EXPORT MOSeatPlan:public MOSeatPlanAbstract { Q_GADGET WOBJECT(MOSeatPlan) public: - void f(){} + bool isGraphical()const{return !size().isNull() && size().data().trimmed()!="";} + QSize qSize()const{QStringList sz=size().data().trimmed().split(' ');if(sz.size()==2)return QSize(sz[0].toInt(),sz[1].toInt());else return QSize();} }; Q_DECLARE_METATYPE(MOSeatPlan) diff --git a/magicsmoke.aurora b/magicsmoke.aurora index 2169454..ed3dbd6 100644 --- a/magicsmoke.aurora +++ b/magicsmoke.aurora @@ -8,7 +8,7 @@ release: 2.11.33 (2-major, 11-minor, 33- patch) hint: "-" is ASCII code 0x2d, "." is 0x2e --> - + diff --git a/pack b/pack index fd67824..ea368b8 160000 --- a/pack +++ b/pack @@ -1 +1 @@ -Subproject commit fd67824a15e5e6732291c519c0db523afa92c8c8 +Subproject commit ea368b8d4490835ee46b1043d4b0fce403a18f01 diff --git a/seatplangui/seatplangui.pro b/seatplangui/seatplangui.pro new file mode 100644 index 0000000..08df9a4 --- /dev/null +++ b/seatplangui/seatplangui.pro @@ -0,0 +1,17 @@ +TEMPLATE = app +TARGET = seatplangui + +include(../basics.pri) +include(../iface/iface.pri) +include(../commonlib/commonlib.pri) + +#sources +SOURCES += spgui.cpp +HEADERS += spgui.h + +#pathes +INCLUDEPATH += . +DEPENDPATH += $$INCLUDEPATH + +#make sure the correct Qt DLLs are used +QT += gui widgets diff --git a/seatplangui/spgui.cpp b/seatplangui/spgui.cpp new file mode 100644 index 0000000..c8f5290 --- /dev/null +++ b/seatplangui/spgui.cpp @@ -0,0 +1,121 @@ +// +// C++ Implementation: seatplan viewer exe +// +// Description: simple executable that can view seat plans +// +// +// Author: Konrad Rosenbaum , (C) 2017-18 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "seatplanview.h" +#include "spgui.h" + +#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) +#define DOPRINTF 1 +#include +#else +#define DOPRINTF 0 +#endif + + +SPGUI::SPGUI() +{ + setWindowTitle("Seat Plan Viewer"); + setSizeGripEnabled(true); + + QVBoxLayout*vl; + QHBoxLayout*hl; + setLayout(vl=new QVBoxLayout); + vl->addLayout(hl=new QHBoxLayout,0); + hl->addWidget(fname=new QLineEdit,10); + QPushButton*p; + hl->addWidget(p=new QPushButton("..."),0); + connect(p,SIGNAL(clicked()),this,SLOT(openfile())); + hl->addWidget(p=new QPushButton("Reload"),0); + connect(p,SIGNAL(clicked()),this,SLOT(reload())); + vl->addWidget(view=new MSeatPlanView(MOSeatPlan()),10); + view->setRightIsBlock(true); +} + +void SPGUI::openfile() +{ + fname->setText(QFileDialog::getOpenFileName(this,"Open SeatPlan")); + reload(); +} + +void SPGUI::reload() +{ + //load file + QFile fd(fname->text()); + QDomDocument doc; + if(fd.open(QIODevice::ReadOnly)){ + qDebug()<<"Loading XML..."; + doc.setContent(&fd); + fd.close(); + }else + QMessageBox::warning(this,"Warning","Unable to open file."); + + //interpret + view->resetPlan(MOSeatPlan::fromXml(doc.documentElement())); +} + +class DGUI:public QDialog +{ + static QPointerinstance; + static void myMessageOutput(QtMsgType, const QMessageLogContext &, const QString &msg) + { +#if DOPRINTF + fprintf(stderr,"%s\n",msg.toUtf8().data()); +#endif + if(instance.isNull())return; + instance->view->append(msg); + instance->view->moveCursor(QTextCursor::End); + } + QTextBrowser *view; +public: + DGUI(QDialog*parent):QDialog(parent) + { + setWindowTitle("Debug Output"); + setSizeGripEnabled(true); + instance=this; + view=new QTextBrowser; + QHBoxLayout*hl; + QVBoxLayout*vl; + setLayout(vl=new QVBoxLayout); + vl->addWidget(view,1); + vl->addLayout(hl=new QHBoxLayout,0); + hl->addStretch(1); + QPushButton*p; + hl->addWidget(p=new QPushButton("Clear"),0); + connect(p,&QPushButton::clicked,this,[=](){this->view->clear();}); + setAttribute(Qt::WA_DeleteOnClose); + qInstallMessageHandler(myMessageOutput); + } +}; +QPointerDGUI::instance; + +int main(int ac,char**av) +{ + QApplication app(ac,av); + SPGUI g; + g.show(); + DGUI d(&g); + d.show(); + return app.exec(); +} diff --git a/seatplangui/spgui.h b/seatplangui/spgui.h new file mode 100644 index 0000000..a0d4aec --- /dev/null +++ b/seatplangui/spgui.h @@ -0,0 +1,39 @@ +// +// C++ Interface: seatplan viewer exe +// +// Description: simple executable that can view seat plans +// +// +// Author: Konrad Rosenbaum , (C) 2017-18 +// +// Copyright: See README/COPYING.GPL files that come with this distribution +// +// + +#ifndef SEATPLANGUI_H +#define SEATPLANGUI_H + +#include + +class MSeatPlanView; +class QLineEdit; + +///GUI main window +class SPGUI:public QDialog +{ + Q_OBJECT +public: + SPGUI(); +public slots: + ///open new seat plan file + void openfile(); + ///reload and redisplay current file + void reload(); +private: + ///file name of seat plan file + QLineEdit*fname; + ///viewer + MSeatPlanView*view; +}; + +#endif diff --git a/src/dialogs/eventedit.cpp b/src/dialogs/eventedit.cpp index f26f312..c121466 100644 --- a/src/dialogs/eventedit.cpp +++ b/src/dialogs/eventedit.cpp @@ -4,7 +4,7 @@ // Description: // // -// Author: Konrad Rosenbaum , (C) 2007-2013 +// Author: Konrad Rosenbaum , (C) 2007-2017 // // Copyright: See README/COPYING.GPL files that come with this distribution // @@ -447,7 +447,7 @@ void MEventEditor::selectSeatPlan() qDebug()<<"plans:"<addLayout(fl=new QFormLayout,1); fl->addRow(tr("Category Name:"),name=new QLineEdit(cat.name())); fl->addRow(tr("Category Abbreviation:"),abbr=new QLineEdit(cat.abbreviation())); + abbr->setMaxLength(10);//limited in DB fl->addRow(tr("Formula:"),form=new QLineEdit(cat.formula())); fl->addRow(tr("Flags:"),hl=new QHBoxLayout); hl->addWidget(flags=new QLabel(cat.flags()),1); diff --git a/taurus b/taurus index 57aebfd..12f3c87 160000 --- a/taurus +++ b/taurus @@ -1 +1 @@ -Subproject commit 57aebfda0d3ec0fc2d614c8c2d4ccb5263ef3028 +Subproject commit 12f3c8701cfc7a78be3f2ee2df46e7a11df3c00e diff --git a/tests/docker/qtenv/Dockerfile b/tests/docker/qtenv/Dockerfile index fa57f52..8a292cf 100644 --- a/tests/docker/qtenv/Dockerfile +++ b/tests/docker/qtenv/Dockerfile @@ -4,6 +4,6 @@ RUN apt-get update RUN apt-get install -y wget git RUN useradd -d /jenkins -m -s /bin/bash -u 12001 -U jenkins COPY start.sh /jenkins -RUN apt-get install -y libfontconfig1 libfreetype6 libx11-6 libxext6 libxfixes3 libxi6 libsm6 libice6 libdbus-1-3 libxrender1 libxcb1 libx11-xcb1 libxcb-glx0 libglib2.0-0 libstdc++6 zlib1g-dev libgcc1 libpcre3 libegl1-mesa libgl1-mesa-glx libgl1-mesa-dev libuuid1 libxau6 libxdmcp6 libexpat1 libpng12-0 libgbm1 libdrm2 libglapi-mesa libffi6 libudev-dev g++ gcc make doxygen qtchooser chrpath patchelf zip autoconf automake gettext +RUN apt-get install -y libfontconfig1 libfreetype6 libx11-6 libxext6 libxfixes3 libxi6 libsm6 libice6 libdbus-1-3 libxrender1 libxcb1 libx11-xcb1 libxcb-glx0 libglib2.0-0 libstdc++6 zlib1g-dev libgcc1 libpcre3 libegl1-mesa libgl1-mesa-glx libgl1-mesa-dev libuuid1 libxau6 libxdmcp6 libexpat1 libpng12-0 libgbm1 libdrm2 libglapi-mesa libffi6 libudev-dev g++ gcc make doxygen qtchooser chrpath patchelf zip autoconf automake gettext graphviz python-sphinx RUN echo deb http://ftp.de.debian.org/debian jessie-backports main contrib >>/etc/apt/sources.list ; apt-get update ; apt-get install -y -t jessie-backports openjdk-8-jre-headless CMD /bin/bash diff --git a/tzone b/tzone index d944922..6528fd2 160000 --- a/tzone +++ b/tzone @@ -1 +1 @@ -Subproject commit d94492246f5b98e0936f5a184e9533496d240a63 +Subproject commit 6528fd26d09b68b51e8d2edba8e71192a71244da diff --git a/wob/classes/seatplan.wolf b/wob/classes/seatplan.wolf index 89a1d91..df55097 100644 --- a/wob/classes/seatplan.wolf +++ b/wob/classes/seatplan.wolf @@ -8,16 +8,31 @@ --> + + This is a helper for parsing seat plans: it defines a single seat + the number of the seat + GUI: geometry information for the row + GUI: rotation of the row rectangle + This is a helper for parsing seat plans: it defines a row of seats the ID of this group, if multiple row elements with the same ID exist, they refer to different sections of the same row - amount of seats in this (part of the) row + human readable name of the row + amount of seats in this (part of the) row; if missing: Seat children are expected GUI: geometry information for the row GUI: background color for seats GUI: foreground color, the one the seat number is rendered in + GUI: background color for taken seats + GUI: background color for wanted seats + GUI: foreground color, the one the group name is rendered in + GUI: foreground color, the one the group name is rendered in + GUI: foreground color, the one the seat ID is rendered in + GUI: background color for unavailable seats number of the first seat in this row + GUI: rotation of the row rectangle + GUI: seat geometries, if used capacity must be empty @@ -27,9 +42,15 @@ amount of seats in this (part of the) group human readable name of the group defines whether the group contains numbered seats - GUI: geometry information for the group + GUI: geometry information for the group; if missing: uses plan coordinates GUI: background color GUI: foreground color, the one the group name is rendered in + GUI: background color for taken seats + GUI: background color for wanted seats + GUI: foreground color, the one the group name is rendered in + GUI: foreground color, the one the group name is rendered in + GUI: foreground color, the one the seat ID is rendered in + GUI: background color for unavailable seats GUI: rotation of the group rectangle definition of rows in this group The price categories in this group. @@ -53,6 +74,7 @@ GUI: background or fill color GUI: foreground, outline or text color GUI: rotation of the group rectangle + GUI: font family for the text element GUI: font size for text elements GUI: textual content @@ -79,10 +101,19 @@ over the wire, since it is just a helper class. Version of the SeatPlan spec that was implemented. If true: only categories listed can be sold, if false: unlisted categories are not restricted + Width/Height information. + GUI: background color + GUI: foreground color, the one the seat ID is rendered in + GUI: background color for taken seats + GUI: background color for wanted seats + GUI: foreground color, the one the seat ID is rendered in + GUI: foreground color, the one the seatID is rendered in + GUI: foreground color, the one the seat ID is rendered in + GUI: background color for unavailable seats diff --git a/wob/magicsmoke.wolf b/wob/magicsmoke.wolf index 201ecdf..ab936a2 100644 --- a/wob/magicsmoke.wolf +++ b/wob/magicsmoke.wolf @@ -7,48 +7,50 @@ - see COPYING.AGPL for details --> - These files describe the database schema and communication protocol of MagicSmoke. - &copy; Konrad Rosenbaum, 2009-2017 - <br/>these files are protected under the GNU AGPLv3 or at your option any newer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + These files describe the database schema and communication protocol of MagicSmoke. + &copy; Konrad Rosenbaum, 2009-2017 + <br/>these files are protected under the GNU AGPLv3 or at your option any newer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/inc/classes/basevars.php b/www/inc/classes/basevars.php index 4abed0d..657b01c 100644 --- a/www/inc/classes/basevars.php +++ b/www/inc/classes/basevars.php @@ -3,55 +3,7 @@ // protected under the GNU AGPL version 3 or at your option any newer // see COPYING.AGPL -/** \page templates Templates -\section tpl_base Base Variables - -There are some variables available for all templates.

      - -\param script.* variables contain URLs for different modes of the web site:
      - - - - - - - - - - - - - - -
      script.rootroot URL for the index.php script
      script.thisthe URL of the currently called script mode
      script.indexURL of the list index
      script.eventDetailsURL for event detail pages, append the event ID to it to complete it
      script.eventOrderURL for ordering tickets for a specific event, arguments are expected as POST or GET parameters
      script.vouchersURL for listing vouchers
      script.voucherOrderURL for ordering vouchers, arguments are expected as POST or GET parameters
      script.removeItemURL for removing tickets/vouchers/items from the cart, arguments are expected as POST or GET parameters
      script.shopURL for listing shop items
      script.cartURL of the cart, the cart must exist when calling it, otherwise a cookie error will be displayed
      script.mycartURL of the cart that transparently creates the cart if it does not exist yet
      script.checkoutURL to check out the cart
      script.setlanguageURL for setting the language cookie, add the language code to it to complete it

      -\param inputnames.* variables contain names for specific form input elements:
      - - - - - -
      inputnames.amountTicketsamount of tickets to be ordered
      inputnames.eventcontains the event ID
      inputnames.modecontains the display mode
      inputnames.cartidID of the cart of the current customer, usually the cart cookie is used instead

      -\param cartcookie variable contains the name of the cookie that contains the cart ID.

      -\param lang variable is an object of type LanguageManager - it represents translations done for the language the user has chosen.

      - -\param sessionid contains the ID of the current session as it is stored in a cookie and the session table. - -\param user.* contains basic data about the web user. - - - - - -
      user.namethe (family) name of the user
      user.firstnamethe first name of the user
      user.titlethe title of the user
      user.emailthe e-mail and login name of the user
      - -\section tpl_error error.html - -This template is used whenever an error occurs during processing. - -\param ErrorText the text to be shown for an error -\param ErrorTrace a full ASCII version of an exception trace. This is only filled if the $WebShowErrors option is set to true in the config.php file. -**/ - +///helper class to populate template basevars array class BaseVars{ ///if Twig is not yet initialized: initialize it, sets the $twig variable to the interpreter @@ -106,6 +58,7 @@ public static function initPriv(){ $basevars['script']['orderLogin']=$BaseUrl."?mode=orderLogin"; $basevars['script']['placeOrder']=$BaseUrl."?mode=placeOrder"; $basevars['script']['customerLoginOrder']=$BaseUrl."?mode=customerLoginOrder"; + $basevars['script']['customerResetOrder']=$BaseUrl."?mode=customerResetOrder"; $basevars['script']['customerRegistrationOrder']=$BaseUrl."?mode=customerRegistrationOrder"; $basevars['script']['changeInvoiceAddress']=$BaseUrl."?mode=changeInvoiceAddress"; $basevars['script']['changeDeliveryAddress']=$BaseUrl."?mode=changeDeliveryAddress"; diff --git a/www/inc/rendering/cart_listing.php b/www/inc/rendering/cart_listing.php index 09fce8a..4134acd 100644 --- a/www/inc/rendering/cart_listing.php +++ b/www/inc/rendering/cart_listing.php @@ -212,40 +212,6 @@ static public function getNewCartId(){ } -/** \page templates Templates -\section tpl_cart Cart Variables - -The cart.html template is used to render the current cart of the customer. - -\param cart an object of the web type WebCart (PHP: WOWebCart) that represents the entire cart of the user. -\param shipping and array of web objects of type Shipping (PHP: WOShipping) which represent all available shipping types that customers can chose from. -\param forceshipping a boolean that contains whether customers must chose a shipping type. - -\subsection tpl_cartext Cart Extensions - -Some of the sub-objects of cart have extended attributes in addition to those generated by Wob: - -\param cart.isempty true if the cart is empty -\param cart.totalsum the complete sum of all prices in the cart -\param cart.tickets[...].eventprice.*: -\param *.amountInputField name of the input field of a form that should contain the amount of tickets for ordering, see the example on how to use it -\param *.categoryIdWeb part of the URL query that represents this category, used for removing tickets from the cart, see the example on how to use it - -\section tpl_voucher Voucher Variables - -The voucher.html template is used to render the voucher selection form. - -\param voucherprices contains an array of valid voucher prices. - -\section tpl_carterror Cart Error Variables - -The carterror.html template is used to when the cart expired or the customers browser lost the cookie. - -Just the \ref tpl_base Base Variables -are available. -**/ - - /** creates the cart overview for templating info see \ref tpl_cart Cart Variables @@ -304,14 +270,6 @@ static public function createVoucherOverview() return $p->render($list); } -/** \page templates Templates -\section tpl_cout Checkout Variables - -The checkout.html template is used to render the current cart of the customer. - -\param cart an object of the web type WebCart (PHP: WOWebCart) that represents the entire cart of the user. -*/ - /**renders the checkout page see the \ref tpl_cout Checkout Template diff --git a/www/inc/rendering/event_listing.php b/www/inc/rendering/event_listing.php index 09906a9..0972b5e 100644 --- a/www/inc/rendering/event_listing.php +++ b/www/inc/rendering/event_listing.php @@ -5,21 +5,6 @@ // see COPYING.AGPL -/** -\page templates Templates -\section tpl_index Event List - -The index.html template is used to render the list of events available to customers. - -\param events an array of the events available, starting at the current time, ordered by time; the events are of web object type Event (PHP: WOEvent) - -\section tpl_eventdetail Event Details - -The eventdetails.html template is used to render a single event and to offer the user to buy tickets for it. - -\param event the event being rendered; the event is of web object type Event (PHP: WOEvent) -*/ - /** wrapper arount event (list) rendering for the web UI*/ class EventRender { diff --git a/www/inc/wext/customer.php b/www/inc/wext/customer.php index 5d8d106..408f0f2 100644 --- a/www/inc/wext/customer.php +++ b/www/inc/wext/customer.php @@ -372,16 +372,6 @@ class WOCustomer extends WOCustomerAbstract return $p->render($vars); } - /** \page templates Templates - \section tpl_login Login Page - - The login.html template is used to render the customer login and registration page. - - \param script.customerLogin the page to call for logging in - \param script.customerRegistration the page to call for registering a new customer - \param customer_name the name (mail) of the customer (or an empty string if unknown) - */ - /** creates a login page For templating info see \ref tpl_login Login Variables @@ -408,6 +398,7 @@ class WOCustomer extends WOCustomerAbstract $list["cart"]=$cart; $list["customer_name"]=""; $list['script']['customerLogin']=$basevars['script']['customerLogin'.$extension]; + $list['script']['customerReset']=$basevars['script']['customerReset'.$extension]; $list['script']['customerRegistration']=$basevars['script']['customerRegistration'.$extension]; $list["customer_name"]=""; if(isset($HTTPARGS["customer_name"])) @@ -438,15 +429,6 @@ class WOCustomer extends WOCustomerAbstract } - /** \page templates Templates - \section tpl_logerr Login Error - - The loginerror.html template is used to render customer login and registering errors. - - \param errorType the type of error: "login" - the login failed (wrong mail or password), "exist" - an account with the same mail already exists, "mismatch" - the new passwords do no match, "create" for unspecified errors during creation of the new account (e.g. missing or invalid parameters) - \param backUrl the URL to call back to the login page - */ - ///renders a login error page, see \ref tpl_logerr Login Error Template static private function loginError($errorType) { diff --git a/www/index.php b/www/index.php index 133cab2..ca343a0 100644 --- a/www/index.php +++ b/www/index.php @@ -49,6 +49,7 @@ $page="(internal error: no page text yet, probably no template defined)"; try{ //get page template and process it + //NOTE: if you alter the meaning of any mode or add modes do not forget to change doc/web/flow.html! switch($mode){ case "eventDetails": // show details of an event $page=EventRender::createEventDetails(); @@ -76,7 +77,8 @@ try{ case "voucherOrder": // add selected voucher to cart, then redirect to cart WebCart::addVoucher(); break; - case "shop": // redirect to main page - anchor for flow resets + case "shop": // Merchandising Shop + // redirect to main page - anchor for flow resets redirectHome(); break; case "checkout": // redirection target during checkout from login/register pages @@ -91,6 +93,9 @@ try{ case "customerRegistrationOrder": // log in with new customer data $page=WOCustomer::registerCustomer("checkout",true); break; + case "customerResetOrder": // reset password from order login page + //TODO + break; case "changeDeliveryAddress": case "changeInvoiceAddress": // change addresses $page=WebCart::changeAddressPage($mode); @@ -111,9 +116,11 @@ try{ $page=$twig->loadTemplate("index.html")->render($basevars); break; default: // if in doubt: show the event page, customers will probably like it ;-) + //TODO: make this configurable $page=EventRender::createEventList(); break; } + //NOTE: if you alter the meaning of any mode or add modes do not forget to change doc/web/flow.html! }catch(Exception $ex){ //log to (Apache) log file error_log($ex->getMessage()); diff --git a/www/template/en/login.html b/www/template/en/login.html index c781a17..a0e84bf 100644 --- a/www/template/en/login.html +++ b/www/template/en/login.html @@ -60,6 +60,19 @@ +I forgot my password:

      + +{# the login dialog #} +

      +
      + Please enter your login (email address) and click the reset button. You will receive an email with instructions how to reset your password.
      + + +
      E-mail address:

      + +

      +

      +


      {# in any case: allow the user to shop some more... #}

      Abort and Continue Shopping

      @@ -68,20 +81,24 @@