1 /***************************************************************************
2 * SPDX-FileCopyrightText: 2021 S. MANKOWSKI stephane@mankowski.fr
3 * SPDX-FileCopyrightText: 2021 G. DE BURE support@mankowski.fr
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 ***************************************************************************/
6 /** @file
7 * This file defines classes SKGObjectBase.
8 *
9 * @author Stephane MANKOWSKI / Guillaume DE BURE
10 */
11 #include "skgobjectbase.h"
12
13 #include <qsqldatabase.h>
14 #include "qregularexpression.h"
15
16 #include <klocalizedstring.h>
17 #include <kstringhandler.h>
18
19 #include <algorithm>
20
21 #include "skgdocument.h"
22 #include "skgnamedobject.h"
23 #include "skgservices.h"
24 #include "skgtraces.h"
25
26 /**
27 * This private class of SKGObjectBase
28 */
29 class SKGObjectBasePrivate
30 {
31 public:
32 /**
33 * internal id
34 */
35 int id = 0;
36
37 /**
38 * internal table
39 */
40 QString table;
41
42 /**
43 * internal document
44 */
45 SKGDocument* document = nullptr;
46
47 /**
48 * internal attributes
49 */
50 SKGQStringQStringMap attributes;
51
52 /**
53 * objects to save
54 */
55 SKGObjectBase::SKGListSKGObjectBase objects;
56 };
57
SKGObjectBase()58 SKGObjectBase::SKGObjectBase() : SKGObjectBase(nullptr)
59 {}
60
SKGObjectBase(SKGDocument * iDocument,const QString & iTable,int iID)61 SKGObjectBase::SKGObjectBase(SKGDocument* iDocument, const QString& iTable, int iID)
62 : d(new SKGObjectBasePrivate)
63 {
64 d->id = iID;
65 d->table = iTable;
66 d->document = iDocument;
67 if (Q_LIKELY(d->id != 0)) {
68 load();
69 }
70 }
71
~SKGObjectBase()72 SKGObjectBase::~SKGObjectBase()
73 {
74 delete d;
75 }
76
SKGObjectBase(const SKGObjectBase & iObject)77 SKGObjectBase::SKGObjectBase(const SKGObjectBase& iObject)
78 : d(new SKGObjectBasePrivate)
79 {
80 copyFrom(iObject);
81 }
82
SKGObjectBase(SKGObjectBase && iObject)83 SKGObjectBase::SKGObjectBase(SKGObjectBase&& iObject) noexcept
84 : d(iObject.d)
85 {
86 iObject.d = nullptr;
87 }
88
operator =(const SKGObjectBase & iObject)89 SKGObjectBase& SKGObjectBase::operator= (const SKGObjectBase& iObject)
90 {
91 copyFrom(iObject);
92 return *this;
93 }
94
operator ==(const SKGObjectBase & iObject) const95 bool SKGObjectBase::operator==(const SKGObjectBase& iObject) const
96 {
97 return (getUniqueID() == iObject.getUniqueID());
98 }
99
operator !=(const SKGObjectBase & iObject) const100 bool SKGObjectBase::operator!=(const SKGObjectBase& iObject) const
101 {
102 return !(*this == iObject);
103 }
104
operator <(const SKGObjectBase & iObject) const105 bool SKGObjectBase::operator<(const SKGObjectBase& iObject) const
106 {
107 double d1 = SKGServices::stringToDouble(getAttribute(QStringLiteral("f_sortorder")));
108 double d2 = SKGServices::stringToDouble(iObject.getAttribute(QStringLiteral("f_sortorder")));
109 return (d1 < d2);
110 }
111
operator >(const SKGObjectBase & iObject) const112 bool SKGObjectBase::operator>(const SKGObjectBase& iObject) const
113 {
114 double d1 = SKGServices::stringToDouble(getAttribute(QStringLiteral("f_sortorder")));
115 double d2 = SKGServices::stringToDouble(iObject.getAttribute(QStringLiteral("f_sortorder")));
116 return (d1 > d2);
117 }
118
getUniqueID() const119 QString SKGObjectBase::getUniqueID() const
120 {
121 return SKGServices::intToString(d->id) % '-' % getRealTable();
122 }
123
getID() const124 int SKGObjectBase::getID() const
125 {
126 return d->id;
127 }
128
getAttributeFromView(const QString & iView,const QString & iName) const129 QString SKGObjectBase::getAttributeFromView(const QString& iView, const QString& iName) const
130 {
131 QString output;
132
133 SKGStringListList result;
134 QString wc = getWhereclauseId();
135 if (wc.isEmpty()) {
136 wc = "id=" % SKGServices::intToString(d->id);
137 }
138 QString sql = "SELECT " % iName % " FROM " % iView % " WHERE " % wc;
139 if (getDocument() != nullptr) {
140 getDocument()->executeSelectSqliteOrder(sql, result, false);
141 }
142 if (result.count() == 2) {
143 output = result.at(1).at(0);
144 }
145
146 return output;
147 }
148
getDisplayName() const149 QString SKGObjectBase::getDisplayName() const
150 {
151 return getAttributeFromView("v_" % getRealTable() % "_displayname", QStringLiteral("t_displayname"));
152 }
153
copyFrom(const SKGObjectBase & iObject)154 void SKGObjectBase::copyFrom(const SKGObjectBase& iObject)
155 {
156 if (d == nullptr) {
157 d = new SKGObjectBasePrivate;
158 }
159 d->id = iObject.d->id;
160 d->table = iObject.d->table;
161 d->document = iObject.d->document;
162 d->attributes = iObject.d->attributes;
163 }
164
cloneInto()165 SKGObjectBase SKGObjectBase::cloneInto()
166 {
167 return cloneInto(nullptr);
168 }
169
cloneInto(SKGDocument * iDocument)170 SKGObjectBase SKGObjectBase::cloneInto(SKGDocument* iDocument)
171 {
172 SKGDocument* targetDocument = iDocument;
173 if (targetDocument == nullptr) {
174 targetDocument = d->document;
175 }
176
177 SKGObjectBase output;
178 output.copyFrom(*this);
179 output.d->id = 0;
180 output.d->document = targetDocument;
181 return output;
182 }
183
resetID()184 SKGError SKGObjectBase::resetID()
185 {
186 d->id = 0;
187 return SKGError();
188 }
189
getTable() const190 QString SKGObjectBase::getTable() const
191 {
192 return d->table;
193 }
194
getRealTable() const195 QString SKGObjectBase::getRealTable() const
196 {
197 return SKGServices::getRealTable(d->table);
198 }
199
getDocument() const200 SKGDocument* SKGObjectBase::getDocument() const
201 {
202 return d->document;
203 }
204
getAttributes() const205 SKGQStringQStringMap SKGObjectBase::getAttributes() const
206 {
207 return d->attributes;
208 }
209
getNbAttributes() const210 int SKGObjectBase::getNbAttributes() const
211 {
212 return d->attributes.count();
213 }
214
setAttributes(const QStringList & iNames,const QStringList & iValues)215 SKGError SKGObjectBase::setAttributes(const QStringList& iNames, const QStringList& iValues)
216 {
217 SKGError err;
218 int nb = iNames.size();
219 for (int i = 0; !err && i < nb; ++i) {
220 const auto& att = iNames.at(i);
221 const auto& val = iValues.at(i);
222
223 if (Q_LIKELY(att != QStringLiteral("id"))) {
224 err = setAttribute(att, val);
225 } else {
226 d->id = SKGServices::stringToInt(val);
227 }
228 }
229 return err;
230 }
231
setAttribute(const QString & iName,const QString & iValue)232 SKGError SKGObjectBase::setAttribute(const QString& iName, const QString& iValue)
233 {
234 SKGError err;
235 if (Q_LIKELY(iValue != NOUPDATE)) {
236 QString val = iValue;
237
238 // Case modification on pointed document
239 if (this->getRealTable() == this->getTable() && iName != iName.toLower()) {
240 QString realAttribute = d->document->getRealAttribute(iName);
241 if (!realAttribute.isEmpty()) {
242 QStringList l = realAttribute.split('.');
243 if (l.size() == 3) {
244 SKGObjectBase obj;
245 QString refId = getAttribute(l.at(1));
246 if (!refId.isEmpty()) {
247 err = getDocument()->getObject("v_" % l.at(0), "id=" % refId, obj);
248 IFOK(err) {
249 err = obj.setAttribute(l.at(2), iValue);
250 d->objects.push_back(obj);
251 }
252 }
253 }
254 }
255 } else if (iValue.startsWith(QLatin1String("="))) {
256 // Case modificator
257 QString op = iValue.right(iValue.length() - 1).toLower();
258 val = d->attributes[iName];
259 if (op == i18nc("Key word to modify a string into a field", "lower")) {
260 val = val.toLower();
261 } else if (op == i18nc("Key word to modify a string into a field", "upper")) {
262 val = val.toUpper();
263 } else if (op == i18nc("Key word to modify a string into a field", "capwords")) {
264 val = KStringHandler::capwords(val.toLower());
265 } else if (op == i18nc("Key word to modify a string into a field", "capitalize")) {
266 val = val.at(0).toUpper() % val.right(val.length() - 1).toLower();
267 } else if (op == i18nc("Key word to modify a string into a field", "trim")) {
268 val = val.trimmed();
269 } else {
270 val = iValue;
271 }
272 }
273 d->attributes[iName] = val;
274 }
275 return err;
276 }
277
getAttribute(const QString & iName) const278 QString SKGObjectBase::getAttribute(const QString& iName) const
279 {
280 QString output;
281 if (Q_LIKELY(d->attributes.contains(iName))) {
282 output = d->attributes[iName];
283 } else if (iName == QStringLiteral("id")) {
284 output = SKGServices::intToString(getID());
285 } if (iName.startsWith(QLatin1String("p_"))) {
286 output = getProperty(iName.right(iName.length() - 2));
287 } else {
288 // Is the iName a number ?
289 bool ok;
290 int pos = iName.toInt(&ok);
291 if (ok) {
292 // What is the key corresponding to this name ?
293 QStringList keys = d->attributes.keys();
294 std::sort(keys.begin(), keys.end());
295 if (pos >= 0 && pos < keys.count()) {
296 output = d->attributes[keys.at(pos)];
297 }
298 } else {
299 // Case modification on pointed document iName1.(iTable2)iName2 or iName1.iName2
300 int pos = iName.indexOf('.');
301 if (pos != -1) {
302 QString attributeref = iName.left(pos);
303 QString refId = getAttribute(attributeref);
304 if (!refId.isEmpty()) {
305 QString rest = iName.right(iName.length() - pos - 1);
306
307 QString table;
308 QString att;
309 auto rx = QRegularExpression(QStringLiteral("^\\(([^\\)]*)\\)(.*)$")).match(rest);
310 if (rx.hasMatch()) {
311 // Case iName1.(iTable2)iName2
312 table = rx.captured(1);
313 att = rx.captured(2);
314 } else {
315 auto rx2 = QRegularExpression(QStringLiteral("^.*_(.*)_.*$")).match(attributeref);
316 if (rx2.hasMatch()) {
317 // Case iName1.iName2
318 table = "v_" % rx2.captured(1);
319 att = rest;
320 }
321 }
322
323 SKGObjectBase obj;
324 SKGError err = getDocument()->getObject(table, "id=" % refId, obj);
325 IFOK(err) {
326 output = obj.getAttribute(att);
327 }
328 }
329 }
330 }
331 }
332
333 return output;
334 }
335
exist() const336 bool SKGObjectBase::exist() const
337 {
338 SKGTRACEINFUNC(20)
339
340 SKGStringListList result;
341 QString wc = getWhereclauseId();
342 if (wc.isEmpty() && d->id != 0) {
343 wc = "id=" % SKGServices::intToString(d->id);
344 }
345 if (wc.isEmpty()) {
346 return false;
347 }
348
349 QString sql = "SELECT count(1) FROM " % d->table % " WHERE " % wc;
350 if (getDocument() != nullptr) {
351 getDocument()->executeSelectSqliteOrder(sql, result, false);
352 }
353 return (result.size() >= 2 && result.at(1).at(0) != QStringLiteral("0"));
354 }
355
load()356 SKGError SKGObjectBase::load()
357 {
358 SKGError err;
359 SKGTRACEINFUNCRC(20, err)
360
361 if (Q_LIKELY(getDocument() && !getTable().isEmpty())) {
362 // Prepare where clause
363 QString wc = getWhereclauseId();
364 if (wc.isEmpty()) {
365 wc = "id=" % SKGServices::intToString(d->id);
366 }
367
368 // Execute sql order
369 SKGStringListList result;
370 err = getDocument()->executeSelectSqliteOrder("SELECT * FROM " % d->table % " WHERE " % wc, result, false);
371 IFOK(err) {
372 int size = result.size();
373 if (Q_UNLIKELY(size == 1)) {
374 err = SKGError(ERR_INVALIDARG, i18nc("Error message: Could not load something because it is not in the database", "Load of '%1' with '%2' failed because it was not found in the database", d->table, wc));
375 } else if (Q_UNLIKELY(size != 2)) {
376 err = SKGError(ERR_INVALIDARG, i18np("Load of '%2' with '%3' failed because of bad size of result (found one object)",
377 "Load of '%2' with '%3' failed because of bad size of result (found %1 objects)",
378 size - 1, d->table, wc));
379 } else {
380 SKGStringListList::const_iterator itrow = result.constBegin();
381 QStringList columns = *(itrow);
382 ++itrow;
383 QStringList values = *(itrow);
384 err = setAttributes(columns, values);
385 }
386 }
387 }
388 return err;
389 }
390
getWhereclauseId() const391 QString SKGObjectBase::getWhereclauseId() const
392 {
393 int id = getID();
394 if (id != 0) {
395 return "id=" % SKGServices::intToString(id);
396 }
397 return QLatin1String("");
398 }
399
save(bool iInsertOrUpdate,bool iReloadAfterSave)400 SKGError SKGObjectBase::save(bool iInsertOrUpdate, bool iReloadAfterSave)
401 {
402 SKGError err;
403 SKGTRACEINFUNCRC(20, err)
404
405 if (Q_UNLIKELY(!d->document)) {
406 err = SKGError(ERR_POINTER, i18nc("Error message", "Operation impossible because the document is missing"));
407 } else {
408 // Save linking objects
409 int nb = d->objects.count();
410 for (int i = 0; !err && i < nb; ++i) {
411 SKGObjectBase ref = d->objects.at(i);
412 err = ref.save(iInsertOrUpdate, iReloadAfterSave);
413 }
414
415 // Check if we are in a transaction
416 IFOKDO(err, d->document->checkExistingTransaction())
417 IFOK(err) {
418 // Table to use
419 QString tablename = getRealTable();
420
421 // Build order
422 QString part1Insert;
423 QString part2Insert;
424 QString partUpdate;
425
426 SKGQStringQStringMap::const_iterator it;
427 for (it = d->attributes.constBegin() ; it != d->attributes.constEnd(); ++it) {
428 QString att = SKGServices::stringToSqlString(it.key());
429 QString attlower = att.toLower();
430 if (att.length() > 2 && att == attlower) { // We must ignore attributes coming from views
431 QString value = '\'' % SKGServices::stringToSqlString(it.value()) % '\'';
432
433 if (!part1Insert.isEmpty()) {
434 part1Insert.append(',');
435 part2Insert.append(',');
436 partUpdate.append(',');
437 }
438 // Attribute
439 part1Insert.append('\'' % att % '\'');
440
441 // Value
442 part2Insert.append(value);
443
444 // Attribute=Value for update
445 partUpdate.append(att % '=' % value);
446 }
447 }
448
449 // We try an Insert
450 if (d->id == 0) {
451 // We have to try un insert
452 err = getDocument()->executeSqliteOrder("INSERT INTO " % tablename % " (" % part1Insert % ") VALUES (" % part2Insert % ')', &(d->id));
453 } else {
454 // We must try an update
455 err = SKGError(ERR_ABORT, QLatin1String("")); // Just to go in UPDATE code
456 }
457
458 if (err && iInsertOrUpdate) {
459 // INSERT failed, could we try an update ?
460 QString wc = this->getWhereclauseId();
461 if (!wc.isEmpty()) {
462 // Yes ==> Update
463 err = getDocument()->executeSqliteOrder("UPDATE " % tablename % " SET " % partUpdate % " WHERE " % wc);
464 }
465 }
466 }
467 }
468
469 // Reload object is updated
470 if (!err && iReloadAfterSave) {
471 // The object has been updated ==>load
472 err = load();
473 }
474
475 return err;
476 }
477
remove(bool iSendMessage,bool iForce) const478 SKGError SKGObjectBase::remove(bool iSendMessage, bool iForce) const
479 {
480 SKGError err;
481 SKGTRACEINFUNCRC(20, err)
482 if (Q_UNLIKELY(!d->document)) {
483 err = SKGError(ERR_POINTER, i18nc("Error message", "Operation impossible because the document is missing"));
484 } else {
485 // Check if we are in a transaction
486 err = d->document->checkExistingTransaction();
487
488 // delete order
489 QString viewForDelete = QStringLiteral("v_") % getRealTable() % "_delete";
490
491 // Check if the delete view exist
492 SKGStringListList temporaryResult;
493 d->document->executeSelectSqliteOrder("PRAGMA table_info( " % viewForDelete % " );", temporaryResult);
494 if (!iForce && temporaryResult.count() > 1) { // At least one attribute
495 // Delete view exists, check if the delete is authorized
496 err = d->document->executeSelectSqliteOrder("SELECT t_delete_message FROM " % viewForDelete % " WHERE id=" % SKGServices::intToString(d->id), temporaryResult, false);
497 IFOK(err) {
498 QString msg;
499 if (temporaryResult.count() > 1) {
500 msg = temporaryResult.at(1).at(0);
501 }
502 // Should the string below be translated ??? It contains no word
503 if (!msg.isEmpty()) {
504 err = SKGError(ERR_FORCEABLE, i18nc("Error message for an object", "'%1': %2", getDisplayName(), msg));
505 }
506 }
507 }
508
509 QString displayname = getDisplayName(); // Must be done before the delete order
510 IFOKDO(err, d->document->executeSqliteOrder("DELETE FROM " % getRealTable() % " WHERE id=" % SKGServices::intToString(d->id)))
511 if (iSendMessage && !err && !displayname.isEmpty()) {
512 err = d->document->sendMessage(i18nc("An information to the user that something was deleted", "'%1' has been deleted", displayname), SKGDocument::Hidden);
513 }
514 }
515
516 return err;
517 }
518
dump() const519 SKGError SKGObjectBase::dump() const
520 {
521 // dump
522 SKGTRACE << "=== START DUMP [" << getUniqueID() << "]===" << SKGENDL;
523 SKGQStringQStringMap::const_iterator it;
524 for (it = d->attributes.constBegin() ; it != d->attributes.constEnd(); ++it) {
525 SKGTRACE << it.key() << "=[" << it.value() << ']' << SKGENDL;
526 }
527 SKGTRACE << "=== END DUMP [" << getUniqueID() << "]===" << SKGENDL;
528 return SKGError();
529 }
530
getProperties() const531 QStringList SKGObjectBase::getProperties() const
532 {
533 return Q_UNLIKELY(!getDocument()) ? QStringList() : getDocument()->getParameters(getUniqueID());
534 }
535
getProperty(const QString & iName) const536 QString SKGObjectBase::getProperty(const QString& iName) const
537 {
538 return Q_UNLIKELY(!getDocument()) ? QString() : getDocument()->getParameter(iName, getUniqueID());
539 }
540
getPropertyObject(const QString & iName) const541 SKGObjectBase SKGObjectBase::getPropertyObject(const QString& iName) const
542 {
543 SKGObjectBase property;
544 if (getDocument() != nullptr) {
545 getDocument()->getObject(QStringLiteral("parameters"), "t_name='" % SKGServices::stringToSqlString(iName) %
546 "' AND t_uuid_parent='" % SKGServices::stringToSqlString(getUniqueID()) % '\'', property);
547 }
548 return property;
549 }
550
getPropertyBlob(const QString & iName) const551 QVariant SKGObjectBase::getPropertyBlob(const QString& iName) const
552 {
553 return Q_UNLIKELY(!getDocument()) ? QVariant() : getDocument()->getParameterBlob(iName, getUniqueID());
554 }
555
setProperty(const QString & iName,const QString & iValue,const QString & iFileName,SKGPropertyObject * oObjectCreated) const556 SKGError SKGObjectBase::setProperty(const QString& iName, const QString& iValue, const QString& iFileName, SKGPropertyObject* oObjectCreated) const
557 {
558 SKGError err = Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->setParameter(iName, iValue, iFileName, getUniqueID(), oObjectCreated);
559
560 // Send message
561 IFOKDO(err, Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->sendMessage(i18nc("An information to the user", "The property '%1=%2' has been added on '%3'", iName, iValue, getDisplayName()), SKGDocument::Hidden))
562
563 return err;
564 }
565
setProperty(const QString & iName,const QString & iValue,const QVariant & iBlob,SKGPropertyObject * oObjectCreated) const566 SKGError SKGObjectBase::setProperty(const QString& iName, const QString& iValue, const QVariant& iBlob, SKGPropertyObject* oObjectCreated) const
567 {
568 SKGError err = Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->setParameter(iName, iValue, iBlob, getUniqueID(), oObjectCreated);
569
570 // Send message
571 IFOKDO(err, Q_UNLIKELY(!getDocument()) ? SKGError() : getDocument()->sendMessage(i18nc("An information to the user", "The property '%1=%2' has been added on '%3'", iName, iValue, getDisplayName()), SKGDocument::Hidden))
572
573 return err;
574 }
575