mirror of
https://github.com/php/web-pecl.git
synced 2026-03-24 07:12:15 +01:00
1690 lines
55 KiB
PHP
1690 lines
55 KiB
PHP
<?php
|
|
/*
|
|
+----------------------------------------------------------------------+
|
|
| PEAR Web site version 1.0 |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2001-2005 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 2.02 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available at through the world-wide-web at |
|
|
| http://www.php.net/license/2_02.txt. |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Stig Bakken <ssb@fast.no> |
|
|
| Tomas V.V.Cox <cox@php.net> |
|
|
| Martin Jansen <mj@php.net> |
|
|
| Gregory Beaver <cellog@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
$Id: pear-database.php 317357 2011-09-26 21:23:28Z pajoye $
|
|
*/
|
|
|
|
require_once 'DB/storage.php';
|
|
require_once 'PEAR/Common.php';
|
|
require_once 'HTTP.php';
|
|
|
|
// {{{ validate()
|
|
|
|
function validate($entity, $field, $value /* , $oldvalue, $object */) {
|
|
switch ("$entity/$field") {
|
|
case "users/handle":
|
|
if (!preg_match('/^[a-z][a-z0-9]+$/i', $value)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case "users/name":
|
|
if (!$value) {
|
|
return false;
|
|
}
|
|
break;
|
|
case "users/email":
|
|
if (!preg_match('/[a-z0-9_\.\+%]@[a-z0-9\.]+\.[a-z]+$', $email)) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
|
|
// }}}
|
|
// {{{ version_compare_firstelem()
|
|
|
|
function version_compare_firstelem($a, $b)
|
|
{
|
|
reset($a);
|
|
$elem = key($a);
|
|
return version_compare($a[$elem], $b[$elem]);
|
|
}
|
|
|
|
// }}}
|
|
|
|
// These classes correspond to tables and methods define operations on
|
|
// each. They are packaged into classes for easier rest
|
|
// integration.
|
|
|
|
|
|
/**
|
|
* Class to handle maintainers
|
|
*
|
|
* @class maintainer
|
|
* @package pearweb
|
|
* @author Stig S. Bakken <ssb@fast.no>
|
|
* @author Tomas V.V. Cox <cox@php.net>
|
|
* @author Martin Jansen <mj@php.net>
|
|
*/
|
|
class maintainer
|
|
{
|
|
// {{{ +proto int maintainer::add(int|string, string, string) API 1.0
|
|
|
|
/**
|
|
* Add new maintainer
|
|
*
|
|
* @static
|
|
* @param mixed Name of the package or it's ID
|
|
* @param string Handle of the user
|
|
* @param string Role of the user
|
|
* @param integer Is the developer actively working on the project?
|
|
* @return mixed True or PEAR error object
|
|
*/
|
|
function add($package, $user, $role, $active = 1)
|
|
{
|
|
global $dbh, $pear_rest;
|
|
|
|
if (!user::exists($user)) {
|
|
return PEAR::raiseError("User $user does not exist");
|
|
}
|
|
if (is_string($package)) {
|
|
$package = package::info($package, 'id');
|
|
}
|
|
|
|
$err = $dbh->query("INSERT INTO maintains (handle, package, role, active) VALUES (?, ?, ?, ?)",
|
|
array($user, $package, $role, (int)$active));
|
|
|
|
if (DB::isError($err)) {
|
|
return $err;
|
|
}
|
|
$packagename = package::info($package, 'name');
|
|
$pear_rest->savePackageMaintainerREST($packagename);
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto struct maintainer::get(int|string, [bool]) API 1.0
|
|
|
|
/**
|
|
* Get maintainer(s) for package
|
|
*
|
|
* @static
|
|
* @param mixed Name of the package or it's ID
|
|
* @param boolean Only return lead maintainers?
|
|
* @return array
|
|
*/
|
|
function get($package, $lead = false)
|
|
{
|
|
global $dbh;
|
|
if (is_string($package)) {
|
|
$package = package::info($package, 'id');
|
|
}
|
|
$query = "SELECT handle, role, active FROM maintains WHERE package = ?";
|
|
if ($lead) {
|
|
$query .= " AND role = 'lead'";
|
|
}
|
|
$query .= " ORDER BY active DESC";
|
|
|
|
return $dbh->getAssoc($query, true, array($package), DB_FETCHMODE_ASSOC);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto struct maintainer::getByUser(string) API 1.0
|
|
|
|
/**
|
|
* Get the roles of a specific user
|
|
*
|
|
* @static
|
|
* @param string Handle of the user
|
|
* @return array
|
|
*/
|
|
function getByUser($user)
|
|
{
|
|
global $dbh;
|
|
$query = 'SELECT p.name, m.role FROM packages p, maintains m WHERE p.package_type = ? AND p.approved = 1 AND m.package = p.id AND m.handle = ?';
|
|
return $dbh->getAssoc($query, array('pecl'), array($user));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto bool maintainer::isValidRole(string) API 1.0
|
|
|
|
/**
|
|
* Check if role is valid
|
|
*
|
|
* @static
|
|
* @param string Name of the role
|
|
* @return boolean
|
|
*/
|
|
function isValidRole($role)
|
|
{
|
|
require_once "PEAR/Common.php";
|
|
|
|
static $roles;
|
|
if (empty($roles)) {
|
|
$roles = PEAR_Common::getUserRoles();
|
|
}
|
|
return in_array($role, $roles);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool maintainer::remove(int|string, string) API 1.0
|
|
|
|
/**
|
|
* Remove user from package
|
|
*
|
|
* @static
|
|
* @param mixed Name of the package or it's ID
|
|
* @param string Handle of the user
|
|
* @return True or PEAR error object
|
|
*/
|
|
function remove($package, $user)
|
|
{
|
|
global $dbh, $auth_user;
|
|
if (!$auth_user->isAdmin() && !user::maintains($auth_user->handle, $package, 'lead')) {
|
|
return PEAR::raiseError('maintainer::remove: insufficient privileges');
|
|
}
|
|
if (is_string($package)) {
|
|
$package = package::info($package, 'id');
|
|
}
|
|
$sql = "DELETE FROM maintains WHERE package = ? AND handle = ?";
|
|
return $dbh->query($sql, array($package, $user));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool maintainer::updateAll(int, array) API 1.0
|
|
|
|
/**
|
|
* Update user and roles of a package
|
|
*
|
|
* @static
|
|
* @param int $pkgid The package id to update
|
|
* @param array $users Assoc array containing the list of users
|
|
* in the form: '<user>' => array('role' => '<role>', 'active' => '<active>')
|
|
* @return mixed PEAR_Error or true
|
|
*/
|
|
function updateAll($pkgid, $users)
|
|
{
|
|
|
|
global $auth_user;
|
|
|
|
$admin = $auth_user->isAdmin();
|
|
|
|
// Only admins and leads can do this.
|
|
if (maintainer::mayUpdate($pkgid) == false) {
|
|
return PEAR::raiseError('maintainer::updateAll: insufficient privileges');
|
|
}
|
|
|
|
$pkg_name = package::info((int)$pkgid, "name", true); // Needed for logging
|
|
if (empty($pkg_name)) {
|
|
PEAR::raiseError('maintainer::updateAll: no such package');
|
|
}
|
|
|
|
$old = maintainer::get($pkgid);
|
|
if (DB::isError($old)) {
|
|
return $old;
|
|
}
|
|
$old_users = array_keys($old);
|
|
$new_users = array_keys($users);
|
|
|
|
if (!$admin && !in_array($auth_user->handle, $new_users)) {
|
|
return PEAR::raiseError("You can not delete your own maintainer role or you will not ".
|
|
"be able to complete the update process. Set your name ".
|
|
"in package.xml or let the new lead developer upload ".
|
|
"the new release");
|
|
}
|
|
foreach ($users as $user => $u) {
|
|
$role = $u['role'];
|
|
$active = $u['active'];
|
|
|
|
if (!maintainer::isValidRole($role)) {
|
|
return PEAR::raiseError("invalid role '$role' for user '$user'");
|
|
}
|
|
// The user is not present -> add him
|
|
if (!in_array($user, $old_users)) {
|
|
$e = maintainer::add($pkgid, $user, $role, $active);
|
|
if (PEAR::isError($e)) {
|
|
return $e;
|
|
}
|
|
continue;
|
|
}
|
|
// Users exists but role has changed -> update it
|
|
if ($role != $old[$user]['role']) {
|
|
$res = maintainer::update($pkgid, $user, $role, $active);
|
|
if (DB::isError($res)) {
|
|
return $res;
|
|
}
|
|
}
|
|
}
|
|
// Drop users who are no longer maintainers
|
|
foreach ($old_users as $old_user) {
|
|
if (!in_array($old_user, $new_users)) {
|
|
$res = maintainer::remove($pkgid, $old_user);
|
|
if (DB::isError($res)) {
|
|
return $res;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{
|
|
|
|
/**
|
|
* Update maintainer entry
|
|
*
|
|
* @access public
|
|
* @param int Package ID
|
|
* @param string Username
|
|
* @param string Role
|
|
* @param string Is the developer actively working on the package?
|
|
*/
|
|
function update($package, $user, $role, $active) {
|
|
global $dbh;
|
|
|
|
$query = "UPDATE maintains SET role = ?, active = ? " .
|
|
"WHERE package = ? AND handle = ?";
|
|
return $dbh->query($query, array($role, $active, $package, $user));
|
|
}
|
|
|
|
// {{{ NOEXPORT maintainer::mayUpdate(int)
|
|
|
|
/**
|
|
* Checks if the current user is allowed to update the maintainer data
|
|
*
|
|
* @access public
|
|
* @param int ID of the package
|
|
* @return boolean
|
|
*/
|
|
function mayUpdate($package) {
|
|
global $auth_user;
|
|
|
|
$admin = $auth_user->isAdmin();
|
|
|
|
if (!$admin && !user::maintains($auth_user->handle, $package, 'lead')) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
/**
|
|
* Class to handle releases
|
|
*
|
|
* @class release
|
|
* @package pearweb
|
|
* @author Stig S. Bakken <ssb@fast.no>
|
|
* @author Tomas V.V. Cox <cox@php.net>
|
|
* @author Martin Jansen <mj@php.net>
|
|
*/
|
|
class release
|
|
{
|
|
// {{{ proto array release::getRecent([int]) API 1.0
|
|
|
|
/**
|
|
* Get recent releases
|
|
*
|
|
* @static
|
|
* @param integer Number of releases to return
|
|
* @return array
|
|
*/
|
|
function getRecent($n = 5)
|
|
{
|
|
global $dbh;
|
|
$sth = $dbh->limitQuery("SELECT packages.id AS id, ".
|
|
"packages.name AS name, ".
|
|
"packages.summary AS summary, ".
|
|
"releases.version AS version, ".
|
|
"releases.releasedate AS releasedate, ".
|
|
"releases.releasenotes AS releasenotes, ".
|
|
"releases.doneby AS doneby, ".
|
|
"releases.state AS state ".
|
|
"FROM packages, releases ".
|
|
"WHERE packages.id = releases.package ".
|
|
"AND packages.approved = 1 ".
|
|
"AND packages.package_type = 'pecl' ".
|
|
"ORDER BY releases.releasedate DESC", 0, $n);
|
|
$recent = array();
|
|
// XXX Fixme when DB gets limited getAll()
|
|
while ($sth->fetchInto($row, DB_FETCHMODE_ASSOC)) {
|
|
$recent[] = $row;
|
|
}
|
|
return $recent;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto array release::getDateRange(int,int) API 1.0
|
|
|
|
/**
|
|
* Get release in a specific time range
|
|
*
|
|
* @static
|
|
* @param integer Timestamp of start date
|
|
* @param integer Timestamp of end date
|
|
* @return array
|
|
*/
|
|
function getDateRange($start,$end)
|
|
{
|
|
global $dbh;
|
|
|
|
$recent = array();
|
|
if (!is_numeric($start)) {
|
|
return $recent;
|
|
}
|
|
if (!is_numeric($end)) {
|
|
return $recent;
|
|
}
|
|
$start_f = date('Y-m-d 00:00:00',$start);
|
|
$end_f = date('Y-m-d 00:00:00',$end);
|
|
// limited to 50 to stop overkill on the server!
|
|
$sth = $dbh->limitQuery("SELECT packages.id AS id, ".
|
|
"packages.name AS name, ".
|
|
"packages.summary AS summary, ".
|
|
"packages.description AS description, ".
|
|
"releases.version AS version, ".
|
|
"releases.releasedate AS releasedate, ".
|
|
"releases.releasenotes AS releasenotes, ".
|
|
"releases.doneby AS doneby, ".
|
|
"releases.state AS state ".
|
|
"FROM packages, releases ".
|
|
"WHERE packages.id = releases.package ".
|
|
"AND releases.releasedate > '{$start_f}' AND releases.releasedate < '{$end_f}'".
|
|
"ORDER BY releases.releasedate DESC",0,50);
|
|
|
|
while ($sth->fetchInto($row, DB_FETCHMODE_ASSOC)) {
|
|
$recent[] = $row;
|
|
}
|
|
return $recent;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto string release::upload(string, string, string, string, binary, string) API 1.0
|
|
|
|
/**
|
|
* Upload new release
|
|
*
|
|
* @static
|
|
* @param string Name of the package
|
|
* @param string Version string
|
|
* @param string State of the release
|
|
* @param string Release notes
|
|
* @param string Filename of the release tarball
|
|
* @param string MD5 checksum of the tarball
|
|
*/
|
|
function upload($package, $version, $state, $relnotes, $tarball, $md5sum)
|
|
{
|
|
global $auth_user;
|
|
$role = user::maintains($auth_user->handle, $package);
|
|
if ($role != 'lead' && $role != 'developer' && !$auth_user->isAdmin()) {
|
|
return PEAR::raiseError('release::upload: insufficient privileges');
|
|
}
|
|
$ref = release::validateUpload($package, $version, $state, $relnotes, $tarball, $md5sum);
|
|
if (PEAR::isError($ref)) {
|
|
return $ref;
|
|
}
|
|
|
|
return release::confirmUpload($package, $version, $state, $relnotes, $md5sum, $ref['package_id'], $ref['file']);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto string release::validateUpload(string, string, string, string, binary, string) API 1.0
|
|
|
|
/**
|
|
* Determine if uploaded file is a valid release
|
|
*
|
|
* @param string Name of the package
|
|
* @param string Version string
|
|
* @param string State of the release
|
|
* @param string Release notes
|
|
* @param string Filename of the release tarball
|
|
* @param string MD5 checksum of the tarball
|
|
* @return mixed
|
|
*/
|
|
function validateUpload($package, $version, $state, $relnotes, $tarball, $md5sum)
|
|
{
|
|
global $dbh, $auth_user;
|
|
$role = user::maintains($auth_user->handle, $package);
|
|
if ($role != 'lead' && $role != 'developer' && !$auth_user->isAdmin()) {
|
|
return PEAR::raiseError('release::validateUpload: insufficient privileges');
|
|
}
|
|
// (2) verify that package exists
|
|
$package_id = package::info($package, 'id');
|
|
if (PEAR::isError($package_id) || empty($package_id)) {
|
|
return PEAR::raiseError("package `$package' must be registered first");
|
|
}
|
|
|
|
// (3) verify that version does not exist
|
|
$test = $dbh->getOne("SELECT version FROM releases ".
|
|
"WHERE package = ? AND version = ?",
|
|
array($package_id, $version));
|
|
if (PEAR::isError($test)) {
|
|
return $test;
|
|
}
|
|
if ($test) {
|
|
return PEAR::raiseError("already exists: $package $version");
|
|
}
|
|
|
|
// (4) store tar ball to temp file
|
|
$tempfile = sprintf("%s/%s%s-%s.tgz",
|
|
PEAR_TARBALL_DIR, ".new.", $package, $version);
|
|
$file = sprintf("%s/%s-%s.tgz", PEAR_TARBALL_DIR, $package, $version);
|
|
if (!@copy($tarball, $tempfile)) {
|
|
return PEAR::raiseError("writing $tempfile failed: $php_errormsg");
|
|
}
|
|
|
|
if (!isset($package_id)) {
|
|
return PEAR::raiseError("bad upload: package_id missing");
|
|
}
|
|
|
|
// later: do lots of integrity checks on the tarball
|
|
if (!@rename($tempfile, $file)) {
|
|
return PEAR::raiseError("renaming failed: $php_errormsg");
|
|
}
|
|
|
|
// (5) verify MD5 checksum
|
|
$testsum = md5_file($file);
|
|
if ($testsum != $md5sum) {
|
|
$bytes = strlen($data);
|
|
return PEAR::raiseError("bad md5 checksum (checksum=$testsum ($bytes bytes: $data), specified=$md5sum)");
|
|
}
|
|
|
|
$info = array("package_id" => $package_id,
|
|
"package" => $package,
|
|
"version" => $version,
|
|
"state" => $state,
|
|
"relnotes" => $relnotes,
|
|
"md5sum" => $md5sum,
|
|
"file" => $file);
|
|
$infofile = sprintf("%s/%s%s-%s",
|
|
PEAR_TARBALL_DIR, ".info.", $package, $version);
|
|
$fp = @fopen($infofile, "w");
|
|
if (!is_resource($fp)) {
|
|
return PEAR::raiseError("writing $infofile failed: $php_errormsg");
|
|
}
|
|
fwrite($fp, serialize($info));
|
|
fclose($fp);
|
|
return $info;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool release::confirmUpload(string, string, string, string, string, int, binary) API 1.0
|
|
|
|
/**
|
|
* Confirm release upload
|
|
*
|
|
* @param string Package name
|
|
* @param string Package version
|
|
* @param string Package state
|
|
* @param string Release notes
|
|
* @param string md5
|
|
* @param int Package id from database
|
|
* @param string package contents
|
|
* @static
|
|
* @return string the file name of the upload or PEAR_Error object if problems
|
|
*/
|
|
function confirmUpload($package, $version, $state, $relnotes, $md5sum, $package_id, $file)
|
|
{
|
|
require_once "PEAR/Common.php";
|
|
|
|
global $dbh, $auth_user, $_PEAR_Common_dependency_types,
|
|
$_PEAR_Common_dependency_relations;
|
|
|
|
require_once 'Archive/Tar.php';
|
|
$tar = new Archive_Tar($file);
|
|
$oldpackagexml = $tar->extractInString('package.xml');
|
|
if (($packagexml = $tar->extractInString('package2.xml')) ||
|
|
($packagexml = $tar->extractInString('package.xml'))) {
|
|
// success
|
|
} else {
|
|
return PEAR::raiseError('Archive uploaded does not appear to contain a package.xml!');
|
|
}
|
|
if ($oldpackagexml != $packagexml) {
|
|
$compatible = true;
|
|
} else {
|
|
$compatible = false;
|
|
}
|
|
// Update releases table
|
|
$query = "INSERT INTO releases (id,package,version,state,doneby,".
|
|
"releasedate,releasenotes) VALUES(?,?,?,?,?,NOW(),?)";
|
|
$sth = $dbh->prepare($query);
|
|
$release_id = $dbh->nextId("releases");
|
|
$dbh->execute($sth, array($release_id, $package_id, $version, $state,
|
|
$auth_user->handle, $relnotes));
|
|
// Update files table
|
|
$query = "INSERT INTO files ".
|
|
"(id,package,release,md5sum,basename,fullpath,packagexml) ".
|
|
"VALUES(?,?,?,?,?,?,?)";
|
|
$sth = $dbh->prepare($query);
|
|
$file_id = $dbh->nextId("files");
|
|
$ok = $dbh->execute($sth, array($file_id, $package_id, $release_id,
|
|
$md5sum, basename($file), $file, $packagexml));
|
|
/*
|
|
* Code duplication with deps error
|
|
* Should be droped soon or later using transaction
|
|
* (and add mysql4 as a pe(ar|cl)web requirement)
|
|
*/
|
|
if (PEAR::isError($ok)) {
|
|
$dbh->query("DELETE FROM releases WHERE id = $release_id");
|
|
@unlink($file);
|
|
return $ok;
|
|
}
|
|
|
|
// Update dependency table
|
|
$query = "INSERT INTO deps " .
|
|
"(package, release, type, relation, version, name, optional) " .
|
|
"VALUES (?,?,?,?,?,?,?)";
|
|
$sth = $dbh->prepare($query);
|
|
|
|
require_once 'PEAR/PackageFile.php';
|
|
require_once 'PEAR/Config.php';
|
|
$config = &PEAR_Config::singleton();
|
|
$pf = new PEAR_PackageFile($config);
|
|
$pkg_info = $pf->fromXmlString($packagexml, PEAR_VALIDATE_DOWNLOADING,
|
|
$compatible ? 'package2.xml' : 'package.xml');
|
|
|
|
$deps = $pkg_info->getDeps(true); // get the package2.xml actual content
|
|
$storedeps = $pkg_info->getDeps(); // get the BC-compatible content
|
|
$pearused = false;
|
|
if (isset($deps['required']['package'])) {
|
|
if (!isset($deps['required']['package'][0])) {
|
|
$deps['required']['package'] = array($deps['required']['package']);
|
|
}
|
|
foreach ($deps['required']['package'] as $pkgdep) {
|
|
if ($pkgdep['channel'] == 'pear.php.net' && strtolower($pkgdep['name']) == 'pear') {
|
|
$pearused = true;
|
|
}
|
|
}
|
|
}
|
|
if (is_array($storedeps)) {
|
|
foreach ($storedeps as $dep) {
|
|
$prob = array();
|
|
|
|
if (empty($dep['type']) ||
|
|
!in_array($dep['type'], $_PEAR_Common_dependency_types))
|
|
{
|
|
$prob[] = 'type';
|
|
}
|
|
|
|
if (empty($dep['name'])) {
|
|
/*
|
|
* NOTE from pajoye in ver 1.166:
|
|
* This works for now.
|
|
* This would require a 'cleaner' InfoFromXXX
|
|
* which may return a defined set of data using
|
|
* default values if required.
|
|
*/
|
|
if (strtolower($dep['type']) == 'php') {
|
|
$dep['name'] = 'PHP';
|
|
} else {
|
|
$prob[] = 'name';
|
|
}
|
|
} elseif (strtolower($dep['name']) == 'pear') {
|
|
if (!$pearused && $compatible) {
|
|
// there is no need for a PEAR dependency here
|
|
continue;
|
|
}
|
|
if (!$pearused && !$compatible) {
|
|
$dep['name'] = 'PEAR Installer';
|
|
}
|
|
}
|
|
|
|
if (empty($dep['rel']) ||
|
|
!in_array($dep['rel'], $_PEAR_Common_dependency_relations))
|
|
{
|
|
$prob[] = 'rel';
|
|
}
|
|
|
|
if (empty($dep['optional'])) {
|
|
$optional = 0;
|
|
} else {
|
|
if ($dep['optional'] != strtolower($dep['optional'])) {
|
|
$prob[] = 'optional';
|
|
}
|
|
if ($dep['optional'] == 'yes') {
|
|
$optional = 1;
|
|
} else {
|
|
$optional = 0;
|
|
}
|
|
}
|
|
|
|
if (count($prob)) {
|
|
$res = PEAR::raiseError('The following attribute(s) ' .
|
|
'were missing or need proper values: ' .
|
|
implode(', ', $prob));
|
|
} else {
|
|
$res = $dbh->execute($sth,
|
|
array(
|
|
$package_id,
|
|
$release_id,
|
|
$dep['type'],
|
|
$dep['rel'],
|
|
@$dep['version'],
|
|
$dep['name'],
|
|
$optional));
|
|
}
|
|
|
|
if (PEAR::isError($res)) {
|
|
$dbh->query('DELETE FROM deps WHERE ' .
|
|
"release = $release_id");
|
|
$dbh->query('DELETE FROM releases WHERE ' .
|
|
"id = $release_id");
|
|
@unlink($file);
|
|
return $res;
|
|
}
|
|
}
|
|
}
|
|
|
|
$GLOBALS['pear_rest']->saveAllReleasesREST($package);
|
|
$GLOBALS['pear_rest']->saveReleaseREST($file, $packagexml, $pkg_info, $auth_user->handle,
|
|
$release_id);
|
|
$GLOBALS['pear_rest']->savePackagesCategoryREST(package::info($package, 'category'));
|
|
|
|
return $file;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool release::dismissUpload(string) API 1.0
|
|
|
|
/**
|
|
* Dismiss release upload
|
|
*
|
|
* @param string
|
|
* @return boolean
|
|
*/
|
|
function dismissUpload($upload_ref)
|
|
{
|
|
return (bool)@unlink($upload_ref);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ NOEXPORT release::HTTPdownload(string, [string], [string], [bool])
|
|
|
|
/**
|
|
* Download release via HTTP
|
|
*
|
|
* Not for xmlrpc export!
|
|
*
|
|
* @param string Name of the package
|
|
* @param string Version string
|
|
* @param string Filename
|
|
* @param boolean Uncompress file before downloading?
|
|
* @return mixed
|
|
* @static
|
|
*/
|
|
function HTTPdownload($package, $version = null, $file = null, $uncompress = false)
|
|
{
|
|
global $dbh;
|
|
|
|
require_once "HTTP.php";
|
|
|
|
$package_id = package::info($package, 'packageid', true);
|
|
|
|
if (!$package_id) {
|
|
$package_id = $dbh->getOne('SELECT package_id FROM package_aliases WHERE alias_name=' . $dbh->quoteSmart($package));
|
|
if (!$package_id) {
|
|
return PEAR::raiseError("release download:: package '".htmlspecialchars($package).
|
|
"' does not exist");
|
|
}
|
|
}
|
|
|
|
if (PEAR::isError($package_id)) {
|
|
return $package_id;
|
|
}
|
|
|
|
if ($file !== null) {
|
|
if (substr($file, -4) == '.tar') {
|
|
$file = substr($file, 0, -4) . '.tgz';
|
|
$uncompress = true;
|
|
}
|
|
$row = $dbh->getRow("SELECT fullpath, release, id FROM files ".
|
|
"WHERE UPPER(basename) = ?", array(strtoupper($file)),
|
|
DB_FETCHMODE_ASSOC);
|
|
if (PEAR::isError($row)) {
|
|
return $row;
|
|
} elseif ($row === null) {
|
|
return PEAR::raiseError("File '$file' not found");
|
|
}
|
|
$path = $row['fullpath'];
|
|
$log_release = $row['release'];
|
|
$log_file = $row['id'];
|
|
$basename = $file;
|
|
} elseif ($version == null) {
|
|
// Get the most recent version
|
|
$row = $dbh->getRow("SELECT id FROM releases ".
|
|
"WHERE package = $package_id ".
|
|
"ORDER BY releasedate DESC", DB_FETCHMODE_ASSOC);
|
|
if (PEAR::isError($row)) {
|
|
return $row;
|
|
}
|
|
$release_id = $row['id'];
|
|
} elseif (release::isValidState($version)) {
|
|
$version = strtolower($version);
|
|
// Get the most recent version with a given state
|
|
$row = $dbh->getRow("SELECT id FROM releases ".
|
|
"WHERE package = $package_id ".
|
|
"AND state = '$version' ".
|
|
"ORDER BY releasedate DESC",
|
|
DB_FETCHMODE_ASSOC);
|
|
if (PEAR::isError($row)) {
|
|
return $row;
|
|
}
|
|
$release_id = $row['id'];
|
|
if (!isset($release_id)) {
|
|
return PEAR::raiseError("$package does not have any releases with state \"$version\"");
|
|
}
|
|
} else {
|
|
// Get a specific release
|
|
$row = $dbh->getRow("SELECT id FROM releases ".
|
|
"WHERE package = $package_id ".
|
|
"AND version = '$version'",
|
|
DB_FETCHMODE_ASSOC);
|
|
if (PEAR::isError($row)) {
|
|
return $row;
|
|
}
|
|
$release_id = $row['id'];
|
|
}
|
|
if (!isset($path) && isset($release_id)) {
|
|
$sql = "SELECT fullpath, basename, `id` FROM files WHERE `release` = ".
|
|
$release_id;
|
|
$row = $dbh->getRow($sql, DB_FETCHMODE_ORDERED);
|
|
if (PEAR::isError($row)) {
|
|
return $row;
|
|
}
|
|
list($path, $basename, $log_file) = $row;
|
|
if (empty($path) || (!@is_file(PEAR_TARBALL_DIR . '/' . $basename) && !@is_file($path))) {
|
|
return PEAR::raiseError("release download:: no version information found");
|
|
}
|
|
}
|
|
if (isset($path)) {
|
|
if (!isset($log_release)) {
|
|
$log_release = $release_id;
|
|
}
|
|
|
|
release::logDownload($package_id, $log_release, $log_file);
|
|
|
|
header('Last-modified: '.HTTP::date(filemtime($path)));
|
|
header('Content-type: application/octet-stream');
|
|
if ($uncompress) {
|
|
$tarname = preg_replace('/\.tgz$/', '.tar', $basename);
|
|
header('Content-disposition: attachment; filename="'.$tarname.'"');
|
|
readgzfile($path);
|
|
} else {
|
|
header('Content-disposition: attachment; filename="'.$basename.'"');
|
|
header('Content-length: '.filesize($path));
|
|
readfile($path);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
header('HTTP/1.0 404 Not Found');
|
|
print 'File not found';
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto bool release::isValidState(string) API 1.0
|
|
|
|
/**
|
|
* Determine if release state is valid
|
|
*
|
|
* @static
|
|
* @param string State
|
|
* @return boolean
|
|
*/
|
|
function isValidState($state)
|
|
{
|
|
static $states = array('devel', 'snapshot', 'alpha', 'beta', 'stable');
|
|
return in_array($state, $states);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto array release::betterStates(string) API 1.0
|
|
|
|
/**
|
|
* Convert a state into an array of less stable states
|
|
*
|
|
* @param string Release state
|
|
* @param boolean include the state in the array returned
|
|
* @return boolean
|
|
*/
|
|
function betterStates($state, $include = false)
|
|
{
|
|
static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
|
|
$i = array_search($state, $states);
|
|
if ($include) {
|
|
$i--;
|
|
}
|
|
if ($i === false) {
|
|
return false;
|
|
}
|
|
return array_slice($states, $i + 1);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ NOEXPORT release::logDownload(integer, string, string)
|
|
|
|
/**
|
|
* Log release download
|
|
*
|
|
* @param integer ID of the package
|
|
* @param integer ID of the release
|
|
* @param string Filename
|
|
*/
|
|
function logDownload($package, $release_id, $file = null)
|
|
{
|
|
global $dbh;
|
|
|
|
$dbh->query('UPDATE aggregated_package_stats
|
|
SET downloads=downloads+1
|
|
WHERE
|
|
package_id=? AND
|
|
release_id=? AND
|
|
yearmonth="' . date('Y-m-01') . '"',
|
|
array($package, $release_id));
|
|
if ($dbh->affectedRows() == 0) {
|
|
$dbh->query('INSERT INTO aggregated_package_stats
|
|
(package_id, release_id, yearmonth, downloads)
|
|
VALUES(?,?,?,1)',
|
|
array($package, $release_id, date('Y-m-01')));
|
|
}
|
|
|
|
// This method can be used when we have MySQL 4.1,
|
|
// 30% efficiency gain at least over previous method
|
|
// $dbh->query('INSERT INTO aggregated_package_stats
|
|
// (package_id, release_id, yearmonth, downloads)
|
|
// VALUES(?,?,?,1)
|
|
// ON DUPLICATE KEY UPDATE downloads=downloads+1',
|
|
// array($package, $release_id, date('Y-m-01')));
|
|
|
|
// {{{ Update package_stats table
|
|
|
|
// This method can be used when we have MySQL 4.1,
|
|
// 30% efficiency gain at least over previous method
|
|
// $query = 'INSERT INTO package_stats
|
|
// (dl_number, package, release, pid, rid, cid, last_dl)
|
|
// VALUES (1, ?, ?, ?, ?, ?, ?)
|
|
// ON DUPLICATE KEY UPDATE
|
|
// dl_number=dl_number+1,
|
|
// last_dl = "' . date('Y-m-d H:i:s') . '"';
|
|
//
|
|
// $dbh->query($query, array($pkg_info['name'],
|
|
// $version,
|
|
// $package,
|
|
// $release_id,
|
|
// $pkg_info['categoryid'],
|
|
// date('Y-m-d H:i:s')
|
|
// )
|
|
// );
|
|
|
|
$query = 'UPDATE package_stats '
|
|
. ' SET dl_number = dl_number + 1,'
|
|
. " last_dl = '" . date('Y-m-d H:i:s') . "'"
|
|
. ' WHERE pid = ? AND rid = ?';
|
|
$dbh->query($query, array($package, $release_id));
|
|
|
|
if ($dbh->affectedRows() == 0) {
|
|
$pkg_info = package::info($package, null);
|
|
|
|
$query = 'SELECT version FROM releases'
|
|
. ' WHERE package = ? AND id = ?';
|
|
$version = $dbh->getOne($query, array($package, $release_id));
|
|
|
|
if (!$version) {
|
|
return PEAR::raiseError('release:: the package you requested'
|
|
. ' has no release by that number');
|
|
}
|
|
|
|
$query = 'INSERT INTO package_stats'
|
|
. ' (dl_number, package, release, pid, rid, cid, last_dl)'
|
|
. ' VALUES (1, ?, ?, ?, ?, ?, ?)';
|
|
|
|
$dbh->query($query, array($pkg_info['name'],
|
|
$version,
|
|
$package,
|
|
$release_id,
|
|
$pkg_info['categoryid'],
|
|
date('Y-m-d H:i:s')
|
|
)
|
|
);
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto string release::promote(array, string) API 1.0
|
|
|
|
/**
|
|
* Promote new release
|
|
*
|
|
* @param array Coming from PEAR_common::infoFromDescFile('package.xml')
|
|
* @param string Filename of the new uploaded release
|
|
* @return void
|
|
*/
|
|
function promote($pkginfo, $upload)
|
|
{
|
|
if ($_SERVER['SERVER_NAME'] != 'pecl.php.net') {
|
|
return;
|
|
}
|
|
|
|
$authors = package::info($pkginfo['package'], 'authors');
|
|
$txt_authors = '';
|
|
foreach ($authors as $a) {
|
|
$txt_authors .= $a['name'];
|
|
if ($a['showemail']) {
|
|
$txt_authors .= " <{$a['email']}>";
|
|
}
|
|
$txt_authors .= " ({$a['role']})\n";
|
|
}
|
|
$upload = basename($upload);
|
|
$release = "{$pkginfo['package']}-{$pkginfo['version']} ({$pkginfo['release_state']})";
|
|
$txtanounce =<<<END
|
|
The new PECL package $release has been released at http://pecl.php.net/.
|
|
|
|
Release notes
|
|
-------------
|
|
{$pkginfo['release_notes']}
|
|
|
|
Package Info
|
|
-------------
|
|
{$pkginfo['description']}
|
|
|
|
Related Links
|
|
-------------
|
|
Package home: http://pecl.php.net/package/$pkginfo[package]
|
|
Changelog: http://pecl.php.net/package-changelog.php?package=$pkginfo[package]
|
|
Download: http://pecl.php.net/get/$upload
|
|
|
|
Authors
|
|
-------------
|
|
$txt_authors
|
|
END;
|
|
$to = '"PECL developers list" <pecl-dev@lists.php.net>';
|
|
$from = '"PECL Announce" <pecl-dev@lists.php.net>';
|
|
$subject = "[ANNOUNCEMENT] $release Released.";
|
|
mail($to, $subject, $txtanounce, "From: $from", "-f bounce-no-user@php.net");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto string release::promote_v2(array, string) API 1.0
|
|
|
|
/**
|
|
* Promote new release
|
|
*
|
|
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
|
|
* @param string Filename of the new uploaded release
|
|
* @return void
|
|
*/
|
|
function promote_v2($pkginfo, $upload)
|
|
{
|
|
if ($_SERVER['SERVER_NAME'] != 'pecl.php.net') {
|
|
return;
|
|
}
|
|
|
|
$authors = package::info($pkginfo->getPackage(), 'authors');
|
|
$txt_authors = '';
|
|
foreach ($authors as $a) {
|
|
$txt_authors .= $a['name'];
|
|
if ($a['showemail']) {
|
|
$txt_authors .= " <{$a['email']}>";
|
|
}
|
|
$txt_authors .= " ({$a['role']})\n";
|
|
}
|
|
$upload = basename($upload);
|
|
$release = $pkginfo->getPackage() . '-' . $pkginfo->getVersion() .
|
|
' (' . $pkginfo->getState() . ')';
|
|
$txtanounce ='The new PECL package ' . $release . ' has been released at http://pecl.php.net/.
|
|
|
|
Release notes
|
|
-------------
|
|
' . $pkginfo->getNotes() . '
|
|
|
|
Package Info
|
|
-------------
|
|
' . $pkginfo->getDescription() . '
|
|
|
|
Related Links
|
|
-------------
|
|
Package home: http://pecl.php.net/package/' . $pkginfo->getPackage() . '
|
|
Changelog: http://pecl.php.net/package-changelog.php?package=' . $pkginfo->getPackage() . '
|
|
Download: http://pecl.php.net/get/' . $upload . '
|
|
|
|
Authors
|
|
-------------
|
|
' . $txt_authors;
|
|
|
|
$to = '"PECL developers list" <pecl-dev@lists.php.net>';
|
|
$from = '"PECL Announce" <pecl-dev@lists.php.net>';
|
|
$subject = "[ANNOUNCEMENT] $release Released.";
|
|
mail($to, $subject, $txtanounce, "From: $from", "-f bounce-no-user@php.net");
|
|
}
|
|
|
|
// }}}
|
|
// {{{ NOEXPORT release::remove(int, int)
|
|
|
|
/**
|
|
* Remove release
|
|
*
|
|
* @param integer ID of the package
|
|
* @param integer ID of the release
|
|
* @return boolean
|
|
*/
|
|
function remove($package, $release)
|
|
{
|
|
global $dbh, $auth_user;
|
|
if (!$auth_user->isAdmin() &&
|
|
!user::maintains($auth_user->handle, $package, 'lead')) {
|
|
return PEAR::raiseError('release::remove: insufficient privileges');
|
|
}
|
|
|
|
$success = true;
|
|
|
|
// get files that have to be removed
|
|
$query = sprintf("SELECT fullpath FROM files WHERE package = '%s' AND release = '%s'",
|
|
$package,
|
|
$release);
|
|
|
|
$sth = $dbh->query($query);
|
|
|
|
while ($row = $sth->fetchRow(DB_FETCHMODE_ASSOC)) {
|
|
if (!@unlink($row['fullpath'])) {
|
|
$success = false;
|
|
}
|
|
}
|
|
|
|
$query = sprintf("DELETE FROM files WHERE package = '%s' AND release = '%s'",
|
|
$package,
|
|
$release
|
|
);
|
|
$sth = $dbh->query($query);
|
|
|
|
$pname = package::info($package, 'name');
|
|
$version = $dbh->getOne('SELECT version from releases WHERE package = ? and id = ?',
|
|
array($package, $release));
|
|
$query = sprintf("DELETE FROM releases WHERE package = '%s' AND id = '%s'",
|
|
$package,
|
|
$release
|
|
);
|
|
$sth = $dbh->query($query);
|
|
$GLOBALS['pear_rest']->saveAllReleasesREST($pname);
|
|
$GLOBALS['pear_rest']->deleteReleaseREST($pname, $version);
|
|
$GLOBALS['pear_rest']->savePackagesCategoryREST(package::info($pname, 'category'));
|
|
|
|
if (PEAR::isError($sth)) {
|
|
return false;
|
|
} else {
|
|
return $success;
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
|
|
/**
|
|
* Class to handle notes
|
|
*
|
|
* @class note
|
|
* @package pearweb
|
|
* @author Stig S. Bakken <ssb@fast.no>
|
|
* @author Tomas V.V. Cox <cox@php.net>
|
|
* @author Martin Jansen <mj@php.net>
|
|
*/
|
|
class note
|
|
{
|
|
// {{{ +proto bool note::add(string, int, string, string) API 1.0
|
|
|
|
function add($key, $value, $note, $author = "")
|
|
{
|
|
global $dbh, $auth_user;
|
|
if (empty($author)) {
|
|
$author = $auth_user->handle;
|
|
}
|
|
if (!in_array($key, array('uid', 'rid', 'cid', 'pid'), true)) {
|
|
// bad hackers not allowed
|
|
$key = 'uid';
|
|
}
|
|
$nid = $dbh->nextId("notes");
|
|
$stmt = $dbh->prepare("INSERT INTO notes (id,$key,nby,ntime,note) ".
|
|
"VALUES(?,?,?,?,?)");
|
|
$res = $dbh->execute($stmt, array($nid, $value, $author,
|
|
gmdate('Y-m-d H:i'), $note));
|
|
if (DB::isError($res)) {
|
|
return $res;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool note::remove(int) API 1.0
|
|
|
|
function remove($id)
|
|
{
|
|
global $dbh;
|
|
$id = (int)$id;
|
|
$res = $dbh->query("DELETE FROM notes WHERE id = $id");
|
|
if (DB::isError($res)) {
|
|
return $res;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool note::removeAll(string, int) API 1.0
|
|
|
|
function removeAll($key, $value)
|
|
{
|
|
global $dbh;
|
|
$res = $dbh->query("DELETE FROM notes WHERE $key = ". $dbh->quote($value));
|
|
if (DB::isError($res)) {
|
|
return $res;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
class user
|
|
{
|
|
// {{{ *proto bool user::remove(string) API 1.0
|
|
|
|
function remove($uid)
|
|
{
|
|
global $dbh;
|
|
note::removeAll("uid", $uid);
|
|
$GLOBALS['pear_rest']->deleteMaintainerREST($uid);
|
|
$GLOBALS['pear_rest']->saveAllMaintainersREST();
|
|
$dbh->query('DELETE FROM users WHERE handle = '. $dbh->quote($uid));
|
|
return ($dbh->affectedRows() > 0);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ *proto bool user::rejectRequest(string, string) API 1.0
|
|
|
|
function rejectRequest($uid, $reason)
|
|
{
|
|
global $dbh, $auth_user;
|
|
list($email) = $dbh->getRow('SELECT email FROM users WHERE handle = ?',
|
|
array($uid));
|
|
note::add("uid", $uid, "Account rejected: $reason");
|
|
$msg = "Your PECL account request was rejected by " . $auth_user->handle . ":\n".
|
|
"$reason\n";
|
|
$xhdr = "From: " . $auth_user->handle . "@php.net";
|
|
mail($email, "Your PECL Account Request", $msg, $xhdr, "-f bounce-no-user@php.net");
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ *proto bool user::activate(string) API 1.0
|
|
|
|
function activate($uid)
|
|
{
|
|
global $dbh, $auth_user;
|
|
|
|
$user =& new PEAR_User($uid);
|
|
if (@$user->registered) {
|
|
return false;
|
|
}
|
|
@$arr = unserialize($user->userinfo);
|
|
note::removeAll("uid", $uid);
|
|
$user->set('registered', 1);
|
|
/* $user->set('ppp_only', 0); */
|
|
if (is_array($arr)) {
|
|
$user->set('userinfo', $arr[1]);
|
|
}
|
|
$user->set('created', gmdate('Y-m-d H:i'));
|
|
$user->set('createdby', $auth_user->handle);
|
|
$user->set('registered', 1);
|
|
$user->store();
|
|
note::add("uid", $uid, "Account opened");
|
|
$GLOBALS['pear_rest']->saveMaintainerREST($user->handle);
|
|
$GLOBALS['pear_rest']->saveAllmaintainersREST();
|
|
$msg = "Your PECL/PEAR account request has been opened.\n".
|
|
"To log in, go to http://pecl.php.net/ and click on \"login\" in\n".
|
|
"the top-right menu.\n";
|
|
$xhdr = "From: " . $auth_user->handle . "@php.net";
|
|
mail($user->email, "Your PECL Account Request", $msg, $xhdr, "-f bounce-no-user@php.net");
|
|
return true;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool user::isAdmin(string) API 1.0
|
|
|
|
function isAdmin($handle)
|
|
{
|
|
|
|
|
|
global $dbh;
|
|
|
|
$query = "SELECT handle FROM users WHERE handle = ? AND admin = 1";
|
|
$sth = $dbh->query($query, array($handle));
|
|
return ($sth->numRows() > 0);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto bool user::listAdmins() API 1.0
|
|
|
|
function listAdmins()
|
|
{
|
|
global $dbh;
|
|
|
|
$query = "SELECT email FROM users WHERE admin = 1";
|
|
return $dbh->getCol($query);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto bool user::exists(string) API 1.0
|
|
|
|
function exists($handle)
|
|
{
|
|
global $dbh;
|
|
$sql = "SELECT handle FROM users WHERE handle=?";
|
|
$res = $dbh->query($sql, array($handle));
|
|
return ($res->numRows() > 0);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ +proto string user::maintains(string|int, [string]) API 1.0
|
|
|
|
function maintains($user, $pkgid, $role = 'any')
|
|
{
|
|
global $dbh;
|
|
$package_id = package::info($pkgid, 'id');
|
|
if ($role == 'any') {
|
|
return $dbh->getOne('SELECT role FROM maintains WHERE handle = ? '.
|
|
'AND package = ?', array($user, $package_id));
|
|
}
|
|
if (is_array($role)) {
|
|
return $dbh->getOne('SELECT role FROM maintains WHERE handle = ? AND package = ? '.
|
|
'AND role IN ("?")', array($user, $package_id, implode('","', $role)));
|
|
}
|
|
return $dbh->getOne('SELECT role FROM maintains WHERE handle = ? AND package = ? '.
|
|
'AND role = ?', array($user, $package_id, $role));
|
|
}
|
|
|
|
// }}}
|
|
// {{{ proto string user::info(string, [string]) API 1.0
|
|
|
|
function info($user, $field = null)
|
|
{
|
|
global $dbh;
|
|
if ($field === null) {
|
|
return $dbh->getRow('SELECT * FROM users WHERE handle = ?',
|
|
array($user), DB_FETCHMODE_ASSOC);
|
|
}
|
|
if ($field == 'password' || preg_match('/[^a-z]/', $user)) {
|
|
return null;
|
|
}
|
|
return $dbh->getRow('SELECT ! FROM users WHERE handle = ?',
|
|
array($field, $user), DB_FETCHMODE_ASSOC);
|
|
|
|
}
|
|
|
|
// }}}
|
|
// {{{ listAll()
|
|
|
|
function listAll($registered_only = true)
|
|
{
|
|
global $dbh;
|
|
$query = "SELECT * FROM users";
|
|
if ($registered_only === true) {
|
|
$query .= " WHERE registered = 1";
|
|
}
|
|
$query .= " ORDER BY handle";
|
|
return $dbh->getAll($query, null, DB_FETCHMODE_ASSOC);
|
|
}
|
|
|
|
// }}}
|
|
// {{{ add()
|
|
|
|
/**
|
|
* Add a new user account
|
|
*
|
|
* During most of this method's operation, PEAR's error handling
|
|
* is set to PEAR_ERROR_RETURN.
|
|
*
|
|
* But, during the DB_storage::set() phase error handling is set to
|
|
* PEAR_ERROR_CALLBACK the report_warning() function. So, if an
|
|
* error happens a warning message is printed AND the incomplete
|
|
* user information is removed.
|
|
*
|
|
* @param array $data Information about the user
|
|
*
|
|
* @return mixed true if there are no problems, false if sending the
|
|
* email failed, 'set error' if DB_storage::set() failed
|
|
* or an array of error messages for other problems
|
|
*
|
|
* @access public
|
|
*/
|
|
function add(&$data)
|
|
{
|
|
global $dbh;
|
|
|
|
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
|
|
$errors = array();
|
|
|
|
$required = array(
|
|
'handle' => 'Username',
|
|
'firstname' => 'First Name',
|
|
'lastname' => 'Last Name',
|
|
'email' => 'Email address',
|
|
'purpose' => 'Intended purpose',
|
|
);
|
|
|
|
$name = $data['firstname'] . " " . $data['lastname'];
|
|
|
|
foreach ($required as $field => $desc) {
|
|
if (empty($data[$field])) {
|
|
$data['jumpto'] = $field;
|
|
$errors[] = 'Please enter ' . $desc;
|
|
}
|
|
}
|
|
|
|
if (!preg_match(PEAR_COMMON_USER_NAME_REGEX, $data['handle'])) {
|
|
$errors[] = 'Username must start with a letter and contain'
|
|
. ' only letters and digits';
|
|
}
|
|
|
|
// Basic name validation
|
|
|
|
// First- and lastname must be longer than 1 character
|
|
if (strlen($data['firstname']) == 1) {
|
|
$errors[] = 'Your firstname appears to be too short.';
|
|
}
|
|
if (strlen($data['lastname']) == 1) {
|
|
$errors[] = 'Your lastname appears to be too short.';
|
|
}
|
|
|
|
// Firstname and lastname must start with an uppercase letter
|
|
if (!preg_match("/^[A-Z]/", $data['firstname'])) {
|
|
$errors[] = 'Your firstname must begin with an uppercase letter';
|
|
}
|
|
if (!preg_match("/^[A-Z]/", $data['lastname'])) {
|
|
$errors[] = 'Your lastname must begin with an uppercase letter';
|
|
}
|
|
|
|
// No names with only uppercase letters
|
|
if ($data['firstname'] === strtoupper($data['firstname'])) {
|
|
$errors[] = 'Your firstname must not consist of only uppercase letters.';
|
|
}
|
|
if ($data['lastname'] === strtoupper($data['lastname'])) {
|
|
$errors[] = 'Your lastname must not consist of only uppercase letters.';
|
|
}
|
|
|
|
if ($data['password'] != $data['password2']) {
|
|
$data['password'] = $data['password2'] = "";
|
|
$data['jumpto'] = "password";
|
|
$errors[] = 'Passwords did not match';
|
|
}
|
|
|
|
if (!$data['password']) {
|
|
$data['jumpto'] = "password";
|
|
$errors[] = 'Empty passwords not allowed';
|
|
}
|
|
|
|
$handle = strtolower($data['handle']);
|
|
$obj =& new PEAR_User($handle);
|
|
|
|
if (isset($obj->created)) {
|
|
$data['jumpto'] = "handle";
|
|
$errors[] = 'Sorry, that username is already taken';
|
|
}
|
|
|
|
if ($errors) {
|
|
$data['display_form'] = true;
|
|
return $errors;
|
|
}
|
|
|
|
$err = $obj->insert($handle);
|
|
|
|
if (DB::isError($err)) {
|
|
if ($err->getCode() == DB_ERROR_CONSTRAINT) {
|
|
$data['display_form'] = true;
|
|
$data['jumpto'] = 'handle';
|
|
$errors[] = 'Sorry, that username is already taken';
|
|
} else {
|
|
$data['display_form'] = false;
|
|
$errors[] = $err;
|
|
}
|
|
return $errors;
|
|
}
|
|
|
|
$data['display_form'] = false;
|
|
$md5pw = md5($data['password']);
|
|
$showemail = @(bool)$data['showemail'];
|
|
// hack to temporarily embed the "purpose" in
|
|
// the user's "userinfo" column
|
|
$userinfo = serialize(array($data['purpose'], $data['moreinfo']));
|
|
$set_vars = array('name' => $name,
|
|
'email' => $data['email'],
|
|
'homepage' => $data['homepage'],
|
|
'showemail' => $showemail,
|
|
'password' => $md5pw,
|
|
'registered' => 0,
|
|
'userinfo' => $userinfo);
|
|
|
|
PEAR::pushErrorHandling(PEAR_ERROR_CALLBACK, 'report_warning');
|
|
foreach ($set_vars as $var => $value) {
|
|
$err = $obj->set($var, $value);
|
|
if (PEAR::isError($err)) {
|
|
user::remove($data['handle']);
|
|
return 'set error';
|
|
}
|
|
}
|
|
PEAR::popErrorHandling();
|
|
|
|
$msg = "Requested from: {$_SERVER['REMOTE_ADDR']}\n".
|
|
"Username: {$handle}\n".
|
|
"Real Name: {$name}\n".
|
|
(isset($data['showemail']) ? "Email: {$data['email']}\n" : "") .
|
|
"Purpose:\n".
|
|
"{$data['purpose']}\n\n".
|
|
"To handle: http://{$_SERVER['SERVER_NAME']}/admin/?acreq={$handle}\n";
|
|
|
|
if ($data['moreinfo']) {
|
|
$msg .= "\nMore info:\n{$data['moreinfo']}\n";
|
|
}
|
|
|
|
$xhdr = "From: $name <{$data['email']}>\nMessage-Id: <account-request-{$handle}@" .
|
|
PEAR_CHANNELNAME . ">\n";
|
|
// $xhdr .= "\nBCC: pear-group@php.net";
|
|
$subject = "PEAR Account Request: {$handle}";
|
|
|
|
if (DEVBOX == false) {
|
|
if (PEAR_CHANNELNAME == 'pear.php.net') {
|
|
$ok = @mail('pear-group@php.net', $subject, $msg, $xhdr,
|
|
'-f bounce-no-user@php.net');
|
|
}
|
|
} else {
|
|
$ok = true;
|
|
}
|
|
|
|
PEAR::popErrorHandling();
|
|
|
|
return $ok;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ update
|
|
|
|
/**
|
|
* Update user information
|
|
*
|
|
* @access public
|
|
* @param array User information
|
|
* @return object Instance of PEAR_User
|
|
*/
|
|
function update($data) {
|
|
global $dbh;
|
|
|
|
$fields = array("name", "email", "homepage", "showemail", "userinfo", "pgpkeyid", "wishlist");
|
|
|
|
$user =& new PEAR_User($data['handle']);
|
|
foreach ($data as $key => $value) {
|
|
if (!in_array($key, $fields)) {
|
|
continue;
|
|
}
|
|
$user->set($key, $value);
|
|
}
|
|
$user->store();
|
|
|
|
return $user;
|
|
}
|
|
|
|
// }}}
|
|
// {{{ getRecentReleases(string, [int])
|
|
|
|
/**
|
|
* Get recent releases for the given user
|
|
*
|
|
* @access public
|
|
* @param string Handle of the user
|
|
* @param int Number of releases (default is 10)
|
|
* @return array
|
|
*/
|
|
function getRecentReleases($handle, $n = 10)
|
|
{
|
|
global $dbh;
|
|
$recent = array();
|
|
|
|
$query = "SELECT p.id AS id, " .
|
|
"p.name AS name, " .
|
|
"p.summary AS summary, " .
|
|
"r.version AS version, " .
|
|
"r.releasedate AS releasedate, " .
|
|
"r.releasenotes AS releasenotes, " .
|
|
"r.doneby AS doneby, " .
|
|
"r.state AS state " .
|
|
"FROM packages p, releases r, maintains m " .
|
|
"WHERE p.package_type = 'pecl' AND p.id = r.package " .
|
|
"AND p.id = m.package AND m.handle = '" . $handle . "' " .
|
|
"ORDER BY r.releasedate DESC";
|
|
$sth = $dbh->limitQuery($query, 0, $n);
|
|
while ($sth->fetchInto($row, DB_FETCHMODE_ASSOC)) {
|
|
$recent[] = $row;
|
|
}
|
|
return $recent;
|
|
}
|
|
|
|
// }}}
|
|
}
|
|
|
|
// {{{ mail_pear_admins()
|
|
|
|
function mail_pear_admins($subject = "PEAR Account Request", $msg, $xhdr = '')
|
|
{
|
|
global $dbh;
|
|
$admins = $dbh->getAll("SELECT name,email FROM users WHERE admin = 1",
|
|
DB_FETCHMODE_ASSOC);
|
|
if (count($admins) > 0) {
|
|
foreach ($admins as $value) {
|
|
if ($value['name'] == "") {
|
|
$rcpt[] = "<" . $value['email'] . ">";
|
|
} else {
|
|
$rcpt[] = "\"" . $value['name'] . "\" <" . $value['email'] . ">";
|
|
}
|
|
}
|
|
$rcpt = implode(", ", $rcpt);
|
|
return mail($rcpt, $subject, $msg, $xhdr, "-f bounce-no-user@php.net");
|
|
}
|
|
return false;
|
|
}
|
|
// }}}
|
|
|
|
// {{{ class PEAR_User
|
|
/* TODO:
|
|
- add handle check against local SVN handles cache (json or apc cache), external function
|
|
- add other dynamic properties (without local storage but cache)
|
|
. as much as possible info must be stored only on master or people
|
|
*/
|
|
class PEAR_User
|
|
{
|
|
var $handle;
|
|
var $registered = 1;
|
|
private $admin = NULL;
|
|
|
|
function PEAR_User($user)
|
|
{
|
|
$this->handle = strtolower($user);
|
|
$this->email = $user . '@php.net';
|
|
}
|
|
|
|
function is($handle)
|
|
{
|
|
return (strtolower($handle) == $this->handle);
|
|
}
|
|
|
|
function isAdmin()
|
|
{
|
|
if ($this->admin === NULL) {
|
|
global $dbh;
|
|
$admin = $dbh->getOne("SELECT admin FROM users WHERE handle=" . $dbh->quote($this->handle));
|
|
if (!$admin) {
|
|
$this->admin = false;
|
|
return false;
|
|
} else {
|
|
$this->admin = true;
|
|
return true;
|
|
}
|
|
} else {
|
|
return $this->admin;
|
|
}
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ class PEAR_Package
|
|
|
|
class PEAR_Package extends DB_storage
|
|
{
|
|
function PEAR_Package(&$dbh, $package, $keycol = "id")
|
|
{
|
|
$this->DB_storage("packages", $keycol, $dbh);
|
|
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
|
$this->setup($package);
|
|
$this->popErrorHandling();
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
// {{{ class PEAR_Release
|
|
|
|
class PEAR_Release extends DB_storage
|
|
{
|
|
function PEAR_Release(&$dbh, $release)
|
|
{
|
|
$this->DB_storage("releases", "id", $dbh);
|
|
$this->pushErrorHandling(PEAR_ERROR_RETURN);
|
|
$this->setup($release);
|
|
$this->popErrorHandling();
|
|
}
|
|
}
|
|
|
|
// }}}
|
|
|
|
/**
|
|
* Converts a Unix timestamp to a date() formatted string in the UTC time zone
|
|
*
|
|
* @param int $ts a Unix timestamp from the local machine. If none
|
|
* is provided the current time is used.
|
|
* @param string $format a format string, as per http://php.net/date
|
|
*
|
|
* @return string the time formatted time
|
|
*/
|
|
function make_utc_date($ts = null, $format = 'Y-m-d H:i \U\T\C') {
|
|
$d = new DateTime("@$ts");
|
|
return $d->format($format);
|
|
} |