1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Xtools; |
4
|
|
|
|
5
|
|
|
use Mediawiki\Api\MediawikiApi; |
6
|
|
|
use Mediawiki\Api\SimpleRequest; |
7
|
|
|
use Symfony\Component\DependencyInjection\Container; |
8
|
|
|
|
9
|
|
|
class ProjectRepository extends Repository |
10
|
|
|
{ |
11
|
|
|
|
12
|
|
|
/** @var array Project metadata. */ |
13
|
|
|
protected $metadata; |
14
|
|
|
|
15
|
|
|
/** @var string[] Metadata if XTools is in single-wiki mode. */ |
16
|
|
|
protected $singleMetadata; |
17
|
|
|
|
18
|
|
|
/** @var string[][] Metadata of all projects, populated by self::getAll(). */ |
19
|
|
|
protected $projectsMetadata; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Convenience method to get a new Project object based on a given identification string. |
23
|
|
|
* @param string $projectIdent The domain name, database name, or URL of a project. |
24
|
|
|
* @param Container $container Symfony's container. |
25
|
|
|
* @return Project |
26
|
|
|
*/ |
27
|
|
|
public static function getProject($projectIdent, Container $container) |
28
|
|
|
{ |
29
|
|
|
$project = new Project($projectIdent); |
30
|
|
|
$projectRepo = new ProjectRepository(); |
31
|
|
|
$projectRepo->setContainer($container); |
32
|
|
|
if ($container->getParameter('app.single_wiki')) { |
33
|
|
|
$projectRepo->setSingleMetadata([ |
34
|
|
|
'url' => $container->getParameter('wiki_url'), |
35
|
|
|
'dbname' => $container->getParameter('database_replica_name'), |
36
|
|
|
]); |
37
|
|
|
} |
38
|
|
|
$project->setRepository($projectRepo); |
39
|
|
|
return $project; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Get the XTools default project. |
44
|
|
|
* @param Container $container |
45
|
|
|
* @return Project |
46
|
|
|
*/ |
47
|
|
|
public static function getDefaultProject(Container $container) |
48
|
|
|
{ |
49
|
|
|
$defaultProjectName = $container->getParameter('default_project'); |
50
|
|
|
return self::getProject($defaultProjectName, $container); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Get the global 'meta' project, which is either Meta (if this is Labs) or the default project. |
55
|
|
|
* @return Project |
56
|
|
|
*/ |
57
|
|
|
public function getGlobalProject() |
58
|
|
|
{ |
59
|
|
|
if ($this->isLabs()) { |
60
|
|
|
return self::getProject('metawiki', $this->container); |
61
|
|
|
} else { |
62
|
|
|
return self::getDefaultProject($this->container); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* For single-wiki installations, you must manually set the wiki URL and database name |
68
|
|
|
* (because there's no meta.wiki database to query). |
69
|
|
|
* @param $metadata |
70
|
|
|
* @throws \Exception |
71
|
|
|
*/ |
72
|
|
|
public function setSingleMetadata($metadata) |
73
|
|
|
{ |
74
|
|
|
if (!array_key_exists('url', $metadata) || !array_key_exists('dbname', $metadata)) { |
75
|
|
|
$error = "Single-wiki metadata should contain 'url' and 'dbname' keys."; |
76
|
|
|
throw new \Exception($error); |
77
|
|
|
} |
78
|
|
|
$this->singleMetadata = array_intersect_key($metadata, ['url' => '', 'dbname' => '']); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Get metadata about all projects. |
83
|
|
|
* @return string[] Each item has 'dbname' and 'url' keys. |
84
|
|
|
*/ |
85
|
|
|
public function getAll() |
86
|
|
|
{ |
87
|
|
|
// Single wiki mode? |
88
|
|
|
if ($this->singleMetadata) { |
|
|
|
|
89
|
|
|
return [$this->getOne('')]; |
|
|
|
|
90
|
|
|
} |
91
|
|
|
// Maybe we've already fetched it. |
92
|
|
|
if (is_array($this->projectsMetadata)) { |
93
|
|
|
return $this->projectsMetadata; |
94
|
|
|
} |
95
|
|
|
$wikiQuery = $this->getMetaConnection()->createQueryBuilder(); |
96
|
|
|
$wikiQuery->select(['dbname', 'url'])->from('wiki'); |
97
|
|
|
$projects = $wikiQuery->execute()->fetchAll(); |
98
|
|
|
$this->projectsMetadata = []; |
99
|
|
|
foreach ($projects as $project) { |
100
|
|
|
$this->projectsMetadata[$project['url']] = $project; |
101
|
|
|
$this->projectsMetadata[$project['dbname']] = $project; |
102
|
|
|
} |
103
|
|
|
return $this->projectsMetadata; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Get metadata about one project. |
108
|
|
|
* @param string $project A project URL, domain name, or database name. |
109
|
|
|
* @return string[] With 'dbname' and 'url' keys. |
110
|
|
|
*/ |
111
|
|
|
public function getOne($project) |
112
|
|
|
{ |
113
|
|
|
// For single-wiki setups, every project is the same. |
114
|
|
|
if ($this->singleMetadata) { |
|
|
|
|
115
|
|
|
return $this->singleMetadata; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// Maybe we've already fetched it (if we're requesting via a domain). |
119
|
|
|
if (isset($this->projectsMetadata[$project])) { |
120
|
|
|
return $this->projectsMetadata[$project]; |
121
|
|
|
} |
122
|
|
|
if (isset($this->projectsMetadata['https://'.$project])) { |
123
|
|
|
return $this->projectsMetadata['https://'.$project]; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
// For muli-wiki setups, first check the cache. |
127
|
|
|
$cacheKey = "project.$project"; |
128
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
129
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// Otherwise, fetch the project's metadata from the meta.wiki table. |
133
|
|
|
$wikiQuery = $this->getMetaConnection()->createQueryBuilder(); |
134
|
|
|
$wikiQuery->select(['dbname', 'url']) |
135
|
|
|
->from('wiki') |
136
|
|
|
->where($wikiQuery->expr()->eq('dbname', ':project')) |
137
|
|
|
// The meta database will have the project's URL stored as https://en.wikipedia.org |
138
|
|
|
// so we need to query for it accordingly, trying different variations the user |
139
|
|
|
// might have inputted. |
140
|
|
|
->orwhere($wikiQuery->expr()->like('url', ':projectUrl')) |
141
|
|
|
->orwhere($wikiQuery->expr() |
142
|
|
|
->like('url', ':projectUrl2')) |
143
|
|
|
->setParameter('project', $project) |
144
|
|
|
->setParameter('projectUrl', "https://$project") |
145
|
|
|
->setParameter('projectUrl2', "https://$project.org"); |
146
|
|
|
$wikiStatement = $wikiQuery->execute(); |
147
|
|
|
|
148
|
|
|
// Fetch and cache the wiki data. |
149
|
|
|
$projectMetadata = $wikiStatement->fetch(); |
150
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
151
|
|
|
$cacheItem->set($projectMetadata) |
152
|
|
|
->expiresAfter(new \DateInterval('PT1H')); |
153
|
|
|
$this->cache->save($cacheItem); |
154
|
|
|
|
155
|
|
|
return $projectMetadata; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Get metadata about a project. |
160
|
|
|
* |
161
|
|
|
* @param string $projectUrl The project's URL. |
162
|
|
|
* @return array With 'general' and 'namespaces' keys: the former contains 'wikiName', |
163
|
|
|
* 'wikiId', 'url', 'lang', 'articlePath', 'scriptPath', 'script', 'timezone', and |
164
|
|
|
* 'timezoneOffset'; the latter contains all namespace names, keyed by their IDs. |
165
|
|
|
*/ |
166
|
|
|
public function getMetadata($projectUrl) |
167
|
|
|
{ |
168
|
|
|
if ($this->metadata) { |
|
|
|
|
169
|
|
|
return $this->metadata; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
$api = MediawikiApi::newFromPage($projectUrl); |
173
|
|
|
|
174
|
|
|
$params = ['meta' => 'siteinfo', 'siprop' => 'general|namespaces']; |
175
|
|
|
$query = new SimpleRequest('query', $params); |
176
|
|
|
|
177
|
|
|
$this->metadata = [ |
178
|
|
|
'general' => [], |
179
|
|
|
'namespaces' => [], |
180
|
|
|
]; |
181
|
|
|
|
182
|
|
|
$res = $api->getRequest($query); |
183
|
|
|
|
184
|
|
|
if (isset($res['query']['general'])) { |
185
|
|
|
$info = $res['query']['general']; |
186
|
|
|
$this->metadata['general'] = [ |
187
|
|
|
'wikiName' => $info['sitename'], |
188
|
|
|
'wikiId' => $info['wikiid'], |
189
|
|
|
'url' => $info['server'], |
190
|
|
|
'lang' => $info['lang'], |
191
|
|
|
'articlePath' => $info['articlepath'], |
192
|
|
|
'scriptPath' => $info['scriptpath'], |
193
|
|
|
'script' => $info['script'], |
194
|
|
|
'timezone' => $info['timezone'], |
195
|
|
|
'timeOffset' => $info['timeoffset'], |
196
|
|
|
]; |
197
|
|
|
|
198
|
|
|
// if ($this->container->getParameter('app.is_labs') && |
|
|
|
|
199
|
|
|
// substr($result['general']['dbName'], -2) != '_p' |
200
|
|
|
// ) { |
201
|
|
|
// $result['general']['dbName'] .= '_p'; |
202
|
|
|
// } |
203
|
|
|
} |
204
|
|
|
|
205
|
|
View Code Duplication |
if (isset($res['query']['namespaces'])) { |
|
|
|
|
206
|
|
|
foreach ($res['query']['namespaces'] as $namespace) { |
207
|
|
|
if ($namespace['id'] < 0) { |
208
|
|
|
continue; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if (isset($namespace['name'])) { |
212
|
|
|
$name = $namespace['name']; |
213
|
|
|
} elseif (isset($namespace['*'])) { |
214
|
|
|
$name = $namespace['*']; |
215
|
|
|
} else { |
216
|
|
|
continue; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
// FIXME: Figure out a way to i18n-ize this |
220
|
|
|
if ($name === '') { |
221
|
|
|
$name = 'Article'; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$this->metadata['namespaces'][$namespace['id']] = $name; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
return $this->metadata; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Get a list of projects that have opted in to having all their users' restricted statistics |
233
|
|
|
* available to anyone. |
234
|
|
|
* |
235
|
|
|
* @return string[] |
236
|
|
|
*/ |
237
|
|
|
public function optedIn() |
238
|
|
|
{ |
239
|
|
|
$optedIn = $this->container->getParameter('opted_in'); |
240
|
|
|
return $optedIn; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Get a page from the given Project. |
245
|
|
|
* @param Project $project The project. |
246
|
|
|
* @param string $pageName The name of the page. |
247
|
|
|
* @return Page |
248
|
|
|
*/ |
249
|
|
|
public function getPage(Project $project, $pageName) |
250
|
|
|
{ |
251
|
|
|
$pageRepo = new PagesRepository(); |
252
|
|
|
if (!$this->container) { |
253
|
|
|
dump($project);exit(); |
|
|
|
|
254
|
|
|
} |
255
|
|
|
$pageRepo->setContainer($this->container); |
256
|
|
|
$page = new Page($project, $pageName); |
257
|
|
|
$page->setRepository($pageRepo); |
258
|
|
|
return $page; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Check to see if a page exists on this project and has some content. |
263
|
|
|
* @param int $namespaceId The page namespace ID. |
264
|
|
|
* @param string $pageTitle The page title, without namespace, |
265
|
|
|
* @return bool |
266
|
|
|
*/ |
267
|
|
|
public function pageHasContent(Project $project, $namespaceId, $pageTitle) |
268
|
|
|
{ |
269
|
|
|
$conn = $this->getProjectsConnection(); |
270
|
|
|
$pageTable = $this->getTableName($project->getDatabaseName(), 'page'); |
271
|
|
|
$query = "SELECT page_id " |
272
|
|
|
. " FROM $pageTable " |
273
|
|
|
. " WHERE page_namespace = :ns AND page_title = :title AND page_len > 0 " |
274
|
|
|
. " LIMIT 1"; |
275
|
|
|
$params = [ |
276
|
|
|
'ns' => $namespaceId, |
277
|
|
|
'title' => $pageTitle, |
278
|
|
|
]; |
279
|
|
|
$pages = $conn->executeQuery($query, $params)->fetchAll(); |
280
|
|
|
return count($pages) > 0; |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.