aurjson.class.php 20.1 KB
Newer Older
eliott's avatar
eliott committed
1
<?php
2

3
include_once("aur.inc.php");
Dan McGee's avatar
Dan McGee committed
4

5
6
7
8
/*
 * This class defines a remote interface for fetching data from the AUR using
 * JSON formatted elements.
 *
eliott's avatar
eliott committed
9
10
 * @package rpc
 * @subpackage classes
11
 */
eliott's avatar
eliott committed
12
class AurJSON {
13
	private $dbh = false;
14
	private $version = 1;
15
	private static $exposed_methods = array(
16
		'search', 'info', 'multiinfo', 'msearch', 'suggest',
17
		'suggest-pkgbase', 'get-comment-form'
18
	);
19
	private static $exposed_fields = array(
20
21
22
23
24
		'name', 'name-desc', 'maintainer',
		'depends', 'makedepends', 'checkdepends', 'optdepends'
	);
	private static $exposed_depfields = array(
		'depends', 'makedepends', 'checkdepends', 'optdepends'
25
	);
26
27
	private static $fields_v1 = array(
		'Packages.ID', 'Packages.Name',
28
		'PackageBases.ID AS PackageBaseID',
29
		'PackageBases.Name AS PackageBase', 'Version',
30
31
32
33
34
35
		'Description', 'URL', 'NumVotes', 'OutOfDateTS AS OutOfDate',
		'Users.UserName AS Maintainer',
		'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified',
		'Licenses.Name AS License'
	);
	private static $fields_v2 = array(
36
		'Packages.ID', 'Packages.Name',
37
		'PackageBases.ID AS PackageBaseID',
38
		'PackageBases.Name AS PackageBase', 'Version',
39
40
41
42
		'Description', 'URL', 'NumVotes', 'OutOfDateTS AS OutOfDate',
		'Users.UserName AS Maintainer',
		'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified'
	);
43
44
45
46
47
48
49
50
	private static $fields_v4 = array(
		'Packages.ID', 'Packages.Name',
		'PackageBases.ID AS PackageBaseID',
		'PackageBases.Name AS PackageBase', 'Version',
		'Description', 'URL', 'NumVotes', 'Popularity',
		'OutOfDateTS AS OutOfDate', 'Users.UserName AS Maintainer',
		'SubmittedTS AS FirstSubmitted', 'ModifiedTS AS LastModified'
	);
51
	private static $numeric_fields = array(
52
		'ID', 'PackageBaseID', 'NumVotes', 'OutOfDate',
53
		'FirstSubmitted', 'LastModified'
54
	);
55
56
57
	private static $decimal_fields = array(
		'Popularity'
	);
58
59
60
61
62
63
64
65
66
67
68
69
70
71

	/*
	 * Handles post data, and routes the request.
	 *
	 * @param string $post_data The post data to parse and handle.
	 *
	 * @return string The JSON formatted response data.
	 */
	public function handle($http_data) {
		/*
		 * Unset global aur.inc.php Pragma header. We want to allow
		 * caching of data in proxies, but require validation of data
		 * (if-none-match) if possible.
		 */
72
		header_remove('Pragma');
73
74
75
76
		/*
		 * Overwrite cache-control header set in aur.inc.php to allow
		 * caching, but require validation.
		 */
77
		header('Cache-Control: public, must-revalidate, max-age=0');
78
		header('Content-Type: application/json, charset=utf-8');
79

80
81
82
		if (isset($http_data['v'])) {
			$this->version = intval($http_data['v']);
		}
83
		if ($this->version < 1 || $this->version > 5) {
84
85
86
			return $this->json_error('Invalid version specified.');
		}

87
88
89
90
91
92
		if (!isset($http_data['type']) || !isset($http_data['arg'])) {
			return $this->json_error('No request type/data specified.');
		}
		if (!in_array($http_data['type'], self::$exposed_methods)) {
			return $this->json_error('Incorrect request type specified.');
		}
93
94
95
96
97
98

		if (isset($http_data['search_by']) && !isset($http_data['by'])) {
			$http_data['by'] = $http_data['search_by'];
		}
		if (isset($http_data['by']) && !in_array($http_data['by'], self::$exposed_fields)) {
			return $this->json_error('Incorrect by field specified.');
99
		}
100
101
102

		$this->dbh = DB::connect();

Florian Pritz's avatar
Florian Pritz committed
103
104
105
106
107
		if ($this->check_ratelimit($_SERVER['REMOTE_ADDR'])) {
			header("HTTP/1.1 429 Too Many Requests");
			return $this->json_error('Rate limit reached');
		}

108
		$type = str_replace('-', '_', $http_data['type']);
109
110
111
		if ($type == 'info' && $this->version >= 5) {
			$type = 'multiinfo';
		}
112
		$json = call_user_func(array(&$this, $type), $http_data);
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

		$etag = md5($json);
		header("Etag: \"$etag\"");
		/*
		 * Make sure to strip a few things off the
		 * if-none-match header. Stripping whitespace may not
		 * be required, but removing the quote on the incoming
		 * header is required to make the equality test.
		 */
		$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
			trim($_SERVER['HTTP_IF_NONE_MATCH'], "\t\n\r\" ") : false;
		if ($if_none_match && $if_none_match == $etag) {
			header('HTTP/1.1 304 Not Modified');
			return;
		}

129
130
		if (isset($http_data['callback'])) {
			$callback = $http_data['callback'];
131
			if (!preg_match('/^[a-zA-Z0-9()_.]{1,128}$/D', $callback)) {
132
133
				return $this->json_error('Invalid callback name.');
			}
134
			header('content-type: text/javascript');
135
			return '/**/' . $callback . '(' . $json . ')';
136
137
138
139
140
141
		} else {
			header('content-type: application/json');
			return $json;
		}
	}

Florian Pritz's avatar
Florian Pritz committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
	/*
	 * Check if an IP needs to be  rate limited.
	 *
	 * @param $ip IP of the current request
	 *
	 * @return true if IP needs to be rate limited, false otherwise.
	 */
	private function check_ratelimit($ip) {
		$limit = config_get("ratelimit", "request_limit");
		if ($limit == 0) {
			return false;
		}

		$this->update_ratelimit($ip);

157
158
159
160
161
162
163
164
165
166
167
168
		$status = false;
		$value = get_cache_value('ratelimit:' . $ip, $status);
		if (!$status) {
			$stmt = $this->dbh->prepare("
				SELECT Requests FROM ApiRateLimit
				WHERE IP = :ip");
			$stmt->bindParam(":ip", $ip);
			$result = $stmt->execute();

			if (!$result) {
				return false;
			}
Florian Pritz's avatar
Florian Pritz committed
169

170
171
			$row = $stmt->fetch(PDO::FETCH_ASSOC);
			$value = $row['Requests'];
Florian Pritz's avatar
Florian Pritz committed
172
		}
173
174

		return $value > $limit;
Florian Pritz's avatar
Florian Pritz committed
175
176
177
178
179
180
181
182
183
184
185
186
187
188
	}

	/*
	 * Update a rate limit for an IP by increasing it's requests value by one.
	 *
	 * @param $ip IP of the current request
	 *
	 * @return void
	 */
	private function update_ratelimit($ip) {
		$window_length = config_get("ratelimit", "window_length");
		$db_backend = config_get("database", "backend");
		$time = time();
		$deletion_time = $time - $window_length;
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204

		/* Try to use the cache. */
		$status = false;
		$value = get_cache_value('ratelimit-ws:' . $ip, $status);
		if (!$status || ($status && $value < $deletion_time)) {
			if (set_cache_value('ratelimit-ws:' . $ip, $time, $window_length) &&
			    set_cache_value('ratelimit:' . $ip, 1, $window_length)) {
				return;
			}
		} else {
			$value = get_cache_value('ratelimit:' . $ip, $status);
			if ($status && set_cache_value('ratelimit:' . $ip, $value + 1, $window_length))
				return;
		}

		/* Clean up old windows. */
Florian Pritz's avatar
Florian Pritz committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
		$stmt = $this->dbh->prepare("
			DELETE FROM ApiRateLimit
			WHERE WindowStart < :time");
		$stmt->bindParam(":time", $deletion_time);
		$stmt->execute();

		if ($db_backend == "mysql") {
			$stmt = $this->dbh->prepare("
				INSERT INTO ApiRateLimit
				(IP, Requests, WindowStart)
				VALUES (:ip, 1, :window_start)
				ON DUPLICATE KEY UPDATE Requests=Requests+1");
			$stmt->bindParam(":ip", $ip);
			$stmt->bindParam(":window_start", $time);
			$stmt->execute();
		} elseif ($db_backend == "sqlite") {
			$stmt = $this->dbh->prepare("
				INSERT OR IGNORE INTO ApiRateLimit
				(IP, Requests, WindowStart)
				VALUES (:ip, 0, :window_start);");
			$stmt->bindParam(":ip", $ip);
			$stmt->bindParam(":window_start", $time);
			$stmt->execute();

			$stmt = $this->dbh->prepare("
				UPDATE ApiRateLimit
				SET Requests = Requests + 1
				WHERE IP = :ip");
			$stmt->bindParam(":ip", $ip);
			$stmt->execute();
		} else {
			throw new RuntimeException("Unknown database backend");
		}
	}

240
241
242
243
244
245
246
247
248
	/*
	 * Returns a JSON formatted error string.
	 *
	 * @param $msg The error string to return
	 *
	 * @return mixed A json formatted error response.
	 */
	private function json_error($msg) {
		header('content-type: application/json');
249
250
251
252
253
		if ($this->version < 3) {
			return $this->json_results('error', 0, $msg, NULL);
		} elseif ($this->version >= 3) {
			return $this->json_results('error', 0, array(), $msg);
		}
254
255
256
257
258
259
	}

	/*
	 * Returns a JSON formatted result data.
	 *
	 * @param $type The response method type.
260
	 * @param $count The number of results to return
261
	 * @param $data The result data to return
262
	 * @param $error An error message to include in the response
263
264
265
	 *
	 * @return mixed A json formatted result response.
	 */
266
267
	private function json_results($type, $count, $data, $error) {
		$json_array = array(
268
			'version' => $this->version,
269
270
271
			'type' => $type,
			'resultcount' => $count,
			'results' => $data
272
273
274
275
276
277
278
		);

		if ($error) {
			$json_array['error'] = $error;
		}

		return json_encode($json_array);
279
280
	}

281
282
283
284
	/*
	 * Get extended package details (for info and multiinfo queries).
	 *
	 * @param $pkgid The ID of the package to retrieve details for.
285
	 * @param $base_id The ID of the package base to retrieve details for.
286
287
288
	 *
	 * @return array An array containing package details.
	 */
289
	private function get_extended_fields($pkgid, $base_id) {
290
291
292
293
294
295
296
297
298
299
300
301
302
303
		$query = "SELECT DependencyTypes.Name AS Type, " .
			"PackageDepends.DepName AS Name, " .
			"PackageDepends.DepCondition AS Cond " .
			"FROM PackageDepends " .
			"LEFT JOIN DependencyTypes " .
			"ON DependencyTypes.ID = PackageDepends.DepTypeID " .
			"WHERE PackageDepends.PackageID = " . $pkgid . " " .
			"UNION SELECT RelationTypes.Name AS Type, " .
			"PackageRelations.RelName AS Name, " .
			"PackageRelations.RelCondition AS Cond " .
			"FROM PackageRelations " .
			"LEFT JOIN RelationTypes " .
			"ON RelationTypes.ID = PackageRelations.RelTypeID " .
			"WHERE PackageRelations.PackageID = " . $pkgid . " " .
304
305
			"UNION SELECT 'groups' AS Type, `Groups`.`Name`, '' AS Cond " .
			"FROM `Groups` INNER JOIN PackageGroups " .
306
			"ON PackageGroups.PackageID = " . $pkgid . " " .
307
			"AND PackageGroups.GroupID = `Groups`.ID " .
308
309
310
311
			"UNION SELECT 'license' AS Type, Licenses.Name, '' AS Cond " .
			"FROM Licenses INNER JOIN PackageLicenses " .
			"ON PackageLicenses.PackageID = " . $pkgid . " " .
			"AND PackageLicenses.LicenseID = Licenses.ID";
312
313
		$ttl = config_get_int('options', 'cache_pkginfo_ttl');
		$rows = db_cache_result($query, 'extended-fields:' . $pkgid, PDO::FETCH_ASSOC, $ttl);
314
315
316
317
318
319
320
321
322
323
324
325
326

		$type_map = array(
			'depends' => 'Depends',
			'makedepends' => 'MakeDepends',
			'checkdepends' => 'CheckDepends',
			'optdepends' => 'OptDepends',
			'conflicts' => 'Conflicts',
			'provides' => 'Provides',
			'replaces' => 'Replaces',
			'groups' => 'Groups',
			'license' => 'License',
		);
		$data = array();
327
		foreach ($rows as $row) {
328
329
330
331
			$type = $type_map[$row['Type']];
			$data[$type][] = $row['Name'] . $row['Cond'];
		}

332
333
334
335
		if ($this->version >= 5) {
			$query = "SELECT Keyword FROM PackageKeywords " .
				"WHERE PackageBaseID = " . intval($base_id) . " " .
				"ORDER BY Keyword ASC";
336
337
			$ttl = config_get_int('options', 'cache_pkginfo_ttl');
			$rows = db_cache_result($query, 'keywords:' . intval($base_id), PDO::FETCH_NUM, $ttl);
338
			$data['Keywords'] = array_map(function ($x) { return $x[0]; }, $rows);
339
340
		}

341
342
343
		return $data;
	}

344
345
	/*
	 * Retrieve package information (used in info, multiinfo, search and
346
	 * depends requests).
347
348
349
350
351
352
	 *
	 * @param $type The request type.
	 * @param $where_condition An SQL WHERE-condition to filter packages.
	 *
	 * @return mixed Returns an array of package matches.
	 */
353
	private function process_query($type, $where_condition) {
354
		$max_results = config_get_int('options', 'max_rpc_results');
355
356
357
358
359
360
361
362
363
364
365
366
367

		if ($this->version == 1) {
			$fields = implode(',', self::$fields_v1);
			$query = "SELECT {$fields} " .
				"FROM Packages LEFT JOIN PackageBases " .
				"ON PackageBases.ID = Packages.PackageBaseID " .
				"LEFT JOIN Users " .
				"ON PackageBases.MaintainerUID = Users.ID " .
				"LEFT JOIN PackageLicenses " .
				"ON PackageLicenses.PackageID = Packages.ID " .
				"LEFT JOIN Licenses " .
				"ON Licenses.ID = PackageLicenses.LicenseID " .
				"WHERE ${where_condition} " .
368
				"AND PackageBases.PackagerUID IS NOT NULL " .
369
				"LIMIT $max_results";
370
		} elseif ($this->version >= 2) {
371
372
			if ($this->version == 2 || $this->version == 3) {
				$fields = implode(',', self::$fields_v2);
373
			} else if ($this->version == 4 || $this->version == 5) {
374
375
				$fields = implode(',', self::$fields_v4);
			}
376
377
378
379
380
381
			$query = "SELECT {$fields} " .
				"FROM Packages LEFT JOIN PackageBases " .
				"ON PackageBases.ID = Packages.PackageBaseID " .
				"LEFT JOIN Users " .
				"ON PackageBases.MaintainerUID = Users.ID " .
				"WHERE ${where_condition} " .
382
				"AND PackageBases.PackagerUID IS NOT NULL " .
383
				"LIMIT $max_results";
384
		}
385
386
387
388
389
390
391
		$result = $this->dbh->query($query);

		if ($result) {
			$resultcount = 0;
			$search_data = array();
			while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
				$resultcount++;
392
				$row['URLPath'] = sprintf(config_get('options', 'snapshot_uri'), urlencode($row['PackageBase']));
393
394
395
				if ($this->version < 4) {
					$row['CategoryID'] = 1;
				}
396
397
398
399
400
401
402
403

				/*
				 * Unfortunately, mysql_fetch_assoc() returns
				 * all fields as strings. We need to coerce
				 * numeric values into integers to provide
				 * proper data types in the JSON response.
				 */
				foreach (self::$numeric_fields as $field) {
404
405
406
					if (isset($row[$field])) {
						$row[$field] = intval($row[$field]);
					}
407
408
				}

409
				foreach (self::$decimal_fields as $field) {
410
411
412
					if (isset($row[$field])) {
						$row[$field] = floatval($row[$field]);
					}
413
414
				}

415
				if ($this->version >= 2 && ($type == 'info' || $type == 'multiinfo')) {
416
417
418
419
					$extfields = $this->get_extended_fields($row['ID'], $row['PackageBaseID']);
					if ($extfields) {
						$row = array_merge($row, $extfields);
					}
420
421
				}

422
423
424
425
426
427
428
429
				if ($this->version < 3) {
					if ($type == 'info') {
						$search_data = $row;
						break;
					} else {
						array_push($search_data, $row);
					}
				} elseif ($this->version >= 3) {
430
431
					array_push($search_data, $row);
				}
432
433
			}

434
			if ($resultcount === $max_results) {
435
436
437
				return $this->json_error('Too many package results.');
			}

438
			return $this->json_results($type, $resultcount, $search_data, NULL);
439
		} else {
440
			return $this->json_results($type, 0, array(), NULL);
441
442
443
444
445
446
447
448
449
		}
	}

	/*
	 * Parse the args to the multiinfo function. We may have a string or an
	 * array, so do the appropriate thing. Within the elements, both * package
	 * IDs and package names are valid; sort them into the relevant arrays and
	 * escape/quote the names.
	 *
450
	 * @param array $args Query parameters.
451
452
453
	 *
	 * @return mixed An array containing 'ids' and 'names'.
	 */
454
	private function parse_multiinfo_args($args) {
455
456
457
458
459
460
461
462
463
464
		if (!is_array($args)) {
			$args = array($args);
		}

		$id_args = array();
		$name_args = array();
		foreach ($args as $arg) {
			if (!$arg) {
				continue;
			}
465
			if ($this->version < 5 && is_numeric($arg)) {
466
467
468
469
470
471
472
473
474
475
476
477
				$id_args[] = intval($arg);
			} else {
				$name_args[] = $this->dbh->quote($arg);
			}
		}

		return array('ids' => $id_args, 'names' => $name_args);
	}

	/*
	 * Performs a fulltext mysql search of the package database.
	 *
478
	 * @param array $http_data Query parameters.
479
480
481
	 *
	 * @return mixed Returns an array of package matches.
	 */
482
483
	private function search($http_data) {
		$keyword_string = $http_data['arg'];
484

485
486
		if (isset($http_data['by'])) {
			$search_by = $http_data['by'];
487
488
489
		} else {
			$search_by = 'name-desc';
		}
490

491
492
		if ($search_by === 'name' || $search_by === 'name-desc') {
			if (strlen($keyword_string) < 2) {
493
				return $this->json_error('Query arg too small.');
494
495
			}
			$keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%");
496

497
498
499
500
501
502
503
504
505
506
507
508
509
			if ($search_by === 'name') {
				$where_condition = "(Packages.Name LIKE $keyword_string)";
			} else if ($search_by === 'name-desc') {
				$where_condition = "(Packages.Name LIKE $keyword_string OR ";
				$where_condition .= "Description LIKE $keyword_string)";
			}
		} else if ($search_by === 'maintainer') {
			if (empty($keyword_string)) {
				$where_condition = "Users.ID is NULL";
			} else {
				$keyword_string = $this->dbh->quote($keyword_string);
				$where_condition = "Users.Username = $keyword_string ";
			}
510
511
512
513
514
515
516
517
518
519
520
521
522
		} else if (in_array($search_by, self::$exposed_depfields)) {
			if (empty($keyword_string)) {
				return $this->json_error('Query arg is empty.');
			} else {
				$keyword_string = $this->dbh->quote($keyword_string);
				$search_by = $this->dbh->quote($search_by);
				$subquery = "SELECT PackageDepends.DepName FROM PackageDepends ";
				$subquery .= "LEFT JOIN DependencyTypes ";
				$subquery .= "ON PackageDepends.DepTypeID = DependencyTypes.ID ";
				$subquery .= "WHERE PackageDepends.PackageID = Packages.ID ";
				$subquery .= "AND DependencyTypes.Name = $search_by";
				$where_condition = "$keyword_string IN ($subquery)";
			}
523
		}
524
525
526
527
528
529
530

		return $this->process_query('search', $where_condition);
	}

	/*
	 * Returns the info on a specific package.
	 *
531
	 * @param array $http_data Query parameters.
532
533
534
	 *
	 * @return mixed Returns an array of value data containing the package data
	 */
535
536
	private function info($http_data) {
		$pqdata = $http_data['arg'];
537
		if ($this->version < 5 && is_numeric($pqdata)) {
538
539
540
541
542
543
544
545
546
547
548
			$where_condition = "Packages.ID = $pqdata";
		} else {
			$where_condition = "Packages.Name = " . $this->dbh->quote($pqdata);
		}

		return $this->process_query('info', $where_condition);
	}

	/*
	 * Returns the info on multiple packages.
	 *
549
	 * @param array $http_data Query parameters.
550
551
552
	 *
	 * @return mixed Returns an array of results containing the package data
	 */
553
554
	private function multiinfo($http_data) {
		$pqdata = $http_data['arg'];
555
556
557
558
559
		$args = $this->parse_multiinfo_args($pqdata);
		$ids = $args['ids'];
		$names = $args['names'];

		if (!$ids && !$names) {
560
			return $this->json_error('Invalid query arguments.');
561
562
563
564
565
		}

		$where_condition = "";
		if ($ids) {
			$ids_value = implode(',', $args['ids']);
566
			$where_condition .= "Packages.ID IN ($ids_value) ";
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
		}
		if ($ids && $names) {
			$where_condition .= "OR ";
		}
		if ($names) {
			/*
			 * Individual names were quoted in
			 * parse_multiinfo_args().
			 */
			$names_value = implode(',', $args['names']);
			$where_condition .= "Packages.Name IN ($names_value) ";
		}

		return $this->process_query('multiinfo', $where_condition);
	}

	/*
	 * Returns all the packages for a specific maintainer.
	 *
586
	 * @param array $http_data Query parameters.
587
588
589
	 *
	 * @return mixed Returns an array of value data containing the package data
	 */
590
	private function msearch($http_data) {
591
		$http_data['by'] = 'maintainer';
592
		return $this->search($http_data);
593
594
595
596
597
	}

	/*
	 * Get all package names that start with $search.
	 *
598
	 * @param array $http_data Query parameters.
599
600
601
	 *
	 * @return string The JSON formatted response data.
	 */
602
603
	private function suggest($http_data) {
		$search = $http_data['arg'];
604
605
606
607
608
609
610
		$query = "SELECT Packages.Name FROM Packages ";
		$query.= "LEFT JOIN PackageBases ";
		$query.= "ON PackageBases.ID = Packages.PackageBaseID ";
		$query.= "WHERE Packages.Name LIKE ";
		$query.= $this->dbh->quote(addcslashes($search, '%_') . '%');
		$query.= " AND PackageBases.PackagerUID IS NOT NULL ";
		$query.= "ORDER BY Name ASC LIMIT 20";
611
612
613
614
615
616
617
618
619
620

		$result = $this->dbh->query($query);
		$result_array = array();

		if ($result) {
			$result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
		}

		return json_encode($result_array);
	}
621
622
623
624

	/*
	 * Get all package base names that start with $search.
	 *
625
	 * @param array $http_data Query parameters.
626
627
628
	 *
	 * @return string The JSON formatted response data.
	 */
629
630
	private function suggest_pkgbase($http_data) {
		$search = $http_data['arg'];
631
632
633
634
		$query = "SELECT Name FROM PackageBases WHERE Name LIKE ";
		$query.= $this->dbh->quote(addcslashes($search, '%_') . '%');
		$query.= " AND PackageBases.PackagerUID IS NOT NULL ";
		$query.= "ORDER BY Name ASC LIMIT 20";
635
636
637
638
639
640
641
642
643
644

		$result = $this->dbh->query($query);
		$result_array = array();

		if ($result) {
			$result_array = $result->fetchAll(PDO::FETCH_COLUMN, 0);
		}

		return json_encode($result_array);
	}
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670

	/**
	 * Get the HTML markup of the comment form.
	 *
	 * @param array $http_data Query parameters.
	 *
	 * @return string The JSON formatted response data.
	 */
	private function get_comment_form($http_data) {
		if (!isset($http_data['base_id']) || !isset($http_data['pkgbase_name'])) {
			$output = array(
				'success' => 0,
				'error' => __('Package base ID or package base name missing.')
			);
			return json_encode($output);
		}

		$comment_id = intval($http_data['arg']);
		$base_id = intval($http_data['base_id']);
		$pkgbase_name = $http_data['pkgbase_name'];

		list($user_id, $comment) = comment_by_id($comment_id);

		if (!has_credential(CRED_COMMENT_EDIT, array($user_id))) {
			$output = array(
				'success' => 0,
671
				'error' => __('You are not allowed to edit this comment.')
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
			);
			return json_encode($output);
		} elseif (is_null($comment)) {
			$output = array(
				'success' => 0,
				'error' => __('Comment does not exist.')
			);
			return json_encode($output);
		}

		ob_start();
		include('pkg_comment_form.php');
		$html = ob_get_clean();
		$output = array(
			'success' => 1,
			'form' => $html
		);

		return json_encode($output);
	}
eliott's avatar
eliott committed
692
}
693