1 /*
2 SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de>
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6
7 #include "phpdocsplugin.h"
8 #include "phpdocsmodel.h"
9
10 #include <KPluginFactory>
11 #include <KPluginLoader>
12 #include <KAboutData>
13 #include <ksettings/Dispatcher>
14
15 #include <interfaces/idocumentation.h>
16 #include <interfaces/icore.h>
17 #include <interfaces/idocumentationcontroller.h>
18
19 #include <language/duchain/duchain.h>
20 #include <language/duchain/declaration.h>
21 #include <language/duchain/duchainlock.h>
22
23 #include <language/duchain/classdeclaration.h>
24 #include <language/duchain/functiondeclaration.h>
25 #include <language/duchain/classmemberdeclaration.h>
26 #include <language/duchain/classfunctiondeclaration.h>
27
28 #include <QFile>
29
30 #include "phpdocsdebug.h"
31 #include "phpdocumentation.h"
32 #include "phpdocssettings.h"
33
34 using namespace KDevelop;
35
36 K_PLUGIN_FACTORY_WITH_JSON(PhpDocsFactory, "kdevphpdocs.json", registerPlugin<PhpDocsPlugin>();)
37
PhpDocsPlugin(QObject * parent,const QVariantList & args)38 PhpDocsPlugin::PhpDocsPlugin(QObject* parent, const QVariantList& args)
39 : IPlugin(QStringLiteral("kdevphpdocs"), parent)
40 , m_model(new PhpDocsModel(this))
41 {
42 Q_UNUSED(args);
43
44 readConfig();
45
46 KSettings::Dispatcher::registerComponent( QStringLiteral("kdevphpdocs_config"), this, "readConfig" );
47 }
48
~PhpDocsPlugin()49 PhpDocsPlugin::~PhpDocsPlugin()
50 {
51 }
52
name() const53 QString PhpDocsPlugin::name() const
54 {
55 return QStringLiteral("PHP");
56 }
57
icon() const58 QIcon PhpDocsPlugin::icon() const
59 {
60 static QIcon icon = QIcon::fromTheme(QStringLiteral("application-x-php"));
61 return icon;
62 }
63
readConfig()64 void PhpDocsPlugin::readConfig()
65 {
66 // since PhpDocsSettings pointer in this plugin is distinct from the one in the KCM
67 // we have to trigger reading manually
68 PhpDocsSettings::self()->load();
69 }
70
71 ///TODO: possibly return multiple filenames (i.e. fallbacks) when doing local lookups
getDocumentationFilename(KDevelop::Declaration * dec,const bool & isLocal) const72 QString PhpDocsPlugin::getDocumentationFilename( KDevelop::Declaration* dec, const bool& isLocal ) const
73 {
74 QString fname;
75
76 //TODO: most of the SPL stuff is not found for me in the deb package php-doc
77 // => check newer documentation or give a fallback to ref.spl.html
78 if ( ClassFunctionDeclaration* fdec = dynamic_cast<ClassFunctionDeclaration*>( dec ) ) {
79 // class methods -> php.net/CLASS.METHOD
80 // local: either CLASS.METHOD.html or function.CLASS-METHOD.html... really sucks :-/
81 // for now, use the latter...
82 if ( dec->context() && dec->context()->type() == DUContext::Class && dec->context()->owner() ) {
83 QString className = dec->context()->owner()->identifier().toString();
84
85 if ( !isLocal ) {
86 fname = className + '.' + fdec->identifier().toString();
87 } else {
88 if ( fdec->isConstructor() ) {
89 fname = QStringLiteral("construct");
90 } else if ( fdec->isDestructor() ) {
91 fname = QStringLiteral("destruct");
92 } else {
93 fname = fdec->identifier().toString();
94 }
95 //TODO: CLASS.METHOD.html e.g. for xmlreader etc. pp.
96 fname = "function." + className + '-' + fname;
97 }
98 }
99 } else if ( dynamic_cast<ClassDeclaration*>(dec) ) {
100 fname = "class." + dec->identifier().toString();
101 } else if ( dynamic_cast<FunctionDeclaration*>(dec) ) {
102 fname = "function." + dec->identifier().toString();
103 }
104 // check for superglobals / reserved variables
105 else if ( dec->identifier() == Identifier(QStringLiteral("GLOBALS")) ||
106 dec->identifier() == Identifier(QStringLiteral("php_errormsg")) ||
107 dec->identifier() == Identifier(QStringLiteral("HTTP_RAW_POST_DATA")) ||
108 dec->identifier() == Identifier(QStringLiteral("http_response_header")) ||
109 dec->identifier() == Identifier(QStringLiteral("argc")) ||
110 dec->identifier() == Identifier(QStringLiteral("argv")) ||
111 dec->identifier() == Identifier(QStringLiteral("_GET")) ||
112 dec->identifier() == Identifier(QStringLiteral("_POST")) ||
113 dec->identifier() == Identifier(QStringLiteral("_FILES")) ||
114 dec->identifier() == Identifier(QStringLiteral("_REQUEST")) ||
115 dec->identifier() == Identifier(QStringLiteral("_SESSION")) ||
116 dec->identifier() == Identifier(QStringLiteral("_ENV")) ||
117 dec->identifier() == Identifier(QStringLiteral("_COOKIE")) ) {
118 if ( isLocal ) {
119 fname = QStringLiteral("reserved.variables.") + dec->identifier().toString().remove('_');
120 } else {
121 fname = dec->identifier().toString();
122 }
123 }
124
125 qCDebug(DOCS) << fname;
126
127 if ( !fname.isEmpty() && isLocal ) {
128 fname = fname.toLower();
129 fname.replace('_', '-');
130 fname.append(".html");
131 }
132
133 return fname;
134 }
135
documentationForDeclaration(Declaration * dec) const136 IDocumentation::Ptr PhpDocsPlugin::documentationForDeclaration( Declaration* dec ) const
137 {
138 if ( dec ) {
139 DUChainReadLocker lock( DUChain::lock() );
140
141 // filter non global or non-php declarations
142 if ( dec->topContext()->url() != m_model->internalFunctionFile() ) {
143 return {};
144 }
145
146 QUrl url = PhpDocsSettings::phpDocLocation();
147 qCDebug(DOCS) << url;
148
149 QString file = getDocumentationFilename( dec, url.isLocalFile() );
150 if ( file.isEmpty() ) {
151 qCDebug(DOCS) << "no documentation pattern found for" << dec->toString();
152 return {};
153 }
154
155 url.setPath( url.path() + '/' + file);
156 if ( url.isLocalFile() && !QFile::exists( url.toLocalFile() ) ) {
157 qCDebug(DOCS) << "bad path" << url << "for documentation of" << dec->toString() << " - aborting";
158 return {};
159 }
160
161 qCDebug(DOCS) << "php documentation located at " << url << "for" << dec->toString();
162 return documentationForUrl(url, dec->qualifiedIdentifier().toString());
163 }
164
165 return {};
166 }
167
documentation(const QUrl & url) const168 IDocumentation::Ptr PhpDocsPlugin::documentation(const QUrl& url) const
169 {
170 if (url.toString().startsWith(PhpDocsSettings::phpDocLocation().toString())) {
171 return documentationForUrl(url, QString());
172 }
173 return {};
174 }
175
176
indexModel() const177 QAbstractListModel* PhpDocsPlugin::indexModel() const
178 {
179 return m_model;
180 }
181
documentationForIndex(const QModelIndex & index) const182 IDocumentation::Ptr PhpDocsPlugin::documentationForIndex(const QModelIndex& index) const
183 {
184 return documentationForDeclaration(static_cast<Declaration*>(
185 index.data(PhpDocsModel::DeclarationRole).value<DeclarationPointer>().data()
186 ));
187 }
188
loadUrl(const QUrl & url) const189 void PhpDocsPlugin::loadUrl(const QUrl& url) const
190 {
191 qCDebug(DOCS) << "loading URL" << url;
192 auto doc = documentationForUrl(url, QString());
193 ICore::self()->documentationController()->showDocumentation(doc);
194 }
195
showDocumentation(const QUrl & url)196 void PhpDocsPlugin::showDocumentation(const QUrl& url)
197 {
198 auto doc = documentationForUrl(url, url.toString());
199 ICore::self()->documentationController()->showDocumentation(doc);
200 }
201
documentationForUrl(const QUrl & url,const QString & name,const QByteArray & description) const202 IDocumentation::Ptr PhpDocsPlugin::documentationForUrl(const QUrl& url, const QString& name, const QByteArray& description) const
203 {
204 return IDocumentation::Ptr(new PhpDocumentation( url, name, description, const_cast<PhpDocsPlugin*>(this)));
205 }
206
homePage() const207 IDocumentation::Ptr PhpDocsPlugin::homePage() const
208 {
209 QUrl url = PhpDocsSettings::phpDocLocation();
210 if ( url.isLocalFile() ) {
211 url.setPath(url.path() + "/index.html");
212 } else {
213 url.setPath(url.path() + "/manual");
214 }
215 return documentationForUrl(url, i18n("PHP Documentation"));
216 }
217
218 #include "phpdocsplugin.moc"
219