<li><a href="prog_tickettemplate.html">XML Ticket printer template</a></li>
</ul><p>
-Filenames of templates are restricted to the regular expression <tt>^[a-z0-9_.]+$</tt> and are case-insensitive.
+Base names of templates are restricted to the regular expression <tt>^[a-z0-9_]+$</tt> and are case-insensitive. They may have an extension (legal values: "od[ts]t", "xtt") and a variant ID (at the moment numeric, but may match "^[a-z0-9_]+$").<p>
+
+Below "file name" refers to the complete concatenation of base name, extension, and variant:
+<i>basename</i><b>.</b><i>extension</i>[<b>,</b><i>variant</i>]
<h3>List of Templates</h3>
<pre>
<TList>
- <Template name="filename" hash="md5-hash"/>
+ <Template name="filename" hash="md5-hash">Description text</Template>
...
</TList>
</pre>
<h3>Setting a Template</h3>
-The <tt>settemplate</tt> transaction creates a new template file. The request contains the file name terminated with a newline ("\n") and then the binary content of the template - it is important that there are no additional characters between the newline and the template content. The response contains the new hash of the template or error details.
\ No newline at end of file
+The <tt>settemplate</tt> transaction creates a new template file. The request contains the file name terminated with a newline ("\n") and then the binary content of the template - it is important that there are no additional characters between the newline and the template content. The response contains the new hash of the template or error details. The description is initially set to NULL and can be changed later with <tt>settemplatedescription</tt>.<p>
+
+The <tt>settemplatedescription</tt> transaction sets a new description for a template. The first line of the request contains the filename, the remainder the description.
+
+<h3>Deleting a Template</h3>
+
+The <tt>deletetemplate</tt> transaction deletes a template from the database. The request contains the file name (full name). The response is empty. The transaction does not return any errors.
void MEventSummary::print()
{
- QString tf=req->getTemplate("eventsummary.odtt");
- if(tf==""){
- QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary.odtt). Giving up."));
+ MTemplate tf=req->getTemplate("eventsummary");
+ if(!tf.isValid()){
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary). Giving up."));
return;
}
MOdtSignalRenderer rend(tf);
void MEventSummary::saveas()
{
- QString tf=req->getTemplate("eventsummary.odtt");
- if(tf==""){
- QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary.odtt). Giving up."));
+ MTemplate tf=req->getTemplate("eventsummary");
+ if(!tf.isValid()){
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary). Giving up."));
return;
}
QFileDialog fd(this);
fd.setAcceptMode(QFileDialog::AcceptSave);
fd.setFileMode(QFileDialog::AnyFile);
fd.setConfirmOverwrite(true);
- fd.setFilter("Open Document Text (*.odt)");
- fd.setDefaultSuffix("odt");
+ fd.setFilter(tr("Open Document File (*.%1)").arg(tf.targetExtension()));
+ fd.setDefaultSuffix(tf.targetExtension());
QString fname;
if(fd.exec()){
QStringList fn=fd.selectedFiles();
QFile tfile;
};
-MOdtRenderer::MOdtRenderer(QString file)
+MOdtRenderer::MOdtRenderer(MTemplate file)
{
- d=new MOdtRendererPrivate(file,this);
+ d=new MOdtRendererPrivate(file.cacheFileName(),this);
}
MOdtRendererPrivate::MOdtRendererPrivate(QString file,MOdtRenderer*p)
/********************************************************************/
-MOdtSignalRenderer::MOdtSignalRenderer(QString file)
+MOdtSignalRenderer::MOdtSignalRenderer(MTemplate file)
:MOdtRenderer(file)
{
}
#include <QObject>
#include <QString>
+#include "templates.h"
+
class MOdtRendererPrivate;
class QFile;
{
public:
/**instantiates a renderer loaded from template file*/
- MOdtRenderer(QString file);
+ MOdtRenderer(MTemplate file);
/**deletes the renderer*/
virtual ~MOdtRenderer();
Q_OBJECT
public:
/**instantiates a renderer loaded from template file*/
- MOdtSignalRenderer(QString file);
+ MOdtSignalRenderer(MTemplate file);
/**deletes the renderer*/
~MOdtSignalRenderer();
return;
}
//get template
- QString tf=req->getTemplate("ticket.xtt");
- if(tf==""){
+ MTemplate tf=req->getTemplate("ticket");
+ if(!tf.isValid()){
QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (ticket.xtt). Giving up."));
return;
}
return;
}
//get template
- QString tf=req->getTemplate("voucher.xtt");
- if(tf==""){
+ MTemplate tf=req->getTemplate("voucher");
+ if(!tf.isValid()){
QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (voucher.xtt). Giving up."));
return;
}
void MOrderWindow::printBill()
{
//get template
- QString tf=req->getTemplate("bill.odtt");
- if(tf==""){
- QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (bill.odtt). Giving up."));
+ MTemplate tf=req->getTemplate("bill");
+ if(!tf.isValid()){
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (bill). Giving up."));
return;
}
//mark order as shipped?
void MOrderWindow::saveBill()
{
//get template
- QString tf=req->getTemplate("bill.odtt");
- if(tf==""){
- QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary.odtt). Giving up."));
+ MTemplate tf=req->getTemplate("bill");
+ if(!tf.isValid()){
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to get template file (eventsummary). Giving up."));
return;
}
//get target file name
fd.setAcceptMode(QFileDialog::AcceptSave);
fd.setFileMode(QFileDialog::AnyFile);
fd.setConfirmOverwrite(true);
- fd.setFilter("Open Document Text (*.odt)");
- fd.setDefaultSuffix("odt");
+ fd.setFilter(tr("Open Document File (*.%1)").arg(tf.targetExtension()));
+ fd.setDefaultSuffix(tf.targetExtension());
QString fname;
if(fd.exec()){
QStringList fn=fd.selectedFiles();
cb->addItem(tr("Voucher: ")+vouchers[i].voucherID());
vl->addWidget(disp=new QLabel,10);
//get the templates
- trender=new MTicketRenderer(req->getTemplate("ticket.xtt"));
- vrender=new MVoucherRenderer(req->getTemplate("voucher.xtt"));
+ trender=new MTicketRenderer(req->getTemplate("ticket"));
+ vrender=new MVoucherRenderer(req->getTemplate("voucher"));
changeItem(0);
connect(cb,SIGNAL(currentIndexChanged(int)),this,SLOT(changeItem(int)));
}
m->addAction(tr("&Offline mode"))->setEnabled(false);
m->addAction(tr("Change my &Password"),this,SLOT(setMyPassword())) ->setEnabled(req->hasRole("setmypasswd"));
m->addSeparator();
- m->addAction(tr("&Upload Template..."),this,SLOT(uploadTemplate()));
+ m->addAction(tr("&Edit Templates..."),req,SLOT(editTemplates()));
+ m->addAction(tr("&Update Templates Now"),req,SLOT(updateTemplates()));
m->addSeparator();
m->addAction(tr("&Close Session"),this,SLOT(close()));
ordermode->setCurrentIndex(ordermode->count()-1);
}
-void MOverview::uploadTemplate()
-{
- //get file
- QString fn=QFileDialog::getOpenFileName(this,tr("Please select a template file."));
- if(fn=="")return;
- //get template name
- QString tn=QFileInfo(fn).fileName();
- QRegExp fr("[a-z0-9_\\.]+");
- do{
- bool ok;
- tn=QInputDialog::getText(this,tr("Enter Template Name"),tr("Please enter a name for the template file, it should contain only letters, digits, underscores and dots:"),QLineEdit::Normal,tn,&ok).toLower();
- if(!ok)return;
- if(fr.exactMatch(tn))break;
- QMessageBox::warning(this,tr("Warning"),tr("The template name must only contain letters, digits, underscores and dots."));
- }while(true);
- //send it
- if(req->setTemplate(tn,fn))
- QMessageBox::information(this,tr("Success"),tr("Successfully uploaded the template."));
- else
- QMessageBox::warning(this,tr("Warning"),tr("Unable to upload the template."));
-}
-
void MOverview::ticketReturn()
{
//get ticket
/**react on entry in Entrance tab*/
void entranceValidate();
- /**upload Templates*/
- void uploadTemplate();
-
/**return a ticket*/
void ticketReturn();
/**return a voucher*/
orderwin.cpp \
labeldlg.cpp \
version.cpp \
- templates.cpp
+ templates.cpp \
+ templatedlg.cpp
HEADERS = \
keygen.h \
orderwin.h \
labeldlg.h \
misc.h \
- templates.h
+ templates.h \
+ templatedlg.h
#some PHP files are listed in this file to scan them for translatable items
#use genphpscan.sh to regenerate it.
--- /dev/null
+//
+// C++ Implementation: templatedlg
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2008
+//
+// Copyright: See README/COPYING files that come with this distribution
+//
+//
+
+#include "templatedlg.h"
+
+#include <QComboBox>
+#include <QBoxLayout>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QSettings>
+#include <QLabel>
+#include <QStandardItemModel>
+#include <QTreeView>
+
+MTemplateChoice::MTemplateChoice(const QString&cd,const QString&tname,const QStringList&choices,const QString&sg)
+ :sgroup(sg),cachedir(cd)
+{
+ setWindowTitle(tr("Chose Template"));
+ QVBoxLayout *vl;
+ setLayout(vl=new QVBoxLayout);
+ vl->addWidget(new QLabel(tr("Please chose a variant of template %s:").arg(tname)));
+ vl->addWidget(box=new QComboBox);
+ box->setEditable(false);
+ QSettings set;
+ set.beginGroup(sg);
+ for(int i=0;i<choices.size();i++){
+ QStringList vs=choices[i].split(",");
+ QString v,d;
+ if(vs.size()>1)v=vs[1];
+ else v=tr("(default)","default template pseudo-variant");
+ d=set.value(choices[i]+"/description").toString();
+ box->addItem(v+": "+d,choices[i]);
+ }
+ vl->addStretch(1);
+ QHBoxLayout *hl;
+ vl->addLayout(hl=new QHBoxLayout);
+ hl->addStretch(1);
+ QPushButton*p;
+ hl->addWidget(p=new QPushButton(tr("Ok")));
+ p->setDefault(true);
+ connect(p,SIGNAL(clicked()),this,SLOT(accept()));
+}
+
+MTemplate MTemplateChoice::choice()const
+{
+ QString fn=box->itemData(box->currentIndex()).toString();
+ QSettings set;
+ set.beginGroup(sgroup+"/"+fn);
+ return MTemplate(cachedir+"/"+fn, set.value("checksum").toString(), set.value("description").toString());
+}
+
+
+
+/**************************************************************/
+
+MTemplateEditor::MTemplateEditor(MTemplateStore*s)
+{
+ store=s;
+ nochange=false;
+
+ setWindowTitle(tr("Edit Template Directory"));
+
+ QHBoxLayout*hl;
+ QVBoxLayout*vl,*vl2;
+
+ setLayout(vl=new QVBoxLayout);
+ vl->addLayout(hl=new QHBoxLayout,1);
+ hl->addWidget(tree=new QTreeView,1);
+ tree->setModel(model=new QStandardItemModel(this));
+ connect(model,SIGNAL(itemChanged(QStandardItem*)),this,SLOT(changeDescr(QStandardItem*)));
+ QPushButton*p;
+ hl->addLayout(vl2=new QVBoxLayout);
+ vl2->addWidget(p=new QPushButton(tr("Update Now")));
+ connect(p,SIGNAL(clicked()),this,SLOT(forceUpdate()));
+ vl2->addWidget(p=new QPushButton(tr("Add Variant")));
+ connect(p,SIGNAL(clicked()),this,SLOT(addItem()));
+ vl2->addWidget(p=new QPushButton(tr("Delete Variant")));
+ connect(p,SIGNAL(clicked()),this,SLOT(deleteItem()));
+ vl2->addStretch(1);
+
+ vl->addSpacing(15);
+ vl->addLayout(hl=new QHBoxLayout);
+ hl->addStretch(1);
+ hl->addWidget(p=new QPushButton(tr("Close")));
+ connect(p,SIGNAL(clicked()),this,SLOT(accept()));
+
+ updateView();
+}
+
+static const int BASEROLE=Qt::UserRole;
+static const int NAMEROLE=Qt::UserRole+1;
+
+void MTemplateEditor::updateView()
+{
+ //basic cleanup
+ nochange=true;
+ model->clear();
+ model->insertColumns(0,3);
+ model->setHorizontalHeaderLabels(QStringList()<<tr("Template/Variant")<<tr("Description")<<tr("Checksum"));
+ //get base names
+ QStringList base=MTemplate::legalBaseNames();
+ //get all templates, sort them into buckets
+ QList<MTemplate>all=store->allTemplates();
+ QMap<QString,QList<MTemplate> >hier;
+ for(int i=0;i<all.size();i++){
+ QString b=all[i].baseName();
+ if(!base.contains(b))base.append(b);
+ if(!hier.contains(b))hier.insert(b,QList<MTemplate>());
+ hier[b].append(all[i]);
+ }
+ //create hierarchy
+ qSort(base);
+ model->insertRows(0,base.size());
+ for(int i=0;i<base.size();i++){
+ QModelIndex idx=model->index(i,0);
+ model->setData(idx,base[i]);
+ model->setData(idx,base[i],BASEROLE);
+ model->setData(idx,"",NAMEROLE);
+ model->itemFromIndex(idx)->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
+ model->setData(model->index(i,1),"");
+ model->itemFromIndex(model->index(i,1))->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
+ model->setData(model->index(i,2),"");
+ model->itemFromIndex(model->index(i,1))->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
+ if(!hier.contains(base[i]))continue;
+ if(hier[base[i]].size()<1)continue;
+ QList<MTemplate>sub=hier[base[i]];
+ model->insertRows(0,sub.size(),idx);
+ model->insertColumns(0,3,idx);
+ for(int j=0;j<sub.size();j++){
+ QModelIndex idx2=model->index(j,0,idx);
+ model->setData(idx2,sub[j].fileName()+" ("+sub[j].variantID()+")");
+ model->setData(idx2,base[i],BASEROLE);
+ model->setData(idx2,sub[j].completeFileName(),NAMEROLE);
+ model->itemFromIndex(idx2)->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
+ model->setData(model->index(j,1,idx),sub[j].description());
+ model->setData(model->index(j,2,idx),sub[j].checksum());
+ model->itemFromIndex(model->index(j,2,idx))->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
+ }
+ }
+ //expand all
+ tree->expandAll();
+ nochange=false;
+}
+void MTemplateEditor::deleteItem()
+{
+ //get selection
+ QModelIndex idx=tree->currentIndex();
+ if(!idx.isValid())return;
+ QModelIndex bidx=model->index(idx.row(),0,idx.parent());
+ QString fn=model->data(bidx,NAMEROLE).toString();
+ if(fn=="")return;
+ //delete
+ if(store->deleteTemplate(fn))
+ model->removeRow(idx.row(),idx.parent());
+ else
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to delete this template."));
+}
+
+void MTemplateEditor::addItem()
+{
+ //get selection
+ QModelIndex idx=tree->currentIndex();
+ if(!idx.isValid())return;
+ QModelIndex pidx=idx.parent();
+ QModelIndex bidx=model->index(idx.row(),0,pidx);
+ QString base=model->data(bidx,BASEROLE).toString();
+ if(base=="")return;
+ //query file name
+ QString fn=QFileDialog::getOpenFileName(this,tr("Select Template File"));
+ if(fn=="")return;
+ //check extension
+ QString ext=QFileInfo(fn).completeSuffix();
+ if(!MTemplate::legalSuffixes(base).contains(ext)){
+ QMessageBox::warning(this,tr("Warning"),tr("Files with this extension (%1) are not legal for this template.").arg(ext));
+ return;
+ }
+ //get new variant name
+ QStringList lst;
+ for(int i=0;i<model->rowCount(pidx);i++){
+ QStringList f=model->data(model->index(i,0,pidx),NAMEROLE).toString().split(",");
+ if(f.size()>1)
+ lst<<f[1];
+ }
+ QString nvar;
+ for(int i=0;i<1000000000;i++){
+ nvar=QString::number(i,16).toLower();
+ if(!lst.contains(nvar))break;
+ }
+ //send up
+ if(store->setTemplate(base+"."+ext+","+nvar,fn))
+ forceUpdate();
+ else
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to upload file."));
+}
+
+void MTemplateEditor::changeDescr(QStandardItem*item)
+{
+ if(nochange)return;
+ if(!item)return;
+ //sanity check
+ if(item->column()!=1)return;
+ //get full name & data
+ QModelIndex idx=item->index();
+ QString dsc=model->data(idx).toString();
+ QModelIndex bidx=model->index(item->row(),0,idx.parent());
+ QString fn=model->data(bidx,NAMEROLE).toString();
+ if(fn=="")return;
+ //send to server
+ if(!store->setTemplateDescription(fn,dsc))
+ QMessageBox::warning(this,tr("Warning"),tr("Unable to send new description to server."));
+}
+
+void MTemplateEditor::forceUpdate()
+{
+ store->updateTemplates(true);
+ updateView();
+}
--- /dev/null
+//
+// C++ Interface: templatedlg
+//
+// Description:
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2008
+//
+// Copyright: See README/COPYING files that come with this distribution
+//
+//
+
+#ifndef MAGICSMOKE_TEMPLATEDLG_H
+#define MAGICSMOKE_TEMPLATEDLG_H
+
+#include <QDialog>
+#include <QString>
+
+#include "templates.h"
+
+class QComboBox;
+class QStandardItem;
+class QStandardItemModel;
+class QStringList;
+class QTreeView;
+
+/**gives the user a choice of template variants; used by MTemplateStore only!*/
+class MTemplateChoice:public QDialog
+{
+ Q_OBJECT
+ public:
+ MTemplateChoice(const QString&,const QString&,const QStringList&,const QString&);
+
+ MTemplate choice()const;
+ private:
+ //selection box
+ QComboBox*box;
+ //settings group where to find data
+ QString sgroup,cachedir;
+};
+
+/**lets the user add and remove templates and variants to/from the database; used by MWebRequest!*/
+class MTemplateEditor:public QDialog
+{
+ Q_OBJECT
+ public:
+ MTemplateEditor(MTemplateStore*);
+
+ private slots:
+ void updateView();
+ void deleteItem();
+ void addItem();
+ void changeDescr(QStandardItem*);
+ void forceUpdate();
+ private:
+ MTemplateStore*store;
+ QTreeView*tree;
+ QStandardItemModel*model;
+ bool nochange;
+};
+
+
+
+#endif
//
//
+/*****************************
+ * Settings hierarchy for templates:
+ *
+ * /templates/$PROFILEID -> template settings for this profile
+ * .../$TEMPLATENAME -> the settings for a template, uses the complete name (eg. bill.odtt,2)
+ * ....../checksum -> key that stores the checksum (calculated by server!!)
+ * ....../description -> key that stores the description (as received from server)
+ *
+ *
+ * Template buffer on disk:
+ *
+ * req->dataDir()/templates -> directory for template files;
+ * each file is stored with complete name, no meta-info
+ ***************************/
+
+#include "main.h"
+#include "templatedlg.h"
#include "templates.h"
#include "webrequest.h"
-#include "main.h"
-#include <QStringList>
-#include <QSettings>
-#include <QRegExp>
+#include <QCoreApplication>
#include <QDateTime>
-#include <QFile>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
+#include <QFile>
+#include <QProgressDialog>
+#include <QRegExp>
+#include <QSettings>
+#include <QStringList>
-MTemplates::MTemplates(MWebRequest*r,QString p)
+MTemplateStore::MTemplateStore(MWebRequest*r,QString p)
{
req=r;
profileid=p;
}
-QString MTemplates::getTemplate(QString f)
+MTemplate MTemplateStore::getTemplate(QString f)
{
//syntax check
f=f.toLower();
- QRegExp fregexp("[a-z0-9_\\.]+");
- if(!fregexp.exactMatch(f))return "";
+ QRegExp fregexp("[a-z0-9_]+");
+ if(!fregexp.exactMatch(f))return MTemplate();
+ //update directory (no force)
+ updateTemplates(false);
+ //find files matching the pattern
+ //basics
+ QString dname=req->dataDir()+"/templates/";
+ QSettings set;
+ set.beginGroup("templates/"+profileid);
+ //get directory
+ QStringList dir=set.childGroups();
+ QStringList tdir;
+ for(int i=0;i<dir.size();i++){
+ if(dir[i].split(".")[0].startsWith(f,Qt::CaseInsensitive))
+ tdir<<dir[i];
+ }
+ //check dir size
+ if(tdir.size()==0)return MTemplate();
+ if(tdir.size()==1)return MTemplate(dname+"/"+tdir[0], set.value(tdir[0]+"/checksum").toString(), set.value(tdir[0]+"/description").toString());
+ //hmm, more than one choice
+ MTemplateChoice c(dname,f,tdir,"templates/"+profileid);
+ if(c.exec()==QDialog::Accepted)return c.choice();
+ else return MTemplate();
+}
+
+struct STemp{
+ STemp(QString f,QString c,QString d){fname=f;checksum=c;descr=d;}
+ STemp(){}
+ QString fname,checksum,descr;
+};
+
+void MTemplateStore::updateTemplates(bool force)
+{
//basics
QString dname=req->dataDir()+"/templates/";
QSettings set;
set.beginGroup("templates/"+profileid);
- //do we need an update?
+ //do we need an update yet?
QDateTime last=QDateTime::fromTime_t(set.value("lastupdate",0).toInt()+300);
- if(last<QDateTime::currentDateTime()){do{//pseudo-loop, so we can use break instead of goto
- //get local info
- set.beginGroup("checksum");
- QStringList files=set.childKeys();
- QMap<QString,QString> fmap;
- for(int i=0;i<files.size();i++)
- fmap.insert(files[i],set.value(files[i]).toString());
- set.endGroup();
- //get remote info, assume it is still valid if unable to retrieve it
- if(!req->request("gettemplatelist",""))break;
- if(req->responseStatus()!=MWebRequest::Ok)break;
- //remember update time
- set.setValue("lastupdate",QDateTime::currentDateTime().toTime_t());
- //parse info
- QDomDocument doc;
- if(!doc.setContent(req->responseBody()))break;
- //prune old stuff
- set.remove("checksum");
- //scan and create new list
- QDomNodeList nl=doc.elementsByTagName("Template");
- QMap<QString,QString>nfmap;
- for(int i=0;i<nl.size();i++){
- QDomElement el=nl.at(i).toElement();
- if(el.isNull())continue;
- QString f=el.attribute("name","");
- QString h=el.attribute("hash","");
- if(f=="" || !fregexp.exactMatch(f))continue;
- nfmap.insert(f,h);
- set.setValue("checksum/"+f,h);
- }
- //compare old with new list, delete changed files
- for(int i=0;i<files.size();i++){
- if(!nfmap.contains(files[i]))
- QFile(dname+"/"+f).remove();
- else
- if(nfmap[files[i]]!=fmap[files[i]])
- QFile(dname+"/"+f).remove();
+ if(last>=QDateTime::currentDateTime() && !force)return;
+
+ //get local info
+ QStringList files=set.childGroups();
+ QMap<QString,STemp> fmap;
+ for(int i=0;i<files.size();i++){
+ STemp t(files[i],set.value(files[i]+"/checksum").toString(), set.value(files[i]+"/description").toString());
+ fmap.insert(files[i],t);
+ }
+
+ //display progress dialog
+ QProgressDialog progress(QCoreApplication::translate("MTemplateStore","Retrieving templates from server."),"",0,1);
+ progress.setCancelButton(0);
+ progress.setMinimumDuration(0);
+ progress.setValue(0);
+
+ //get remote info, assume it is still valid if unable to retrieve it
+ if(!req->request("gettemplatelist",""))return;
+ if(req->responseStatus()!=MWebRequest::Ok)return;
+ //remember update time
+ set.setValue("lastupdate",QDateTime::currentDateTime().toTime_t());
+ //parse info
+ QDomDocument doc;
+ if(!doc.setContent(req->responseBody()))return;
+ //scan and create new list
+ QDomNodeList nl=doc.elementsByTagName("Template");
+ QMap<QString,STemp>nfmap;
+ for(int i=0;i<nl.size();i++){
+ QDomElement el=nl.at(i).toElement();
+ if(el.isNull())continue;
+ QString f=el.attribute("name","");
+ QString h=el.attribute("hash","");
+ QString d=el.text();
+ if(f=="")continue;
+ nfmap.insert(f,STemp(f,h,d));
+ }
+
+ //check old list for removed or changed entries; remove them
+ for(int i=0;i<files.size();i++){
+ if(!nfmap.contains(files[i]))
+ fmap.remove(files[i]);
+ else
+ if(nfmap[files[i]].checksum != fmap[files[i]].checksum)
+ fmap.remove(files[i]);
+ }
+ files=fmap.keys();
+
+ //retrieve new files from server
+ QStringList nfiles=nfmap.keys();
+ progress.setRange(0,nfiles.size());
+ for(int i=0;i<nfiles.size();i++){
+ progress.setValue(i);
+ if(!files.contains(nfiles[i])){
+ //file is not in current cache (or was outdated)
+ if(retrieveFile(dname,nfiles[i]))
+ fmap.insert(nfiles[i],nfmap[nfiles[i]]);
+ }else{
+ //file is already up to date, still copy the current description
+ fmap[nfiles[i]].descr=nfmap[nfiles[i]].descr;
}
- }while(false);}
- //check that it is a valid template file
- if(!set.contains("checksum/"+f))return "";
- //get the file if it does not exist
- if(!QFile(dname+"/"+f).exists()){
- //make sure directory exists
- QDir(req->dataDir()).mkpath("templates");
- //retrieve template file
- if(!req->request("gettemplate",f.toAscii()))
- return "";
- if(req->responseStatus()!=MWebRequest::Ok)
- return "";
- QFile fl(dname+"/"+f);
- if(!fl.open(QIODevice::WriteOnly))
- return "";
- fl.write(req->responseBody());
- fl.close();
}
- //return file name
- return dname+"/"+f;
+ files=fmap.keys();
+
+ //clean up directory
+ QDir dir(dname);
+ QStringList dfiles=dir.entryList(QDir::Files);
+ for(int i=0;i<dfiles.size();i++)
+ if(!files.contains(dfiles[i]))
+ dir.remove(dfiles[i]);
+
+ //settings: prune old stuff
+ set.remove("");
+ //settings: create new entries
+ for(int i=0;i<files.size();i++){
+ STemp s=fmap[files[i]];
+ set.setValue(s.fname+"/checksum",s.checksum);
+ set.setValue(s.fname+"/description",s.descr);
+ }
+}
+
+bool MTemplateStore::retrieveFile(QString dname,QString fn)
+{
+ //request
+ if(!req->request("gettemplate",fn.toUtf8()))return false;
+ if(req->responseStatus()!=MWebRequest::Ok)return false;
+ //store
+ QFile f(dname+"/"+fn);
+ if(!f.open(QIODevice::WriteOnly|QIODevice::Truncate))return false;
+ f.write(req->responseBody());
+ return true;
}
-bool MTemplates::setTemplate(QString n,QString f)
+bool MTemplateStore::setTemplate(QString n,QString f)
{
- //sanity check
- QRegExp fregexp("[a-z0-9_\\.]+");
+ //very rough sanity check
+ QRegExp fregexp("[a-z0-9_\\.,]+");
if(!fregexp.exactMatch(n))return false;
//get content
QFile fl(f);
QByteArray ba=fl.readAll();
fl.close();
//send to server
- if(!req->request("settemplate",n.toAscii()+"\n"+ba))
+ if(!req->request("settemplate",n.toUtf8()+"\n"+ba))
+ return false;
+ if(req->responseStatus()!=MWebRequest::Ok)
+ return false;
+ //delete it from cache, so it is retrieved again; force retrieval
+ //TODO: the server returns the hash (since dec08), use it and update the cache without retrieval
+ QSettings set;
+ set.remove("templates/"+profileid+"/"+n);
+ set.setValue("templates/"+profileid+"/lastupdate",0);
+ QFile(req->dataDir()+"/templates/"+n).remove();
+ //return success
+ return true;
+}
+
+bool MTemplateStore::deleteTemplate(QString n)
+{
+ //very rough sanity check
+ QRegExp fregexp("[a-z0-9_\\.,]+");
+ if(!fregexp.exactMatch(n))return false;
+ //send to server
+ if(!req->request("deletetemplate",n.toUtf8()))
return false;
if(req->responseStatus()!=MWebRequest::Ok)
return false;
- //delete it from cache, so it is retrieved again
- QSettings().remove("templates/"+profileid+"/checksum/"+n);
+ //delete it from cache
+ QSettings set;
+ set.remove("templates/"+profileid+"/"+n);
QFile(req->dataDir()+"/templates/"+n).remove();
+ return true;
+}
+
+bool MTemplateStore::setTemplateDescription(QString n,QString d)
+{
+ //very rough sanity check
+ QRegExp fregexp("[a-z0-9_\\.,]+");
+ if(!fregexp.exactMatch(n))return false;
+ //check that we have a real change
+ QSettings set;
+ set.beginGroup("templates/"+profileid+"/"+n);
+ QString o=set.value("description").toString();
+ qDebug("setting %s '%s' -> '%s'",n.toAscii().data(),o.toAscii().data(),d.toAscii().data());
+ if(o==d)return true;
+ //send to server
+ if(!req->request("settemplatedescription",n.toUtf8()+"\n"+d.toUtf8()))
+ return false;
+ if(req->responseStatus()!=MWebRequest::Ok)
+ return false;
+ //update internal description
+ set.setValue("description",d);
//return success
return true;
}
+
+QList<MTemplate> MTemplateStore::allTemplates()
+{
+ updateTemplates(false);
+ QString dname=req->dataDir()+"/templates/";
+ QSettings set;
+ set.beginGroup("templates/"+profileid);
+ QStringList names=set.childGroups();
+ QList<MTemplate> ret;
+ for(int i=0;i<names.size();i++)
+ ret.append(MTemplate(dname+"/"+names[i],set.value(names[i]+"/checksum").toString(),set.value(names[i]+"/description").toString()));
+ return ret;
+}
+
+/****************************************************************************/
+
+MTemplate::MTemplate(){}
+MTemplate::MTemplate(QString fn,QString chk,QString dsc){m_fname=fn;m_checksum=chk;m_descr=dsc;}
+QString MTemplate::cacheFileName()const{return m_fname;}
+
+QString MTemplate::fileName()const
+{
+ return QFileInfo(m_fname).fileName().split(",")[0];
+}
+
+QString MTemplate::completeFileName()const
+{
+ return QFileInfo(m_fname).fileName();
+}
+
+QString MTemplate::baseName()const
+{
+ return QFileInfo(m_fname).baseName();
+}
+
+QString MTemplate::extension()const
+{
+ return QFileInfo(m_fname).completeSuffix().split(",")[0];
+}
+
+QString MTemplate::variantID()const
+{
+ QStringList lst=QFileInfo(m_fname).fileName().split(",");
+ if(lst.size()>1)return lst[1];
+ return "";
+}
+
+QString MTemplate::checksum()const{return m_checksum;}
+
+bool MTemplate::isValid()const{return m_fname!="";}
+
+QString MTemplate::targetExtension()const
+{
+ QString x=extension();
+ //ODF file?
+ if(QRegExp("od.t").exactMatch(x))return x.left(3);
+ //all else: not storable
+ return "";
+}
+
+bool MTemplate::isOdf()const{return (type()&OdfTemplate)!=0;}
+
+bool MTemplate::isLabel()const{return type()==LabelTemplate;}
+MTemplate::Type MTemplate::type()const
+{
+ QString x=extension();
+ //ODF file?
+ if(x=="odtt")return OdtTemplate;
+ if(x=="odst")return OdsTemplate;
+ if(x=="odpt")return OdpTemplate;
+ if(x=="odgt")return OdgTemplate;
+ if(x=="odbt")return OdbTemplate;
+ //label?
+ if(x=="xtt")return LabelTemplate;
+ //hmm, unknown one
+ return UnknownTemplate;
+}
+
+QString MTemplate::description()const{return m_descr;}
+
+
+static QStringList tempBaseNames;
+//static
+QStringList MTemplate::legalBaseNames()
+{
+ if(tempBaseNames.size()==0){
+ tempBaseNames<<"ticket"<<"voucher"<<"bill"<<"eventsummary";
+ }
+ return tempBaseNames;
+}
+
+//static
+MTemplate::Types MTemplate::legalTypes(QString bn)
+{
+ if(bn=="ticket" || bn=="voucher")
+ return LabelTemplate;
+ if(bn=="bill" || bn=="eventsummary")
+ return OdfTemplate;
+ return UnknownTemplate;
+}
+
+//static
+QStringList MTemplate::legalSuffixes(QString bn)
+{
+ if(bn=="ticket" || bn=="voucher")
+ return QStringList()<<"xtt";
+ if(bn=="bill" || bn=="eventsummary")
+ return QStringList()<<"odtt"<<"odst"<<"odpt"<<"odgt"<<"odbt";
+ return QStringList();
+}
class MWebRequest;
-class MTemplates
+/**this class wraps a single template*/
+class MTemplate
{
public:
- MTemplates(MWebRequest*,QString);
+ /**creates an invalid template*/
+ MTemplate();
- QString getTemplate(QString);
+ /**returns the name/path of the cache file, if it exists*/
+ QString cacheFileName()const;
- bool setTemplate(QString,QString);
+ /**returns the file name of the template (eg. mytemplate.odtt)*/
+ QString fileName()const;
+
+ /**returns the complete encoded name of the template (eg. mytemplate.odtt,v1)*/
+ QString completeFileName()const;
+
+ /**returns the base name of the template (eg. mytemplate for mytemplate.odtt)*/
+ QString baseName()const;
+
+ /**returns the extension of the template (eg. odtt for mytemplate.odtt)*/
+ QString extension()const;
+
+ /**returns the variant ID of the template (warning: it has no meaning outside the template storage system*/
+ QString variantID()const;
+
+ /**returns the checksum (calculated by the server)*/
+ QString checksum()const;
+
+ /**returns the description*/
+ QString description()const;
+
+ /**returns whether this is a valid template*/
+ bool isValid()const;
+
+ /**returns the extension of the target document if it is saved (empty if the template cannot be saved)*/
+ QString targetExtension()const;
+
+ /**returns whether this is an ODF template*/
+ bool isOdf()const;
+
+ /**returns whether this is a label template*/
+ bool isLabel()const;
+
+ /**template type; currently only ODF and Labels are known*/
+ enum Type{
+ /**uninitialized or unknown label type*/
+ UnknownTemplate=0,
+ /**ODF Text template*/
+ OdtTemplate=1,
+ /**ODF SpreadSheet template*/
+ OdsTemplate=2,
+ /**ODF Presentation*/
+ OdpTemplate=4,
+ /**ODF Drawing*/
+ OdgTemplate=8,
+ /**ODF DataBase*/
+ OdbTemplate=0x10,
+ /**ODF template*/
+ OdfTemplate=0xff,
+ /**Label template*/
+ LabelTemplate=0x100
+ };
+ Q_DECLARE_FLAGS(Types,Type)
+
+ /**returns the template type*/
+ Type type()const;
+
+ /**returns the currently known (by the client) template base names*/
+ static QStringList legalBaseNames();
+
+ /**returns the legal file types (as understood by the client) for a template base name; returns UnknownTemplate if none is legal*/
+ static Types legalTypes(QString);
+
+ /**returns the legal template file suffixes (as understood by the client) for a template base name; returns empty if none is legal*/
+ static QStringList legalSuffixes(QString);
+
+ protected:
+ friend class MTemplateStore;
+ friend class MTemplateChoice;
+ /**creates a template wrapper from a cache file name; this constructor is used internally in the storage system*/
+ MTemplate(QString fn,QString chk,QString dsc);
+ private:
+ QString m_fname,m_checksum,m_descr;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(MTemplate::Types)
+
+/**this class implements the storage end of the template subsystem, its only instance exists in the webrequest*/
+class MTemplateStore
+{
+ protected:
+ friend class MWebRequest;
+ friend class MTemplateEditor;
+ /**instantiates the template subsystem*/
+ MTemplateStore(MWebRequest*,QString);
+
+ /**stores a specific template*/
+ bool setTemplate(QString templatename,QString localfile);
+
+ /**stores a new description for a template*/
+ bool setTemplateDescription(QString templatename,QString description);
+
+ /**deletes a template (requires full name), used by MTemplateEditor*/
+ bool deleteTemplate(QString);
+
+ /**updates the template directory, does not do anything if force==false and the last update was less than 5min ago*/
+ void updateTemplates(bool force);
+
+ /**returns all templates (for MTemplateEditor)*/
+ QList<MTemplate> allTemplates();
+
+ private:
+ /**unused, just here to remove it from accessability*/
+ MTemplateStore(){}
+
+ /**helper for updateTemplates: retrieves a single file from the server*/
+ bool retrieveFile(QString,QString);
+
+ public:
+ /**returns a specific template, opens a template choice dialog if necessary*/
+ MTemplate getTemplate(QString);
private:
MWebRequest*req;
QString profileid;
-
};
#endif
QSizeF tonatural(const QPaintDevice&,QSizeF);
};
-MLabelRenderer::MLabelRenderer(QString file)
+MLabelRenderer::MLabelRenderer(MTemplate file)
{
- d=new MLabelRendererPrivate(file,this);
+ d=new MLabelRendererPrivate(file.cacheFileName(),this);
}
MLabelRendererPrivate::MLabelRendererPrivate(QString file,MLabelRenderer*p)
return "";
}
-MTicketRenderer::MTicketRenderer(QString f):MLabelRenderer(f){}
+MTicketRenderer::MTicketRenderer(MTemplate f):MLabelRenderer(f){}
bool MTicketRenderer::render(const MTicket&label,QPaintDevice&pdev,QPainter*painter,QPointF offset)
{
return MLabelRenderer::render(MTicketLabel(label),pdev,painter,offset);
return "";
}
-MVoucherRenderer::MVoucherRenderer(QString f):MLabelRenderer(f){}
+MVoucherRenderer::MVoucherRenderer(MTemplate f):MLabelRenderer(f){}
bool MVoucherRenderer::render(const MVoucher&label,QPaintDevice&pdev,QPainter*painter,QPointF offset)
{
return MLabelRenderer::render(MVoucherLabel(label),pdev,painter,offset);
#include <QPointF>
#include <QSizeF>
+#include "templates.h"
+
class MLabelRendererPrivate;
class MTicket;
class MVoucher;
{
public:
/**instantiates a renderer loaded from template file*/
- MLabelRenderer(QString file);
+ MLabelRenderer(MTemplate file);
/**deletes the renderer*/
virtual ~MLabelRenderer();
class MVoucherRenderer:public MLabelRenderer
{
public:
- MVoucherRenderer(QString f);
+ MVoucherRenderer(MTemplate f);
bool render(const MVoucher&label,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF());
};
class MTicketRenderer:public MLabelRenderer
{
public:
- MTicketRenderer(QString f);
+ MTicketRenderer(MTemplate f);
bool render(const MTicket&label,QPaintDevice&pdev,QPainter*painter=0,QPointF offset=QPointF());
};
#include "hmac.h"
#include "keygen.h"
#include "main.h"
+#include "templatedlg.h"
#include "version.h"
#include "webrequest.h"
return hostname;
}
-QString MWebRequest::getTemplate(QString f)
+MTemplate MWebRequest::getTemplate(QString f)
{
return temp.getTemplate(f);
}
-bool MWebRequest::setTemplate(QString n,QString f)
+void MWebRequest::editTemplates()
{
- return temp.setTemplate(n,f);
+ MTemplateEditor ed(&temp);
+ ed.exec();
+}
+
+void MWebRequest::updateTemplates()
+{
+ temp.updateTemplates(true);
}
/**return current host name of this session*/
QString hostName();
- /**return the (local) file name of a template, queries the DB if necessary - cache timeout is 5 minutes; returns an empty string if the template does not exist*/
- QString getTemplate(QString);
-
- /**sends the template file to the server, returns whether it was successful*/
- bool setTemplate(QString templatename,QString localfilename);
+ /**returns the requested template as cached locally (see MTemplateStore for details)*/
+ MTemplate getTemplate(QString);
public slots:
/**set how long to wait for a web request*/
/**change password; returns error string if it fails, empty if it succeeds*/
QString changeMyPassword(QString oldpwd,QString newpwd);
+ /**force an update of templates*/
+ void updateTemplates();
+
+ /**opens the template editor*/
+ void editTemplates();
+
private slots:
/**internal: used by wait loop for web requests*/
void httpFin(int,bool);
//profile name for lookups
QString profileid;
//template subsystem
- MTemplates temp;
+ MTemplateStore temp;
/**used by login and relogin to do the actual work*/
bool doLogin();
{
global $db;
header("X-MagicSmoke-Status: Ok");
- print("<TList>\n");
- $res=$db->select("template","filename,hash","");
+ $xml=new DomDocument;
+ $root=$xml->createElement("TList");
+ $res=$db->select("template","filename,hash,description","");
for($i=0;$i<count($res);$i++){
- //it is safe to do this since setTemplate checks syntax:
- print("<Template name=\"".$res[$i]["filename"]."\" hash=\"".$res[$i]["hash"]."\"/>\n");
+ $el=$xml->createElement("Template");
+ $el->setAttribute("name",$res[$i]["filename"]);
+ $el->setAttribute("hash",$res[$i]["hash"]);
+ if(!$db->isNull($res[$i]["description"]))
+ $el->appendChild($xml->createTextNode($res[$i]["description"]));
+ $root->appendChild($el);
}
- print("</TList>\n");
+ $xml->appendChild($root);
+ print($xml->saveXml());
}
function getTemplate($fname)
$fname=strtolower(trim(substr($data,0,$pos)));
$data=substr($data,$pos+1);
//syntax check
- if(ereg("^[a-z0-9_\\.]+$",$fname)===false){
+ if(ereg("^[a-z0-9_]+\\.[a-z0-9_]+(,[a-z0-9_]+)?$",$fname)===false){
header("X-MagicSmoke-Status: Error");
die(tr("Illegal File Name"));
}
}
$db->commitTransaction();
header("X-MagicSmoke-Status: Ok");
+ echo $hash;
+}
+
+function setTemplateDescription($data)
+{
+ $pos=strpos($data,"\n");
+ if($pos===false){
+ header("X-MagicSmoke-Status: Error");
+ die(tr("Unable to find file name"));
+ }
+ //split
+ $fname=strtolower(trim(substr($data,0,$pos)));
+ $data=substr($data,$pos+1);
+ //syntax check
+ if(ereg("^[a-z0-9_]+\\.[a-z0-9_]+(,[a-z0-9_]+)?$",$fname)===false){
+ header("X-MagicSmoke-Status: Error");
+ die(tr("Illegal File Name"));
+ }
+ //store
+ global $db;
+ $r=$db->update("template",array("description"=>$data),"filename=".$db->escapeString($fname));
+ if($r===false || $r<1){
+ header("X-MagicSmoke-Status: Error");
+ die(tr("Template file does not exist"));
+ }
+ header("X-MagicSmoke-Status: Ok");
+}
+
+function deleteTemplate($fname)
+{
+ global $db;
+ $db->deleteRows("template","filename=".$db->escapeString($fname));
+ header("X-MagicSmoke-Status: Ok");
+ echo $hash;
}
?>
\ No newline at end of file
//minimum version that the server understands (4 hex digits)
defversion(MINSERVER,0000)
//current version of the server
-defversion(CURSERVER,0002)
+defversion(CURSERVER,0003)
//current human readable version of the server
defversion(HRSERVER,0.2 beta)
//minimum version that the client requires
defversion(MINCLIENT,0000)
//current version of the client
-defversion(CURCLIENT,0002)
+defversion(CURCLIENT,0003)
//current human readable version of the client
defversion(HRCLIENT,0.2 beta)
tr("getshipping"),tr("setshipping"),tr("deleteshipping"), //shipping info
tr("getticket"),tr("useticket"),tr("changeticketprice"),tr("ticketreturn"),//ticket management
tr("getvoucherprices"),tr("cancelvoucher"),tr("emptyvoucher"),tr("usevoucher"),tr("getvoucher"), //voucher management
- tr("gettemplatelist"),tr("gettemplate"),tr("settemplate") //templates
+ tr("gettemplatelist"),tr("gettemplate"),tr("settemplate"),tr("settemplatedescription"),tr("deletetemplate") //templates
);
/**special roles begin with _ and are listed here (in lower case and wrapped in tr())*/
$SPECIALROLES=array(
setTemplate($REQUESTDATA);
exit();
}
+//set a specific template description
+if($SMOKEREQUEST=="settemplatedescription"){
+ setTemplateDescription($REQUESTDATA);
+ exit();
+}
+//delete a template
+if($SMOKEREQUEST=="deletetemplate"){
+ deleteTemplate(trim($REQUESTDATA));
+ exit();
+}
//get the list of customers