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