pkgfuncs.inc.php 26.8 KB
Newer Older
1
<?php
2

3
include_once("pkgbasefuncs.inc.php");
eric's avatar
eric committed
4

5
6
7
8
9
10
11
12
13
14
/**
 * Determine if the user can delete a specific package comment
 *
 * Only the comment submitter, Trusted Users, and Developers can delete
 * comments. This function is used for the backend side of comment deletion.
 *
 * @param string $comment_id The comment ID in the database
 *
 * @return bool True if the user can delete the comment, otherwise false
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
15
function can_delete_comment($comment_id=0) {
16
	$dbh = DB::connect();
Lukas Fleischer's avatar
Lukas Fleischer committed
17

18
19
	$q = "SELECT UsersID FROM PackageComments ";
	$q.= "WHERE ID = " . intval($comment_id);
canyonknight's avatar
canyonknight committed
20
	$result = $dbh->query($q);
Lukas Fleischer's avatar
Lukas Fleischer committed
21
22
23

	if (!$result) {
		return false;
24
	}
Lukas Fleischer's avatar
Lukas Fleischer committed
25

26
27
28
	$uid = $result->fetch(PDO::FETCH_COLUMN, 0);

	return has_credential(CRED_COMMENT_DELETE, array($uid));
29
}
eric's avatar
eric committed
30

31
32
33
34
35
36
37
38
39
40
/**
 * Determine if the user can delete a specific package comment using an array
 *
 * Only the comment submitter, Trusted Users, and Developers can delete
 * comments. This function is used for the frontend side of comment deletion.
 *
 * @param array $comment All database information relating a specific comment
 *
 * @return bool True if the user can delete the comment, otherwise false
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
41
42
function can_delete_comment_array($comment) {
	return has_credential(CRED_COMMENT_DELETE, array($comment['UsersID']));
43
44
}

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
 * Determine if the user can edit a specific package comment
 *
 * Only the comment submitter, Trusted Users, and Developers can edit
 * comments. This function is used for the backend side of comment editing.
 *
 * @param string $comment_id The comment ID in the database
 *
 * @return bool True if the user can edit the comment, otherwise false
 */
function can_edit_comment($comment_id=0) {
	$dbh = DB::connect();

	$q = "SELECT UsersID FROM PackageComments ";
	$q.= "WHERE ID = " . intval($comment_id);
	$result = $dbh->query($q);

	if (!$result) {
		return false;
	}

	$uid = $result->fetch(PDO::FETCH_COLUMN, 0);

	return has_credential(CRED_COMMENT_EDIT, array($uid));
}

Marcel Korpel's avatar
Marcel Korpel committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
 * Determine if the user can edit a specific package comment using an array
 *
 * Only the comment submitter, Trusted Users, and Developers can edit
 * comments. This function is used for the frontend side of comment editing.
 *
 * @param array $comment All database information relating a specific comment
 *
 * @return bool True if the user can edit the comment, otherwise false
 */
function can_edit_comment_array($comment) {
	return has_credential(CRED_COMMENT_EDIT, array($comment['UsersID']));
}

85
86
87
/**
 * Determine if the user can pin a specific package comment
 *
88
89
90
 * Only the Package Maintainer, Package Co-maintainers, Trusted Users, and
 * Developers can pin comments. This function is used for the backend side of
 * comment pinning.
91
92
93
94
95
96
97
98
99
100
 *
 * @param string $comment_id The comment ID in the database
 *
 * @return bool True if the user can pin the comment, otherwise false
 */
function can_pin_comment($comment_id=0) {
	$dbh = DB::connect();

	$q = "SELECT MaintainerUID FROM PackageBases AS pb ";
	$q.= "LEFT JOIN PackageComments AS pc ON pb.ID = pc.PackageBaseID ";
101
102
	$q.= "WHERE pc.ID = " . intval($comment_id) . " ";
	$q.= "UNION ";
103
	$q.= "SELECT pcm.UsersID FROM PackageComaintainers AS pcm ";
104
105
	$q.= "LEFT JOIN PackageComments AS pc ";
	$q.= "ON pcm.PackageBaseID = pc.PackageBaseID ";
106
107
108
109
110
111
112
	$q.= "WHERE pc.ID = " . intval($comment_id);
	$result = $dbh->query($q);

	if (!$result) {
		return false;
	}

113
	$uids = $result->fetchAll(PDO::FETCH_COLUMN, 0);
114

115
	return has_credential(CRED_COMMENT_PIN, $uids);
116
117
118
119
120
}

/**
 * Determine if the user can edit a specific package comment using an array
 *
121
122
123
 * Only the Package Maintainer, Package Co-maintainers, Trusted Users, and
 * Developers can pin comments. This function is used for the frontend side of
 * comment pinning.
124
125
126
127
128
129
130
131
132
 *
 * @param array $comment All database information relating a specific comment
 *
 * @return bool True if the user can edit the comment, otherwise false
 */
function can_pin_comment_array($comment) {
	return can_pin_comment($comment['ID']);
}

133
134
135
136
137
138
139
/**
 * Check to see if the package name already exists in the database
 *
 * @param string $name The package name to check
 *
 * @return string|void Package name if it already exists
 */
140
function pkg_from_name($name="") {
141
	if (!$name) {return NULL;}
142
	$dbh = DB::connect();
143
	$q = "SELECT ID FROM Packages ";
canyonknight's avatar
canyonknight committed
144
145
146
147
148
149
	$q.= "WHERE Name = " . $dbh->quote($name);
	$result = $dbh->query($q);
	if (!$result) {
		return;
	}
	$row = $result->fetch(PDO::FETCH_NUM);
Eli Schwartz's avatar
Eli Schwartz committed
150
151
152
	if ($row) {
		return $row[0];
	}
153
154
}

155
156
157
158
159
160
161
162
163
/**
 * Get licenses for a specific package
 *
 * @param int $pkgid The package to get licenses for
 *
 * @return array All licenses for the package
 */
function pkg_licenses($pkgid) {
	$pkgid = intval($pkgid);
164
165
	if (!$pkgid) {
		return array();
166
	}
167
168
169
	$q = "SELECT l.Name FROM Licenses l ";
	$q.= "INNER JOIN PackageLicenses pl ON pl.LicenseID = l.ID ";
	$q.= "WHERE pl.PackageID = ". $pkgid;
170
171
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	$rows = db_cache_result($q, 'licenses:' . $pkgid, PDO::FETCH_NUM, $ttl);
172
	return array_map(function ($x) { return $x[0]; }, $rows);
173
174
}

175
176
177
178
179
180
181
182
183
/**
 * Get package groups for a specific package
 *
 * @param int $pkgid The package to get groups for
 *
 * @return array All package groups for the package
 */
function pkg_groups($pkgid) {
	$pkgid = intval($pkgid);
184
185
	if (!$pkgid) {
		return array();
186
	}
187
188
189
	$q = "SELECT g.Name FROM `Groups` g ";
	$q.= "INNER JOIN PackageGroups pg ON pg.GroupID = g.ID ";
	$q.= "WHERE pg.PackageID = ". $pkgid;
190
191
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	$rows = db_cache_result($q, 'groups:' . $pkgid, PDO::FETCH_NUM, $ttl);
192
	return array_map(function ($x) { return $x[0]; }, $rows);
193
194
}

195
196
197
198
199
200
201
202
203
204
/**
 * Get providers for a specific package
 *
 * @param string $name The name of the "package" to get providers for
 *
 * @return array The IDs and names of all providers of the package
 */
function pkg_providers($name) {
	$dbh = DB::connect();
	$q = "SELECT p.ID, p.Name FROM Packages p ";
205
206
207
	$q.= "WHERE p.Name = " . $dbh->quote($name) . " ";
	$q.= "UNION ";
	$q.= "SELECT p.ID, p.Name FROM Packages p ";
208
209
	$q.= "LEFT JOIN PackageRelations pr ON pr.PackageID = p.ID ";
	$q.= "LEFT JOIN RelationTypes rt ON rt.ID = pr.RelTypeID ";
210
	$q.= "WHERE (rt.Name = 'provides' ";
211
	$q.= "AND pr.RelName = " . $dbh->quote($name) . ")";
212
213
214
	$q.= "UNION ";
	$q.= "SELECT 0, Name FROM OfficialProviders ";
	$q.= "WHERE Provides = " . $dbh->quote($name);
215
216
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	return db_cache_result($q, 'providers:' . $name, PDO::FETCH_NUM, $ttl);
217
218
}

219
220
221
222
/**
 * Get package dependencies for a specific package
 *
 * @param int $pkgid The package to get dependencies for
223
 * @param int $limit An upper bound for the number of packages to retrieve
224
225
226
 *
 * @return array All package dependencies for the package
 */
227
function pkg_dependencies($pkgid, $limit) {
228
	$pkgid = intval($pkgid);
229
230
	if (!$pkgid) {
		return array();
231
	}
232
233
234
235
236
237
238
	$q = "SELECT pd.DepName, dt.Name, pd.DepDesc, ";
	$q.= "pd.DepCondition, pd.DepArch, p.ID ";
	$q.= "FROM PackageDepends pd ";
	$q.= "LEFT JOIN Packages p ON pd.DepName = p.Name ";
	$q.= "LEFT JOIN DependencyTypes dt ON dt.ID = pd.DepTypeID ";
	$q.= "WHERE pd.PackageID = ". $pkgid . " ";
	$q.= "ORDER BY pd.DepName LIMIT " . intval($limit);
239
240
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	return db_cache_result($q, 'dependencies:' . $pkgid, PDO::FETCH_NUM, $ttl);
241
242
}

243
244
245
246
247
248
249
250
251
/**
 * Get package relations for a specific package
 *
 * @param int $pkgid The package to get relations for
 *
 * @return array All package relations for the package
 */
function pkg_relations($pkgid) {
	$pkgid = intval($pkgid);
252
253
	if (!$pkgid) {
		return array();
254
	}
255
256
257
258
259
	$q = "SELECT pr.RelName, rt.Name, pr.RelCondition, pr.RelArch, p.ID FROM PackageRelations pr ";
	$q.= "LEFT JOIN Packages p ON pr.RelName = p.Name ";
	$q.= "LEFT JOIN RelationTypes rt ON rt.ID = pr.RelTypeID ";
	$q.= "WHERE pr.PackageID = ". $pkgid . " ";
	$q.= "ORDER BY pr.RelName";
260
261
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	return db_cache_result($q, 'relations:' . $pkgid, PDO::FETCH_NUM, $ttl);
262
263
}

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/**
 * Get the HTML code to display a package dependency link annotation
 * (dependency type, architecture, ...)
 *
 * @param string $type The name of the dependency type
 * @param string $arch The package dependency architecture
 * @param string $desc An optdepends description
 *
 * @return string The HTML code of the label to display
 */
function pkg_deplink_annotation($type, $arch, $desc=false) {
	if ($type == 'depends' && !$arch) {
		return '';
	}

	$link = ' <em>(';

	if ($type == 'makedepends') {
		$link .= 'make';
	} elseif ($type == 'checkdepends') {
		$link .= 'check';
	} elseif ($type == 'optdepends') {
		$link .= 'optional';
	}

	if ($type != 'depends' && $arch) {
		$link .= ', ';
	}

	if ($arch) {
		$link .= htmlspecialchars($arch);
	}

	$link .= ')';
	if ($type == 'optdepends' && $desc) {
		$link .= ' &ndash; ' . htmlspecialchars($desc) . ' </em>';
	}
	$link .= '</em>';

	return $link;
}

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/**
 * Get the HTML code to display a package provider link
 *
 * @param string $name The name of the provider
 * @param bool $official True if the package is in the official repositories
 *
 * @return string The HTML code of the link to display
 */
function pkg_provider_link($name, $official) {
	$link = '<a href="';
	if ($official) {
		$link .= 'https://www.archlinux.org/packages/?q=' .
			urlencode($name);
	} else {
		$link .= htmlspecialchars(get_pkg_uri($name), ENT_QUOTES);
	}
	$link .= '" title="' . __('View packages details for') . ' ';
	$link .= htmlspecialchars($name) . '">';
	$link .= htmlspecialchars($name) . '</a>';

	return $link;
}

329
330
331
332
333
/**
 * Get the HTML code to display a package dependency link
 *
 * @param string $name The name of the dependency
 * @param string $type The name of the dependency type
334
 * @param string $desc The (optional) description of the dependency
335
 * @param string $cond The package dependency condition string
336
 * @param string $arch The package dependency architecture
337
338
339
340
 * @param int $pkg_id The package of the package to display the dependency for
 *
 * @return string The HTML code of the label to display
 */
341
function pkg_depend_link($name, $type, $desc, $cond, $arch, $pkg_id) {
342
343
344
345
346
347
348
349
	/*
	 * TODO: We currently perform one SQL query per nonexistent package
	 * dependency. It would be much better if we could annotate dependency
	 * data with providers so that we already know whether a dependency is
	 * a "provision name" or a package from the official repositories at
	 * this point.
	 */
	$providers = pkg_providers($name);
350

351
352
353
354
355
356
357
358
359
	if (count($providers) == 0) {
		$link = '<span class="broken">';
		$link .= htmlspecialchars($name);
		$link .= '</span>';
		$link .= htmlspecialchars($cond) . ' ';
		$link .= pkg_deplink_annotation($type, $arch, $desc);
		return $link;
	}

360
361
362
363
364
365
366
367
368
	$link = htmlspecialchars($name);
	foreach ($providers as $provider) {
		if ($provider[1] == $name) {
			$is_official = ($provider[0] == 0);
			$name = $provider[1];
			$link = pkg_provider_link($name, $is_official);
			break;
		}
	}
369
	$link .= htmlspecialchars($cond) . ' ';
370
371
372
373
374
375
376

	foreach ($providers as $key => $provider) {
		if ($provider[1] == $name) {
			unset($providers[$key]);
		}
	}

377
378
379
	if (count($providers) > 0) {
		$link .= '<span class="virtual-dep">(';
		foreach ($providers as $provider) {
380
			$is_official = ($provider[0] == 0);
381
			$name = $provider[1];
382
			$link .= pkg_provider_link($name, $is_official) . ', ';
383
384
385
		}
		$link = substr($link, 0, -2);
		$link .= ')</span>';
386
387
	}

388
389
390
	$link .= pkg_deplink_annotation($type, $arch, $desc);

	return $link;
391
}
392

393
394
395
396
/**
 * Get the HTML code to display a package requirement link
 *
 * @param string $name The name of the requirement
397
 * @param string $depends The (literal) name of the dependency of $name
398
399
 * @param string $type The name of the dependency type
 * @param string $arch The package dependency architecture
400
 * @param string $pkgname The name of dependant package
401
402
403
 *
 * @return string The HTML code of the link to display
 */
404
function pkg_requiredby_link($name, $depends, $type, $arch, $pkgname) {
405
406
407
408
409
	$link = '<a href="';
	$link .= htmlspecialchars(get_pkg_uri($name), ENT_QUOTES);
	$link .= '" title="' . __('View packages details for') .' ' . htmlspecialchars($name) . '">';
	$link .= htmlspecialchars($name) . '</a>';

410
411
	if ($depends != $pkgname) {
		$link .= ' <span class="virtual-dep">(';
412
		$link .= __('requires %s', htmlspecialchars($depends));
413
414
415
		$link .= ')</span>';
	}

416
	return $link . pkg_deplink_annotation($type, $arch);
417
}
418

419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
/**
 * Get the HTML code to display a package relation
 *
 * @param string $name The name of the relation
 * @param string $cond The package relation condition string
 * @param string $arch The package relation architecture
 *
 * @return string The HTML code of the label to display
 */
function pkg_rel_html($name, $cond, $arch) {
	$html = htmlspecialchars($name) . htmlspecialchars($cond);

	if ($arch) {
		$html .= ' <em>(' . htmlspecialchars($arch) . ')</em>';
	}

	return $html;
}

438
439
440
441
442
/**
 * Get the HTML code to display a source link
 *
 * @param string $url The URL of the source
 * @param string $arch The source architecture
443
 * @param string $package The name of the package
444
445
446
 *
 * @return string The HTML code of the label to display
 */
447
function pkg_source_link($url, $arch, $package) {
448
449
450
451
452
453
	$url = explode('::', $url);
	$parsed_url = parse_url($url[0]);

	if (isset($parsed_url['scheme']) || isset($url[1])) {
		$link = '<a href="' .  htmlspecialchars((isset($url[1]) ? $url[1] : $url[0]), ENT_QUOTES) . '">' . htmlspecialchars($url[0]) . '</a>';
	} else {
454
455
		$file_url = sprintf(config_get('options', 'source_file_uri'), htmlspecialchars($url[0]), $package);
		$link = '<a href="' . $file_url . '">' . htmlspecialchars($url[0]) . '</a>';
456
457
458
459
460
461
462
463
464
	}

	if ($arch) {
		$link .= ' <em>(' . htmlspecialchars($arch) . ')</em>';
	}

	return $link;
}

465
466
467
468
/**
 * Determine packages that depend on a package
 *
 * @param string $name The package name for the dependency search
469
 * @param array $provides A list of virtual provisions of the package
470
 * @param int $limit An upper bound for the number of packages to retrieve
471
472
473
 *
 * @return array All packages that depend on the specified package name
 */
474
function pkg_required($name="", $provides, $limit) {
475
	$deps = array();
Dan McGee's avatar
Dan McGee committed
476
	if ($name != "") {
477
		$dbh = DB::connect();
478
479
480
481
482
483

		$name_list = $dbh->quote($name);
		foreach ($provides as $p) {
			$name_list .= ',' . $dbh->quote($p[0]);
		}

484
485
		$q = "SELECT p.Name, pd.DepName, dt.Name, pd.DepArch ";
		$q.= "FROM PackageDepends pd ";
486
487
		$q.= "LEFT JOIN Packages p ON p.ID = pd.PackageID ";
		$q.= "LEFT JOIN DependencyTypes dt ON dt.ID = pd.DepTypeID ";
488
		$q.= "WHERE pd.DepName IN (" . $name_list . ") ";
489
		$q.= "ORDER BY p.Name LIMIT " . intval($limit);
490
491
		/* Not invalidated by package updates. */
		return db_cache_result($q, 'required:' . $name, PDO::FETCH_NUM);
492
493
494
495
	}
	return $deps;
}

496
497
498
499
500
501
502
/**
 * Get all package sources for a specific package
 *
 * @param string $pkgid The package ID to get the sources for
 *
 * @return array All sources associated with a specific package
 */
503
function pkg_sources($pkgid) {
504
	$pkgid = intval($pkgid);
505
506
	if (!$pkgid) {
		return array();
507
	}
508
509
510
511
512
	$q = "SELECT Source, SourceArch FROM PackageSources ";
	$q.= "WHERE PackageID = " . $pkgid;
	$q.= " ORDER BY Source";
	$ttl = config_get_int('options', 'cache_pkginfo_ttl');
	return db_cache_result($q, 'required:' . $pkgid, PDO::FETCH_NUM, $ttl);
513
514
}

515
/**
516
 * Get the package details
517
 *
518
 * @param string $id The package ID to get description for
519
 *
520
521
 * @return array The package's details OR error message
 **/
522
function pkg_get_details($id=0) {
523
	$dbh = DB::connect();
524

525
	$q = "SELECT Packages.*, PackageBases.ID AS BaseID, ";
526
527
	$q.= "PackageBases.Name AS BaseName, PackageBases.NumVotes, ";
	$q.= "PackageBases.Popularity, PackageBases.OutOfDateTS, ";
528
529
	$q.= "PackageBases.SubmittedTS, PackageBases.ModifiedTS, ";
	$q.= "PackageBases.SubmitterUID, PackageBases.MaintainerUID, ";
530
	$q.= "PackageBases.PackagerUID, PackageBases.FlaggerUID, ";
531
532
533
	$q.= "(SELECT COUNT(*) FROM PackageRequests ";
	$q.= " WHERE PackageRequests.PackageBaseID = Packages.PackageBaseID ";
	$q.= " AND PackageRequests.Status = 0) AS RequestCount ";
534
	$q.= "FROM Packages, PackageBases ";
535
	$q.= "WHERE PackageBases.ID = Packages.PackageBaseID ";
Loui Chang's avatar
Loui Chang committed
536
	$q.= "AND Packages.ID = " . intval($id);
canyonknight's avatar
canyonknight committed
537
	$result = $dbh->query($q);
eric's avatar
eric committed
538

539
540
	$row = array();

canyonknight's avatar
canyonknight committed
541
	if (!$result) {
542
		$row['error'] = __("Error retrieving package details.");
543
544
	}
	else {
canyonknight's avatar
canyonknight committed
545
		$row = $result->fetch(PDO::FETCH_ASSOC);
eric's avatar
eric committed
546
		if (empty($row)) {
547
			$row['error'] = __("Package details could not be found.");
548
		}
549
550
551
552
553
554
555
556
557
	}

	return $row;
}

/**
 * Display the package details page
 *
 * @param string $id The package ID to get details page for
558
 * @param array $row Package details retrieved by pkg_get_details()
559
560
561
562
 * @param string $SID The session ID of the visitor
 *
 * @return void
 */
563
function pkg_display_details($id=0, $row, $SID="") {
564
	$dbh = DB::connect();
565

566
567
568
569
	if (isset($row['error'])) {
		print "<p>" . $row['error'] . "</p>\n";
	}
	else {
570
571
572
		$base_id = pkgbase_from_pkgid($id);
		$pkgbase_name = pkgbase_name_from_id($base_id);

573
574
575
		include('pkg_details.php');

		if ($SID) {
576
			include('pkg_comment_box.php');
577
578
		}

579
		$include_deleted = has_credential(CRED_COMMENT_VIEW_DELETED);
580
581
582
583

		$limit_pinned = isset($_GET['pinned']) ? 0 : 5;
		$pinned = pkgbase_comments($base_id, $limit_pinned, false, true);
		if (!empty($pinned)) {
584
			$comment_section = "package";
585
586
			include('pkg_comments.php');
		}
587
		unset($pinned);
588

589

590
591
		$total_comment_count = pkgbase_comments_count($base_id, $include_deleted);
		list($pagination_templs, $per_page, $offset) = calculate_pagination($total_comment_count);
592

593
		$comments = pkgbase_comments($base_id, $per_page, $include_deleted, false, $offset);
594
		if (!empty($comments)) {
595
			$comment_section = "package";
596
			include('pkg_comments.php');
eric's avatar
eric committed
597
598
599
600
		}
	}
}

Lukas Fleischer's avatar
Lukas Fleischer committed
601
602
603
604
605
606
607
/**
 * Output the body of the search results page
 *
 * @param array $params Search parameters
 * @param bool $show_headers True if statistics should be included
 * @param string $SID The session ID of the visitor
 *
608
 * @return int The total number of packages matching the query
Simo Leone's avatar
Simo Leone committed
609
 */
Lukas Fleischer's avatar
Lukas Fleischer committed
610
function pkg_search_page($params, $show_headers=true, $SID="") {
611
	$dbh = DB::connect();
Loui Chang's avatar
Loui Chang committed
612

613
614
615
616
	/*
	 * Get commonly used variables.
	 * TODO: Reduce the number of database queries!
	 */
Loui Chang's avatar
Loui Chang committed
617
	if ($SID)
618
		$myuid = uid_from_sid($SID);
Loui Chang's avatar
Loui Chang committed
619

620
	/* Sanitize paging variables. */
Lukas Fleischer's avatar
Lukas Fleischer committed
621
	if (isset($params['O'])) {
622
		$params['O'] = bound(intval($params['O']), 0, 2500);
623
	} else {
Lukas Fleischer's avatar
Lukas Fleischer committed
624
		$params['O'] = 0;
Loui Chang's avatar
Loui Chang committed
625
626
	}

Lukas Fleischer's avatar
Lukas Fleischer committed
627
628
	if (isset($params["PP"])) {
		$params["PP"] = bound(intval($params["PP"]), 50, 250);
629
	} else {
Lukas Fleischer's avatar
Lukas Fleischer committed
630
		$params["PP"] = 50;
Loui Chang's avatar
Loui Chang committed
631
632
	}

633
634
635
636
	/*
	 * FIXME: Pull out DB-related code. All of it! This one's worth a
	 * choco-chip cookie, one of those nice big soft ones.
	 */
Loui Chang's avatar
Loui Chang committed
637

638
	/* Build the package search query. */
639
	$q_select = "SELECT ";
Loui Chang's avatar
Loui Chang committed
640
	if ($SID) {
641
		$q_select .= "PackageNotifications.UserID AS Notify,
642
			   PackageVotes.UsersID AS Voted, ";
Loui Chang's avatar
Loui Chang committed
643
	}
644
	$q_select .= "Users.Username AS Maintainer,
645
	Packages.Name, Packages.Version, Packages.Description,
646
647
	PackageBases.NumVotes, PackageBases.Popularity, Packages.ID,
	Packages.PackageBaseID, PackageBases.OutOfDateTS ";
Loui Chang's avatar
Loui Chang committed
648

649
	$q_from = "FROM Packages
650
	LEFT JOIN PackageBases ON (PackageBases.ID = Packages.PackageBaseID)
651
	LEFT JOIN Users ON (PackageBases.MaintainerUID = Users.ID) ";
Loui Chang's avatar
Loui Chang committed
652
	if ($SID) {
653
		/* This is not needed for the total row count query. */
654
		$q_from_extra = "LEFT JOIN PackageVotes
655
		ON (PackageBases.ID = PackageVotes.PackageBaseID AND PackageVotes.UsersID = $myuid)
656
657
		LEFT JOIN PackageNotifications
		ON (PackageBases.ID = PackageNotifications.PackageBaseID AND PackageNotifications.UserID = $myuid) ";
658
659
	} else {
		$q_from_extra = "";
Loui Chang's avatar
Loui Chang committed
660
661
	}

662
	$q_where = 'WHERE PackageBases.PackagerUID IS NOT NULL ';
Loui Chang's avatar
Loui Chang committed
663

Lukas Fleischer's avatar
Lukas Fleischer committed
664
665
	if (isset($params['K'])) {
		if (isset($params["SeB"]) && $params["SeB"] == "m") {
666
			/* Search by maintainer. */
Lukas Fleischer's avatar
Lukas Fleischer committed
667
			$q_where .= "AND Users.Username = " . $dbh->quote($params['K']) . " ";
Loui Chang's avatar
Loui Chang committed
668
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
669
		elseif (isset($params["SeB"]) && $params["SeB"] == "c") {
670
671
672
673
			/* Search by co-maintainer. */
			$q_where .= "AND EXISTS (SELECT * FROM PackageComaintainers ";
			$q_where .= "INNER JOIN Users ON Users.ID = PackageComaintainers.UsersID ";
			$q_where .= "WHERE PackageComaintainers.PackageBaseID = PackageBases.ID ";
Lukas Fleischer's avatar
Lukas Fleischer committed
674
			$q_where .= "AND Users.Username = " . $dbh->quote($params['K']) . ")";
675
		}
676
677
678
679
680
681
682
683
		elseif (isset($params["SeB"]) && $params["SeB"] == "M") {
			/* Search by maintainer and co-maintainer. */
			$q_where .= "AND (Users.Username = " . $dbh->quote($params['K']) . " ";
			$q_where .= "OR EXISTS (SELECT * FROM PackageComaintainers ";
			$q_where .= "INNER JOIN Users ON Users.ID = PackageComaintainers.UsersID ";
			$q_where .= "WHERE PackageComaintainers.PackageBaseID = PackageBases.ID ";
			$q_where .= "AND Users.Username = " . $dbh->quote($params['K']) . "))";
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
684
		elseif (isset($params["SeB"]) && $params["SeB"] == "s") {
685
			/* Search by submitter. */
Lukas Fleischer's avatar
Lukas Fleischer committed
686
			$q_where .= "AND SubmitterUID = " . intval(uid_from_username($params['K'])) . " ";
Loui Chang's avatar
Loui Chang committed
687
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
688
		elseif (isset($params["SeB"]) && $params["SeB"] == "n") {
689
			/* Search by name. */
Lukas Fleischer's avatar
Lukas Fleischer committed
690
			$K = "%" . addcslashes($params['K'], '%_') . "%";
691
			$q_where .= "AND (Packages.Name LIKE " . $dbh->quote($K) . ") ";
Andrea Scarpino's avatar
Andrea Scarpino committed
692
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
693
		elseif (isset($params["SeB"]) && $params["SeB"] == "b") {
694
			/* Search by package base name. */
Lukas Fleischer's avatar
Lukas Fleischer committed
695
			$K = "%" . addcslashes($params['K'], '%_') . "%";
696
697
			$q_where .= "AND (PackageBases.Name LIKE " . $dbh->quote($K) . ") ";
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
698
		elseif (isset($params["SeB"]) && $params["SeB"] == "k") {
699
700
701
702
			/* Search by name. */
			$q_where .= "AND (";
			$q_where .= construct_keyword_search($dbh, $params['K'], false, true);
			$q_where .= ") ";
Marcel Korpel's avatar
Marcel Korpel committed
703
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
704
		elseif (isset($params["SeB"]) && $params["SeB"] == "N") {
705
			/* Search by name (exact match). */
Lukas Fleischer's avatar
Lukas Fleischer committed
706
			$q_where .= "AND (Packages.Name = " . $dbh->quote($params['K']) . ") ";
707
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
708
		elseif (isset($params["SeB"]) && $params["SeB"] == "B") {
709
			/* Search by package base name (exact match). */
Lukas Fleischer's avatar
Lukas Fleischer committed
710
			$q_where .= "AND (PackageBases.Name = " . $dbh->quote($params['K']) . ") ";
711
		}
Loui Chang's avatar
Loui Chang committed
712
		else {
713
			/* Keyword search (default). */
714
715
716
			$q_where .= "AND (";
			$q_where .= construct_keyword_search($dbh, $params['K'], true, true);
			$q_where .= ") ";
Loui Chang's avatar
Loui Chang committed
717
718
719
		}
	}

Lukas Fleischer's avatar
Lukas Fleischer committed
720
	if (isset($params["do_Orphans"])) {
721
		$q_where .= "AND MaintainerUID IS NULL ";
Loui Chang's avatar
Loui Chang committed
722
	}
723

Lukas Fleischer's avatar
Lukas Fleischer committed
724
725
	if (isset($params['outdated'])) {
		if ($params['outdated'] == 'on') {
726
			$q_where .= "AND OutOfDateTS IS NOT NULL ";
727
		}
Lukas Fleischer's avatar
Lukas Fleischer committed
728
		elseif ($params['outdated'] == 'off') {
729
			$q_where .= "AND OutOfDateTS IS NULL ";
730
		}
731
732
	}

Lukas Fleischer's avatar
Lukas Fleischer committed
733
	$order = (isset($params["SO"]) && $params["SO"] == 'd') ? 'DESC' : 'ASC';
Loui Chang's avatar
Loui Chang committed
734

735
	$q_sort = "ORDER BY ";
Lukas Fleischer's avatar
Lukas Fleischer committed
736
	$sort_by = isset($params["SB"]) ? $params["SB"] : '';
737
	switch ($sort_by) {
Loui Chang's avatar
Loui Chang committed
738
	case 'v':
739
		$q_sort .= "NumVotes " . $order . ", ";
Loui Chang's avatar
Loui Chang committed
740
		break;
741
742
743
	case 'p':
		$q_sort .= "Popularity " . $order . ", ";
		break;
744
745
	case 'w':
		if ($SID) {
746
			$q_sort .= "Voted " . $order . ", ";
747
748
749
750
		}
		break;
	case 'o':
		if ($SID) {
751
			$q_sort .= "Notify " . $order . ", ";
752
753
		}
		break;
Loui Chang's avatar
Loui Chang committed
754
	case 'm':
755
		$q_sort .= "Maintainer " . $order . ", ";
Loui Chang's avatar
Loui Chang committed
756
		break;
757
758
759
	case 'l':
		$q_sort .= "ModifiedTS " . $order . ", ";
		break;
Loui Chang's avatar
Loui Chang committed
760
	case 'a':
761
		/* For compatibility with old search links. */
762
		$q_sort .= "-ModifiedTS " . $order . ", ";
Loui Chang's avatar
Loui Chang committed
763
764
765
766
		break;
	default:
		break;
	}
767
	$q_sort .= " Packages.Name " . $order . " ";
Loui Chang's avatar
Loui Chang committed
768

Lukas Fleischer's avatar
Lukas Fleischer committed
769
	$q_limit = "LIMIT ".$params["PP"]." OFFSET ".$params["O"];
770

771
772
	$q = $q_select . $q_from . $q_from_extra . $q_where . $q_sort . $q_limit;
	$q_total = "SELECT COUNT(*) " . $q_from . $q_where;
Loui Chang's avatar
Loui Chang committed
773

canyonknight's avatar
canyonknight committed
774
775
	$result = $dbh->query($q);
	$result_t = $dbh->query($q_total);
776
	if ($result_t) {
canyonknight's avatar
canyonknight committed
777
		$row = $result_t->fetch(PDO::FETCH_NUM);
778
779
		$total = min($row[0], 2500);
	} else {
780
781
		$total = 0;
	}
Simo Leone's avatar
Simo Leone committed
782

783
	if ($result && $total > 0) {
Lukas Fleischer's avatar
Lukas Fleischer committed
784
		if (isset($params["SO"]) && $params["SO"] == "d"){
785
			$SO_next = "a";
Loui Chang's avatar
Loui Chang committed
786
787
		}
		else {
788
			$SO_next = "d";
Loui Chang's avatar
Loui Chang committed
789
		}
790
	}
Simo Leone's avatar
Simo Leone committed
791

792
	/* Calculate the results to use. */
Lukas Fleischer's avatar
Lukas Fleischer committed
793
	$first = $params['O'] + 1;
Simo Leone's avatar
Simo Leone committed
794

795
	/* Calculation of pagination links. */
Lukas Fleischer's avatar
Lukas Fleischer committed
796
	$per_page = ($params['PP'] > 0) ? $params['PP'] : 50;
797
798
799
	$current = ceil($first / $per_page);
	$pages = ceil($total / $per_page);
	$templ_pages = array();
800

801
	if ($current > 1) {
802
803
		$templ_pages['&laquo; ' . __('First')] = 0;
		$templ_pages['&lsaquo; ' . __('Previous')] = ($current - 2) * $per_page;
804
	}
805

806
807
	if ($current - 5 > 1)
		$templ_pages["..."] = false;
808

809
810
811
	for ($i = max($current - 5, 1); $i <= min($pages, $current + 5); $i++) {
		$templ_pages[$i] = ($i - 1) * $per_page;
	}
812

813
814
	if ($current + 5 < $pages)
		$templ_pages["... "] = false;
815

816
	if ($current < $pages) {
817
818
		$templ_pages[__('Next') . ' &rsaquo;'] = $current * $per_page;
		$templ_pages[__('Last') . ' &raquo;'] = ($pages - 1) * $per_page;
819
	}
Simo Leone's avatar
Simo Leone committed
820

821
	$searchresults = array();
canyonknight's avatar
canyonknight committed
822
823
824
825
	if ($result) {
		while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
			$searchresults[] = $row;
		}
826
827
	}

828
829
	include('pkg_search_results.php');

830
	return $total;
eric's avatar
eric committed
831
832
}

Marcel Korpel's avatar
Marcel Korpel committed
833
834
835
836
/**
 * Construct the WHERE part of the sophisticated keyword search
 *
 * @param handle $dbh Database handle
Lukas Fleischer's avatar
Lukas Fleischer committed
837
838
 * @param string $keywords The search term
 * @param bool $namedesc Search name and description fields
839
 * @param bool $keyword Search packages with a matching PackageBases.Keyword
Marcel Korpel's avatar
Marcel Korpel committed
840
841
842
 *
 * @return string WHERE part of the SQL clause
 */
843
function construct_keyword_search($dbh, $keywords, $namedesc, $keyword=false) {
Marcel Korpel's avatar
Marcel Korpel committed
844
845
846
847
848
	$count = 0;
	$where_part = "";
	$q_keywords = "";
	$op = "";

Lukas Fleischer's avatar
Lukas Fleischer committed
849
	foreach (str_getcsv($keywords, ' ') as $term) {
Marcel Korpel's avatar
Marcel Korpel committed
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
		if ($term == "") {
			continue;
		}
		if ($count > 0 && strtolower($term) == "and") {
			$op = "AND ";
			continue;
		}
		if ($count > 0 && strtolower($term) == "or") {
			$op = "OR ";
			continue;
		}
	        if ($count > 0 && strtolower($term) == "not") {
			$op .= "NOT ";
			continue;
		}

		$term = "%" . addcslashes($term, '%_') . "%";
		$q_keywords .= $op . " (";
868
		$q_keywords .= "Packages.Name LIKE " . $dbh->quote($term) . " ";
Marcel Korpel's avatar
Marcel Korpel committed
869
		if ($namedesc) {
870
871
872
873
874
875
876
877
878
			$q_keywords .= "OR Description LIKE " . $dbh->quote($term) . " ";
		}

		if ($keyword) {
			$q_keywords .= "OR EXISTS (SELECT * FROM PackageKeywords WHERE ";
			$q_keywords .= "PackageKeywords.PackageBaseID = Packages.PackageBaseID AND ";
			$q_keywords .= "PackageKeywords.Keyword LIKE " . $dbh->quote($term) . ")) ";
		} else {
			$q_keywords .= ") ";
Marcel Korpel's avatar
Marcel Korpel committed
879
880
881
882
883
884
885
886
887
		}

		$count++;
		if ($count >= 20) {
			break;
		}
		$op = "AND ";
	}

888
	return $q_keywords;
Marcel Korpel's avatar
Marcel Korpel committed
889
890
}

891
892
893
894
895
896
897
/**
 * Determine if a POST string has been sent by a visitor
 *
 * @param string $action String to check has been sent via POST
 *
 * @return bool True if the POST string was used, otherwise false
 */
Dan McGee's avatar
Dan McGee committed
898
899
900
901
902
function current_action($action) {
	return (isset($_POST['action']) && $_POST['action'] == $action) ||
		isset($_POST[$action]);
}

903
/**
904
905
906
907
908
 * Determine if sent IDs are valid integers
 *
 * @param array $ids IDs to validate
 *
 * @return array All sent IDs that are valid integers
909
910
911
912
913
914
915
916
917
918
919
920
 */
function sanitize_ids($ids) {
	$new_ids = array();
	foreach ($ids as $id) {
		$id = intval($id);
		if ($id > 0) {
			$new_ids[] = $id;
		}
	}
	return $new_ids;
}

921
922
923
924
925
926
927
928
929
930
/**
 * Determine package information for latest package
 *
 * @param int $numpkgs Number of packages to get information on
 *
 * @return array $packages Package info for the specified number of recent packages
 */
function latest_pkgs($numpkgs) {
	$dbh = DB::connect();

Lukas Fleischer's avatar
Lukas Fleischer committed
931
932
	$q = "SELECT Packages.*, MaintainerUID, SubmittedTS ";
	$q.= "FROM Packages LEFT JOIN PackageBases ON ";
933
	$q.= "PackageBases.ID = Packages.PackageBaseID ";
934
	$q.= "ORDER BY SubmittedTS DESC ";
935
	$q.= "LIMIT " . intval($numpkgs);
936
937
	$result = $dbh->query($q);

938
	$packages = array();
939
940
941
942
943
944
945
946
	if ($result) {
		while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
			$packages[] = $row;
		}
	}

	return $packages;
}