1 /* This file is part of the KDE project
2    Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
3    Copyright (C) 2004 David Faure <faure@kde.org>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19 */
20 
21 #include "KoDocumentInfo.h"
22 
23 #include "KoDocumentBase.h"
24 #include "KoOdfWriteStore.h"
25 #include "KoXmlNS.h"
26 
27 #include <QDateTime>
28 #include <KoStoreDevice.h>
29 #include <KoXmlWriter.h>
30 #include <QDomDocument>
31 #include <KoXmlReader.h>
32 #include <QDir>
33 
34 #include <kconfig.h>
35 #include <kconfiggroup.h>
36 #include <OdfDebug.h>
37 #include <klocalizedstring.h>
38 #include <kuser.h>
39 #include <kemailsettings.h>
40 
41 #include <KritaVersionWrapper.h>
42 
43 
KoDocumentInfo(QObject * parent)44 KoDocumentInfo::KoDocumentInfo(QObject *parent) : QObject(parent)
45 {
46     m_aboutTags << "title" << "description" << "subject" << "abstract"
47     << "keyword" << "initial-creator" << "editing-cycles" << "editing-time"
48     << "date" << "creation-date" << "language" << "license";
49 
50     m_authorTags << "creator" << "creator-first-name" << "creator-last-name" << "initial" << "author-title" << "position" << "company";
51     m_contactTags << "email" << "telephone" << "telephone-work" << "fax" << "country" << "postal-code" << "city" << "street";
52     setAboutInfo("editing-cycles", "0");
53     setAboutInfo("time-elapsed", "0");
54     setAboutInfo("initial-creator", i18n("Unknown"));
55     setAboutInfo("creation-date", QDateTime::currentDateTime()
56                  .toString(Qt::ISODate));
57 }
58 
KoDocumentInfo(const KoDocumentInfo & rhs,QObject * parent)59 KoDocumentInfo::KoDocumentInfo(const KoDocumentInfo &rhs, QObject *parent)
60     : QObject(parent),
61       m_aboutTags(rhs.m_aboutTags),
62       m_authorTags(rhs.m_authorTags),
63       m_contact(rhs.m_contact),
64       m_authorInfo(rhs.m_authorInfo),
65       m_authorInfoOverride(rhs.m_authorInfoOverride),
66       m_aboutInfo(rhs.m_aboutInfo),
67       m_generator(rhs.m_generator)
68 
69 {
70 }
71 
~KoDocumentInfo()72 KoDocumentInfo::~KoDocumentInfo()
73 {
74 }
75 
load(const KoXmlDocument & doc)76 bool KoDocumentInfo::load(const KoXmlDocument &doc)
77 {
78     m_authorInfo.clear();
79 
80     if (!loadAboutInfo(doc.documentElement()))
81         return false;
82 
83     if (!loadAuthorInfo(doc.documentElement()))
84         return false;
85 
86     return true;
87 }
88 
89 
save(QDomDocument & doc)90 QDomDocument KoDocumentInfo::save(QDomDocument &doc)
91 {
92     updateParametersAndBumpNumCycles();
93 
94     QDomElement s = saveAboutInfo(doc);
95     if (!s.isNull())
96         doc.documentElement().appendChild(s);
97 
98     s = saveAuthorInfo(doc);
99     if (!s.isNull())
100         doc.documentElement().appendChild(s);
101 
102 
103     if (doc.documentElement().isNull())
104         return QDomDocument();
105 
106     return doc;
107 }
108 
setAuthorInfo(const QString & info,const QString & data)109 void KoDocumentInfo::setAuthorInfo(const QString &info, const QString &data)
110 {
111     if (!m_authorTags.contains(info) && !m_contactTags.contains(info) && !info.contains("contact-mode-")) {
112         return;
113     }
114 
115     m_authorInfoOverride.insert(info, data);
116 }
117 
setActiveAuthorInfo(const QString & info,const QString & data)118 void KoDocumentInfo::setActiveAuthorInfo(const QString &info, const QString &data)
119 {
120     if (!m_authorTags.contains(info) && !m_contactTags.contains(info) && !info.contains("contact-mode-")) {
121         return;
122     }
123     if (m_contactTags.contains(info)) {
124         m_contact.insert(data, info);
125     } else {
126         m_authorInfo.insert(info, data);
127     }
128     emit infoUpdated(info, data);
129 }
130 
authorInfo(const QString & info) const131 QString KoDocumentInfo::authorInfo(const QString &info) const
132 {
133     if (!m_authorTags.contains(info)  && !m_contactTags.contains(info) && !info.contains("contact-mode-"))
134         return QString();
135 
136     return m_authorInfo[ info ];
137 }
138 
authorContactInfo() const139 QStringList KoDocumentInfo::authorContactInfo() const
140 {
141     return m_contact.keys();
142 }
143 
setAboutInfo(const QString & info,const QString & data)144 void KoDocumentInfo::setAboutInfo(const QString &info, const QString &data)
145 {
146     if (!m_aboutTags.contains(info))
147         return;
148 
149     m_aboutInfo.insert(info, data);
150     emit infoUpdated(info, data);
151 }
152 
aboutInfo(const QString & info) const153 QString KoDocumentInfo::aboutInfo(const QString &info) const
154 {
155     if (!m_aboutTags.contains(info)) {
156         return QString();
157     }
158 
159     return m_aboutInfo[info];
160 }
161 
saveOasisAuthorInfo(KoXmlWriter & xmlWriter)162 bool KoDocumentInfo::saveOasisAuthorInfo(KoXmlWriter &xmlWriter)
163 {
164     Q_FOREACH (const QString & tag, m_authorTags) {
165         if (!authorInfo(tag).isEmpty() && tag == "creator") {
166             xmlWriter.startElement("dc:creator");
167             xmlWriter.addTextNode(authorInfo("creator"));
168             xmlWriter.endElement();
169         } else if (!authorInfo(tag).isEmpty()) {
170             xmlWriter.startElement("meta:user-defined");
171             xmlWriter.addAttribute("meta:name", tag);
172             xmlWriter.addTextNode(authorInfo(tag));
173             xmlWriter.endElement();
174         }
175     }
176 
177     return true;
178 }
179 
loadOasisAuthorInfo(const KoXmlNode & metaDoc)180 bool KoDocumentInfo::loadOasisAuthorInfo(const KoXmlNode &metaDoc)
181 {
182     KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, "creator");
183     if (!e.isNull() && !e.text().isEmpty())
184         setActiveAuthorInfo("creator", e.text());
185 
186     KoXmlNode n = metaDoc.firstChild();
187     for (; !n.isNull(); n = n.nextSibling()) {
188         if (!n.isElement())
189             continue;
190 
191         KoXmlElement e = n.toElement();
192         if (!(e.namespaceURI() == KoXmlNS::meta &&
193                 e.localName() == "user-defined" && !e.text().isEmpty()))
194             continue;
195 
196         QString name = e.attributeNS(KoXmlNS::meta, "name", QString());
197         setActiveAuthorInfo(name, e.text());
198     }
199 
200     return true;
201 }
202 
loadAuthorInfo(const KoXmlElement & e)203 bool KoDocumentInfo::loadAuthorInfo(const KoXmlElement &e)
204 {
205     m_contact.clear();
206     KoXmlNode n = e.namedItem("author").firstChild();
207     for (; !n.isNull(); n = n.nextSibling()) {
208         KoXmlElement e = n.toElement();
209         if (e.isNull())
210             continue;
211 
212         if (e.tagName() == "full-name") {
213             setActiveAuthorInfo("creator", e.text().trimmed());
214         } else if (e.tagName() == "contact") {
215             m_contact.insert(e.text(), e.attribute("type"));
216         } else {
217             setActiveAuthorInfo(e.tagName(), e.text().trimmed());
218         }
219     }
220 
221     return true;
222 }
223 
saveAuthorInfo(QDomDocument & doc)224 QDomElement KoDocumentInfo::saveAuthorInfo(QDomDocument &doc)
225 {
226     QDomElement e = doc.createElement("author");
227     QDomElement t;
228 
229     Q_FOREACH (const QString &tag, m_authorTags) {
230         if (tag == "creator")
231             t = doc.createElement("full-name");
232         else
233             t = doc.createElement(tag);
234 
235         e.appendChild(t);
236         t.appendChild(doc.createTextNode(authorInfo(tag)));
237     }
238     for (int i=0; i<m_contact.keys().size(); i++) {
239         t = doc.createElement("contact");
240         e.appendChild(t);
241         QString key = m_contact.keys().at(i);
242         t.setAttribute("type", m_contact[key]);
243         t.appendChild(doc.createTextNode(key));
244     }
245 
246     return e;
247 }
248 
saveOasisAboutInfo(KoXmlWriter & xmlWriter)249 bool KoDocumentInfo::saveOasisAboutInfo(KoXmlWriter &xmlWriter)
250 {
251     Q_FOREACH (const QString &tag, m_aboutTags) {
252         if (!aboutInfo(tag).isEmpty() || tag == "title") {
253             if (tag == "keyword") {
254                 Q_FOREACH (const QString & tmp, aboutInfo("keyword").split(';')) {
255                     xmlWriter.startElement("meta:keyword");
256                     xmlWriter.addTextNode(tmp);
257                     xmlWriter.endElement();
258                 }
259             } else if (tag == "title" || tag == "description" || tag == "subject" ||
260                        tag == "date" || tag == "language") {
261                 QByteArray elementName(QString("dc:" + tag).toLatin1());
262                 xmlWriter.startElement(elementName.constData());
263                 xmlWriter.addTextNode(aboutInfo(tag));
264                 xmlWriter.endElement();
265             } else {
266                 QByteArray elementName(QString("meta:" + tag).toLatin1());
267                 xmlWriter.startElement(elementName.constData());
268                 xmlWriter.addTextNode(aboutInfo(tag));
269                 xmlWriter.endElement();
270             }
271         }
272     }
273 
274     return true;
275 }
276 
loadOasisAboutInfo(const KoXmlNode & metaDoc)277 bool KoDocumentInfo::loadOasisAboutInfo(const KoXmlNode &metaDoc)
278 {
279     QStringList keywords;
280     KoXmlElement e;
281     forEachElement(e, metaDoc) {
282         QString tag(e.localName());
283         if (! m_aboutTags.contains(tag) && tag != "generator") {
284             continue;
285         }
286 
287         //debugOdf<<"localName="<<e.localName();
288         if (tag == "keyword") {
289             if (!e.text().isEmpty())
290                 keywords << e.text().trimmed();
291         } else if (tag == "description") {
292             //this is the odf way but add meta:comment if is already loaded
293             KoXmlElement e  = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
294             if (!e.isNull() && !e.text().isEmpty())
295                 setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
296         } else if (tag == "abstract") {
297             //this was the old way so add it to dc:description
298             KoXmlElement e  = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
299             if (!e.isNull() && !e.text().isEmpty())
300                 setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
301         } else if (tag == "title"|| tag == "subject"
302                    || tag == "date" || tag == "language") {
303             KoXmlElement e  = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
304             if (!e.isNull() && !e.text().isEmpty())
305                 setAboutInfo(tag, e.text().trimmed());
306         } else if (tag == "generator") {
307             setOriginalGenerator(e.text().trimmed());
308         } else {
309             KoXmlElement e  = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
310             if (!e.isNull() && !e.text().isEmpty())
311                 setAboutInfo(tag, e.text().trimmed());
312         }
313     }
314 
315     if (keywords.count() > 0) {
316         setAboutInfo("keyword", keywords.join(", "));
317     }
318 
319     return true;
320 }
321 
loadAboutInfo(const KoXmlElement & e)322 bool KoDocumentInfo::loadAboutInfo(const KoXmlElement &e)
323 {
324     KoXmlNode n = e.namedItem("about").firstChild();
325     KoXmlElement tmp;
326     for (; !n.isNull(); n = n.nextSibling()) {
327         tmp = n.toElement();
328         if (tmp.isNull())
329             continue;
330 
331         if (tmp.tagName() == "abstract")
332             setAboutInfo("abstract", tmp.text());
333 
334         setAboutInfo(tmp.tagName(), tmp.text());
335     }
336 
337     return true;
338 }
339 
saveAboutInfo(QDomDocument & doc)340 QDomElement KoDocumentInfo::saveAboutInfo(QDomDocument &doc)
341 {
342     QDomElement e = doc.createElement("about");
343     QDomElement t;
344 
345     Q_FOREACH (const QString &tag, m_aboutTags) {
346         if (tag == "abstract") {
347             t = doc.createElement("abstract");
348             e.appendChild(t);
349             t.appendChild(doc.createCDATASection(aboutInfo(tag)));
350         } else {
351             t = doc.createElement(tag);
352             e.appendChild(t);
353             t.appendChild(doc.createTextNode(aboutInfo(tag)));
354         }
355     }
356 
357     return e;
358 }
359 
updateParametersAndBumpNumCycles()360 void KoDocumentInfo::updateParametersAndBumpNumCycles()
361 {
362     KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
363     if (doc && doc->isAutosaving()) {
364         return;
365     }
366 
367     setAboutInfo("editing-cycles", QString::number(aboutInfo("editing-cycles").toInt() + 1));
368     setAboutInfo("date", QDateTime::currentDateTime().toString(Qt::ISODate));
369 
370     updateParameters();
371 }
372 
updateParameters()373 void KoDocumentInfo::updateParameters()
374 {
375     KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
376     if (doc && (!doc->isModified())) {
377         return;
378     }
379 
380     KConfig config("kritarc");
381     config.reparseConfiguration();
382     KConfigGroup appAuthorGroup(&config, "Author");
383     QString profile = appAuthorGroup.readEntry("active-profile", "");
384 
385     QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/";
386     QDir dir(authorInfo);
387     QStringList filters = QStringList() << "*.authorinfo";
388 
389     //Anon case
390     setActiveAuthorInfo("creator", QString());
391     setActiveAuthorInfo("initial", "");
392     setActiveAuthorInfo("author-title", "");
393     setActiveAuthorInfo("position", "");
394     setActiveAuthorInfo("company", "");
395     if (dir.entryList(filters).contains(profile+".authorinfo")) {
396         QFile file(dir.absoluteFilePath(profile+".authorinfo"));
397         if (file.exists()) {
398             file.open(QFile::ReadOnly);
399             QByteArray ba = file.readAll();
400             file.close();
401             QDomDocument doc = QDomDocument();
402             doc.setContent(ba);
403             QDomElement root = doc.firstChildElement();
404 
405             QDomElement el = root.firstChildElement("nickname");
406             if (!el.isNull()) {
407                 setActiveAuthorInfo("creator", el.text());
408             }
409             el = root.firstChildElement("givenname");
410             if (!el.isNull()) {
411                 setActiveAuthorInfo("creator-first-name", el.text());
412             }
413             el = root.firstChildElement("middlename");
414             if (!el.isNull()) {
415                 setActiveAuthorInfo("initial", el.text());
416             }
417             el = root.firstChildElement("familyname");
418             if (!el.isNull()) {
419                setActiveAuthorInfo("creator-last-name", el.text());
420             }
421             el = root.firstChildElement("title");
422             if (!el.isNull()) {
423                 setActiveAuthorInfo("author-title", el.text());
424             }
425             el = root.firstChildElement("position");
426             if (!el.isNull()) {
427                 setActiveAuthorInfo("position", el.text());
428             }
429             el = root.firstChildElement("company");
430             if (!el.isNull()) {
431                 setActiveAuthorInfo("company", el.text());
432             }
433 
434             m_contact.clear();
435             el = root.firstChildElement("contact");
436             while (!el.isNull()) {
437                 m_contact.insert(el.text(), el.attribute("type"));
438                 el = el.nextSiblingElement("contact");
439             }
440         }
441     }
442 
443     //allow author info set programmatically to override info from author profile
444     Q_FOREACH (const QString &tag, m_authorTags) {
445         if (m_authorInfoOverride.contains(tag)) {
446             setActiveAuthorInfo(tag, m_authorInfoOverride.value(tag));
447         }
448     }
449 }
450 
resetMetaData()451 void KoDocumentInfo::resetMetaData()
452 {
453     setAboutInfo("editing-cycles", QString::number(0));
454     setAboutInfo("initial-creator", authorInfo("creator"));
455     setAboutInfo("creation-date", QDateTime::currentDateTime().toString(Qt::ISODate));
456     setAboutInfo("editing-time", QString::number(0));
457 }
458 
originalGenerator() const459 QString KoDocumentInfo::originalGenerator() const
460 {
461     return m_generator;
462 }
463 
setOriginalGenerator(const QString & generator)464 void KoDocumentInfo::setOriginalGenerator(const QString &generator)
465 {
466     m_generator = generator;
467 }
468